天天看點

neutron-server的啟動流程(二)

1.2 extension resource

#/neutron/api/v2/router.py:APIRouter
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
           

1.2.1 check extension resource

#/neutron/api/v2/router.py:APIRouter
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
           
#/neutron/api/extensions.py:PluginAwareExtensionManager
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls(get_extensions_path(),
                                manager.NeutronManager.get_service_plugins())
        return cls._instance
           

利用extension所在path和service plugin去初始化PluginAwareExtensionManager對象。

#/neutron/api/extensions.py
# Returns the extension paths from a config entry and the __path__
# of neutron.extensions
def get_extensions_path():
    paths = neutron.extensions.__path__

    neutron_mods = repos.NeutronModules()
    for x in neutron_mods.installed_list():
        try:
            paths += neutron_mods.module(x).extensions.__path__
        except AttributeError:
            # Occurs normally if module has no extensions sub-module
            pass

    if cfg.CONF.api_extensions_path:
        paths.append(cfg.CONF.api_extensions_path)

    # If the path has dups in it, from discovery + conf file, the duplicate
    # import of the same module and super() do not play nicely, so weed
    # out the duplicates, preserving search order.

    z = collections.OrderedDict()
    for x in paths:
        z[x] = 1
    paths = z.keys()

    LOG.debug("get_extension_paths = %s", paths)

    path = ':'.join(paths)
    return path
           

get_extensions_path函數加載extension resource的path,本OpenStack環境傳回的路徑為/neutron/extensions目錄。

#/neutron/manager.py:NeutronManager
    @classmethod
    def get_service_plugins(cls):
        # Return weakrefs to minimize gc-preventing references.
        return dict((x, weakref.proxy(y))
                    for x, y in cls.get_instance().service_plugins.iteritems())
           

get_service_plugins函數傳回service plugin。其中service plugin包括core plugin(因為core plugin中也可能包括extension resource,當然service plugin中都是extension resource)。service plugin資訊如下。

{'L3_ROUTER_NAT': <neutron.services.l3_router.l3_router_plugin.L3RouterPlugin object at 0x42038d0>, 'CORE': <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x360d910>}
#/neutron/api/extensions.py:PluginAwareExtensionManager
class PluginAwareExtensionManager(ExtensionManager):

    _instance = None

    def __init__(self, path, plugins):
        self.plugins = plugins
        super(PluginAwareExtensionManager, self).__init__(path)
        self.check_if_plugin_extensions_loaded()

#/neutron/api/extensions.py:ExtensionManager
class ExtensionManager(object):
    """Load extensions from the configured extension path.

    See tests/unit/extensions/foxinsocks.py for an
    example extension implementation.
    """

    def __init__(self, path):
        LOG.info(_LI('Initializing extension manager.'))
        self.path = path
        self.extensions = {}
        self._load_all_extensions()
           

這裡主要分析如何load all extensions。

#/neutron/api/extensions.py:ExtensionManager
    def _load_all_extensions(self):
        """Load extensions from the configured path.

        The extension name is constructed from the module_name. If your
        extension module is named widgets.py, the extension class within that
        module should be 'Widgets'.

        See tests/unit/extensions/foxinsocks.py for an example extension
        implementation.
        """

        for path in self.path.split(':'):
            if os.path.exists(path):
                self._load_all_extensions_from_path(path)
            else:
                LOG.error(_LE("Extension path '%s' doesn't exist!"), path)

#/neutron/api/extensions.py:ExtensionManager
    def _load_all_extensions_from_path(self, path):
        # Sorting the extension list makes the order in which they
        # are loaded predictable across a cluster of load-balanced
        # Neutron Servers
        for f in sorted(os.listdir(path)):
            try:
                LOG.debug('Loading extension file: %s', f)
                mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
                ext_path = os.path.join(path, f)
                if file_ext.lower() == '.py' and not mod_name.startswith('_'):
                    mod = imp.load_source(mod_name, ext_path)
                    ext_name = mod_name[0].upper() + mod_name[1:]
                    new_ext_class = getattr(mod, ext_name, None)
                    if not new_ext_class:
                        LOG.warn(_LW('Did not find expected name '
                                     '"%(ext_name)s" in %(file)s'),
                                 {'ext_name': ext_name,
                                  'file': ext_path})
                        continue
                    new_ext = new_ext_class()
                    self.add_extension(new_ext)
            except Exception as exception:
                LOG.warn(_LW("Extension file %(f)s wasn't loaded due to "
                             "%(exception)s"),
                         {'f': f, 'exception': exception})
           

_load_all_extensions函數周遊所有擴充目錄(有可能不止/neutron/extensions目錄),然後在_load_all_extensions_from_path函數中周遊每個擴充目錄下的檔案,且将檔案擴充以.py結束且檔案名不以’_’開頭的的檔案中的所對應的類進行加載。比如/neutron/extensions目錄下的agent.py檔案滿足檔案擴充以.py結束且檔案名不以’_’開頭的條件,然後檢視agent.py中的Agent類,如果有Agent類,則建立Agent對象,否則提示warning。

對建立的每個對象,調用add_extension函數進行check并加載。

#/neutron/api/extensions.py:ExtensionManager
    def add_extension(self, ext):
        # Do nothing if the extension doesn't check out
        if not self._check_extension(ext):
            return

        alias = ext.get_alias()
        LOG.info(_LI('Loaded extension: %s'), alias)

        if alias in self.extensions:
            raise exceptions.DuplicatedExtension(alias=alias)
        self.extensions[alias] = ext


#/neutron/api/extensions.py:PluginAwareExtensionManager
    def _check_extension(self, extension):
        """Check if an extension is supported by any plugin."""
        extension_is_valid = super(PluginAwareExtensionManager,
                                   self)._check_extension(extension)
        return (extension_is_valid and
                self._plugins_support(extension) and
                self._plugins_implement_interface(extension))

#/neutron/api/extensions.py:ExtensionManager
    def _check_extension(self, extension):
        """Checks for required methods in extension objects."""
        try:
            LOG.debug('Ext name: %s', extension.get_name())
            LOG.debug('Ext alias: %s', extension.get_alias())
            LOG.debug('Ext description: %s', extension.get_description())
            LOG.debug('Ext namespace: %s', extension.get_namespace())
            LOG.debug('Ext updated: %s', extension.get_updated())
        except AttributeError as ex:
            LOG.exception(_LE("Exception loading extension: %s"), unicode(ex))
            return False
        return True


#/neutron/api/extensions.py:PluginAwareExtensionManager
    def _plugins_support(self, extension):
        alias = extension.get_alias()
        supports_extension = any((hasattr(plugin,
                                          "supported_extension_aliases") and
                                  alias in plugin.supported_extension_aliases)
                                 for plugin in self.plugins.values())
        if not supports_extension:
            LOG.warn(_LW("Extension %s not supported by any of loaded "
                         "plugins"),
                     alias)
        return supports_extension

#/neutron/api/extensions.py:PluginAwareExtensionManager
    def _plugins_implement_interface(self, extension):
        if(not hasattr(extension, "get_plugin_interface") or
           extension.get_plugin_interface() is None):
            return True
        for plugin in self.plugins.values():
            if isinstance(plugin, extension.get_plugin_interface()):
                return True
        LOG.warn(_LW("Loaded plugins do not implement extension %s interface"),
                 extension.get_alias())
        return False
           

add_extension函數首先調用_check_extension函數check建立的extension對象是否滿足條件。

條件1: 加載的extension類必須實作5個函數: get_name, get_alias,get_description, get_namespace以及get_updated。

條件2: 加載的extension必須得到plugin(core plugin和service plugin)的support。

判斷是否support的方法是: core plugin和service plugin所建立的對象(如core plugin的Ml2Plugin和service plugin的L3RouterPlugin)中的字典變量supported_extension_aliases是否有加載extension類的alias。如果supported_extension_aliases中沒有,則說明plugin不支援該extension。

比如core plugin的Ml2Plugin中的supported_extension_aliases

#/neutron/plugins/ml2/plugin.py:Ml2Plugin
    # List of supported extensions
    _supported_extension_aliases = ["provider", "external-net", "binding",
                                    "quotas", "security-group", "agent",
                                    "dhcp_agent_scheduler",
                                    "multi-provider", "allowed-address-pairs",
                                    "extra_dhcp_opt", "subnet_allocation",
                                    "net-mtu", "vlan-transparent"]

    @property
    def supported_extension_aliases(self):
        if not hasattr(self, '_aliases'):
            aliases = self._supported_extension_aliases[:]
            aliases += self.extension_manager.extension_aliases()
            sg_rpc.disable_security_group_extension_by_config(aliases)
            vlantransparent.disable_extension_by_config(aliases)
            self._aliases = aliases
        return self._aliases
           

經過supported_extension_aliases函數check後,vlan-transparent被remove了,是以supported_extension_aliases函數最終傳回的字典為:

['provider', 'external-net', 'binding', 'quotas', 'security-group', 'agent', 'dhcp_agent_scheduler', 'multi-provider', 'allowed-address-pairs', 'extra_dhcp_opt', 'subnet_allocation', 'net-mtu']

