neutron-server的启动流程

Posted gj4990

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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命令进行说明。

[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

以上是关于neutron-server的启动流程的主要内容,如果未能解决你的问题,请参考以下文章

neutron 启动流程

neutron-server源码分析总纲

《构建之法》第七章

pdb /usr/bin/neutron-server

pdb /usr/bin/neutron-server

centos系列的启动流程及基础知识点

(c)2006-2024 SYSTEM All Rights Reserved IT常识