上一篇文章我们简要介绍了一下在初始化过程中,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模块处理。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()就可以找到对应的处理方法,
{
"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,通过修改这个文件来实现。
action = self._plugin_handlers[self.CREATE]
再次使用Python的反射机制getattr从plugin中找到create_vnf方法并调用
obj_creator = getattr(self._plugin, action)
obj = obj_creator(request.context, **kwargs)
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来做一下模型转换。
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"
}
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。
可见,VNF的instance_id对应于heat中的stack id,而nova instance id能够跟虚拟机配置文件中的uuid对应。