而service plugin的L3RouterPlugin中的supported_extension_aliases

#/neutron/services/l3_router/l3_router_plugin.py:L3RouterPlugin
    supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
                                   "extraroute", "l3_agent_scheduler",
                                   "l3-ha"]
           

在/neutron/extensions目錄下有個portsecurity.py檔案,該檔案中所對應的extension類定義如下。

class Portsecurity(object):
    """Extension class supporting port security."""

    @classmethod
    def get_name(cls):
        return "Port Security"

    @classmethod
    def get_alias(cls):
        return "port-security"

    @classmethod
    def get_description(cls):
        return "Provides port security"

    @classmethod
    def get_namespace(cls):
        return "http://docs.openstack.org/ext/portsecurity/api/v1.0"

    @classmethod
    def get_updated(cls):
        return "2012-07-23T10:00:00-00:00"

    def get_extended_resources(self, version):
        if version == "2.0":
            return EXTENDED_ATTRIBUTES_2_0
        else:
            return {}
           

而Portsecurity類的alias為’port-security’,而在core plugin的Ml2Plugin和service plugin的L3RouterPlugin中的supported_extension_aliases都沒有’port-security’的别名,是以Portsecurity類不被plugin support。我們也可以通過log檢視是否支援,如下。

而Portsecurity類的alias為’port-security’,而在core plugin的Ml2Plugin和service plugin的L3RouterPlugin中的supported_extension_aliases都沒有’port-security’的别名,是以Portsecurity類不被plugin support。我們也可以通過log檢視是否支援,如下。

2016-04-30 09:18:26.513 8740 INFO neutron.api.extensions [-] Initializing extension manager.

2016-04-30 09:18:26.513 8740 INFO neutron.api.extensions [-] Loaded extension: agent

2016-04-30 09:18:26.514 8740 INFO neutron.api.extensions [-] Loaded extension: allowed-address-pairs

2016-04-30 09:18:26.514 8740 INFO neutron.api.extensions [-] Loaded extension: dhcp_agent_scheduler

2016-04-30 09:18:26.515 8740 INFO neutron.api.extensions [-] Loaded extension: dvr

2016-04-30 09:18:26.515 8740 INFO neutron.api.extensions [-] Loaded extension: external-net

2016-04-30 09:18:26.515 8740 INFO neutron.api.extensions [-] Loaded extension: extra_dhcp_opt

2016-04-30 09:18:26.516 8740 INFO neutron.api.extensions [-] Loaded extension: extraroute

2016-04-30 09:18:26.516 8740 WARNING neutron.api.extensions [-] Extension flavor not supported by any of loaded plugins

2016-04-30 09:18:26.517 8740 INFO neutron.api.extensions [-] Loaded extension: router

2016-04-30 09:18:26.517 8740 INFO neutron.api.extensions [-] Loaded extension: ext-gw-mode

2016-04-30 09:18:26.518 8740 INFO neutron.api.extensions [-] Loaded extension: l3-ha

2016-04-30 09:18:26.518 8740 INFO neutron.api.extensions [-] Loaded extension: l3_agent_scheduler

2016-04-30 09:18:26.524 8740 WARNING neutron.api.extensions [-] Extension metering not supported by any of loaded plugins

2016-04-30 09:18:26.525 8740 INFO neutron.api.extensions [-] Loaded extension: multi-provider

2016-04-30 09:18:26.526 8740 INFO neutron.api.extensions [-] Loaded extension: net-mtu

2016-04-30 09:18:26.526 8740 INFO neutron.api.extensions [-] Loaded extension: binding

2016-04-30 09:18:26.527 8740 WARNING neutron.api.extensions [-] Extension port-security not supported by any of loaded plugins

2016-04-30 09:18:26.527 8740 INFO neutron.api.extensions [-] Loaded extension: provider

2016-04-30 09:18:26.528 8740 INFO neutron.api.extensions [-] Loaded extension: quotas

2016-04-30 09:18:26.528 8740 WARNING neutron.api.extensions [-] Extension router-service-type not supported by any of loaded plugins

2016-04-30 09:18:26.530 8740 INFO neutron.api.extensions [-] Loaded extension: security-group

2016-04-30 09:18:26.531 8740 WARNING neutron.api.extensions [-] Extension service-type not supported by any of loaded plugins

2016-04-30 09:18:26.531 8740 INFO neutron.api.extensions [-] Loaded extension: subnet_allocation

2016-04-30 09:18:26.532 8740 WARNING neutron.api.extensions [-] Extension vlan-transparent not supported by any of loaded plugins

條件3: 判斷plugin所使用的類與加載的extension中的get_plugin_interface函數(如果有則判斷,否則直接傳回true,說明該extension加載成功)傳回類是否是同一個父類,如果是繼承同一父類則該extension加載成功,否則失敗。

最終self.extensions儲存所有的extension resource資訊。

self.extensions:

{

'security-group': <securitygroup.Securitygroup object at 0x3d00e10>,

'l3_agent_scheduler': <l3agentscheduler.L3agentscheduler object at 0x3cd7f90>,

'net-mtu': <netmtu.Netmtu object at 0x3cf7990>,

'ext-gw-mode': <l3_ext_gw_mode.L3_ext_gw_mode object at 0x3cd7d90>,

'binding': <portbindings.Portbindings object at 0x3cf7c50>,

'provider': <providernet.Providernet object at 0x3d00450>,

'agent': <agent.Agent object at 0x3c55d10>,

'quotas': <quotasv2.Quotasv2 object at 0x3d00750>,

'subnet_allocation': <subnetallocation.Subnetallocation object at 0x3d6b250>,

'dhcp_agent_scheduler': <dhcpagentscheduler.Dhcpagentscheduler object at 0x3cd06d0>,

'l3-ha': <l3_ext_ha_mode.L3_ext_ha_mode object at 0x3cd7e10>,

'multi-provider': <multiprovidernet.Multiprovidernet object at 0x3ce35d0>,

'external-net': <external_net.External_net object at 0x3cd07d0>,

'router': <l3.L3 object at 0x3cd7d10>,

'allowed-address-pairs': <allowedaddresspairs.Allowedaddresspairs object at 0x3c55d50>,

'extraroute': <extraroute.Extraroute object at 0x3cd7950>,

'extra_dhcp_opt': <extra_dhcp_opt.Extra_dhcp_opt object at 0x3cd7350>,

'dvr': <dvr.Dvr object at 0x3cd0750>

}

1.2.2 Deal with extension resource

#/neutron/api/v2/router.py:APIRouter
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
           
#/neutron/api/extensions.py:ExtensionManager
    def extend_resources(self, version, attr_map):
        """Extend resources with additional resources or attributes.

        :param: attr_map, the existing mapping from resource name to
        attrs definition.

        After this function, we will extend the attr_map if an extension
        wants to extend this map.
        """
        update_exts = []
        processed_exts = set()
        exts_to_process = self.extensions.copy()
        # Iterate until there are unprocessed extensions or if no progress
        # is made in a whole iteration
        while exts_to_process:
            processed_ext_count = len(processed_exts)
            for ext_name, ext in exts_to_process.items():
                if not hasattr(ext, 'get_extended_resources'):
                    del exts_to_process[ext_name]
                    continue
                if hasattr(ext, 'update_attributes_map'):
                    update_exts.append(ext)
                if hasattr(ext, 'get_required_extensions'):
                    # Process extension only if all required extensions
                    # have been processed already
                    required_exts_set = set(ext.get_required_extensions())
                    if required_exts_set - processed_exts:
                        continue
                try:
                    extended_attrs = ext.get_extended_resources(version)
                    for resource, resource_attrs in extended_attrs.iteritems():
                        if attr_map.get(resource, None):
                            attr_map[resource].update(resource_attrs)
                        else:
                            attr_map[resource] = resource_attrs
                except AttributeError:
                    LOG.exception(_LE("Error fetching extended attributes for "
                                      "extension '%s'"), ext.get_name())
                processed_exts.add(ext_name)
                del exts_to_process[ext_name]
            if len(processed_exts) == processed_ext_count:
                # Exit loop as no progress was made
                break
        if exts_to_process:
            # NOTE(salv-orlando): Consider whether this error should be fatal
            LOG.error(_LE("It was impossible to process the following "
                          "extensions: %s because of missing requirements."),
                      ','.join(exts_to_process.keys()))

        # Extending extensions' attributes map.
        for ext in update_exts:
            ext.update_attributes_map(attr_map)
           

extend_resources函數主要是處理extension resource(擴充現有resource或增加一些新的resource)。

首先,extensionresource所包含的類必須實作了get_extended_resources函數。

其次,如果extensionresource所包含的類想使用其他的resource(包括core resource和extension resource)來更新自身的resource attribute,則需要實作update_attributes_map函數。

再者,有些extensionresource需要依賴其他resource,則需實作get_required_extensions函數,且其依賴的resource需在自身加載前加載到resource池中。

