OpenStack Restful API框架介绍

1  pecan框架介绍

1.1  什么是pecan

pecan是一个轻量级的python web框架,最主要的特点是提供了简单的配置即可创建一个wsgi对象并提供了基于对象的路由方式。

主要提供的功能点:

(1)基于对象的路由分发

(2)支持restful接口方式

(3)可拓展的安全框架

(4)可拓展的模板语言支持

(5)可拓展的json支持

(6)简单的python配置

1.2  安装部署

为了不影响原有环境我们使用virtualenv工具创建一个隔离的python环境来做实验

$ virtualenv pecan-env
$ cd pecan-env
$ source bin/activate

安装pecan:

$ pip install pecan

我的实验环境是pecan 1.3.3的,可以用pecan --version命令查看

创建一个pecan项目:

$ pecan create pecan_test_project

用tree命令查看下生成的项目文件结构:

可以根据自己的需要筛减些,比如我只想要用pecan来帮我实现restful接口,那么public、templates等目录是可以去除掉的

这里介绍几个常用的文件或目录:

pecan_test_project/controllers:这个目录是用来存放要路由的对象类和要调用的对象方法的

pecan_test_project/model:用来存放模型的,比如一个数据库表的model,做ORM映射时会用到

pecan_test_project/tests:可以用来写一些单元测试

pecan_test_project/app.py:该文件用来控制如何构建你的pecan应用,该文件里会包含setup_app函数用来生成和返回一个wsgi app,一般来说,该文件是不用再修改的

config.py:该文件定义了服务的ip和端口号、服务的根目录类等和日志设置等。

直接执行pecan serve config.py命令服务就开始跑起来了

默认监听IP是0.0.0.0,端口号是8080

然后直接在页面访问,比如我的服务器是192.168.0.107,则浏览器访问http://192.168.0.107:8080就会看到如下页面了

这是因为我们访问的是它的根路径,根据pecan的基于对象的分发,它对调用如下的index方法,返回一个index.html页面了(这个index方法是被默认为当get请求分发到这个对象时会被路由到该方法,它其实是包裹在了expose里,可以理解它为get方法):

1.3  安装部署pecan的对象分发路由策略

Pecan的根路径是通过配置文件中指定的某个类开始的,比如上面的RootController类,并且会把请求路径按/分成多份,比如/test1/test2/test3,则会从RootController找test1对象,再从test1对象中找test2对象,再从test2对象中找test3对象,最后调用test3类对象的方法。

举个例子:

修改root.py文件为:

from pecan import expose, redirect
from webob.exc import status_map

class BooksController(object):
    @expose()
    def index(self):
        return "Welcome to book section."

    @expose()
    def bestsellers(self):
        return "We have 5 books in the top 10."

class CatalogController(object):
    @expose()
    def index(self):
        return "Welcome to the catalog."

    books = BooksController()

