理解 neutron

时间:2022-01-16 05:05:34

neutron 网络总览  (gitbook

之前大师发个结构图.

understanding_neutron.pdf

自己走读了代码:

1.  get_extensions_path()

# 在/opt/stack/neutron/neutron/api/extensions.py 中, get_extensions_path()

# 为什么 paths 是个list,也就是说 extensions 应该可以放在多个位置。

# 是不是说,我可以在config entry中配置的plugin, 不用放在neutron的目录下面?

# Returns the extension paths from a config entry and the __path__
# of neutron.extensions
def get_extensions_path(service_plugins=None):
paths = collections.OrderedDict() # 首先, 加载 core extensions 的路径。
# Add Neutron core extensions
paths[neutron.extensions.__path__[0]] = 1 # 为什么要设置value为1, 其实没啥意义对吧? 可以是任何值? 只是确保 path的key不重合就可以吧。
if service_plugins:
# Add Neutron *-aas extensions # 什么是 Neutron *-aas 的 extensions
for plugin in service_plugins.values(): # 根据plugin找extensions的位置。
neutron_mod = provider_configuration.NeutronModule(
plugin.__module__.split('.')[0])
try:
paths[neutron_mod.module().extensions.__path__[0]] = 1
except AttributeError:
# Occurs normally if module has no extensions sub-module
pass
# 而且我可以在 config中配置 extentions的路径。 这个代码不需要检查, path存不存在吗?
# Add external/other plugins extensions
if cfg.CONF.api_extensions_path:
for path in cfg.CONF.api_extensions_path.split(":"):
paths[path] = 1 LOG.debug("get_extension_paths = %s", paths) # Re-build the extension string
path = ':'.join(paths)
return path

看了一下, service_plugins 是可以配置的。
就是说可以放到不用在 neutron的目录下面。

第三方可以任意写

自己的plugin,
放到自己的目录, 是这样的吗?

Neutron-fwaas

Neutron-lbaas

等等都是service plugin,都不在neutron目录下面,都是单独的repo

$ grep plugin  etc/neutron.conf.sample 

# The core plugin Neutron will use (string
value)
#core_plugin = <None>
# The service plugins Neutron will use
(list value)
#service_plugins =

$ grep Plugin setup.cfg

    ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
    dummy =
neutron.tests.unit.dummy_plugin:DummyServicePlugin
    router =
neutron.services.l3_router.l3_router_plugin:L3RouterPlugin
    metering =
neutron.services.metering.metering_plugin:MeteringPlugin
    qos =
neutron.services.qos.qos_plugin:QoSPlugin
    tag =
neutron.services.tag.tag_plugin:TagPlugin
    flavors =
neutron.services.flavors.flavors_plugin:FlavorsPlugin
    auto_allocate =
neutron.services.auto_allocate.plugin:Plugin
    segments =
neutron.services.segments.plugin:Plugin
    network_ip_availability =
neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
    revisions =
neutron.services.revisions.revision_plugin:RevisionPlugin
    timestamp =
neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
    trunk =
neutron.services.trunk.plugin:TrunkPlugin
    neutron_tests =
neutron.tests.tempest.plugin:NeutronTempestPlugin

2. 怎么写一个extension的例子

$ pydoc neutron.api.extensions.ExtensionManager
neutron.api.extensions.ExtensionManager = class
ExtensionManager(__builtin__.object)
 |  Load extensions from the configured extension
path.
 | 
 |  See tests/unit/extensions/foxinsocks.py for an
 |  example extension implementation.
 | 
doc string 告诉我们,

怎么写一个extension的例子。

neutron的文档写的还是不错的。

$ cat neutron/tests/unit/extensions/foxinsocks.py

3. plugin 加载的过程。

NeutronManager 再 获取 plugin和service_plugin 的时候, 实际上是个 Singleton

而且, NeutronManager
在获取 plugin和service_plugin
的时候,返回的是弱引用。