我們這裡舉例說明extensionresource加載的過程。

1. 更新現有資源(如ports resource)

在加載extensionresource之前,我們知道coreresource ports定義(即attributes.RESOURCE_ATTRIBUTE_MAP)如下。

#/neutron/api/v2/attributes.py
# Define constants for base resource name
NETWORK = 'network'
NETWORKS = '%ss' % NETWORK
PORT = 'port'
PORTS = '%ss' % PORT
SUBNET = 'subnet'
SUBNETS = '%ss' % SUBNET
SUBNETPOOL = 'subnetpool'
SUBNETPOOLS = '%ss' % SUBNETPOOL
        ... ... ...

RESOURCE_ATTRIBUTE_MAP = {
        ... ... ...
    PORTS: {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'name': {'allow_post': True, 'allow_put': True, 'default': '',
                 'validate': {'type:string': NAME_MAX_LEN},
                 'is_visible': True},
        'network_id': {'allow_post': True, 'allow_put': False,
                       'required_by_policy': True,
                       'validate': {'type:uuid': None},
                       'is_visible': True},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': convert_to_boolean,
                           'is_visible': True},
        'mac_address': {'allow_post': True, 'allow_put': True,
                        'default': ATTR_NOT_SPECIFIED,
                        'validate': {'type:mac_address': None},
                        'enforce_policy': True,
                        'is_visible': True},
        'fixed_ips': {'allow_post': True, 'allow_put': True,
                      'default': ATTR_NOT_SPECIFIED,
                      'convert_list_to': convert_kvp_list_to_dict,
                      'validate': {'type:fixed_ips': None},
                      'enforce_policy': True,
                      'is_visible': True},
        'device_id': {'allow_post': True, 'allow_put': True,
                      'validate': {'type:string': DEVICE_ID_MAX_LEN},
                      'default': '',
                      'is_visible': True},
        'device_owner': {'allow_post': True, 'allow_put': True,
                         'validate': {'type:string': DEVICE_OWNER_MAX_LEN},
                         'default': '',
                         'is_visible': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string': TENANT_ID_MAX_LEN},
                      'required_by_policy': True,
                      'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
    },        
... ... ...
}
           

而extensionresource中也包括ports相關的resource。是以将執行extend_resources 函數中的attr_map[resource].update(resource_attrs)代碼進行update。

其中,extensionresource的ports資訊如下。

#/neutron/extensions/portbindings.py
# The type of vnic that this port should be attached to
VNIC_TYPE = 'binding:vnic_type'
# The service will return the vif type for the specific port.
VIF_TYPE = 'binding:vif_type'
# The service may return a dictionary containing additional
# information needed by the interface driver. The set of items
# returned may depend on the value of VIF_TYPE.
VIF_DETAILS = 'binding:vif_details'
# In some cases different implementations may be run on different hosts.
# The host on which the port will be allocated.
HOST_ID = 'binding:host_id'
# The profile will be a dictionary that enables the application running
# on the specific host to pass and receive vif port specific information to
# the plugin.
PROFILE = 'binding:profile'

... ... ...

EXTENDED_ATTRIBUTES_2_0 = {
    'ports': {
        VIF_TYPE: {'allow_post': False, 'allow_put': False,
                   'default': attributes.ATTR_NOT_SPECIFIED,
                   'enforce_policy': True,
                   'is_visible': True},
        VIF_DETAILS: {'allow_post': False, 'allow_put': False,
                      'default': attributes.ATTR_NOT_SPECIFIED,
                      'enforce_policy': True,
                      'is_visible': True},
        VNIC_TYPE: {'allow_post': True, 'allow_put': True,
                    'default': VNIC_NORMAL,
                    'is_visible': True,
                    'validate': {'type:values': VNIC_TYPES},
                    'enforce_policy': True},
        HOST_ID: {'allow_post': True, 'allow_put': True,
                  'default': attributes.ATTR_NOT_SPECIFIED,
                  'is_visible': True,
                  'enforce_policy': True},
        PROFILE: {'allow_post': True, 'allow_put': True,
                  'default': attributes.ATTR_NOT_SPECIFIED,
                  'enforce_policy': True,
                  'validate': {'type:dict_or_none': None},
                  'is_visible': True},
    }
}

#/neutron/extensions/securitygroup.py
SECURITYGROUPS = 'security_groups'
EXTENDED_ATTRIBUTES_2_0 = {
    'ports': {SECURITYGROUPS: {'allow_post': True,
                               'allow_put': True,
                               'is_visible': True,
                               'convert_to': convert_to_uuid_list_or_none,
                               'default': attr.ATTR_NOT_SPECIFIED}}}

#/neutron/extensions/extra_dhcp_opt.py
# Attribute Map
EXTRADHCPOPTS = 'extra_dhcp_opts'

# Common definitions for maximum string field length
DHCP_OPT_NAME_MAX_LEN = 64
DHCP_OPT_VALUE_MAX_LEN = 255

EXTENDED_ATTRIBUTES_2_0 = {
    'ports': {
        EXTRADHCPOPTS:
        {'allow_post': True,
         'allow_put': True,
         'is_visible': True,
         'default': None,
         'validate': {
             'type:list_of_dict_or_none': {
                 'id': {'type:uuid': None, 'required': False},
                 'opt_name': {'type:not_empty_string': DHCP_OPT_NAME_MAX_LEN,
                              'required': True},
                 'opt_value': {'type:not_empty_string_or_none':
                               DHCP_OPT_VALUE_MAX_LEN,
                               'required': True},
                 'ip_version': {'convert_to': attr.convert_to_int,
                                'type:values': [4, 6],
                                'required': False}}}}}}

#/neutron/extensions/allowedaddresspairs.py
ADDRESS_PAIRS = 'allowed_address_pairs'
EXTENDED_ATTRIBUTES_2_0 = {
    'ports': {
        ADDRESS_PAIRS: {'allow_post': True, 'allow_put': True,
                        'convert_list_to':
                        attr.convert_kvp_list_to_dict,
                        'validate': {'type:validate_allowed_address_pairs':
                                     None},
                        'enforce_policy': True,
                        'default': attr.ATTR_NOT_SPECIFIED,
                        'is_visible': True},
    }
}
           

将core resource的ports(10個屬性)與extensionresource的ports(總和8個屬性)更新到attributes.RESOURCE_ATTRIBUTE_MAP中,最終的ports資訊為:

'ports':

{

'status': {'is_visible': True, 'allow_put': False, 'allow_post': False},

'extra_dhcp_opts': {'default': None, 'is_visible': True, 'validate': {'type:list_of_dict_or_none': {'opt_value': {'type:not_empty_string_or_none': 255, 'required': True}, 'ip_version': {'convert_to': <function convert_to_int at 0x32cb6e0>, 'required': False, 'type:values': [4, 6]}, 'opt_name': {'type:not_empty_string': 64, 'required': True}, 'id': {'type:uuid': None, 'required': False}}}, 'allow_put': True, 'allow_post': True},

'binding:host_id': {'default': <object object at 0x7fa11087bc00>, 'is_visible': True, 'allow_put': True, 'allow_post': True, 'enforce_policy': True},

'name': {'default': '', 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': True, 'allow_post': True},

'allowed_address_pairs': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'convert_list_to': <function convert_kvp_list_to_dict at 0x32cb848>, 'enforce_policy': True, 'validate': {'type:validate_allowed_address_pairs': None}},

'admin_state_up': {'default': True, 'convert_to': <function convert_to_boolean at 0x32cb5f0>, 'allow_put': True, 'allow_post': True, 'is_visible': True},

'network_id': {'required_by_policy': True, 'is_visible': True, 'validate': {'type:uuid': None}, 'allow_put': False, 'allow_post': True},

'tenant_id': {'required_by_policy': True, 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': False, 'allow_post': True},

'binding:vif_details': {'default': <object object at 0x7fa11087bc00>, 'enforce_policy': True, 'allow_put': False, 'allow_post': False, 'is_visible': True},

'binding:vnic_type': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': 'normal', 'enforce_policy': True, 'validate': {'type:values': ['normal', 'direct', 'macvtap']}},

'binding:vif_type': {'default': <object object at 0x7fa11087bc00>, 'enforce_policy': True, 'allow_put': False, 'allow_post': False, 'is_visible': True},

'device_owner': {'default': '', 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': True, 'allow_post': True},

'mac_address': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'enforce_policy': True, 'validate': {'type:mac_address': None}},

'binding:profile': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'enforce_policy': True, 'validate': {'type:dict_or_none': None}},

'fixed_ips': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'convert_list_to': <function convert_kvp_list_to_dict at 0x32cb848>, 'enforce_policy': True, 'validate': {'type:fixed_ips': None}},

'id': {'is_visible': True, 'validate': {'type:uuid': None}, 'allow_put': False, 'primary_key': True, 'allow_post': False},

'security_groups': {'default': <object object at 0x7fa11087bc00>, 'is_visible': True, 'allow_put': True, 'allow_post': True, 'convert_to': <function convert_to_uuid_list_or_none at 0x4e6f230>},