class RootController(object):

    @expose(generic=True, template=‘index.html‘)
    def index(self):
        return dict()

    @index.when(method=‘POST‘)
    def index_post(self, q):
        redirect(‘https://pecan.readthedocs.io/en/latest/search.html?q=%s‘ % q)

    @expose(‘error.html‘)
    def error(self, status):
        try:
            status = int(status)
        except ValueError:  # pragma: no cover
            status = 500
        message = getattr(status_map.get(status), ‘explanation‘, ‘‘)
        return dict(status=status, message=message)

catalog = CatalogController()

则如下几个url就会对象如下几个方法:

http://192.168.0.107:8080/catalog  --> CatalogController类index方法

http://192.168.0.107:8080/catalog/books/bestsellers -->  BooksController类bestsellers方法

这里主要是通过expose装饰器将类对象的方法暴露出来,使得能够被路由到。Expose中还能指定要返回的对象格式,比如json的,则expose(‘json’)

基于http的请求方法来选择方法:

class BaseMethodController(object):

    # HTTP GET /
    @expose(generic=True, template=‘json‘)
    def index(self):
        return dict()

    # HTTP POST /
    @index.when(method=‘POST‘, template=‘json‘)
    def index_POST(self, **kw):
        return kw

在RootController类下加上:

basemethod = BaseMethodController()

可以用curl命令来测试:

curl http://127.0.0.1:8080/basemethod/
curl http://127.0.0.1:8080/basemethod/ -X POST -d ‘hello=world‘

pecan也提供了一些额外的特殊方法来提高url处理的灵活性,有_lookup()、_default()和_route()等方法。

_lookup():

它可以获取一个或多个参数并经过处理后返回一个新的控制器对象并把未处理的url保存于remainder中继续由新的控制器来路由。

class Student(object):
    def __init__(self, name):
        self.name = name

class StudentController(object):
    def __init__(self, student):
        self.student = student

    @expose()
    def name(self):
        return self.student.name

class LookupTest(object):
    @expose()
    def _lookup(self, name, *remainder):
        print ‘enr‘
        student = Student(name)
        if student:
            return StudentController(student), remainder
        else:
            abort(404)

在RootController类下加上:

look_test = LookupTest()

测试:

curl http://127.0.0.1:8080/look_test/66/name

_default():

当其它方法没匹配上时就调用_default方法

_route():

该方法允许你完全覆盖pecan原有的路由机制,pecan也使用该方法实现了RestController。

1.4  pecan的请求和返回对象

我们知道每一个http都是有一个请求对象和相应对象的,这在pecan中是分别对应着pecan.request和pecan.response对象

比如我可以赋值pecan.response.status = 403

1.5  RestController

pecan已经帮我们封装好了RestController的接口,有如下:

举一个简单的get例子:

class MyRestController(rest.RestController):
    @expose()
    def get(self, id):
        return id

在RootController类下加上:

my_restcontroller = MyRestController()

测试:

curl http://127.0.0.1:8080/my_restcontroller/13579

1.6  一些常用配置项

hook机制配置

hook机制可以使得我们控制当一个http请求在被pecan框架执行过程中各个关键点上执行相对应的代码,以下是四个关键点:

on_route:当url还没被路由前调用

before:路由到某个函数但执行代码之前调用

after:执行了代码之后调用

on_error:执行代码过程中发生错误调用

举例:

在app.py的同级目录下创建my_hooks.py文件:

from pecan.hooks import PecanHook

class SimpleHook(PecanHook):

    def on_route(self, state):
        print ‘it is on route‘

    def before(self, state):
        print ‘it is before exec‘

    def after(self, state):
        print ‘it is after exec‘

    def on_error(self, state, exc):
        print ‘it is on error‘

修改app.py文件,加上hoos:

比如一个比较常用的是在这里获取数据库连接对象,在路由到的方法里就可以通过request获取到数据库连接对象进行操作了,省去了重复获取代码。比如:

日志配置:

修改config.py文件:

默认是console的,可以改成logfile形式

在controllers如下编写即可:

1.7  Pecan和WSGI

部署pecan的wsgi app一般有很多种方式,但一般来说,可以使用deploy()函数生成任何Pecan应用程序的WSGI入口点。

这里举我自己项目中部署的两种方式:

(1)使用Werkzeug通用库

这里的第6行返回的就是使用pecan框架的deploy.loadapp()函数产生的wsgi app

Werkzeug就是用python对WSGI的实现一个通用库。它是Flask所使用的底层WSGI库

Serving是Werkzeug库的一个用来运行wsgi的函数,将wsgi app跑起来

(2)Apache + mod_wsgi的方式

这种方式通过依托于Apache服务的形式,随Apache启动时一同启动wsgi app。

这里以gnocchi-api服务为例讲解

查看该服务的.wsgi配置文件:

这里重点是看红框里的app文件,这个文件定义了怎么生成wsgi app

查看app文件:

再查看gnocchi.rest模块下的app文件:

可以看到其实最后也是使用deploy.loadapp生成一个wsgi app,然后由appache来运行这个wsgi app。

这里我们用Apache + mod_wsgi的方式来运行我们实例的pecan程序:

(1)首先安装httpd服务和mod_wsgi包

yum install httpd mod_wsgi -y

(2)部署pecan服务

cd pecan_test_project

python setup.py build

python setup.py install

(3)创建好用户和配置文件

创建test1用户:

adduser test1

创建配置文件:

/etc/httpd/conf.d/simple_wsgi.conf:

Listen 0.0.0.0:8080

<VirtualHost *:8080>
  ServerName thinstack-JxRcqm

  ## Vhost docroot
  DocumentRoot "/var/www/simpleapp/"

  <Directory "/var/www/simpleapp/">
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    Require all granted
  </Directory>

  ## Logging
  ErrorLog "/var/log/httpd/simpleapp_wsgi_error.log"
  ServerSignature Off
  CustomLog "/var/log/httpd/simpleapp_wsgi_access.log" combined
  SetEnvIf X-Forwarded-Proto https HTTPS=1
  WSGIApplicationGroup %{GLOBAL}
  WSGIDaemonProcess test1 display-name=simpleapp_wsgi group=test1 processes=4 threads=4 user=test1
  WSGIProcessGroup test1
  WSGIScriptAlias / "/var/www/simpleapp/app.wsgi"
</VirtualHost>

/var/www/simpleapp/config.py:

# Server Specific Configurations
from pecan_test_project.my_hooks import SimpleHook
server = {
    ‘port‘: ‘8080‘,
    ‘host‘: ‘0.0.0.0‘
}

# Pecan Application Configurations
app = {
    ‘root‘: ‘pecan_test_project.controllers.root.RootController‘,
    #‘hooks‘: lambda: [SimpleHook],
    ‘modules‘: [‘pecan_test_project‘],
    ‘static_root‘: ‘%(confdir)s/public‘,
    ‘template_path‘: ‘%(confdir)s/pecan_test_project/templates‘,
    ‘debug‘: True,
    ‘errors‘: {
        404: ‘/error/404‘,
        ‘__force_dict__‘: True
    }
}

logging = {
    ‘root‘: {‘level‘: ‘INFO‘, ‘handlers‘: [‘console‘]},
    ‘loggers‘: {
        ‘pecan_test_project‘: {‘level‘: ‘DEBUG‘, ‘handlers‘: [‘console‘], ‘propagate‘: False},
        ‘pecan‘: {‘level‘: ‘DEBUG‘, ‘handlers‘: [‘console‘], ‘propagate‘: False},
        ‘py.warnings‘: {‘handlers‘: [‘console‘]},
        ‘__force_dict__‘: True
    },
    ‘handlers‘: {
        ‘console‘: {
            ‘level‘: ‘DEBUG‘,
            ‘class‘: ‘logging.StreamHandler‘,
            ‘formatter‘: ‘color‘
        },
    },
    ‘formatters‘: {
        ‘simple‘: {
            ‘format‘: (‘%(asctime)s %(levelname)-5.5s [%(name)s]‘
                       ‘[%(threadName)s] %(message)s‘)
        },
        ‘color‘: {
            ‘()‘: ‘pecan.log.ColorFormatter‘,
            ‘format‘: (‘%(asctime)s [%(padded_color_levelname)s] [%(name)s]‘
                       ‘[%(threadName)s] %(message)s‘),
        ‘__force_dict__‘: True
        }
    }
}

/var/www/simpleapp/app.wsgi:

from pecan.deploy import deploy
application = deploy(‘/var/www/simpleapp/config.py‘)

(4)启动服务

systemctl start httpd

查看服务状态:

用浏览器访问:http://192.168.0.107:8080/my_restcontroller/13579

就可以看到响应值了

注意点:

Python setup.py install安装报这种错时:

执行:

pip install soupsieve

1.8  实例项目下载地址

https://github.com/luohaixiannz/pecan_test_project

2  Paste + PasteDeploy + Routes

2.1  Paste + PasteDeploy + Routes

这里以nova项目来举例

在一些较老的openstack的核心组件中用的都是比较传统的Paste + PasteDeploy + Routes + WebOb的方式来实现Restful API,比如nova组件,后来openstack社区的人受不了这繁琐的方式,就在新的组件里采用了pecan框架来构建Restful API,这里以nova组件为例进行该种方式的解析

首先我们看下如何使用Paste + PasteDeploy来构建nova的wsgi应用,查看nova关于paste的配置文件,/etc/nova/api-paste.ini:

############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta

[pipeline:meta]
pipeline = cors metaapp

[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21

[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21

[composite:openstack_compute_api_v21_legacy_v2_compatible]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler noauth2 legacy_v2_compatible osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21

[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory

[filter:compute_req_id]
paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory

[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory

[filter:noauth2]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory

[filter:osprofiler]
paste.filter_factory = nova.profiler:WsgiMiddleware.factory

[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory

[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory

[filter:legacy_v2_compatible]
paste.filter_factory = nova.api.openstack:LegacyV2CompatibleWrapper.factory

[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

[pipeline:oscomputeversions]
pipeline = cors faultwrap http_proxy_to_wsgi oscomputeversionapp

[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory

##########
# Shared #
##########

[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = nova

[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

可以看到这个配置文件是ini格式的,都是由[type:name]这种形式为单元,在paste中,type包括以下几种类型:

(1)应用:app,application

(2)过滤器:filter,filte-app

(3)管道:pipeline,一般结合添加多个过滤器的时候使用(最后一个是wsgi应用)

(4)组合:composite,表示它由若干个应用和若干个过滤器构成

下面分析个nova比较熟悉的

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21

这是一个composite类型,看到它的key是use表明它使用了paste中的urlmap的功能,该功能是根据url前缀请求路由到不同的WSGI应用

其中use可以使用以下几种形式:

egg:使用一个URI指定的egg包中的对象

call:使用某个模块中的可调用对象

config:使用另外一个配置文件

osapi_compute是的use用的是call,查看urlmap_factory的实现:

def urlmap_factory(loader, global_conf, **local_conf):
    if ‘not_found_app‘ in local_conf:
        not_found_app = local_conf.pop(‘not_found_app‘)
    else:
        not_found_app = global_conf.get(‘not_found_app‘)
    if not_found_app:
        not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
    urlmap = URLMap(not_found_app=not_found_app)
    for path, app_name in local_conf.items():
        path = paste.urlmap.parse_path_expression(path)
        app = loader.get_app(app_name, global_conf=global_conf)
        urlmap[path] = app
return urlmap

这里的 local_conf.items()是在枚举/、/v2和/v2.1

逐个加载生成app,并与path对应起来保存到urlmap中

这里以/v2.1作为分析

[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21

查看pipeline_factory_v21实现:

def pipeline_factory_v21(loader, global_conf, **local_conf):
    """A paste pipeline replica that keys off of auth_strategy."""
return _load_pipeline(loader, local_conf[CONF.api.auth_strategy].split())

def _load_pipeline(loader, pipeline):
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    app = loader.get_app(pipeline[-1])
    filters.reverse()
    for filter in filters:
        app = filter(app)
return app

可以看到pipeline_factory_v21函数中根据配置文件auth_strategy的指定来选择对应配置,项目中默认是使用keystone,于是使用了这行的配置:

keystone = cors http_proxy_to_wsgi compute_req_id faultwrap sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21

接着调用_load_pipeline函数先生成osapi_compute_app_v21的app,接着加载各个filter来按序封装app,最后返回一个经过filter封装后的app应用

这里看下生成osapi_compute_app_v21的app

[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

查看factory实现:

@classmethod
    def factory(cls, global_config, **local_config):
        """Simple paste factory, :class:`nova.wsgi.Router` doesn‘t have one."""
        return cls()

生成一个APIRouterV21类实例,查看__init__实现方法:

def __init__(self):
        self._loaded_extension_info = extension_info.LoadedExtensionInfo()
        super(APIRouterV21, self).__init__()

先通过加载扩展插件信息(一个controller可当做是一个扩展插件)保存到_loaded_extension_info变量中,再调用父类__init__方法:

def __init__(self):
        def _check_load_extension(ext):
            return self._register_extension(ext)

        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
            namespace=self.api_extension_namespace(),
            check_func=_check_load_extension,
            invoke_on_load=True,
            invoke_kwds={"extension_info": self.loaded_extension_info})

        mapper = ProjectMapper()

        self.resources = {}

        # NOTE(cyeoh) Core API support is rewritten as extensions
        # but conceptually still have core
        if list(self.api_extension_manager):
            # NOTE(cyeoh): Stevedore raises an exception if there are
            # no plugins detected. I wonder if this is a bug.
            self._register_resources_check_inherits(mapper)
            self.api_extension_manager.map(self._register_controllers)

        LOG.info(_LI("Loaded extensions: %s"),
                 sorted(self.loaded_extension_info.get_extensions().keys()))
        super(APIRouterV21, self).__init__(mapper)

这里的关键点在于mapper 实例和_register_resources_check_inherits函数调用生成映射关系

在_register_resources_check_inherits函数中通过遍历加载每一个扩展插件并且通过调用_register_resources函数来生成该扩展插件的url映射关系保存到mapper 中

_register_resources函数实现关键代码:

在收集了该扩展插件的信息比如collection和member信息后通过调用mapper实例的resource方法来生成url和controller类方法的对应关系,collection和member是在扩展插件里定义的方法名称,用来后面routes进行映射生成。

查看resource方法实现:

红框标注的是关键调用,通过调用routes模块的方法来生成映射关系。

routes模块根据用户传入的信息如collection和member信息生成url映射,如果没有传入也会默认生成常用的映射url,比如/xxx/的get请求对应到index方法、/xx/的put请求对应到update方法等。

所以这里生成一个app的过程其实也把url映射关系建立起来了

再举一个filter keystonecontext的生成实现:

[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory

查看factory的实现(它继承了Middleware):

可以看到filter的特性是传入了app或filter对象给它,到最后它会在完成自己的调用后,调用下一个filter或者是app

2.2  查看url映射和查看请求解析结果

(1)列出当前所有url映射的方法

编辑文件:

/usr/lib/python2.7/site-packages/nova/api/openstack/__init__.py:

保存退出后重启服务成功可在日志中找到如下日志:

(2)获取具体controller类和方法名

查看某个请求通过url映射解析后得到的controller类和其对应的action方法从而找到具体的调用函数。

编辑该文件:

/usr/lib/python2.7/site-packages/nova/api/openstack/__init__.py:

重启nova-api服务

然后比如做列出云主机列表操作,就可以在日志中找到如下日志:

第一个红框是请求url,第二个红框是通过路由模块解析后生成的结果,可以看到它映射到的controller类是:nova.api.openstack.compute.servers.ServersController

方法是detail

我们可以很容易的找到该方法(nova/api/openstack/compute/servers.py文件中):

2.3  创建一个扩展插件到nova组件上

这里我以创建一个名为groups的扩展插件举例如何在nova服务中添加扩展插件

创建控制资源文件

nova/api/openstack/compute/groups.py:

# --*-- coding:utf8 --*--
import webob
from webob import exc
from nova import compute
from nova.api.openstack.compute.schemas import groups
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api import validation
from nova import exception
from nova import db
from nova.i18n import _
from nova.policies import groups as groups_policies
#from thvmware.manage import VCenterManagement
from nova import context
from oslo_log import log as logging
from nova.console import type as ctype
import nova.conf
from oslo_utils import uuidutils
from nova.consoleauth import rpcapi as consoleauth_rpcapi
import requests
import traceback

CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)

ALIAS = ‘groups‘

class GroupsController(wsgi.Controller):

    def __init__(self):
        super(GroupsController, self).__init__()
        self.context = context.get_admin_context()
        self.consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
        self.compute_api = compute.API()

    @extensions.expected_errors(404)
    @validation.schema(groups.create)
    def create(self, req, body):
        context = req.environ[‘nova.context‘]
        context.can(groups_policies.BASE_POLICY_NAME)
        context.can(groups_policies.POLICY_ROOT % ‘create‘)
        LOG.info(body)

    @extensions.expected_errors((400, 404))
    def add_group(self, req, body):
        context = req.environ[‘nova.context‘]
        ret = {‘ret_state‘:‘failed‘}
        values = body[‘group‘]
        if not values.has_key(‘group_id‘) or values[‘group_id‘] == ‘‘:
            values[‘group_id‘] = uuidutils.generate_uuid()
        try:
            db.add_instance_group(context, values)
        except Exception,e:
            LOG.error(‘%s‘, traceback.format_exc())
            ret[‘error‘] = e.message
            return ret
        ret[‘ret_state‘] = ‘success‘
        return ret

    @extensions.expected_errors(())
    def get_groups(self, req):
        ret = {‘ret_state‘: ‘failed‘}
        context = req.environ[‘nova.context‘]
        try:
            groups = db.get_groups(context)
            ret[‘data‘] = groups
        except Exception, e:
            LOG.error(‘%s‘, traceback.format_exc())
            ret[‘error‘] = e.message
            return ret
        ret[‘ret_state‘] = ‘success‘
        return ret

class Groups(extensions.V21APIExtensionBase):
    """extended common support."""

    name = "Groups"
    alias = ALIAS
    version = 1

    def get_resources(self):
        member_actions = {‘update‘: ‘POST‘, ‘detail‘: ‘GET‘}

        collection_actions = {‘add_group‘: ‘POST‘,
                              ‘get_groups‘: ‘GET‘}
        resources = []
        # mapper = Route name Methods Path
        # mapper.connect = <bound method ProjectMapper.connect of <cinder.api.openstack.ProjectMapper object at 0x2e5bc10>>
        # # 示例:
        # # map.resource("message", "messages", collection={"rss": "GET"})
        # # "GET /message/rss"  =>  ``Messages.rss()``.
        # # map.resource(‘message‘, ‘messages‘, member={‘mark‘:‘POST‘})
        # # "POST /message/1/mark"  =>  ``Messages.mark(1)``
        res = extensions.ResourceExtension(
            ALIAS,
            GroupsController(),
            collection_actions=collection_actions,
            member_actions=member_actions)
        resources.append(res)

        return resources

    def get_controller_extensions(self):
        return []

创建API会调用到的数据结构定义文件

nova/api/openstack/compute/schemas/groups.py:

from nova.api.validation import parameter_types

create = {
    ‘type‘: ‘object‘,
    ‘properties‘: {
        ‘MyTest‘: {
            ‘type‘: ‘object‘,
            ‘properties‘: {
                ‘myhost‘: parameter_types.hostname,
            },
            ‘additionalProperties‘: False,
        },
    },
    ‘additionalProperties‘: False,
}

创建权限设定文件:

nova/policies/groups.py:

from oslo_policy import policy
from nova.policies import base

BASE_POLICY_NAME = ‘os_compute_api:groups‘
POLICY_ROOT = ‘os_compute_api:groups:%s‘

groups_policies = [
    policy.RuleDefault(
        name=BASE_POLICY_NAME,
        check_str=base.RULE_ADMIN_API),
    policy.RuleDefault(
        name=POLICY_ROOT % ‘create‘,
        check_str=base.RULE_ANY),
    policy.RuleDefault(
        name=POLICY_ROOT % ‘discoverable‘,
        check_str=base.RULE_ANY),
    policy.RuleDefault(
        name=POLICY_ROOT % ‘show‘,
        check_str=base.RULE_ANY),
]

def list_rules():
return groups_policies

注册API:

nova/policies/__init__.py:

from nova.policies import groups
......
def list_rules():
    return itertools.chain(
        ......
        groups.list_rules(),
        ......

添加entry_points:

nova.egg-info/entry_points.txt:

[nova.api.v21.extensions]
........
groups = nova.api.openstack.compute.groups:Groups
........

重启nova-api服务:

可以在日志中看到Loaded extensions中加载的插件中包含了我们新加的插件名字:

也可以使用命令nova list-extensions查看加载的插件

通过之前查看url映射的方法也可以看到建立的映射:

原文地址:https://www.cnblogs.com/luohaixian/p/11706616.html

时间: 2024-08-29 14:48:32

OpenStack Restful API框架介绍的相关文章

python RESTful API框架:Eve 快速入门

Eve是一款Python的REST API框架,用于发布高可定制的.全功能的RESTful的Web服务,帮你轻松创建和部署API,本文翻译自Eve官方网站: http://python-eve.org/quickstart.html#database-interlude Eve 快速入门: 渴望开始吗?这个页面将提供Eve一个很好的介绍.在这之前假设: 你已经安装好了Eve.如果你还没有,可以点击到安装页面. 已经安装了MongoDB. 并且MongoDB 已经运行了. 一个最小的应用 一个最小

如何设计好的RESTful API之安全性

原文:http://blog.csdn.net/ywk253100/article/details/25654101 导读:安全是恒久的话题,对于基于WSDL和SOAP的Web Service,我们有WS-Security这样的安全规范来指导实现认证.授权.身份管理等安全需求.如何保证RESTful API的安全性呢. 关键词:RESTful API API安全性 前面讲了好的RESTful API具有的一些特征,本文会继续探讨RESTful API的安全性问题. InfoQ:安全是恒久的话题,

虚拟研讨会:如何设计好的RESTful API(转)

原文:虚拟研讨会:如何设计好的RESTful API? REST架构风格最初由Roy T. Fielding(HTTP/1.1协议专家组负责人)在其2000年的博士学位论文中提出.HTTP就是该架构风格的一个典型应用.从其诞生之日开始,它就因其可扩展性和简单性受到越来越多的架构师和开发者们的青睐.它必将得到更大的发展.一方面,随着云计算和移动计算的兴起,许多企业愿意在互联网上共享自己的数据.功能:另一方面,在企业中,RESTful API(也称RESTful Web服务)也逐渐超越SOAP成为实

三分钟学会API接口设计 之 Compass 的Restful API 快速入门指南 -- 使用Flask框架

声明: 本博客欢迎转载,但请保留原作者信息! 作者:曾国仕 团队:华为杭州OpenStack团队 引子 大部分开源框架基本上都是使用Curl + RPC的方式构筑系统,以提供对外\对内的交互能力. 这种设计,本人认为更多地是出于层次化与模块化设计的考量,简化整个架构,使得开发轻量简单化. 本文主要介绍Compass的REST API的设计与实现. 通过本文档,读者至少能快速搭建一个属于自己的REST API 框架,并且能够基于该框架进行功能扩展以建立一个完整的系统. Compass的结构简介 图

[CI] 使用CodeIgniter框架搭建RESTful API服务

在2011年8月的时候,我写了一篇博客<使用CodeIgniter框架搭建RESTful API服务>,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法.转眼两年过去了,REST在这两年里有了很大的改进.我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本.我的项目基于Phil Sturgeon的CodeIgniter REST Server,遵循他自己的DBAD协议.Phil的这个项目很棒,干净利落,简单实用,并

openstack:RESTful API的查看与调试

1 相关文档及官网示例 官网API介绍: <openstack-bk-api-ref.pdf> 网址: http://developer.openstack.org/api-ref.html 下载pdf: http://developer.openstack.org/api-ref-guides/bk-api-ref.pdf 关于RESTful可以参考官方文档: http://docs.openstack.org/zh_CN/api/quick-start/content/index.html

使用CodeIgniter框架搭建RESTful API服务

使用CodeIgniter框架搭建RESTful API服务 发表于 2014-07-12   |   分类于 翻译笔记   |   6条评论 在2011年8月的时候,我写了一篇博客<使用CodeIgniter框架搭建RESTful API服务>,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法.转眼两年过去了,REST在这两年里有了很大的改进.我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本. 我的项目基于P

RESTful API - 介绍

目录 RESTful API 介绍 RESTful介绍 RESTful API设计指南 API与用户的通信协议 域名 版本(Versioning) 路径(Endpoint) 浏览器请求方式(method) 过滤信息(Filtering) 状态码 错误处理(Error handling) 返回结果 Hypermedia API 其他 RESTful API 介绍 RESTful介绍 REST是Representational StateTransfer的简称,中文翻译为"表征状态转移"或

Yii2框架RESTful API教程(二) - 格式化响应,授权认证和速率限制

之前写过一篇Yii2框架RESTful API教程(一) - 快速入门,今天接着来探究一下Yii2 RESTful的格式化响应,授权认证和速率限制三个部分 一.目录结构 先列出需要改动的文件.目录如下: web ├─ common │ └─ models │ └ User.php └─ frontend ├─ config │ └ main.php └─ controllers └ BookController.php 二.格式化响应 Yii2 RESTful支持JSON和XML格式,如果想指定