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的注释

添加class

`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 -g -i  http://controller:9696/v2.0/tests/ -X POST -d '{"test":{"name":"","pid":"","project_id":""}}' c2e8bca9-5876-41b7-909e-d765837e4b92 -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"

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

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<test_id>[^/]+)/$'

    @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查找问题吧

猜你喜欢

转载自blog.csdn.net/energysober/article/details/80289478
今日推荐