'device_id': {'default': '', 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': True, 'allow_post': True}

}

2. 增加新的resource(比如routers resource)

在加載extensionresource之前,attributes.RESOURCE_ATTRIBUTE_MAP中沒有routers resource。在執行extend_resources函數後,attributes.RESOURCE_ATTRIBUTE_MAP中包含routers resource。即在extend_resources的attr_map[resource] = resource_attrs代碼進行加載。

其實,隻有第一次加載routersresource執行的attr_map[resource] =resource_attrs代碼,後面有關routers resource的加載都是執行attr_map[resource].update(resource_attrs)代碼。因為在第一加載routers resource後attributes.RESOURCE_ATTRIBUTE_MAP中已經有routers resource了。

加載的routersresource的資訊如下。

#/neutron/extensions/l3.py
ROUTERS = 'routers'
EXTERNAL_GW_INFO = 'external_gateway_info'

RESOURCE_ATTRIBUTE_MAP = {
    ROUTERS: {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'name': {'allow_post': True, 'allow_put': True,
                 'validate': {'type:string': attr.NAME_MAX_LEN},
                 'is_visible': True, 'default': ''},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': attr.convert_to_boolean,
                           'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'required_by_policy': True,
                      'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
                      'is_visible': True},
        EXTERNAL_GW_INFO: {'allow_post': True, 'allow_put': True,
                           'is_visible': True, 'default': None,
                           'enforce_policy': True,
                           'validate': {
                               'type:dict_or_nodata': {
                                   'network_id': {'type:uuid': None,
                                                  'required': True},
                                   'external_fixed_ips': {
                                       'convert_list_to':
                                       attr.convert_kvp_list_to_dict,
                                       'type:fixed_ips': None,
                                       'default': None,
                                       'required': False,
                                   }
                               }
                           }}
    },
... ... ...
}

#/neutron/extensions/extraroute.py
# Attribute Map
EXTENDED_ATTRIBUTES_2_0 = {
    'routers': {
        'routes': {'allow_post': False, 'allow_put': True,
                   'validate': {'type:hostroutes': None},
                   'convert_to': attr.convert_none_to_empty_list,
                   'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
    }
}

#/neutron/extensions/l3_ext_ha_mode.py
HA_INFO = 'ha'
EXTENDED_ATTRIBUTES_2_0 = {
    'routers': {
        HA_INFO: {'allow_post': True, 'allow_put': False,
                  'default': attributes.ATTR_NOT_SPECIFIED, 'is_visible': True,
                  'enforce_policy': True,
                  'convert_to': attributes.convert_to_boolean_if_not_none}
    }
}

#/neutron/extensions/dvr.py
DISTRIBUTED = 'distributed'
EXTENDED_ATTRIBUTES_2_0 = {
    'routers': {
        DISTRIBUTED: {'allow_post': True,
                      'allow_put': True,
                      'is_visible': True,
                      'default': attributes.ATTR_NOT_SPECIFIED,
                      'convert_to': attributes.convert_to_boolean_if_not_none,
                      'enforce_policy': True},
    }
}
           

最終加載routersresource完成後,attributes.RESOURCE_ATTRIBUTE_MAP有關routers resource資訊如下。

'routers':

{

'status': {'is_visible': True, 'allow_put': False, 'allow_post': False},

'external_gateway_info': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': None, 'enforce_policy': True, 'validate': {'type:dict_or_nodata': {'network_id': {'type:uuid': None, 'required': True}, 'enable_snat': {'convert_to': <function convert_to_boolean at 0x32cb5f0>, 'required': False, 'type:boolean': None}, 'external_fixed_ips': {'default': None, 'validate': {'type:fixed_ips': None}, 'convert_list_to': <function convert_kvp_list_to_dict at 0x32cb848>, 'required': False}}}},

'name': {'default': '', 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': True, 'allow_post': True},

'admin_state_up': {'default': True, 'convert_to': <function convert_to_boolean at 0x32cb5f0>, 'allow_put': True, 'allow_post': True, 'is_visible': True},

'tenant_id': {'required_by_policy': True, 'is_visible': True, 'validate': {'type:string': 255}, 'allow_put': False, 'allow_post': True},

'distributed': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'convert_to': <function convert_to_boolean_if_not_none at 0x32cb668>, 'enforce_policy': True},

'routes': {'is_visible': True, 'allow_put': True, 'allow_post': False, 'default': <object object at 0x7fa11087bc00>, 'convert_to': <function convert_none_to_empty_list at 0x32cb8c0>, 'validate': {'type:hostroutes': None}},

'ha': {'is_visible': True, 'allow_put': False, 'allow_post': True, 'default': <object object at 0x7fa11087bc00>, 'convert_to': <function convert_to_boolean_if_not_none at 0x32cb668>, 'enforce_policy': True},

'id': {'is_visible': True, 'validate': {'type:uuid': None}, 'allow_put': False, 'primary_key': True, 'allow_post': False}

}

最終,extensionresource中與core resource相關的資源也加載并更新到attributes.RESOURCE_ATTRIBUTE_MAP字典中去了。我們知道neutron-server接收到使用者的HTTP請求後會通過Router子產品将其路由到相關資源的Controller中去執行對應的操作。而這個Controller的生成就是下面将介紹的。

#/neutron/api/v2/router.py:APIRouter
class APIRouter(wsgi.Router):

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)

    def __init__(self, **local_config):
        mapper = routes_mapper.Mapper()
        plugin = manager.NeutronManager.get_plugin()
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
        ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
                          member_actions=MEMBER_ACTIONS)

        def _map_resource(collection, resource, params, parent=None):
            allow_bulk = cfg.CONF.allow_bulk
            allow_pagination = cfg.CONF.allow_pagination
            allow_sorting = cfg.CONF.allow_sorting
            controller = base.create_resource(
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=allow_pagination,
                allow_sorting=allow_sorting)
            path_prefix = None
            if parent:
                path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
                                                  parent['member_name'],
                                                  collection)
            mapper_kwargs = dict(controller=controller,
                                 requirements=REQUIREMENTS,
                                 path_prefix=path_prefix,
                                 **col_kwargs)
            return mapper.collection(collection, resource,
                                     **mapper_kwargs)

        mapper.connect('index', '/', controller=Index(RESOURCES))
        for resource in RESOURCES:
            _map_resource(RESOURCES[resource], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              RESOURCES[resource], dict()))

        for resource in SUB_RESOURCES:
            _map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              SUB_RESOURCES[resource]['collection_name'],
                              dict()),
                          SUB_RESOURCES[resource]['parent'])

        # Certain policy checks require that the extensions are loaded
        # and the RESOURCE_ATTRIBUTE_MAP populated before they can be
        # properly initialized. This can only be claimed with certainty
        # once this point in the code has been reached. In the event
        # that the policies have been initialized before this point,
        # calling reset will cause the next policy check to
        # re-initialize with all of the required data in place.
        policy.reset()
        super(APIRouter, self).__init__(mapper)
           

/neutron/api/v2/router.py:APIRouter的__init__函數剩餘的代碼便是建立core resource的Controller。對于core resource來說,都使用了base.py檔案中的類Controller去實作,隻是封裝成WSGI Application的時候調用這個檔案中的create_resource()函數根據不同的參數動态建立對應的Controller對象。

#/neutron/api/v2/base.py
def create_resource(collection, resource, plugin, params, allow_bulk=False,
                    member_actions=None, parent=None, allow_pagination=False,
                    allow_sorting=False):
    controller = Controller(plugin, collection, resource, params, allow_bulk,
                            member_actions=member_actions, parent=parent,
                            allow_pagination=allow_pagination,
                            allow_sorting=allow_sorting)

    return wsgi_resource.Resource(controller, FAULT_MAP)

#/neutron/api/v2/base.py:Controller
class Controller(object):
    LIST = 'list'
    SHOW = 'show'
    CREATE = 'create'
    UPDATE = 'update'
    DELETE = 'delete'

    def __init__(self, plugin, collection, resource, attr_info,
                 allow_bulk=False, member_actions=None, parent=None,
                 allow_pagination=False, allow_sorting=False):
        if member_actions is None:
            member_actions = []
        self._plugin = plugin
        self._collection = collection.replace('-', '_')
        self._resource = resource.replace('-', '_')
        self._attr_info = attr_info
        self._allow_bulk = allow_bulk
        self._allow_pagination = allow_pagination
        self._allow_sorting = allow_sorting
        self._native_bulk = self._is_native_bulk_supported()
        self._native_pagination = self._is_native_pagination_supported()
        self._native_sorting = self._is_native_sorting_supported()
        self._policy_attrs = [name for (name, info) in self._attr_info.items()
                              if info.get('required_by_policy')]
        self._notifier = n_rpc.get_notifier('network')
        # use plugin's dhcp notifier, if this is already instantiated
        agent_notifiers = getattr(plugin, 'agent_notifiers', {})
        self._dhcp_agent_notifier = (
            agent_notifiers.get(const.AGENT_TYPE_DHCP) or
            dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
        )
        if cfg.CONF.notify_nova_on_port_data_changes:
            from neutron.notifiers import nova
            self._nova_notifier = nova.Notifier()
        self._member_actions = member_actions
        self._primary_key = self._get_primary_key()
        if self._allow_pagination and self._native_pagination:
            # Native pagination need native sorting support
            if not self._native_sorting:
                raise exceptions.Invalid(
                    _("Native pagination depend on native sorting")
                )
            if not self._allow_sorting:
                LOG.info(_LI("Allow sorting is enabled because native "
                             "pagination requires native sorting"))
                self._allow_sorting = True

        if parent:
            self._parent_id_name = '%s_id' % parent['member_name']
            parent_part = '_%s' % parent['member_name']
        else:
            self._parent_id_name = None
            parent_part = ''
        self._plugin_handlers = {
            self.LIST: 'get%s_%s' % (parent_part, self._collection),
            self.SHOW: 'get%s_%s' % (parent_part, self._resource)
        }
        for action in [self.CREATE, self.UPDATE, self.DELETE]:
            self._plugin_handlers[action] = '%s%s_%s' % (action, parent_part,
                                                         self._resource)
           