这个文档也写的很好:
$ pydoc neutron.manager.NeutronManager
neutron.manager.NeutronManager = class
NeutronManager(__builtin__.object)
 | 
Neutron's Manager class.
 | 
 |  Neutron's Manager class is responsible for
parsing a config file and
 |  instantiating the correct plugin that concretely
implements
 |  neutron_plugin_base class.
 |  The caller should make sure that NeutronManager
is a singleton.
 | 
 |  Methods defined here:

_load_service_plugins的neutron分析。

1. 首先获得core_plugin
def _load_services_from_core_plugin():
该函数 只获取了
core_plugin
的service
self.plugin
<neutron.plugins.ml2.plugin.Ml2Plugin object at
0x7f77afd95690>
self.plugin 支持的扩展有:

getattr(self.plugin, "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', 'address-scope', 'availability_zone', 'network_availability_zone', 'default-subnetpools', 'subnet-service-types', 'port-security']

而constants.EXT_TO_SERVICE_MAPPING

{'lbaasv2': 'LOADBALANCERV2', 'dummy': 'DUMMY', 'qos': 'QOS', 'fwaas': 'FIREWALL', 'router': 'L3_ROUTER_NAT', 'vpnaas': 'VPN', 'metering': 'METERING', 'lbaas': 'LOADBALANCER'}

# Maps
extension alias to service type
that                                                                  

# can be implemented by the core plugin.

EXT_TO_SERVICE_MAPPING = {
'dummy': DUMMY,
'lbaas': LOADBALANCER,
'lbaasv2': LOADBALANCERV2,
'fwaas': FIREWALL,
'vpnaas': VPN,
'metering': METERING,
'router': L3_ROUTER_NAT,
'qos': QOS,
}

没有一个在 self.plugin 支持的扩展中,所以并没有加载。

2.  获取 service_plugins,
并将之实例化。 每一种plugin
有type定义。
_load_service_plugins ,
还将系统默认的plugins 加载进来了。
不仅仅加载了conf中的。

$ grep service_plugins  /etc/neutron/neutron.conf
service_plugins =
neutron.services.l3_router.l3_router_plugin.L3RouterPlugin

cfg.CONF.service_plugins

['neutron.services.l3_router.l3_router_plugin.L3RouterPlugin']

系统还支持一些默认的plugins

    self._get_default_service_plugins()
['flavors', 'auto_allocate', 'timestamp', 'network_ip_availability', 'tag', 'revisions'] # Maps default service plugins entry points to their extension aliases
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp': 'timestamp',
'network_ip_availability': 'network-ip-availability',
'flavors': 'flavors',
'revisions': 'revisions',
}

实例化的过程, 可以是alias的名字(entry_point 定义), 也可以是类名conf中定义(其实conf中可以用类名, 也可以是alias)。
utils.load_class_by_alias_or_classname

实例化的过程中,如果有agent,
会更新到 core_plugin中。
self.plugin.agent_notifiers.update(plugin_inst.agent_notifiers)

4. 走读PluginAwareExtensionManager

这个thread 主要是extension相关代码初始化的走读。

这个是APIRouter 类中很重要的一个环节。 Router 是最靠近wsgi的。
因为, Plugin的目的就是提供服务, 提供的服务最终都是经过 wsgi 的形式提供。

PluginAwareExtensionManager 实例化的过程,实际上是传入了前面我们分析的 service_plugins
所在的extensions路径 和 service_plugins 本身。
获得了所有的 Extensions(父类做的事情), 并且做了check。

获得了所有的 Extensions的过程,其实就是遍历 extensions路径下的文件是不是 有效的python文件。
是的话,就加载, 存在 extensions 中, key就是alias。

PluginAwareExtensionManager 很重要的一个功能,就是怎么扩展 resources, 这些resources 就是wsgi暴露出去的。

# 都段代码没有读懂啊 , 见下文。

