tacker源码分析(Pike版本)--实例化VNF

上一篇文章我们简要介绍了一下在初始化过程中,Tacker是如何建立路由的映射关系。本文将从REST请求到来开始,分析一下请求的处理流程以及实例化VNF的详细过程。

WSGI服务器在侦听端口上接收到HTTP请求后,会对HTTP协议层面做一些检查,比如HTTP header的一些信息,然后调用WSGI中间件处理。按照WSGI PEP333的定义,所有WSGI中间件的接口都形如:app(environ, start_response)。在各个中间件处理流程中,Paste的urlmap将请求URL拆分为两部分,分别设置到环境变量environ['SCRIPT_NAME’]和environ['PATH_INFO’]中,比如“/v1.0/vnfds”,script_name 是“/v1.0”,path_info是“/vnfds”,前者由paste负责分发,后者由routes模块处理。
之后,按照pipeline中定义的filter顺序,request经过如下处理:
request_id:在环境变量中增加openstack.request_id变量,可以用于追踪一个请求的调用流程。
catch_error: 是请求处理出异常时最外层的一层错误处理,如果里面没有返回正确的HTTP响应,那这里就会返回HTTPInternalServerError。
alarm_receiver: 这个filter相对比较特殊,它只针对告警的trigger请求做处理,并且修改了URL格式以便匹配routes的规则,其它请求不做处理。
authtoken:验证token有效性。根据token拿到access info,检查是否expire。
keystonecontext:给请求增加context环境变量,包含诸如tenant name, user name, role, token等信息。extension中大多数方法都会带着context参数调用。
extensions:其它所有业务逻辑,下面展开讲。

如果对上一篇文章中讲的routes模块有所理解,那应该清楚routes将每一个资源对应到一个controller,而这个资源是能够从URL中提取出来的,比如vnf或者vnfd。解析 URL的过程中获得的数据如action存放到环境变量environ['wsgiorg.routing_args'] 中, 然后后续的应用程序就可以使用这些参数进行相应的操作。前面我们也提到过在扩展资源时,controller是通过调用base.create_resource()方法构造的,我们再看一下这个方法:
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)
那controller到底是什么呢?注意在工程中不同地方定义的controller变量含义不同,不要互相混淆。真正处理请求的controller,也就是create_resource方法的返回值,说白了也是一个wsgify的方法,这个方法还是一个闭包,包含了base.Controller对象,也就相当于包含了具体的插件,如VNFM plugin。当routes模块收到wsgi请求时,就会触发这个方法的调用,这个函数位于api.v1.resource模块:
@webob.dec.wsgify(RequestClass=Request)
def resource(request):
    route_args = request.environ.get('wsgiorg.routing_args')
    if route_args:
        args = route_args[1].copy()
    …
    action = args.pop('action', None)                    # 得到具体的action,如index/show/create/update/delete
    …
   try:
        if request.body:
            args['body'] = deserializer.deserialize(request.body)['body']
        method = getattr(controller, action)                # 反射,得到对应action的方法
        result = method(request=request, **args)       # 调用base.Controller内的CRUD操作
    except Exception as e:
        ...
    status = action_status.get(action, 200)
    body = serializer.serialize(result)
    return webob.Response(request=request, status=status, content_type=content_type, body=body)
上篇文章我们简单提过map.resource()方法能够一次生成CRUD多种操作的映射关系,具体的,包括:index/show/create/update/delete。在base.Controller类定义中,有5个同名方法分别对应这5种action,这样根据action的不同,通过python的getattr()就可以找到对应的处理方法,
getattr()是python的一种反射机制或者叫自省机制。