這裡core resource的建立Controller對象采用的plugin為Ml2Plugin對象。是以core resource的HTTP請求最終會調用到Ml2Plugin類或其父類中的函數。後面将舉例說明。

而對于extension resource,neutron仍然使用了傳統的方式來實作。它們在/neutron/extensions目錄下都分别有對應的實作檔案和對應的Controller類,位于/neutron/api目錄下的extension.py檔案隻是一些基類和共用的代碼。即extension resource是使用plugin_aware_extension_middleware_factory函數進行建立Controller的。在/etc/neutron/ api-paste.ini檔案能檢視的。

[composite:neutron]

use = egg:Paste#urlmap

/: neutronversions

/v2.0: neutronapi_v2_0

[composite:neutronapi_v2_0]

use = call:neutron.auth:pipeline_factory

noauth = request_id catch_errors extensions neutronapiapp_v2_0

keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

[filter:request_id]

paste.filter_factory = oslo.middleware:RequestId.factory

[filter:catch_errors]

paste.filter_factory = oslo.middleware:CatchErrors.factory

[filter:keystonecontext]

paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory

[filter:authtoken]

paste.filter_factory = keystonemiddleware.auth_token:filter_factory

[filter:extensions]

paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

[app:neutronversions]

paste.app_factory = neutron.api.versions:Versions.factory

[app:neutronapiapp_v2_0]

paste.app_factory = neutron.api.v2.router:APIRouter.factory

plugin_aware_extension_middleware_factory函數的代碼如下。

#/neutron/api/extensions.py
def plugin_aware_extension_middleware_factory(global_config, **local_config):
    """Paste factory."""
    def _factory(app):
        ext_mgr = PluginAwareExtensionManager.get_instance()
        return ExtensionMiddleware(app, ext_mgr=ext_mgr)
    return _factory

#/neutron/api/extensions.py:ExtensionMiddleware
class ExtensionMiddleware(wsgi.Middleware):
    """Extensions middleware for WSGI."""

    def __init__(self, application,
                 ext_mgr=None):
        self.ext_mgr = (ext_mgr
                        or ExtensionManager(get_extensions_path()))
        mapper = routes.Mapper()

        # extended resources
        for resource in self.ext_mgr.get_resources():
            path_prefix = resource.path_prefix
            if resource.parent:
                path_prefix = (resource.path_prefix +
                               "/%s/{%s_id}" %
                               (resource.parent["collection_name"],
                                resource.parent["member_name"]))

            LOG.debug('Extended resource: %s',
                      resource.collection)
            for action, method in resource.collection_actions.iteritems():
                conditions = dict(method=[method])
                path = "/%s/%s" % (resource.collection, action)
                with mapper.submapper(controller=resource.controller,
                                      action=action,
                                      path_prefix=path_prefix,
                                      conditions=conditions) as submap:
                    submap.connect(path)
                    submap.connect("%s.:(format)" % path)

            mapper.resource(resource.collection, resource.collection,
                            controller=resource.controller,
                            member=resource.member_actions,
                            parent_resource=resource.parent,
                            path_prefix=path_prefix)

        # extended actions
        action_controllers = self._action_ext_controllers(application,
                                                          self.ext_mgr, mapper)
        for action in self.ext_mgr.get_actions():
            LOG.debug('Extended action: %s', action.action_name)
            controller = action_controllers[action.collection]
            controller.add_action(action.action_name, action.handler)

        # extended requests
        req_controllers = self._request_ext_controllers(application,
                                                        self.ext_mgr, mapper)
        for request_ext in self.ext_mgr.get_request_extensions():
            LOG.debug('Extended request: %s', request_ext.key)
            controller = req_controllers[request_ext.key]
            controller.add_handler(request_ext.handler)

        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          mapper)
        super(ExtensionMiddleware, self).__init__(application)
           

其實重點在get_resources函數,這裡将調用每個extension resource所對應對象的get_resources函數。比如l3.py檔案中的extension resource

#/neutron/extensions/l3.py:L3
    @classmethod
    def get_resources(cls):
        """Returns Ext Resources."""
        plural_mappings = resource_helper.build_plural_mappings(
            {}, RESOURCE_ATTRIBUTE_MAP)
        plural_mappings['external_fixed_ips'] = 'external_fixed_ip'
        attr.PLURALS.update(plural_mappings)
        action_map = {'router': {'add_router_interface': 'PUT',
                                 'remove_router_interface': 'PUT'}}
        return resource_helper.build_resource_info(plural_mappings,
                                                   RESOURCE_ATTRIBUTE_MAP,
                                                   constants.L3_ROUTER_NAT,
                                                   action_map=action_map,
                                                   register_quota=True)

#/neutron/api/v2/resource_helper.py
def build_resource_info(plural_mappings, resource_map, which_service,
                        action_map=None, register_quota=False,
                        translate_name=False, allow_bulk=False):
    """Build resources for advanced services.

    Takes the resource information, and singular/plural mappings, and creates
    API resource objects for advanced services extensions. Will optionally
    translate underscores to dashes in resource names, register the resource,
    and accept action information for resources.

    :param plural_mappings: mappings between singular and plural forms
    :param resource_map: attribute map for the WSGI resources to create
    :param which_service: The name of the service for which the WSGI resources
                          are being created. This name will be used to pass
                          the appropriate plugin to the WSGI resource.
                          It can be set to None or "CORE" to create WSGI
                          resources for the core plugin
    :param action_map: custom resource actions
    :param register_quota: it can be set to True to register quotas for the
                           resource(s) being created
    :param translate_name: replaces underscores with dashes
    :param allow_bulk: True if bulk create are allowed
    """
    resources = []
    if not which_service:
        which_service = constants.CORE
    if action_map is None:
        action_map = {}
    if which_service != constants.CORE:
        plugin = manager.NeutronManager.get_service_plugins()[which_service]
    else:
        plugin = manager.NeutronManager.get_plugin()
    for collection_name in resource_map:
        resource_name = plural_mappings[collection_name]
        params = resource_map.get(collection_name, {})
        if translate_name:
            collection_name = collection_name.replace('_', '-')
        if register_quota:
            quota.QUOTAS.register_resource_by_name(resource_name)
        member_actions = action_map.get(resource_name, {})
        controller = base.create_resource(
            collection_name, resource_name, plugin, params,
            member_actions=member_actions,
            allow_bulk=allow_bulk,
            allow_pagination=cfg.CONF.allow_pagination,
            allow_sorting=cfg.CONF.allow_sorting)
        resource = extensions.ResourceExtension(
            collection_name,
            controller,
            path_prefix=constants.COMMON_PREFIXES[which_service],
            member_actions=member_actions,
            attr_map=params)
        resources.append(resource)
    return resources
           

因為/neutron/extensions/l3.py:L3的get_resources函數調用build_resource_info函數傳入的which_service為constants.L3_ROUTER_NAT,而manager.NeutronManager.get_service_plugins()函數傳回的資訊為:

{'L3_ROUTER_NAT': <neutron.services.l3_router.l3_router_plugin.L3RouterPlugin object at 0x42038d0>, 'CORE': <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x360d910>}

是以執行base.create_resource函數傳入的plugin參數為L3RouterPlugin對象。是以對于HTTP請求extensionresource的router資訊時,最終會調用L3RouterPlugin類或其父類的函數。其中/neutron/extensions/l3.py的RouterPluginBase類為L3RouterPlugin的頂級父類。