def extend_resources(self, version, attr_map):
$ pydoc neutron.api.extensions.ExtensionManager.extend_resources
neutron.api.extensions.ExtensionManager.extend_resources = extend_resources(self, version, attr_map) unbound neutron.api.extensions.ExtensionManager method
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. 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.
"""
processed_exts = {}
exts_to_process = self.extensions.copy()
check_optionals = True
# Iterate until there are unprocessed extensions or if no progress
# is made in a whole iteration
while exts_to_process: # 不能用len, 因为需要处理的extension, 有依赖关系。
processed_ext_count = len(processed_exts)
for ext_name, ext in list(exts_to_process.items()):
# Process extension only if all required extensions
# have been processed already
required_exts_set = set(ext.get_required_extensions())
if required_exts_set - set(processed_exts): # 有需要提前 处理的 extension, 那么就不先处理。
continue
optional_exts_set = set(ext.get_optional_extensions())
if check_optionals and optional_exts_set - set(processed_exts): # 有需要提前 处理的 extension, 那么就不先处理。
continue
extended_attrs = ext.get_extended_resources(version)
for res, resource_attrs in six.iteritems(extended_attrs):
attr_map.setdefault(res, {}).update(resource_attrs) # 用extension的RESOURCE_ATTRIBUTE_MAP 更新 attr_map
processed_exts[ext_name] = ext # 没处理一个,增加一个。
del exts_to_process[ext_name] # 同时需要处理的,减去一个。
if len(processed_exts) == processed_ext_count: # 这段代码没有读懂啊
# if we hit here, it means there are unsatisfied # 感觉是我们可以容忍 optional 的依赖出问题
# dependencies. try again without optionals since optionals
# are only necessary to set order if they are present.
if check_optionals:
check_optionals = False
continue
# Exit loop as no progress was made
break
if exts_to_process:  # 感觉是我们不能容忍 require 的依赖出问题
unloadable_extensions = set(exts_to_process.keys())
LOG.error(_LE("Unable to process extensions (%s) because "
"the configured plugins do not satisfy "
"their requirements. Some features will not "
"work as expected."),
', '.join(unloadable_extensions))
self._check_faulty_extensions(unloadable_extensions)
# Extending extensions' attributes map. # 最后讲 attr_map 反更新 extension的RESOURCE_ATTRIBUTE_MAP,这个逻辑有点绕。
for ext in processed_exts.values():
ext.update_attributes_map(attr_map)

我们可以打开一个extensions的例子,或者看 neutron 提供的example
$ cat neutron/tests/unit/extensions/foxinsocks.py

1.

每个extension, 有如下属性:  这实际上就是指明了依赖关系。
get_required_extensions
get_optional_extensions   交叉的resource

    def get_required_extensions(self):
"""Returns a list of extensions to be processed before this one."""
return [] def get_optional_extensions(self):
"""Returns a list of extensions to be processed before this one. Unlike get_required_extensions. This will not fail the loading of
the extension if one of these extensions is not present. This is
useful for an extension that extends multiple resources across
other extensions that should still work for the remaining extensions
when one is missing.
"""
return []

2. 每个extension, 有get_extended_resources函数:
neutron的代码,描述的非常清楚。
这个函数,就是用来指明, 这个改extension 
怎么去扩展core plugin的。
扩展完成后的效果,跟我们直接改core plugin的效果一样。 访问wsgi的话, 就会多一些属性了。

当然, 也可以产生自己的wsgi的resource,
这些resource的属性,会被集成到RESOURCE_ATTRIBUTE_MAP

RESOURCE_ATTRIBUTE_MAP 是这个neutron wsgi暴露出去的所有的资源。
在我的机器上,最后暴露出来的resource有:

print attr_map.keys()
['flavors', 'subnets', 'availability_zones', 'rbac_policies', 'floatingips', 'routers', 'segments', 'service_providers', 'quotas', 'address_scopes', 'networks', 'service_profiles', 'trunks', 'agents', 'security_group_rules', 'policies', 'auto_allocated_topologies', 'subnetpools', 'network_ip_availabilities', 'security_groups', 'ports']
    def get_extended_resources(self, version):
"""Retrieve extended resources or attributes for core resources. Extended attributes are implemented by a core plugin similarly
to the attributes defined in the core, and can appear in
request and response messages. Their names are scoped with the
extension's prefix. The core API version is passed to this
function, which must return a
map[<resource_name>][<attribute_name>][<attribute_property>]
specifying the extended resource attribute properties required
by that API version. Extension can add resources and their attr definitions too.
The returned map can be integrated into RESOURCE_ATTRIBUTE_MAP.
"""
return {}

3. update_attributes_map

每个extend_resources 过程中, 每个 extension 最终会调用这个函数 来更新 attributes

    def update_attributes_map(self, extended_attributes,
extension_attrs_map=None):
"""Update attributes map for this extension. This is default method for extending an extension's attributes map.
An extension can use this method and supplying its own resource
attribute map in extension_attrs_map argument to extend all its
attributes that needs to be extended. If an extension does not implement update_attributes_map, the method
does nothing and just return.
"""
if not extension_attrs_map:
return for resource, attrs in six.iteritems(extension_attrs_map):
extended_attrs = extended_attributes.get(resource)
if extended_attrs:
attrs.update(extended_attrs)

5. Router 代码(luyao 分析)

首先我们知道一个shell下的命令到具体函数的执行分为三个阶段

1.nova list命令转化为HTTP请求

2.HTTP请求到WSGI Application

3.WSGI Application到具体的执行函数

先从第三阶段直接介绍,第二阶段在下面会有所提及,第一阶段不做介绍

************

第三阶段 WSGI Application到具体的执行函数

************

这是APIRouter

/opt/stack/neutron/neutron/api/v2/router.py

Class APIRouter(base_wsgi.Router):完成路由规则建立

......

def __init__(self, **local_config):

......

# 主要是为所有的resource生成一个mapper,这个mapper包含了所有resource对应的路径

mapper.connect('index', '/', controller=Index(RESOURCES))

for resource in RESOURCES:

_map_resource(RESOURCES[resource], resource,

attributes.RESOURCE_ATTRIBUTE_MAP.get(

RESOURCES[resource], dict()))

resource_registry.register_resource_by_name(resource)

# 这里的SUB_RESOURCES类似NOVA中的扩展资源

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'])

......

# 这一句调用父类的__init__方法,接下来看父类Router中做了那些操作

super(APIRouter, self).__init__(mapper)

......

这是Router

/usr/local/lib/python2.7/dist-packages/oslo_service/wsgi.py

base_wsgi.Router:mapper和dispatch()关联起来,是为了找到真正的我们所需要的WSGI app

class Router(object):

"""WSGI middleware that maps incoming requests to WSGI apps."""

def __init__(self, mapper):

self.map = mapper

self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)

# 这里的__call__函数进行了wsgify的封装,在这个Router被call的时候,或者是它的子类APIRouterV21在被call的时候,

# 需要看webob.dec.wsgify里面是怎么进行封装的才能知道具体的执行过程

# webob.dec.wsgify定义了当函数被call的时候,会把“外衣”脱掉,继续call外衣里面的函数,一直到最核心的app,

# 这个app是直接接受参数(environ, start_response)的wsgi app

# 这里提到的“外衣”是从HTTP请求到WSGI app的时候一层层穿上的,其实是一系列的filter,为了做验证等一系列的操作

@webob.dec.wsgify(RequestClass=Request)

def __call__(self, req):

return self._router

@staticmethod

@webob.dec.wsgify(RequestClass=Request)

def _dispatch(req):

match = req.environ['wsgiorg.routing_args'][1]

if not match:

return webob.exc.HTTPNotFound()

app = match['controller']

return app

**************

HTTP请求到WSGI Application

**************

这是pipeline对应的filter

这部分是HTTP到WSGI application的过程,主要是根据paste配置文件做相应的操作

这是在Router之前的部分

/opt/stack/neutron/etc/api-paste.ini

[composite:neutronapi_v2_0]

use = call:neutron.auth:pipeline_factory

noauth = cors request_id catch_errors extensions neutronapiapp_v2_0

keystone = cors request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 #这就是一系列filter

如果大家觉得有需要我可以再介绍HTTP到WSGI Application的详细过程

6.  SUB_RESOURCES

好了, 回到 APIRouter, 继续走读。 具体生成理由的代码, 直接看python的routes库, docs介绍的很详细。
我们剩下的需要把 SUB_RESOURCES 怎么生成的看看, 目前SUB_RESOURCES 默认值是空的。

 def __init__(self, **local_config):
mapper = routes_mapper.Mapper() # 这个是用来做路由用的。
plugin = manager.NeutronManager.get_plugin()
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
  # 这个需要重点分析,怎么扩展resources, 这些resources就是最终通过wsgi可访问到的资源。 我们之前已经分析过了啊。
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP) col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,    # 这里的 collection 就是collection
member_actions=MEMBER_ACTIONS)   # 这里的 member 就是具体的resource 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( # controller 是动态生成的。
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)) # 生成URL的根, 这个可以做个实验。
# TOKEN=`openstack token issue -f value -c id`
# curl -s -H "X-Auth-Token:$TOKEN" http://10.238.155.177:9696/v2.0/ | python -m json.tool
# 这两条命令, 会call 到 root
for resource in RESOURCES:    # 生成URL的第一级resource目录, 这个可以做个实验。
# TOKEN=`openstack token issue -f value -c id`
# curl -s -H "X-Auth-Token:$TOKEN" http://10.238.155.177:9696/v2.0/networks | python -m json.tool
# 这个 请求 会 call 到 neutron.api.v2.base.Controller.index
# 最终会call 到 ml2的 plugin的get_networks 方法。
_map_resource(RESOURCES[resource], resource,
attributes.RESOURCE_ATTRIBUTE_MAP.get(
RESOURCES[resource], dict()))
resource_registry.register_resource_by_name(resource) for resource in SUB_RESOURCES:    # 生成URL的子resource目录, 这个可以做个实验。
_map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
attributes.RESOURCE_ATTRIBUTE_MAP.get(
SUB_RESOURCES[resource]['collection_name'],
dict()),
SUB_RESOURCES[resource]['parent']) # openstack的homebrew的app框架, 好像目前只能生成两级的resource。  # 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)

8.  实验阶段: inline

只做两个实验, 获得根, 获得networks.

其他的extension扩展的实验, 由志愿者来补充。

好了,我们终于跨越了*初级阶段了。 
可以进行高级阶段了。 激动吗, 高级阶段 就是干活啦。

不过万里长征刚刚起步, neutron还是很复杂的, 尤其需要很多网络背景知识和linux的内核知识。

9. 最近发现很多人做个neuron的分析

openstack學習之neutron_linuxbridge_agent分析  作者zhengleiguo还有其他相关的分析

用ml2 plugin配置OpenStack Neutron flat 网络   做过很多nuetron的分析

Neutron分析(5)—— neutron-l3-agent中的iptables 相关分析

nova boot代码流程分析(四):nova与neutron的l2 agent(neutron-linuxbridge-agent)交互

nova boot代码流程分析(三):nova与neutron的plugin交互 , SRIOV的创建流程很清楚了。《nova boot代码流程分析(一):Claim机制》文章继续分析后面的代码,即后面的代码涉及nova与neutron的交互。

zookeeper入门 各个系列

Python 弱引用 学习 (org doc

例子: Write Neutron ML2 Mechanism Driver