From 4e0402395546d58e35cdcdbd7e5a31e0e28867a5 Mon Sep 17 00:00:00 2001 From: Kanagaraj Manickam Date: Sun, 20 Mar 2016 12:57:49 +0530 Subject: [PATCH] 1.0 Change-Id: I4de4d2c2e8f9fe4462d3dc68e209c168fff2ce48 --- os_namos/__init__.py | 9 +- os_namos/common/__init__.py | 0 os_namos/common/config.py | 36 ++++++++ os_namos/common/exception.py | 30 +++++++ os_namos/common/messaging.py | 111 ++++++++++++++++++++++++ os_namos/common/rpcapi.py | 67 +++++++++++++++ os_namos/sync.py | 158 +++++++++++++++++++++++++++++++++++ requirements.txt | 4 + 8 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 os_namos/common/__init__.py create mode 100644 os_namos/common/config.py create mode 100644 os_namos/common/exception.py create mode 100644 os_namos/common/messaging.py create mode 100644 os_namos/common/rpcapi.py create mode 100644 os_namos/sync.py diff --git a/os_namos/__init__.py b/os_namos/__init__.py index 4c17e3b..b35496e 100644 --- a/os_namos/__init__.py +++ b/os_namos/__init__.py @@ -12,8 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. -import pbr.version +from os_namos import sync - -__version__ = pbr.version.VersionInfo( - 'os-namos').version_string() +RegistrationInfo = sync.RegistrationInfo +Config = sync.Config +register_myself = sync.register_myself +collect_registration_info = sync.collect_registration_info diff --git a/os_namos/common/__init__.py b/os_namos/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/os_namos/common/config.py b/os_namos/common/config.py new file mode 100644 index 0000000..afa6966 --- /dev/null +++ b/os_namos/common/config.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg +from oslo_log import log as logging + +import os_namos # noqa + +PROJECT_NAME = 'namos' +VERSION = '0.0.1' +MESSAGE_QUEUE_CONDUCTOR_TOPIC = '%s.conductor' % PROJECT_NAME +CONF = cfg.CONF + + +def init_conf(prog): + CONF(project=PROJECT_NAME, + version=VERSION, + prog=prog) + + +def init_log(project=PROJECT_NAME): + logging.register_options(cfg.CONF) + logging.setup(cfg.CONF, + project, + version=VERSION) diff --git a/os_namos/common/exception.py b/os_namos/common/exception.py new file mode 100644 index 0000000..09bd845 --- /dev/null +++ b/os_namos/common/exception.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class NamosException(Exception): + def __init__(self, **kwargs): + self.message = kwargs.get('message') or "UNKNOWN" + self.data = kwargs.get('data') or {} + self.error_code = kwargs.get('error_code') or -1 + self.http_status_code = kwargs.get('http_status_code') or 500 + + def __str__(self): + return unicode(self.message).encode('UTF-8') + + def __unicode__(self): + return unicode(self.message) + + def __deepcopy__(self, memo): + return self.__class__(**self.kwargs) diff --git a/os_namos/common/messaging.py b/os_namos/common/messaging.py new file mode 100644 index 0000000..cbee49c --- /dev/null +++ b/os_namos/common/messaging.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_config import cfg +import oslo_messaging +from oslo_serialization import jsonutils + +from oslo_context import context + +DEFAULT_URL = "__default__" +TRANSPORTS = {} + +_ALIASES = { + 'namos.openstack.common.rpc.impl_kombu': 'rabbit', + 'namos.openstack.common.rpc.impl_qpid': 'qpid', + 'namos.openstack.common.rpc.impl_zmq': 'zmq', +} + + +class RequestContextSerializer(oslo_messaging.Serializer): + def __init__(self, base): + self._base = base + + def serialize_entity(self, ctxt, entity): + if not self._base: + return entity + return self._base.serialize_entity(ctxt, entity) + + def deserialize_entity(self, ctxt, entity): + if not self._base: + return entity + return self._base.deserialize_entity(ctxt, entity) + + @staticmethod + def serialize_context(ctxt): + return ctxt.to_dict() + + @staticmethod + def deserialize_context(ctxt): + return context.RequestContext(ctxt) + + +class JsonPayloadSerializer(oslo_messaging.NoOpSerializer): + @classmethod + def serialize_entity(cls, context, entity): + return jsonutils.to_primitive(entity, convert_instances=True) + + +def get_transport(url=None, optional=False, cache=True): + """Initialise the olso.messaging layer.""" + global TRANSPORTS, DEFAULT_URL + cache_key = url or DEFAULT_URL + transport = TRANSPORTS.get(cache_key) + if not transport or not cache: + try: + transport = oslo_messaging.get_transport(cfg.CONF, url, + aliases=_ALIASES) + except oslo_messaging.InvalidTransportURL as e: + if not optional or e.url: + # NOTE(sileht): olso.messaging is configured but unloadable + # so reraise the exception + raise + return None + else: + if cache: + TRANSPORTS[cache_key] = transport + return transport + + +def get_rpc_server(host, topic, version, endpoint): + """Return a configured olso.messaging rpc server.""" + + target = oslo_messaging.Target(server=host, topic=topic, version=version) + serializer = RequestContextSerializer(JsonPayloadSerializer()) + transport = get_transport(optional=True) + return oslo_messaging.get_rpc_server(transport, target, + [endpoint], executor='eventlet', + serializer=serializer) + + +def get_rpc_client(topic, version, retry=None, **kwargs): + """Return a configured olso.messaging RPCClient.""" + + target = oslo_messaging.Target(version=version, + topic=topic, **kwargs) + serializer = RequestContextSerializer(JsonPayloadSerializer()) + transport = get_transport(optional=True) + return oslo_messaging.RPCClient(transport, target, + serializer=serializer, + retry=retry, + version_cap=version) + + +def cleanup(): + """Cleanup the olso.messaging layer.""" + global TRANSPORTS + + for url in TRANSPORTS: + TRANSPORTS[url].cleanup() + del TRANSPORTS[url] diff --git a/os_namos/common/rpcapi.py b/os_namos/common/rpcapi.py new file mode 100644 index 0000000..9609e18 --- /dev/null +++ b/os_namos/common/rpcapi.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Client side of the OSLO CONFIG NAMOS +""" +import functools + +import json +import oslo_messaging +from oslo_messaging import RemoteError + +from os_namos.common import config # noqa +from os_namos.common import exception as namos_exception +from os_namos.common import messaging as rpc + + +def wrapper_function(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + try: + return func(*args, **kwargs) + except RemoteError as e: + kwargs = json.loads(e.value) + raise namos_exception.NamosException(**kwargs) + + return wrapped + + +class ConductorAPI(object): + RPC_API_VERSION = '1.0' + + def __init__(self, project): + super(ConductorAPI, self).__init__() + self.topic = 'namos.conductor' + + # Setup the messaging tweaks ! here + rpc._ALIASES.update( + { + '%s.openstack.common.rpc.impl_kombu' % project: 'rabbit', + '%s.openstack.common.rpc.impl_qpid' % project: 'qpid', + '%s.openstack.common.rpc.impl_zmq' % project: 'zmq', + } + ) + + oslo_messaging.set_transport_defaults(project) + + self.client = rpc.get_rpc_client(version=self.RPC_API_VERSION, + topic=self.topic) + + @wrapper_function + def register_myself(self, context, registration_info): + # TODO(mrkanag): is to be call instead of cast + return self.client.cast(context, + 'register_myself', + registration_info=registration_info) diff --git a/os_namos/sync.py b/os_namos/sync.py new file mode 100644 index 0000000..75ea614 --- /dev/null +++ b/os_namos/sync.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import socket + +from oslo_context import context + +from os_namos.common import rpcapi + + +NAMOS_RPCAPI = None + + +class RegistrationInfo(object): + def __init__(self, + host, + project_name, + prog_name, + fqdn=socket.gethostname(), + pid=os.getpid(), + config_file_list=None, + config_dict=None): + self.host = host + self.project_name = project_name + self.fqdn = fqdn + self.prog_name = prog_name + self.pid = pid + self.config_file_list = config_file_list or list() + + # List of configuration which CONF is already updated with + self.config_dict = config_dict or dict() + + +class Config(object): + def __init__(self, + name, + type, + value, + help=None, + default_value=None, + required=False, + secret=False, + file=None): + self.name = name + self.default_value = default_value + self.help = help + self.type = type + self.value = value + self.required = required + self.secret = secret + self.file = file + + +def collect_registration_info(): + from oslo_config import cfg + self = cfg.CONF + + def normalize_type(type): + if str(type).find('function'): + return 'String' + return type + + def get_host(): + try: + return getattr(self, 'host') + except: # noqa + import socket + return socket.gethostname() + + reg_info = RegistrationInfo(host=get_host(), + project_name=self.project, + prog_name=self.prog, + config_file_list=self.default_config_files) + + config_dict = dict() + for opt_name in sorted(self._opts): + opt = self._get_opt_info(opt_name)['opt'] + cfg = Config(name='%s' % opt_name, + type='%s' % normalize_type(opt.type), + value='%s' % getattr(self, opt_name), + help='%s' % opt.help, + required=opt.required, + secret=opt.secret, + default_value='%s' % opt.default) + config_dict[cfg.name] = cfg + + for group_name in self._groups: + group_attr = self.GroupAttr(self, self._get_group(group_name)) + for opt_name in sorted(self._groups[group_name]._opts): + opt = self._get_opt_info(opt_name, group_name)['opt'] + cfg = Config(name="%s.%s" % (group_name, opt_name), + type='%s' % normalize_type(opt.type), + value='%s' % getattr(group_attr, opt_name), + help='%s' % opt.help, + required=opt.required, + secret=opt.secret, + default_value='%s' % opt.default) + config_dict[cfg.name] = cfg + reg_info.config_dict = config_dict + + return reg_info + + +def register_myself(registration_info=None): + global NAMOS_RPCAPI + + if registration_info is None: + registration_info = collect_registration_info() + + if NAMOS_RPCAPI is None: + NAMOS_RPCAPI = rpcapi.ConductorAPI( + project=registration_info.project_name) + + ctx = context.RequestContext() + return NAMOS_RPCAPI.register_myself(ctx, registration_info) + + +def add_config(config): + pass + + +def remove_config(config): + pass + + +def update_config(config): + pass + + +if __name__ == '__main__': + # TODO(mrkanag) Remove this before production ! + from os_namos.common import config + + config.init_log() + config.init_conf('test-run') + + reg_info = RegistrationInfo( + host='namos_development', + project_name=config.PROJECT_NAME, + prog_name='sync', + config_file_list=['/etc/namos/namos.conf'], + config_dict={}) + + print (reg_info.__dict__) + + print (register_myself(reg_info)) diff --git a/requirements.txt b/requirements.txt index 30806d5..ac98f04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,7 @@ # process, which may cause wedges in the gate later. pbr>=1.6 +oslo.context>=0.2.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 +oslo.messaging>=4.0.0 # Apache-2.0 +oslo.utils>=3.5.0 # Apache-2.0 \ No newline at end of file