#/neutron/extensions/l3.py:RouterPluginBase
class RouterPluginBase(object):

    @abc.abstractmethod
    def create_router(self, context, router):
        pass

    @abc.abstractmethod
    def update_router(self, context, id, router):
        pass

    @abc.abstractmethod
    def get_router(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def delete_router(self, context, id):
        pass

    @abc.abstractmethod
    def get_routers(self, context, filters=None, fields=None,
                    sorts=None, limit=None, marker=None, page_reverse=False):
        pass

    @abc.abstractmethod
    def add_router_interface(self, context, router_id, interface_info):
        pass

    @abc.abstractmethod
    def remove_router_interface(self, context, router_id, interface_info):
        pass

    @abc.abstractmethod
    def create_floatingip(self, context, floatingip):
        pass

    @abc.abstractmethod
    def update_floatingip(self, context, id, floatingip):
        pass

    @abc.abstractmethod
    def get_floatingip(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def delete_floatingip(self, context, id):
        pass

    @abc.abstractmethod
    def get_floatingips(self, context, filters=None, fields=None,
                        sorts=None, limit=None, marker=None,
                        page_reverse=False):
        pass

    def get_routers_count(self, context, filters=None):
        raise NotImplementedError()

    def get_floatingips_count(self, context, filters=None):
        raise NotImplementedError()
           

下面我們舉例說明如何調用core resource的函數的,這裡我利用neutron port-show指令進行說明。

[[email protected] ~(keystone_admin)]# neutron port-list

+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+

| id                                   | name | mac_address       | fixed_ips                                                                          |

+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+

| 7a00401c-ecbf-493d-9841-e902b40f66a7 |      | fa:16:3e:77:46:16 | {"subnet_id": "4751cf4d-2aba-46fa-94cb-63cbfc854592", "ip_address": "192.168.0.2"} |

+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+

我們檢視上述port的具體資訊,即執行neutron port-show指令

[[email protected] ~(keystone_admin)]# neutron port-show 7a00401c-ecbf-493d-9841-e902b40f66a7

+-----------------------+------------------------------------------------------------------------------------+

| Field                 | Value                                                                              |

+-----------------------+------------------------------------------------------------------------------------+

| admin_state_up        | True                                                                               |

| allowed_address_pairs |                                                                                    |

| binding:host_id       | jun2                                                                               |

| binding:profile       | {}                                                                                 |

| binding:vif_details   | {"port_filter": true}                                                              |

| binding:vif_type      | bridge                                                                             |

| binding:vnic_type     | normal                                                                             |

| device_id             | dhcp2156d71d-f5c3-5752-9e43-4e8290a5696a-5eea5aca-a126-4bb9-b21e-907c33d4200b      |

| device_owner          | network:dhcp                                                                       |

| extra_dhcp_opts       |                                                                                    |

| fixed_ips             | {"subnet_id": "4751cf4d-2aba-46fa-94cb-63cbfc854592", "ip_address": "192.168.0.2"} |

| id                    | 7a00401c-ecbf-493d-9841-e902b40f66a7                                               |

| mac_address           | fa:16:3e:77:46:16                                                                  |

| name                  |                                                                                    |

| network_id            | 5eea5aca-a126-4bb9-b21e-907c33d4200b                                               |

| security_groups       |                                                                                    |

| status                | ACTIVE                                                                             |

| tenant_id             | 09e04766c06d477098201683497d3878                                                   |

+-----------------------+------------------------------------------------------------------------------------+

show查詢出來的port資訊的Field有18條,正好是core resource的ports(10個屬性)與extensionresource的ports(總和8個屬性)的總和。其實這些資訊其實在neutron資料庫的不同table中,那麼我想知道如何去查詢到的呢?

首先,neutronclient發送HTTP請求到neutron-server的show函數中(core resource的show函數),如下

#/neutron/api/v2/base.py:Controller
    def show(self, request, id, **kwargs):
        """Returns detailed information about the requested entity."""
        try:
            # NOTE(salvatore-orlando): The following ensures that fields
            # which are needed for authZ policy validation are not stripped
            # away by the plugin before returning.
            field_list, added_fields = self._do_field_list(
                api_common.list_args(request, "fields"))
            parent_id = kwargs.get(self._parent_id_name)
            # Ensure policy engine is initialized
            policy.init()
            return {self._resource:
                    self._view(request.context,
                               self._item(request,
                                          id,
                                          do_authz=True,
                                          field_list=field_list,
                                          parent_id=parent_id),
                               fields_to_strip=added_fields)}
        except common_policy.PolicyNotAuthorized:
            # To avoid giving away information, pretend that it
            # doesn't exist
            msg = _('The resource could not be found.')
            raise webob.exc.HTTPNotFound(msg)

#/neutron/api/v2/base.py:Controller
    def _item(self, request, id, do_authz=False, field_list=None,
              parent_id=None):
        """Retrieves and formats a single element of the requested entity."""
        kwargs = {'fields': field_list}
        action = self._plugin_handlers[self.SHOW]
        if parent_id:
            kwargs[self._parent_id_name] = parent_id
        obj_getter = getattr(self._plugin, action)
        obj = obj_getter(request.context, id, **kwargs)
        # Check authz
        # FIXME(salvatore-orlando): obj_getter might return references to
        # other resources. Must check authZ on them too.
        if do_authz:
            policy.enforce(request.context,
                           action,
                           obj,
                           pluralized=self._collection)
        return obj
           

port的資訊在_item函數中執行core plugin上相應的函數後,傳回到show函數。那麼将調用的core plugin的哪個函數呢?根據Controller的初始化函數(__init__),将調用get_port函數,而core plugin為Ml2Plugin對象。如下

#/neutron/db/db_base_plugin_v2.py:NeutronDbPluginV2
    def get_port(self, context, id, fields=None):
        port = self._get_port(context, id)
        return self._make_port_dict(port, fields)

#/neutron/db/db_base_plugin_v2.py:NeutronDbPluginV2
    def _get_port(self, context, id):
        try:
            port = self._get_by_id(context, models_v2.Port, id)
        except exc.NoResultFound:
            raise n_exc.PortNotFound(port_id=id)
        return port
           

由于Ml2Plugin類繼承NeutronDbPluginV2類,且Ml2Plugin并未重寫get_port函數,是以将調用NeutronDbPluginV2類的get_port函數。

從上面可以看出,get_port函數将調用_get_port函數根據port id去查詢models_v2.Port表(對于neutron資料庫中ports表)中的port資訊。如下

MariaDB [neutron]> select * from ports;

+----------------------------------+--------------------------------------+------+--------------------------------------+-------------------+----------------+--------+-------------------------------------------------------------------------------+--------------+

| tenant_id                        | id                                   | name | network_id                           | mac_address       | admin_state_up | status | device_id                                                                     | device_owner |

+----------------------------------+--------------------------------------+------+--------------------------------------+-------------------+----------------+--------+-------------------------------------------------------------------------------+--------------+

| 09e04766c06d477098201683497d3878 | 7a00401c-ecbf-493d-9841-e902b40f66a7 |      | 5eea5aca-a126-4bb9-b21e-907c33d4200b | fa:16:3e:77:46:16 |              1 | ACTIVE | dhcp2156d71d-f5c3-5752-9e43-4e8290a5696a-5eea5aca-a126-4bb9-b21e-907c33d4200b | network:dhcp |

+----------------------------------+--------------------------------------+------+--------------------------------------+-------------------+----------------+--------+-------------------------------------------------------------------------------+--------------+

1 row in set (0.00 sec)

_get_port函數查詢的結果如下:

<neutron.db.models_v2.Port[object at 432c090]

{

tenant_id=u'09e04766c06d477098201683497d3878',

id=u'7a00401c-ecbf-493d-9841-e902b40f66a7',

name=u'',

network_id=u'5eea5aca-a126-4bb9-b21e-907c33d4200b',

mac_address=u'fa:16:3e:77:46:16',

admin_state_up=True,

status=u'ACTIVE',

device_id=u'dhcp2156d71d-f5c3-5752-9e43-4e8290a5696a-5eea5aca-a126-4bb9-b21e-907c33d4200b',

device_owner=u'network:dhcp'

}

但是這裡隻有9條Field,但是models_v2.Port表定義了fixed_ips Field,為什麼neutron資料庫的ports表沒有fixed_ips Field?這樣才會恰好有10條Field與core resource的port Field相對應。我們檢視models_v2.Port表的定義。

#/neutron/db/models_v2.py:Port
class Port(model_base.BASEV2, HasId, HasTenant):
    """Represents a port on a Neutron v2 network."""

    name = sa.Column(sa.String(attr.NAME_MAX_LEN))
    network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
                           nullable=False)
    fixed_ips = orm.relationship(IPAllocation, backref='ports', lazy='joined')
    mac_address = sa.Column(sa.String(32), nullable=False)
    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
    status = sa.Column(sa.String(16), nullable=False)
    device_id = sa.Column(sa.String(attr.DEVICE_ID_MAX_LEN), nullable=False)
    device_owner = sa.Column(sa.String(attr.DEVICE_OWNER_MAX_LEN),
                             nullable=False)
    __table_args__ = (
        sa.Index(
            'ix_ports_network_id_mac_address', 'network_id', 'mac_address'),
        sa.Index(
            'ix_ports_network_id_device_owner', 'network_id', 'device_owner'),
        sa.UniqueConstraint(
            network_id, mac_address,
            name='uniq_ports0network_id0mac_address'),
        model_base.BASEV2.__table_args__
    )
           

結果發現fixed_ipsField與其他Field定義不同,fixed_ips Filed與models_v2.IPAllocation表相關聯。models_v2.IPAllocation表與neutron資料庫中的ipallocations表相對應。ipallocations表資訊如下。

MariaDB [neutron]> select * from ipallocations;

+--------------------------------------+-------------+--------------------------------------+--------------------------------------+

| port_id                              | ip_address  | subnet_id                            | network_id                           |

+--------------------------------------+-------------+--------------------------------------+--------------------------------------+

| 7a00401c-ecbf-493d-9841-e902b40f66a7 | 192.168.0.2 | 4751cf4d-2aba-46fa-94cb-63cbfc854592 | 5eea5aca-a126-4bb9-b21e-907c33d4200b |

+--------------------------------------+-------------+--------------------------------------+--------------------------------------+

是以猜測會在get_port函數的self._make_port_dict(port, fields)去查詢,且extension resource的Field也将在self._make_port_dict(port, fields)去查詢。

#/neutron/db/db_base_plugin_v2.py:NeutronDbPluginV2
    def _make_port_dict(self, port, fields=None,
                        process_extensions=True):
        res = {"id": port["id"],
               'name': port['name'],
               "network_id": port["network_id"],
               'tenant_id': port['tenant_id'],
               "mac_address": port["mac_address"],
               "admin_state_up": port["admin_state_up"],
               "status": port["status"],
               "fixed_ips": [{'subnet_id': ip["subnet_id"],
                              'ip_address': ip["ip_address"]}
                             for ip in port["fixed_ips"]],
               "device_id": port["device_id"],
               "device_owner": port["device_owner"]}
        # Call auxiliary extend functions, if any
        if process_extensions:
            self._apply_dict_extend_functions(
                attributes.PORTS, res, port)
        return self._fields(res, fields)
           

在self._apply_dict_extend_functions函數被調用之前(process_extensions為True),fixed_ips Field将被查詢到,不過不知道怎麼從ipallocations表查詢到的,應該跟sqlalchemy資料庫元件有關,希望知道的大神可以告知。

目前調用self._apply_dict_extend_functions函數之前,10個core resource所對應的Field已經查詢完成。如下

{

'status': u'ACTIVE',

'device_owner': u'network:dhcp',

'name': u'',

'mac_address': u'fa:16:3e:77:46:16',

'network_id': u'5eea5aca-a126-4bb9-b21e-907c33d4200b',

'tenant_id': u'09e04766c06d477098201683497d3878',

'admin_state_up': True,

'fixed_ips': [{'subnet_id': u'4751cf4d-2aba-46fa-94cb-63cbfc854592', 'ip_address': u'192.168.0.2'}],

'id': u'7a00401c-ecbf-493d-9841-e902b40f66a7',

'device_id': u'dhcp2156d71d-f5c3-5752-9e43-4e8290a5696a-5eea5aca-a126-4bb9-b21e-907c33d4200b'

}

但是,neutronport-show查詢的port資訊還包括extension resource的Field,那麼這些Field隻可能在self._apply_dict_extend_functions函數中查詢,并将查詢結果append到res變量中。

#/neutron/db/common_db_mixin.py:CommonDbMixin
    def _apply_dict_extend_functions(self, resource_type,
                                     response, db_object):
        for func in self._dict_extend_functions.get(
            resource_type, []):
            args = (response, db_object)
            if isinstance(func, basestring):
                func = getattr(self, func, None)
            else:
                # must call unbound method - use self as 1st argument
                args = (self,) + args
            if func:
                func(*args)
           

原來_apply_dict_extend_functions函數将根據_dict_extend_functions字典所對應的resource_type的函數,去執行相應的操作,是以我猜測extension resource會注冊與resource_type相對應的函數到_dict_extend_functions字典中。即調用這個函數(register_dict_extend_funcs)

#/neutron/db/common_db_mixin.py:CommonDbMixin
    @classmethod
    def register_dict_extend_funcs(cls, resource, funcs):
        cls._dict_extend_functions.setdefault(resource, []).extend(funcs)
           

首先列印這些函數為:

<bound method Ml2Plugin._extend_port_dict_allowed_address_pairs of <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x2c5f8d0>

<bound method Ml2Plugin._extend_port_dict_extra_dhcp_opt of <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x2c5f8d0>>

<bound method Ml2Plugin._extend_port_dict_security_group of <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x2c5f8d0>>

<bound method Ml2Plugin._ml2_extend_port_dict_binding of <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x2c5f8d0>>

<bound method Ml2Plugin._ml2_md_extend_port_dict of <neutron.plugins.ml2.plugin.Ml2Plugin object at 0x2c5f8d0>>

有5個函數,不過剩下的8個Field隻對應4個extension resource,怎麼會有5個函數,最後一個_ml2_md_extend_port_dict函數是ExtensionManager(/neutron/plugins/ml2/managers.py)類中的函數,在Ml2Plugin對象初始化時,什麼事都沒做,是以這裡我們暫時不看,或許以後有作用,隻是我還不清楚。

我們舉_ml2_extend_port_dict_binding進行分析extension resource關于port的resource查詢流程。

#/neutron/plugins/ml2/plugin.py:Ml2Plugin
    def _ml2_extend_port_dict_binding(self, port_res, port_db):
        # None when called during unit tests for other plugins.
        if port_db.port_binding:
            self._update_port_dict_binding(port_res, port_db.port_binding)

    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
        attributes.PORTS, ['_ml2_extend_port_dict_binding'])
           

