openstack neutron网络模块分析(四)--- 添加extension

neutron 添加extension

环境:
ocata版本
neutron-10.0.5
python-neutronclient-6.1.1
horizon-11.0.4

主要讲述如何在ocata版本的openstack网络模块neutron中添加extension。

流程图
先看一张从dashboard到neutron相应组件api的流程图

整个流程大致就是这样:
dashboard(shell)–>neutronclient–>neutron-server–>plugin–>agent
详细的流程讲解这里不作展开,只做相应步骤的代码添加,详细的讲解会在后续的文章提及。

我们这里讲解的是如何添加extension,当然就是要在/neutron/extension目录添加我们的py文件,这里的例子是对

数据库表格的操作

#neutron-server

这一步是对neutron-server访问数据库的操作分析。
如何新增数据库表 请看这里https://blog.csdn.net/energysober/article/details/80289394
##步骤一 添加extensions/test.py

下面时分段讲解

头文件

安装需要添加,不同版本可能存在文件的变动

	from neutron_lib.api import extensions as api_extensions
	from neutron_lib.plugins import directory
	from neutron_lib.db import constants as db_const
	from neutron_lib import exceptions as nexception

	from neutron.quota import resource_registry	
	from neutron._i18n import _

	from neutron.api import extensions
	from neutron.api.v2 import base

添加RESOURCE_ATTRIBUTE_MAP

    `
	TESTS = 'tests'
	RESOURCE_ATTRIBUTE_MAP = {
    TESTS: {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True, 'primary_key': True},
        'pid': {'allow_post': True, 'allow_put': True,
               'validate': {'type:uuid': None},
               'is_visible': True},
        'project_id': {'allow_post': True, 'allow_put': True
                'validate': {'type:string': db_const.PROJECT_ID_FIELD_SIZE},
               'is_visible': True},
        'name': {'allow_post': True, 'allow_put': False,
                 'validate': {'type:string': db_const.NAME_FIELD_SIZE},
                 'default': '',
                 'is_visible': True},
    },`

这个RESOURCE_ATTRIBUTE_MAP 很重要,不可缺少,这里面的字段就是之后你的create、delete、update、get等操作的对象。在这里也就是数据库所有的字段
上面的id由于是调用uuidutils.generate_uuid()生成(步骤四),所以这里设置为False

allow_post : 是否允许post请求

vaildate : 指定类型等

is_visible : 返回信息中是否有此字段

ps:详细的解释请看/neutron/api/v2/attributes.py的注释