在create_resource方法中可以看到,所有的资源操作都由base.Controller处理,所以在Tacker中,不管哪类资源,vnfd、vnf或者是vnffg等,他们的创建都从base.Controller的create()方法开始,但是不同资源类型对应base.Controller的不同实例。
以create VNF为例,Tacker处理流程大概包括下面几步:
1.请求参数验证
上篇文章我提过RESOURCE_ATTRIBUTE_MAP结构非常重要,它定义了某个extension支持哪些资源,资源有哪些attribute,每个attribute的property如何。base.Controller的create方法首先会根据RESOURCE_ATTRIBUTE_MAP的定义进行参数校验,如果在请求中包含了一个未定义的attribute或者缺少一个必填attribute,或者attribute跟action不匹配,那么请求会失败。
2.RBAC鉴权
Tacker使用oslo.policy组件,根据用户的role、project以及具体操作,比如’create_vnf’,进行验证,判断当前操作是否有权限。policy的配置文件是/etc/tacker/policy.json,文件内容如下:
{
    "context_is_admin":  "role:admin or user_name:tacker",
    "admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s",
    "admin_only": "rule:context_is_admin",
    "regular_user": "",
    "shared": "field:vims:shared=True",
    "default": "rule:admin_or_owner",


    "get_vim": "rule:admin_or_owner or rule:shared"
}
目前里面基本没有什么内容,进一步细致的权限控制可以参考其它工程,比如neutron,通过修改这个文件来实现。
3.调用具体实现vnfm extension的插件
前面已经通过调用下面的代码生成了方法名:create_vnf
action = self._plugin_handlers[self.CREATE]
再次使用Python的反射机制getattr从plugin中找到create_vnf方法并调用
obj_creator = getattr(self._plugin, action)
obj = obj_creator(request.context, **kwargs)

目前业界主流的NFV模型定义语言是TOSCA,使用YAML格式描述,Tacker中的VNFD以及VNFFGD等都采用了TOSCA语言。OpenStack中现在有一个专门的项目叫做heat-translator来完成TOSCA到heat template的转化。用法:
git clone https://github.com/openstack/heat-translator
python setup.py install
python heat_translator.py --template-file== --template-type=
目前实际上只支持tosca,所以template-type不是必须的。
举例:
(venv) ➜  heat-translator git:(master) cat translator/tests/data/tosca_helloworld.yaml
tosca_definitions_version: tosca_simple_yaml_1_0


description: Template for deploying a single server with predefined properties.


topology_template:
  node_templates:
    my_server:
      type: tosca.nodes.Compute
      capabilities:
        # Host container properties
        host:
         properties:
           num_cpus: 2
           disk_size: 10 GB
           mem_size: 512 MB
        # Guest Operating System properties
        os:
          properties:
            # host Operating System image properties
            architecture: x86_64
            type: Linux
            distribution: RHEL
            version: 6.5
执行转化命令,生成heat template
(venv) ➜  heat-translator git:(master) python heat_translator.py --template-file=translator/tests/data/tosca_helloworld.yaml
heat_template_version: 2013-05-23


description: >
  Template for deploying a single server with predefined properties.


parameters: {}
resources:
  my_server:
    type: OS::Nova::Server
    properties:
      flavor: m1.medium
      user_data_format: SOFTWARE_CONFIG
      image: rhel-6.5-test-image
outputs: {}
因为Tacker中使用TOSCA,而OpenStack中使用HOT,所以Tacker在向OpenStack下发请求之前需要调用heat-translator来做一下模型转换。

插件内实例化VNF的流程,大体顺序是建立数据库对象,调用OpenStack heat接口向OpenStack下发请求,然后异步调用heat API检查执行状态,最后更新数据库其它属性。同其他OpenStack工程一样,Tacker使用SQLAlchemy来进行数据库操作,技术细节请参阅SQLAlchemy的手册。
VNFM插件针对infrastructure、monitor和management分别有不同的driver。基础设施目前只有openstack一种,monitor有ceilometer、http_ping和ping三种,management支持openwrt。
VNFM插件调用infrastructure driver执行create操作,driver_name是根据Tacker注册的VIM信息中得到。
instance_id = self._vnf_manager.invoke(driver_name, 'create', plugin=self, context=context, vnf=vnf_dict, auth_attr=vim_auth)

openstack插件首先调用heat-translator将TOSCA转为HOT,需要注意的是,如果TOSCA模型中有scaling policy,那生成的HOT会有两部分,主模板是scaling policy,从模板是scaling group的resource。相关代码:

