<>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
<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 <http://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<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
<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查找问题吧
热门工具 换一换