添加cl `class Test(api_extensions.ExtensionDescriptor): “”“Agent management extension.”""

@classmethod
def get_name(cls):
    return "test"

@classmethod
def get_alias(cls):
    return "test"

@classmethod
def get_description(cls):
    return "curd a test ."

@classmethod
def get_namespace(cls):
    return "http://docs.openstack.org/ext/agent/api/v2.0"

@classmethod
def get_updated(cls):
    return "2018-05-10T10:00:00-00:00"

@classmethod
def get_resources(cls):
    """Returns Ext Resources."""

    plugin = directory.get_plugin()
    resource_registry.register_resource_by_name(TESTS)
    params = RESOURCE_ATTRIBUTE_MAP.get(TESTS)
    controller = base.create_resource(TESTS,
                                      TEST,
                                      plugin, params,
                                      allow_bulk=True,
                                      allow_pagination=True,
                                      allow_sorting=True)

    ex = extensions.ResourceExtension(GROUPS, controller)

    return [ex]

def get_extended_resources(self, version):
    if version == "2.0":
        return RESOURCE_ATTRIBUTE_MAP
    else:
        return {}        

基类 api_extensions.ExtensionDescriptor 是必须要继承的。
前面的几个方法就不做讲解了,主要是get_resources 这个。
这部分不难,只有几行代码,只要认真看,就会发现这个方法实现的是资源的创建,controller的创建。(这里有兴趣的可以看看这篇文件,对neutron-server根据请求调用controller的剖析)

注意:这个的class名并不是顺便起的,要和本文件名相应,就是将本文件名的首字母换成大写(如:test.py class Test)

好了 extension下的test.py文件就这样完成了

步骤二 添加 /neutron/db/model/test.py

既然是要操作数据库当然是要有db文件了,在添加db文件之前就要在model下添加一个文件,这个文件的作用就是定义的字段的格式

    from neutron_lib.db import constants as db_const
	from neutron_lib.db import model_base
	import sqlalchemy as sa
	from neutron.db import standard_attr
	from neutron.extensions import group


	class Test(standard_attr.HasStandardAttributes, model_base.BASEV2,
                    model_base.HasId, model_base.HasProject):
    	"""Represents a v2 neutron  group."""
    	__tablename__ = 'groups'
    	name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE))
    	pid = sa.Column(sa.String(255), nullable=False)
    	project_id = sa.Column(sa.String(255), nullable=False)

这个文件就只有这点东西了

步骤三 添加/neutron/db/test_db.py

下面就到操作db的文件了
常见的操作都有create,delete,get,update,所以这里就要对这几个操作写对应的方法了

	from neutron.db import _utils as db_utils
	from neutron.db.models import test as test_model
	from sqlalchemy import orm
	from neutron.db import api as db_api
	from neutron.db import common_db_mixin as base_db
	from neutron.extensions import group

	
	class TestDbMixin(base_db.CommonDbMixin):

    	@staticmethod
    	def _make_test_dict(g, fields=None):
        	res = {'id': g['id'],
        	       'pid': g['pid'],
        	       'name': g['name'],
        	       'project_id': g['project_id']}
        	return db_utils.resource_fields(res, fields)


    	def create_test(self, context, test):
        	t = test['test']

        	with context.session.begin(subtransactions=True):
        	    test_db = test_model.Test(
        	        id=uuidutils.generate_uuid(),
        	        pid=t['pid'],
        	        project_id=t['project_id'],
        	        name=t['name'])
        	    context.session.add(test_db)

        	return self._make_test_dict(test_db)



    	def delete_test(self, context, test_id):
        	with db_api.context_manager.writer.using(context):
        	    try:
        	        test_ = self._get_by_id(context,
                                        test_model.Test,
        	                                test_id)
        	    except orm.exc.NoResultFound:
        	        raise test.TestNotFound(test_id=test_id)

        	    context.session.delete(test_)



    	@db_api.retry_if_session_inactive()
    	def get_test(self, context, test_id, fields=None):
        	try:
        	    test_ = self._get_by_id(context,
        	                             test_model.Test,
        	                             test_id)
        	except orm.exc.NoResultFound:
        	    raise test.TestNotFound(test_id=test_id)

        	return self._make_test_dict(test_, fields)



    	@db_api.retry_if_session_inactive()
    	def get_tests(self, context, filters=None, 	fields=None,
        	                    sorts=None, limit=None, marker=None,
        	                    page_reverse=False):
        	marker_obj = self._get_marker_obj(context, 'test', limit,
                                          marker)
        	tests = self._get_collection(context, test_model.Test,
                                    	self._make_test_dict,
    	                                filters=filters, fields=fields,
    	                                sorts=sorts,
    	                                limit=limit,
                                    	marker_obj=marker_obj,
                                    page_reverse=page_reverse)

        	return self._make_test_dict(tests, fields)



    	def _get_test(self, context, test_id):
        	try:
        	    query = self._model_query(context, test_model.Test)
        	    t = query.filter(test_model.Test.id == test_id).one()

        	except orm.exc.NoResultFound:
        	    raise test.TestNotFound(test_id=test_id)
        	return t



    	@db_api.retry_if_session_inactive()
    	def update_test(self, context, test, id):
        	t = test['test']
        	with context.session.begin(subtransactions=True):
            	test_ = self._get_test(context, id)

	            test_.update(g)
	        test_dict = self._make_test_dict(test_)
	    
        	return test_dict

到这里neutron-server的文件基本添加完了

注意

def create_test(self, context, test):
这里的参数test一定要和create_test的test对应起来,这个坑我找了半天才发现,具体的原因目前还不是很清楚。要再跟代码才行
发现这个原因是/neutron/api/v2/base.py这个文件的do_create()中return请打印**kwargs参数时发现参数结构是这样的{“test”:“test”:{}}, 不知道从什么时候加了一层test的
!!没遇到的请忽略。。。。

还有最后的步骤

步骤四 修改/neutron/plugins/ml2/plugin.py

这里是在ml2/plugin,也就是core plugin下添加extension,如果想在其它service plugin中添加的话就在/neutron/services/ 目录下,选择操作的plugin的文件夹(想要在metering加就选择metering/metering_plugin.py)的plugin文件
如果想要自己添加一个service plugin的话就请看后续的文章。

添加test_db文件

from neutron.db import test_db

添加父类
在class Ml2Plugin继承的父类最后添加

test_db.TestDbMixin

添加别名

_supported_extension_aliases后添加"test"

这里的名字要和 extension目录下的test.py的一致(这里都是test)

    @classmethod
    def get_alias(cls):
        return "test"

上面就neutron的所有步骤了,不对,还有最后一步
编辑/etc/neutron/policy.json文件
添加一下内容,

	"update_test": "rule:admin_or_owner",
	"delete_test": "rule:admin_or_owner",
	"create_test": "rule:admin_or_owner"

这里对这些接口做了权限

步骤六 验证

重启neutron-server服务
获取token

    curl -X POST -d '{"auth": {"passwordCredentials": {"username": "admin", "password": "password"}, "tenantName": "admin"}}' -H "Content-type: application/json" http://controller:5000/v2.0/tokens | python -m json.tool

返回结果一大堆,直接找到下面这一段

    "token": {
            "audit_ids": [
                "98C63xlDSzKxUsHJKHxEfA"
            ],
            "expires": "2018-05-11T09:34:16.000000Z",
            "id": "gAAAAABa9VWIssxDBgLRUBipjKp4k1LSBtlplzXDDB8qNNRX6UYm7_k1W9ux0Pc1ouLlauZNfK4_8VQXFUrXox4NeUAr3Jb8GBhtiPqW3kEJlsacUBWQv4zQeVtJiqnYQUkP010UnO2VWUBMYjCRmUErO7L-6d_7YqnsPhPRV0GXLO68i0MFPUM",
            "issued_at": "2018-05-11T08:34:16.000000Z",
            "tenant": {
                "description": "Bootstrap project for initializing the cloud.",
                "enabled": true,
                "id": "ae0d927ea53147c587f0256aad476f1e",
                "name": "admin"
            }
        },

复制id的值
get 获取所有数据

    curl -g -i GET  http://controller:9696/v2.0/tests/ -H "User-Agent: openstacksdk/0.9.13 keystoneauth1/2.18.0 python-requests/2.11.1 CPython/2.7.5" -H "Accept: application/json" -H "X-Auth-Token: gAAAAABa9UXN6ge_yuMo-VMiDGxJnhVMLK_cJ-hWNTr0HD8ND1BXRjyVwOXvsm2meLRAKbjxa4KJbqMMpY5vqgCrwVbNybgmMfF6TigXZT6xgPU5-FqXxJx3Q4JBMdoUobVjfLqYMkWydrEQYo0Jst4e5zA9n9j9WaAWDOlklUknzR0izqggZ1s"

创建数据 post

    curl -X POST http://127.0.0.1:9600/v1/controller/  -d '{"name": "name", "id": "id"}' -H "User-Agent: openstacksdk/0.9.13 keystoneauth1/2.18.0 python-requests/2.11.1 CPython/2.7.5" -H "Content-Type: application/json" -H "X-Auth-Token: gAAAAABa9UXN6ge_yuMo-VMiDGxJnhVMLK_cJ-hWNTr0HD8ND1BXRjyVwOXvsm2meLRAKbjxa4KJbqMMpY5vqgCrwVbNybgmMfF6TigXZT6xgPU5-FqXxJx3Q4JBMdoUobVjfLqYMkWydrEQYo0Jst4e5zA9n9j9WaAWDOlklUknzR0izqggZ1s"

其它就不一一填出来了,方法都一样

neutronclient

上面讲到的是neutron-server通过sqlalchemy操作数据库,最后生成了restful的api
下面就讲解用neutronclient端调用这些api

这里暂时不对shell命令的支持,只讲解dashboard那条路线

首先打开 /neutronclient/v2_0/client.py文件
直接找到Client这个类

	class Client(ClientBase):

	    networks_path = "/networks"
	    network_path = "/networks/%s"
	    ports_path = "/ports"
	    port_path = "/ports/%s"
	    subnets_path = "/subnets"
	    subnet_path = "/subnets/%s"
	    subnetpools_path = "/subnetpools"
	    subnetpool_path = "/subnetpools/%s"
	    address_scopes_path = "/address-scopes"
	    address_scope_path = "/address-scopes/%s"
	    quotas_path = "/quotas"
	    quota_path = "/quotas/%s"
	    quota_default_path = "/quotas/%s/default"
	    extensions_path = "/extensions"
	    extension_path = "/extensions/%s"
	    routers_path = "/routers"
	    router_path = "/routers/%s"
	    floatingips_path = "/floatingips"
	    floatingip_path = "/floatingips/%s"
	    security_groups_path = "/security-groups"

上面是类的一部分代码,上面的xxx_path就是将要访问的资源的地址,顺便跟踪一个请求接口就能发现,这个neutronclient最后就是组成一个http请求,和我们在命令行用curl命令组件的http请求是相似的。

回到正题
我们需要将我们新增的test接口加到上面
在path的最后加上我们的

tests_path = “/test/”
test_path = “/test/%s”

然后就是在EXTED_PLURALS这个dict中增加

“test” : “test”

接着就要添加对test 做curd操作的接口了

    def list_tests(self, retrieve_all=True, **_params):
        """Fetches a list of all tests for a project."""
        # Pass filters in "params" argument to do_request
        return self.list('test', self.tests_path, retrieve_all,
                         **_params)

    def show_test(self, test, **_params):
        """Fetches information of a certain floatingip."""
        return self.get(self.test_path % (group), params=_params)

    def create_test(self, body=None):
        """Creates a new test."""
        return self.post(self.tests_path, body=body)

    def update_test(self, test, body=None):
        """Updates a test."""
        return self.put(self.test_path % (test), body=body)

    def delete_test(self, group):
        """Deletes the specified test."""
        return self.delete(self.test_path % (test))

这里的代码就不多讲解了,跟踪一下代码就能发现原理了

上面就是neutronclient的全部了,当然,这里只支持dashboard调用这里接口,并没有对命令行的支持,如果需要对命令行的支持就要解析命令行的参数,再调用这些接口,这些neutronclient都有实现,只需要把我们新增的接口加上去,如果需要的可以参考原因逻辑自行增加,在/neutronclient/shell.py文件中

dashboard

下面就到了最后一步,dashboard的接口
由于我们的test是在neutron下的,所以接口也就写在neutron模块下好了 /openstack_dashboard/api/neutron.py

添加一下代码:

    @profiler.trace
	def test_list(request, **params):
	    LOG.debug("test_list(): params=%s" % (params))
	    test = neutronclient(request).list_test(**params).get('test')
	    return test
	
	
	@profiler.trace
	def test_get(request, test_id, **params):
	    LOG.debug("test_get(): testid=%s, params=%s" % (test_id, params))
	    test = neutronclient(request).show_test(test_id,
	                                                **params).get('test')
	    return test
	
	
	@profiler.trace
	def test_create(request, **kwargs):
	    """Create a test .
	    """
	    LOG.debug("test_create(): kwargs=%s" % kwargs)
	    if 'project_id' not in kwargs:
	        kwargs['project_id'] = request.user.project_id
	    body = {'test': kwargs}
	    test = neutronclient(request).create_test(body=body).get('test')
	    return test
	
	
	@profiler.trace
	def test_update(request, test_id, **kwargs):
	    LOG.debug("test_update(): testid=%s, kwargs=%s" % (test_id, kwargs))
	    body = {'test': kwargs}
	    test = neutronclient(request).update_test(test_id,
	                                                  body=body).get('test')
	    return test
	
	
	@profiler.trace
	def test_delete(request, test_id):
	    LOG.debug("test_delete(): testid=%s" % test_id)
	    neutronclient(request).delete_test(test_id)

没错,这段代码就是对neutronclient接口的封装调用,所以就不多讲解了

接来下怎么调用这些接口呢?怎么验证呢?
方法一
openstack是使用django框架的,所以页面都是在/dashboards目录下实现的,接口也就在这下面调用,具体怎么样就不多解析了,不了解django的可以先去了解下,再回来看看这目录下的代码
方法二
openstack还有另一种方法,就是restful方式调用,也实现了前后端的分离,前端只需要调用一个restful api就可以了 下面就用这种方法验证我们写的接口。
目录:/openstack_dashboard/api/rest
我们在neutron.py文件下添加

  class Tests(generic.View):
	    url_regex = r'neutron/test/$'
	
	    @rest_utils.ajax()
	    def get(self, request):
	        """ test list
	        """
	        res = api.neutron.test_list(request)
	        return {"data": res}
	
	    @rest_utils.ajax(data_required=True)
	    def post(self, request):
	        """ create a test
	        """
	        test = api.neutron.test_create(request, **request.DATA)
	        return {"data": test}
	
	
	@urls.register
	class Test(generic.View):
	    url_regex = r'neutron/test/(?P[^/]+)/$'
	
	    @rest_utils.ajax()
	    def get(self, request, test_id):
	        """ get a test
	        """
	        res = api.neutron.test_get(request, test_id)
	        return {"data": res}
	
	    @rest_utils.ajax()
	    def delete(self, request, test_id):
	        """ create a test
	        """
	        test = api.neutron.test_delete(request, **request.DATA)
	        return {"data": test}
	
	
	    @rest_utils.ajax(data_required=True)
	    def delete(self, request, test_id):
	        """ create a test
	        """
	        test = api.neutron.test_update(request, test_id, **request.DATA)
	        return {"data": test}

给前端使用的接口已经写完了,剩下的就是怎么验证呢?
首先在浏览器打开openstack的dashboard界面 按下F12 选到Console 如下图

如果使用post命令出现403的问题就先运行下面的段代码

   $.ajaxSetup({
     beforeSend: function(xhr, settings) {
         function getCookie(name) {
             var cookieValue = null;
             if (document.cookie && document.cookie != '') {
                 var cookies = document.cookie.split(';');
                 for (var i = 0; i < cookies.length; i++) {
                     var cookie = jQuery.trim(cookies[i]);
                     // Does this cookie string begin with the name we want?
                     if (cookie.substring(0, name.length + 1) == (name + '=')) {
                         cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                         break;
                     }
                 }
             }
             return cookieValue;
         }
         if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
             // Only send the token to relative URLs i.e. locally.
             xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
         }
     }
    });

文章链接https://blog.csdn.net/qq_24861509/article/details/50140413

GET

    $.get("/dashboard/api/neutron/groups/",function(result){
    	console.log(result)
  	 });

如果这种方式没有delete的接口就用第二种方式:

   $.ajax({
	  type: "DELETE",
	  url: "/dashboard/api/neutron/test/test_id",
	  success: function(result){
	    	console.log(result)
	  }
	});

上面就是全部内容了,如果有什么问题可以先分析下日志文件,再根据日志文件分析代码,之后再去google查找问题吧

你可能感兴趣的:(openstack,neutron)