openstack的公共库(oslo)的使用

来源:互联网 发布:mac os x 10.9.5 iso 编辑:程序博客网 时间:2024/06/06 02:34

为了降低代码冗余度,openstack社区开发了很多公共库。通过这些公共库,可以很容易弄出一个完善鉴权、分布式、易配置、带调用链日志的REST服务。

oslo库的缺点是需要的背景知识比较多,英文文档写的又很简单,要真正用起来,没有几个demo会寸步难行。本文的目的就是通过demo,降低大家使用oslo库的难度。


1. oslo常用组件的一览表

库名作用背景知识oslo.config配置文件无oslo.utils工具库无oslo.service带ssl的REST服务器wsgioslo.log + oslo.context带调用链的日志系统无oslo.messagingRPC调用amqposlo.db数据库sqlalchemyoslo.rootwrapLinux的sudo无oslo.serialization序列化无oslo.i18n国际化无oslo.policy权限系统deploy pasteoslo.middlewarepipelinedeploy pastekeystonemiddleware用户系统deploy paste + keystoneoslo_test测试unittest

2. 配置文件 oslo.config

它把配置项直接融入你的代码内,例子如下:

app.conf 

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. [DEFAULT]  
  2. username=app  
  3.   
  4. [rabbit]  
  5. host = 192.168.1.7  
  6. port = 5672  

myconfig.py

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2.   
  3. import sys  
  4. from oslo_config import cfg  
  5.   
  6. #默认组的配置项  
  7. service_opts = [  
  8.     cfg.StrOpt('username',  
  9.                default='default',  
  10.                help='user name'),  
  11.     cfg.StrOpt('password',  
  12.                help='password')  
  13. ]  
  14.   
  15. #自定义配置组  
  16. rabbit_group = cfg.OptGroup(  
  17.     name='rabbit',   
  18.     title='RabbitMQ options'  
  19. )  
  20.   
  21. # 配置组中的多配置项模式   
  22. rabbit_Opts = [  
  23.     cfg.StrOpt('host',  
  24.                default='localhost',  
  25.                help='IP/hostname to listen on.'),  
  26.     cfg.IntOpt('port',  
  27.                default=5672,  
  28.                help='Port number to listen on.')  
  29. ]  
  30.   
  31.   
  32. CONF = cfg.CONF  
  33. #注册默认组的配置项  
  34. CONF.register_opts(service_opts)  
  35. #配置组必须在其组件被注册前注册!  
  36. CONF.register_group(rabbit_group)  
  37. #注册配置组中含有多个配置项的模式,必须指明配置组  
  38. CONF.register_opts(rabbit_Opts, rabbit_group)  
  39.   
  40.   
  41. #设置默认的日志文件名  
  42. CONF(sys.argv[1:], default_config_files=['app.conf'])  
  43.   
  44. #使用配置项  
  45. print ("username=%s  rabbitmq.host=%s " % (CONF.username, CONF.rabbit.host))  

3. 工具库 oslo.utils