原來這裡的确調用register_dict_extend_funcs去注冊了_extend_port_dict_allowed_address_pairs函數,resource_type為attr.PORTS(即’ports’)。

#/neutron/plugins/ml2/plugin.py:Ml2Plugin
    def _update_port_dict_binding(self, port, binding):
        port[portbindings.HOST_ID] = binding.host
        port[portbindings.VNIC_TYPE] = binding.vnic_type
        port[portbindings.PROFILE] = self._get_profile(binding)
        port[portbindings.VIF_TYPE] = binding.vif_type
        port[portbindings.VIF_DETAILS] = self._get_vif_details(binding)
           

這裡有個疑惑,_ml2_extend_port_dict_binding函數中的port_db是neutron.db.models_v2.Port對象,該對象的定義中沒有port_binding屬性,但是我列印的時候的确port_binding屬性,且為neutron.plugins.ml2.models.PortBinding對象。檢視PortBinding的定義。

#/neutron/plugins/ml2/plugin.py:PortBinding
class PortBinding(model_base.BASEV2):
    """Represent binding-related state of a port.

    A port binding stores the port attributes required for the
    portbindings extension, as well as internal ml2 state such as
    which MechanismDriver and which segment are used by the port
    binding.
    """

    __tablename__ = 'ml2_port_bindings'

    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
                        primary_key=True)
    host = sa.Column(sa.String(255), nullable=False, default='',
                     server_default='')
    vnic_type = sa.Column(sa.String(64), nullable=False,
                          default=portbindings.VNIC_NORMAL,
                          server_default=portbindings.VNIC_NORMAL)
    profile = sa.Column(sa.String(BINDING_PROFILE_LEN), nullable=False,
                        default='', server_default='')
    vif_type = sa.Column(sa.String(64), nullable=False)
    vif_details = sa.Column(sa.String(4095), nullable=False, default='',
                            server_default='')

    # Add a relationship to the Port model in order to instruct SQLAlchemy to
    # eagerly load port bindings
    port = orm.relationship(
        models_v2.Port,
        backref=orm.backref("port_binding",
                            lazy='joined', uselist=False,
                            cascade='delete'))
           

發現最後一條語句跟neutron.db.models_v2.Port類建立了關系,具體細節怎樣,不是很清楚,後面有時間研究一下sqlalchemy。

neutron資料庫中的ml2_port_bindings資訊如下。

MariaDB [neutron]> select * from ml2_port_bindings;

+--------------------------------------+------+----------+-----------+-----------------------+---------+

| port_id                              | host | vif_type | vnic_type | vif_details           | profile |

+--------------------------------------+------+----------+-----------+-----------------------+---------+

| 7a00401c-ecbf-493d-9841-e902b40f66a7 | jun2 | bridge   | normal    | {"port_filter": true} |         |

+--------------------------------------+------+----------+-----------+-----------------------+---------+

因為port_id Field在core resource已經有了,是以這裡隻記錄了紅色标記的Field。

對于剩餘的3個函數(除_ml2_md_extend_port_dict外)與_ml2_extend_port_dict_binding函數的查詢方式類似。

#/neutron/db/allowedaddresspairs_db.py:AllowedAddressPair
class AllowedAddressPair(model_base.BASEV2):
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
                        primary_key=True)
    mac_address = sa.Column(sa.String(32), nullable=False, primary_key=True)
    ip_address = sa.Column(sa.String(64), nullable=False, primary_key=True)

    port = orm.relationship(
        models_v2.Port,
        backref=orm.backref("allowed_address_pairs",
                            lazy="joined", cascade="delete"))

