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': L3RouterPlugin object at 0x42038d0>, 'CORE': 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': ,

'l3_agent_scheduler': ,

'net-mtu': ,

'ext-gw-mode': ,

'binding': ,

'provider': ,

'agent': ,

'quotas': ,

'subnet_allocation': ,

'dhcp_agent_scheduler': ,

'l3-ha': ,

'multi-provider': ,

'external-net': ,

'router': ,

'allowed-address-pairs': ,

'extraroute': ,

'extra_dhcp_opt': ,

'dvr':

}

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': , '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': , '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': , 'convert_list_to': , 'enforce_policy': True, 'validate': {'type:validate_allowed_address_pairs': None}},

'admin_state_up': {'default': True, 'convert_to': , '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': , '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': , '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': , 'enforce_policy': True, 'validate': {'type:mac_address': None}},

'binding:profile': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': , 'enforce_policy': True, 'validate': {'type:dict_or_none': None}},

'fixed_ips': {'is_visible': True, 'allow_put': True, 'allow_post': True, 'default': , 'convert_list_to': , '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': , 'is_visible': True, 'allow_put': True, 'allow_post': True, 'convert_to': },

'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': , 'required': False, 'type:boolean': None}, 'external_fixed_ips': {'default': None, 'validate': {'type:fixed_ips': None}, 'convert_list_to': , 'required': False}}}},

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

'admin_state_up': {'default': True, 'convert_to': , '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': , 'convert_to': , 'enforce_policy': True},

'routes': {'is_visible': True, 'allow_put': True, 'allow_post': False, 'default': , 'convert_to': , 'validate': {'type:hostroutes': None}},

'ha': {'is_visible': True, 'allow_put': False, 'allow_post': True, 'default': , 'convert_to': , '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': L3RouterPlugin object at 0x42038d0>, 'CORE': 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命令进行说明。

[root@jun ~(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命令

[root@jun ~(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函数查询的结果如下:

{

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)

首先打印这些函数为:

_extend_port_dict_allowed_address_pairs of

_extend_port_dict_extra_dhcp_opt of >

_extend_port_dict_security_group of >

_ml2_extend_port_dict_binding of >

_ml2_md_extend_port_dict of >

有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 /opts files, and each option's
    tag will be referenced in the /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信息都查询完成。所以最终的结果为:

[root@jun ~(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

你可能感兴趣的:(OpenStack,neutron-server,extension,resource,RPC)