函数名作用oslo_utils.encodeutils.exception_to_unicode(exc)异常消息转unicodeoslo_utils.encodeutils.safe_decode(text, incoming=None, errors='strict')其他编码转unicodeoslo_utils.encodeutils.safe_encode(text, incoming=None, encoding='utf-8', errors='strict')unicode转其他编码,默认utf-8oslo_utils.encodeutils.to_utf8(text)unicode转utf-8oslo_utils.eventletutils.fetch_current_thread_functor()获取当前线程的结构体oslo_utils.fileutils.delete_if_exists(path)删除文件oslo_utils.fileutils.ensure_tree(path, mode=511)创建文件夹oslo_utils.fileutils.remove_path_on_error(path)删除文件夹oslo_utils.fileutils.write_to_tempfile(content, path=None, suffix='', prefix='tmp')写入临时文件oslo_utils.importutils.import_any(module, *modules)动态导入一个python包oslo_utils.importutils.import_class(import_str)动态导入一个python类oslo_utils.importutils.import_object(import_str, *args, **kwargs)动态导入一个python对象oslo_utils.importutils.try_import(import_str, default=None)尝试导入一个包,失败了用defaultoslo_utils.netutils.get_my_ipv4()获取本地的ipv4地址oslo_utils.netutils.is_ipv6_enabled()查看本地网络是否允许ipv6oslo_utils.netutils.is_valid_cidr(address)判断一个地址是否合法oslo_utils.netutils.is_valid_ip(address)判断ip是否合法oslo_utils.netutils.is_valid_ipv4(address)判断是否是合法的ipv4地址oslo_utils.netutils.is_valid_ipv6(address)判断是否是合法的ipv6地址oslo_utils.netutils.urlsplit(url, scheme='', allow_fragments=True)类似urlparse.urlsplit(),切分urloslo_utils.reflection.accepts_kwargs(function)查看函数是否接受kwargs类似的参数oslo_utils.reflection.get_class_name(obj, fully_qualified=True)获取对象的类名oslo_utils.reflection.get_all_class_names(obj, up_to=<type 'object'>)获取父类名字oslo_utils.reflection.get_callable_args(function, required_only=False)获取函数能传的参数oslo_utils.reflection.get_member_names(obj, exclude_hidden=True)获取对象的属性名oslo_utils.reflection.get_members(obj, exclude_hidden=True)获取对象的属性oslo_utils.reflection.get_method_self(method)获取函数的selfoslo_utils.reflection.is_subclass(obj, cls)obj是否是cls的子类oslo_utils.strutils.bool_from_string(subject, strict=False, default=False)str转booloslo_utils.strutils.check_string_length(value, name=None, min_length=0, max_length=None)检查字符串长度oslo_utils.strutils.int_from_bool_as_string(subject)bool转intoslo_utils.strutils.is_int_like(val)检查是否是数字oslo_utils.strutils.mask_dict_password(dictionary, secret='***')将字符串中的password替换掉oslo_utils.strutils.mask_password(message, secret='***')将字符串中的password替换掉oslo_utils.strutils.string_to_bytes(text, unit_system='IEC', return_int=False)str转bytesoslo_utils.timeutils.delta_seconds(before, after)计算时间差oslo_utils.timeutils.is_newer_than(after, seconds)比较时间oslo_utils.timeutils.isotime(at=None, subsecond=False)时间转iso格式oslo_utils.timeutils.parse_strtime(timestr, fmt='%Y-%m-%dT%H:%M:%S.%f')字符串转时间oslo_utils.timeutils.strtime(at=None, fmt='%Y-%m-%dT%H:%M:%S.%f')时间转字符串oslo_utils.timeutils.utcnow(with_timezone=False)获取当前时间oslo_utils.uuidutils.generate_uuid()产生一个uuidoslo_utils.uuidutils.is_uuid_like(val)检查字符串是否是uuidoslo_utils.versionutils.convert_version_to_int(version)version转intoslo_utils.versionutils.convert_version_to_str(version_int)version转字符串

4. REST服务器 oslo.service