#/neutron/db/extradhcpopt_db.py:ExtraDhcpOpt
class ExtraDhcpOpt(model_base.BASEV2, models_v2.HasId):
    """Represent a generic concept of extra options associated to a port.

    Each port may have none to many dhcp opts associated to it that can
    define specifically different or extra options to DHCP clients.
    These will be written to the <network_id>/opts files, and each option's
    tag will be referenced in the <network_id>/host file.
    """
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
                        nullable=False)
    opt_name = sa.Column(sa.String(64), nullable=False)
    opt_value = sa.Column(sa.String(255), nullable=False)
    ip_version = sa.Column(sa.Integer, server_default='4', nullable=False)
    __table_args__ = (sa.UniqueConstraint(
        'port_id',
        'opt_name',
        'ip_version',
        name='uniq_extradhcpopts0portid0optname0ipversion'),
                      model_base.BASEV2.__table_args__,)

    # Add a relationship to the Port model in order to instruct SQLAlchemy to
    # eagerly load extra_dhcp_opts bindings
    ports = orm.relationship(
        models_v2.Port,
        backref=orm.backref("dhcp_opts", lazy='joined', cascade='delete'))

#/neutron/db/securitygroups_db.py:SecurityGroupPortBinding
class SecurityGroupPortBinding(model_base.BASEV2):
    """Represents binding between neutron ports and security profiles."""

    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey("ports.id",
                                      ondelete='CASCADE'),
                        primary_key=True)
    security_group_id = sa.Column(sa.String(36),
                                  sa.ForeignKey("securitygroups.id"),
                                  primary_key=True)

    # Add a relationship to the Port model in order to instruct SQLAlchemy to
    # eagerly load security group bindings
    ports = orm.relationship(
        models_v2.Port,
        backref=orm.backref("security_groups",
                            lazy='joined', cascade='delete'))
           

neutron資料庫的查詢結果為:

MariaDB [neutron]> select * from allowedaddresspairs;

Empty set (0.00 sec)

MariaDB [neutron]> select * from extradhcpopts;

Empty set (0.00 sec)

MariaDB [neutron]> select * from securitygroupportbindings;

Empty set (0.00 sec)

最終,core resource和extension resource關于port相關的Field資訊都查詢完成。是以最終的結果為:

[[email protected] ~(keystone_admin)]# neutron port-show 7a00401c-ecbf-493d-9841-e902b40f66a7

+-----------------------+------------------------------------------------------------------------------------+

| Field                 | Value                                                                              |

+-----------------------+------------------------------------------------------------------------------------+

| admin_state_up        | True                                                                               |

| allowed_address_pairs |                                                                                    |

| binding:host_id       | jun2                                                                               |

| binding:profile       | {}                                                                                 |

| binding:vif_details   | {"port_filter": true}                                                              |

| binding:vif_type      | bridge                                                                             |

| binding:vnic_type     | normal                                                                             |

| device_id             | dhcp2156d71d-f5c3-5752-9e43-4e8290a5696a-5eea5aca-a126-4bb9-b21e-907c33d4200b      |

| device_owner          | network:dhcp                                                                       |

| extra_dhcp_opts       |                                                                                    |

| fixed_ips             | {"subnet_id": "4751cf4d-2aba-46fa-94cb-63cbfc854592", "ip_address": "192.168.0.2"} |

| id                    | 7a00401c-ecbf-493d-9841-e902b40f66a7                                               |

| mac_address           | fa:16:3e:77:46:16                                                                  |

| name                  |                                                                                    |

| network_id            | 5eea5aca-a126-4bb9-b21e-907c33d4200b                                               |

| security_groups       |                                                                                    |

| status                | ACTIVE                                                                             |

| tenant_id             | 09e04766c06d477098201683497d3878                                                   |

+-----------------------+------------------------------------------------------------------------------------+

對于如何調用extensionresource的函數也是采用類似的方法分析,比如讀者可以利用在OpenStack環境有router的前提下,利用neutron router-list或neutron router-show ROUTER(具體router名稱或id)去觀察代碼流程。

下面我們将分析RPC相關的建立,包括RPC-server和RPC-client。

2. RPC建立

neutron_rpc = service.serve_rpc()
#/neutron/service.py
def serve_rpc():
    plugin = manager.NeutronManager.get_plugin()

    # If 0 < rpc_workers then start_rpc_listeners would be called in a
    # subprocess and we cannot simply catch the NotImplementedError.  It is
    # simpler to check this up front by testing whether the plugin supports
    # multiple RPC workers.
    if not plugin.rpc_workers_supported():
        LOG.debug("Active plugin doesn't implement start_rpc_listeners")
        if 0 < cfg.CONF.rpc_workers:
            LOG.error(_LE("'rpc_workers = %d' ignored because "
                          "start_rpc_listeners is not implemented."),
                      cfg.CONF.rpc_workers)
        raise NotImplementedError()

    try:
        rpc = RpcWorker(plugin)

        if cfg.CONF.rpc_workers < 1:
            rpc.start()
            return rpc
        else:
            # dispose the whole pool before os.fork, otherwise there will
            # be shared DB connections in child processes which may cause
            # DB errors.
            session.get_engine().pool.dispose()
            launcher = common_service.ProcessLauncher(wait_interval=1.0)
            launcher.launch_service(rpc, workers=cfg.CONF.rpc_workers)
            return launcher
    except Exception:
        with excutils.save_and_reraise_exception():
            LOG.exception(_LE('Unrecoverable error: please check log for '
                              'details.'))
           

首先執行plugin =manager.NeutronManager.get_plugin()代碼擷取core plugin(Ml2Plugin對象),然後check coreplugin是否實作了start_rpc_listeners函數。最終根據nova.conf配置檔案中的rpc_workers參數值建立個數為rpc_workers的RPC-server用于處理RPC-client的請求。

最終将執行RpcWorker類的start函數。

#/neutron/service.py:RpcWorker
class RpcWorker(object):
    """Wraps a worker to be handled by ProcessLauncher"""
    def __init__(self, plugin):
        self._plugin = plugin
        self._servers = []

    def start(self):
        self._servers = self._plugin.start_rpc_listeners()
           

即最終調用RPC-server在core plugin的start_rpc_listeners函數被建立。

#/neutron/plugins/ml2/plugin.py:Ml2Plugin
    def start_rpc_listeners(self):
        self.endpoints = [rpc.RpcCallbacks(self.notifier, self.type_manager),
                          securitygroups_rpc.SecurityGroupServerRpcCallback(),
                          dvr_rpc.DVRServerRpcCallback(),
                          dhcp_rpc.DhcpRpcCallback(),
                          agents_db.AgentExtRpcCallback(),
                          metadata_rpc.MetadataRpcCallback()]
        self.topic = topics.PLUGIN
        self.conn = n_rpc.create_connection(new=True)
        self.conn.create_consumer(self.topic, self.endpoints,
                                  fanout=False)
        return self.conn.consume_in_threads()
           

即RPC-server與RPC-client的對應關系歸納如下(将之前的core plugin和service plugin的RPC一起總結)。

1. core plugin的RPC

RPC-client RPC-server
Class service endpoints topic service
AgentNotifierApi

neutron-server

(core plugin)

OVSNeutronAgent

q-agent-notifier-xxx-yyy

(topics.AGENT-xxx-yyy)

neutron-openvswitch-agent
LinuxBridgeRpcCallbacks

q-agent-notifier-xxx-yyy

(topics.AGENT-xxx-yyy)

neutron-linuxbridge-agent
DhcpAgentNotifyAPI

DhcpAgentWithStateReport

(inherit from DhcpAgent)

dhcp_agent

(topics.DHCP_AGENT)

neutron-dhcp-agent

2. service plugin的RPC

RPC-client RPC-server
Class service endpoints topic service
L3AgentNotifyAPI

neutron-server

(service plugin)

L3NATAgentWithStateReport

(inherit from L3NATAgent)

l3_agent

(topics.L3_AGENT)

neutron-l3-agent
L3PluginApi neutron-l3-agent L3RpcCallback

q-l3-plugin

(topics.L3PLUGIN)

neutron-server

(service plugin)

3. 本節的RPC

RPC-client RPC-server
Class service endpoints topic service
PluginApi neutron-openvswitch-agent RpcCallbacks

q-plugin

(topics.PLUGIN)

neutron-server
neutron-linuxbridge-agent
SecurityGroupServerRpcApi neutron-openvswitch-agent SecurityGroupServerRpcCallback
neutron-linuxbridge-agent
DVRServerRpcApi neutron-openvswitch-agent DVRServerRpcCallback
DhcpPluginApi neutron-dhcp-agent DhcpRpcCallback
PluginReportStateAPI neutron-openvswitch-agent AgentExtRpcCallback
neutron-linuxbridge-agent

neutron-dhcp-agent

neutron-l3-agent

neutron-metadata-agent
MetadataPluginAPI neutron-metadata-agent MetadataRpcCallback

繼續閱讀