def create(self, plugin, context, vnf, auth_attr):
    region_name = vnf.get('placement_attr', {}).get('region_name', None)
    heatclient = hc.HeatClient(auth_attr, region_name)
    tth = translate_template.TOSCAToHOT(vnf, heatclient)
    tth.generate_hot()
    stack = self._create_stack(heatclient, tth.vnf, tth.fields)
    return stack['stack']['id']

def _create_stack(self, heatclient, vnf, fields):
    if 'stack_name' not in fields:
        name = __name__ + '_' + self.__class__.__name__ + '-' + vnf['id']
        fields['stack_name'] = name
    stack = heatclient.create(fields)
    return stack
上面heatclient.create的参数示例,带scaling policy情况:
{
  "files": {
    "SP1_res.yaml": "heat_template_version: 2013-05-23\ndescription: Tacker Scaling template\nresources:\n  VDU1:\n    type: OS::Nova::Server\n    properties:\n      user_data_format: SOFTWARE_CONFIG\n      availability_zone: nova\n      image: cirros-0.3.5-x86_64-disk\n      flavor: m1.tiny\n      networks:\n      - port: {get_resource: CP1}\n      config_drive: false\n  CP1:\n    type: OS::Neutron::Port\n    properties: {port_security_enabled: false, network: net_mgmt}\n  VL1: {type: 'OS::Neutron::Net'}\noutputs:\n  mgmt_ip-VDU1:\n    value:\n      get_attr: [CP1, fixed_ips, 0, ip_address]\n"
  },
  "stack_name": "tacker.vnfm.infra_drivers.openstack.openstack_OpenStack-1d18a259-104f-414f-ab3a-4043f4db535c",
  "template": "heat_template_version: 2013-05-23\ndescription: 'sample-tosca-vnfd-scaling\n\n  '\nparameters: {}\nresources:\n  SP1_scale_out:\n    type: OS::Heat::ScalingPolicy\n    properties:\n      auto_scaling_group_id: {get_resource: SP1_group}\n      adjustment_type: change_in_capacity\n      scaling_adjustment: 1\n      cooldown: 120\n  SP1_group:\n    type: OS::Heat::AutoScalingGroup\n    properties:\n      min_size: 1\n      desired_capacity: 2\n      cooldown: 120\n      resource: {type: SP1_res.yaml}\n      max_size: 3\n  SP1_scale_in:\n    type: OS::Heat::ScalingPolicy\n    properties:\n      auto_scaling_group_id: {get_resource: SP1_group}\n      adjustment_type: change_in_capacity\n      scaling_adjustment: -1\n      cooldown: 120\noutputs: {}\n"
}

由于OpenStack实例化一个虚机可能需要一段时间,所以Tacker在给heat下发stack后就返回了,同时启动一个协程来检查状态
self.spawn_n(create_vnf_wait)
create_vnf_wait方法会调用openstack插件循环检查stack的状态,可以在配置文件中设置重试次数和等待时间
while status == 'CREATE_IN_PROGRESS' and stack_retries > 0:
    time.sleep(self.STACK_RETRY_WAIT)
    try:
        stack = heatclient.get(vnf_id)
        ...
Tacker中注册VIM,需要填入VIM的用户名和密码等信息,这个信息在Pike版本中会保存到Barbican服务中。在需要调用VIM API的流程中,tacker首先会调用Barbican的客户端去查询出VIM的用户名和密码,然后获取token,再发请求给OpenStack。如果在操作中碰到Key manager或者Secret retrieval的错误,那很有可能是当前用户的权限有问题,具体权限要求可以看Barbican的policy.json。
最后看一下生成的虚机,在Tacker、Heat和Nova中是如何对应的。先查看Tacker中的VNF

根据VNF的id来顺藤摸瓜
tacker源码分析(Pike版本)--实例化VNF_第1张图片

可见,VNF的instance_id对应于heat中的stack id,而nova instance id能够跟虚拟机配置文件中的uuid对应。


你可能感兴趣的:(OpenStack)