oslo.service比较负责,因为它透传了很多wsgi的参数,这些其实是开发者不希望直接看到的。下面的例子在oslo.service的基础上再封装了一个小的MiniService,这样用起来会比较方便。

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. import sys  
  3. from webob import Request  
  4. #引入配置文件  
  5. from oslo_config import cfg  
  6. #引入带调用链的日志  
  7. from oslo_log import log as logging  
  8. from oslo_context import context  
  9. #引入REST服务  
  10. from oslo_service import service  
  11. from oslo_service import wsgi  
  12.   
  13. CONF = cfg.CONF  
  14. LOG = logging.getLogger(__name__)  
  15. logging.register_options(CONF)  
  16. logging.setup(CONF, "m19k")  
  17.   
  18. #mini服务  
  19. class MiniService:  
  20.     def __init__(self, host = "0.0.0.0", port = "9000", workers = 1, use_ssl = False, cert_file = None, ca_file = None):  
  21.         self.host = host  
  22.         self.port = port  
  23.         self.workers = workers  
  24.         self.use_ssl = use_ssl  
  25.         self.cert_file = cert_file  
  26.         self.ca_file = ca_file  
  27.         self._actions = {}  
  28.       
  29.     def add_action(self, url_path, action):  
  30.         if (url_path.lower() == "default"or (url_path == "/"or (url_path == ""):  
  31.             url_path = "default"  
  32.         elif (not url_path.startswith("/")):  
  33.             url_path = "/" + url_path  
  34.         self._actions[url_path] = action  
  35.       
  36.     def _app(self, environ, start_response):  
  37.         context.RequestContext()  
  38.         LOG.debug("start action.")  
  39.         request = Request(environ)  
  40.         action = self._actions.get(environ['PATH_INFO'])  
  41.         if action == None:  
  42.             action = self._actions.get("default")  
  43.         if action != None:  
  44.             result = action(environ, request.method, request.path_info, request.query_string, request.body)  
  45.             try:  
  46.                 result[1]  
  47.             except Exception,e:  
  48.                 result = ('200 OK', str(result))  
  49.             start_response(result[0], [('Content-Type''text/plain')])  
  50.             return result[1]  
  51.         start_response("200 OK",[('Content-type''text/html')])  
  52.         return "mini service is ok\n"  
  53.           
  54.     def start(self):  
  55.         self.server = wsgi.Server(CONF,  
  56.                                   "m19k",  
  57.                                   self._app,  
  58.                                   host = self.host,  
  59.                                   port = self.port,  
  60.                                   use_ssl = self.use_ssl)  
  61.         launcher = service.ProcessLauncher(CONF)  
  62.         launcher.launch_service(self.server, workers = self.workers)  
  63.         LOG.debug("launch service (%s:%s)." % (self.host, self.port))  
  64.         launcher.wait()  
使用上述miniserver即可创建一个REST服务器,代码如下
[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. import sys  
  3. from oslo_config import cfg  
  4. from oslo_log import log as logging  
  5. import miniservice  
  6.   
  7. CONF = cfg.CONF  
  8. LOG = logging.getLogger(__name__)  
  9.   
  10. def default_action(env, method, path, query, body):  
  11.     LOG.info("demo action (method:%s, path:%s, query:%s, body:%s)"  
  12.         % (method, path, query, body))  
  13.     return ("200 OK""default")  
  14.   
  15. def test_action(env, method, path, query, body):  
  16.     LOG.info("test (method:%s, path:%s, query:%s, body:%s)"  
  17.         % (method, path, query, body))  
  18.     return ("200 OK""test")  
  19.   
  20. if __name__ == "__main__":  
  21.     CONF(sys.argv[1:])  
  22.     host = getattr(CONF, "host""0.0.0.0")  
  23.     port = getattr(CONF, "port""8001")  
  24.     service = miniservice.MiniService(host, port)  
  25.     service.add_action("", default_action)  
  26.     service.add_action("test", test_action)  
  27.     service.start()  

通过curl即可测试

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. curl http://localhost:8001/test -H "content-type:application/json" -X POST -d "{'a':'b', 'c':'1'}"   
当然还可以通过自定义的python的httpclient,代码如下:
[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. import uuid  
  3. import socket  
  4. import functools  
  5. import requests  
  6. from oslo_config import cfg  
  7. from oslo_log import log as logging  
  8. from oslo_serialization import jsonutils  
  9.   
  10. client_opts = [  
  11.     cfg.BoolOpt('debug',  
  12.                 default=False,  
  13.                 help="Print log in every request"),  
  14. ]  
  15.   
  16. CONF = cfg.CONF  
  17. CONF.register_opts(client_opts)  
  18. LOG = logging.getLogger(__name__)  
  19.   
  20. class HttpClient(object):  
  21.     def __init__(self, cert=None, timeout=None, session=None):  
  22.         self.cert = cert  
  23.         self.timeout = None  
  24.         if not session:  
  25.             session = requests.Session()  
  26.             # Use TCPKeepAliveAdapter to fix bug 1323862  
  27.             for scheme in list(session.adapters):  
  28.                 session.mount(scheme, TCPKeepAliveAdapter())  
  29.         self.session = session  
  30.   
  31.     def request(self, url, method, json=None, connect_retries=0, **kwargs):  
  32.         #设置Http头,一般用于存储认证信息和格式信息  
  33.         headers = kwargs.setdefault('headers', dict())  
  34.   
  35.         if self.cert:  
  36.             kwargs.setdefault('cert'self.cert)  
  37.   
  38.         if self.timeout is not None:  
  39.             kwargs.setdefault('timeout'self.timeout)  
  40.   
  41.         user_agent = headers.setdefault('User-Agent', uuid.uuid4().hex)  
  42.   
  43.         if json is not None:  
  44.             headers['Content-Type'] = 'application/json'  
  45.             kwargs['data'] = jsonutils.dumps(json)  
  46.   
  47.         #设置重试  
  48.         send = functools.partial(self._send_request, url, method, connect_retries)  
  49.   
  50.         #获取response  
  51.         resp = send(**kwargs)  
  52.         return resp  
  53.   
  54.     def _send_request(self, url, method, connect_retries, connect_retry_delay=0.5, **kwargs):  
  55.         try:  
  56.             if CONF.debug:  
  57.                 LOG.debug("REQ:{url:%s, method:%s}" % (url, method))  
  58.             resp = self.session.request(method, url, **kwargs)  
  59.         except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:  
  60.             if connect_retries <= 0:  
  61.                 raise  
  62.   
  63.             time.sleep(connect_retry_delay)  
  64.   
  65.             return self._send_request(  
  66.                 url, method, connect_retries=connect_retries - 1,  
  67.                 connect_retry_delay=connect_retry_delay * 2,  
  68.                 **kwargs)  
  69.         if CONF.debug:  
  70.             LOG.debug("RESP:{url:%s, method:%s, status:%s}" % (url, method, resp.status_code))  
  71.         return resp  
  72.   
  73.     def head(self, url, **kwargs):  
  74.         return self.request(url, 'HEAD', **kwargs)  
  75.   
  76.     def get(self, url, **kwargs):  
  77.         return self.request(url, 'GET', **kwargs)  
  78.   
  79.     def post(self, url, **kwargs):  
  80.         return self.request(url, 'POST', **kwargs)  
  81.   
  82.     def put(self, url, **kwargs):  
  83.         return self.request(url, 'PUT', **kwargs)  
  84.   
  85.     def delete(self, url, **kwargs):  
  86.         return self.request(url, 'DELETE', **kwargs)  
  87.   
  88.     def patch(self, url, **kwargs):  
  89.         return self.request(url, 'PATCH', **kwargs)  
  90.   
  91.   
  92. #用于解决TCP Keep-Alive的补丁  
  93. class TCPKeepAliveAdapter(requests.adapters.HTTPAdapter):  
  94.     def init_poolmanager(self, *args, **kwargs):  
  95.         if 'socket_options' not in kwargs:  
  96.             socket_options = [  
  97.                 # Keep Nagle's algorithm off  
  98.                 (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),  
  99.                 # Turn on TCP Keep-Alive  
  100.                 (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),  
  101.             ]  
  102.             if hasattr(socket, 'TCP_KEEPIDLE'):  
  103.                 socket_options += [  
  104.                     # Wait 60 seconds before sending keep-alive probes  
  105.                     (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)  
  106.                 ]  
  107.   
  108.             if hasattr(socket, 'TCP_KEEPCNT'):  
  109.                 socket_options += [  
  110.                     # Set the maximum number of keep-alive probes  
  111.                     (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)  
  112.                 ]  
  113.   
  114.             if hasattr(socket, 'TCP_KEEPINTVL'):  
  115.                 socket_options += [  
  116.                     # Send keep-alive probes every 15 seconds  
  117.                     (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)  
  118.                 ]  
  119.   
  120.             kwargs['socket_options'] = socket_options  
  121.         super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs)  
  122.   
  123. httpclient = HttpClient()  
  124. print httpclient.request("http://localhost:8001/test""POST""{'a':'b'}")  


5. 日志和调用链 oslo.log + oslo.context
纯粹的oslo.log是很容易使用的,参见下面的例子:

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. from oslo_config import cfg  
  2. from oslo_log import log as logging  
  3.   
  4. LOG = logging.getLogger(__name__)  
  5. CONF = cfg.CONF  
  6. DOMAIN = "demo"  
  7.   
  8. logging.register_options(CONF)  
  9. logging.setup(CONF, DOMAIN)  
  10.   
  11. # Oslo Logging uses INFO as default  
  12. LOG.info("Oslo Logging")  
  13. LOG.warning("Oslo Logging")  
  14. LOG.error("Oslo Logging")  
而oslo.context(所谓的调用链),指的是每个Rest请求里面,在打印日志的时候都会带一个不变的request_id,由此可以分离出单次操作的日志。
在上述miniservice中,在REST的入口处,通过
[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. context.RequestContext()  
即生成了这样的request_id,之后每次log都会自动带上它。

6. RPC调用 oslo.messaging

一个服务对外是REST接口,而服务内部的多个组件走的是RPC。Openstack中,RPC一般用rabbitmq来实现,oslo.messaging就是封装它的。可惜的是,它也要让读者有amqp的背景知识。

server.py

[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. from oslo_config import cfg  
  2. import oslo_messaging  
  3. from oslo_log import log as logging  
  4. import time  
  5.   
  6. CONF = cfg.CONF  
  7. LOG = logging.getLogger(__name__)  
  8. logging.register_options(CONF)  
  9. logging.setup(CONF, "myservice")  
  10. CONF(default_config_files=['app.conf'])  
  11.   
  12. class ServerControlEndpoint(object):  
  13.     target = oslo_messaging.Target(namespace='control',  
  14.                                    version='2.0')  
  15.   
  16.     def __init__(self, server):  
  17.         self.server = server  
  18.   
  19.     def stop(self, ctx):  
  20.         if self.server:  
  21.             self.server.stop()  
  22.   
  23. class TestEndpoint(object):  
  24.     def test(self, ctx, arg):  
  25.         print "test"  
  26.         print arg  
  27.         return arg  
  28.   
  29. transport = oslo_messaging.get_transport(cfg.CONF)  
  30. target = oslo_messaging.Target(topic='test123', server='server1')  
  31. endpoints = [  
  32.     ServerControlEndpoint(None),  
  33.     TestEndpoint(),  
  34. ]  
  35. server = oslo_messaging.get_rpc_server(transport, target, endpoints,  
  36.                                        executor='blocking')  
  37. try:  
  38.     server.start()  
  39.     while True:  
  40.         time.sleep(1)  
  41. except KeyboardInterrupt:  
  42.     print("Stopping server")  
  43.   
  44. server.stop()  
  45. server.wait()  
client.py
[python] view plain copy
在CODE上查看代码片派生到我的代码片
  1. import oslo_messaging as messaging  
  2. from oslo_context import context  
  3. from oslo_config import cfg  
  4. from oslo_log import log as logging  
  5.   
  6. CONF = cfg.CONF  
  7. LOG = logging.getLogger(__name__)  
  8. logging.register_options(CONF)  
  9. logging.setup(CONF, "myservice")  
  10. CONF(default_config_files=['app.conf'])  
  11.   
  12. ctxt = {}  
  13. arg = {'a':'b'}  
  14.   
  15. transport = messaging.get_transport(cfg.CONF)  
  16. target = messaging.Target(topic='test123')  
  17. client = messaging.RPCClient(transport, target)  
  18. client.call(ctxt, 'test', arg=arg)  
0 0
原创粉丝点击