Merge "Reduce image footprint"
authorBin Yang <bin.yang@windriver.com>
Tue, 15 Nov 2022 01:54:05 +0000 (01:54 +0000)
committerGerrit Code Review <gerrit@o-ran-sc.org>
Tue, 15 Nov 2022 01:54:05 +0000 (01:54 +0000)
16 files changed:
o2common/authmw/authmiddleware.py
o2common/config/config.py
o2common/domain/filter.py [new file with mode: 0644]
o2common/views/view.py
o2ims/adapter/clients/ocloud_client.py
o2ims/service/auditor/ocloud_handler.py
o2ims/service/command/notify_alarm_handler.py
o2ims/service/command/notify_handler.py
o2ims/service/command/registration_handler.py
o2ims/views/alarm_route.py
o2ims/views/alarm_view.py
o2ims/views/ocloud_dto.py
o2ims/views/ocloud_route.py
o2ims/views/ocloud_view.py
tests/unit/test_alarm.py
tests/unit/test_ocloud.py

index 13be910..cd9df4b 100644 (file)
@@ -15,6 +15,8 @@
 from werkzeug.wrappers import Request, Response
 from o2common.helper import o2logging
 from o2common.authmw.authprov import auth_definer
+from flask_restx._http import HTTPStatus
+import json
 
 logger = o2logging.get_logger(__name__)
 
@@ -28,6 +30,30 @@ class AuthRequiredExp(Exception):
             'WWW-Authenticate': '{}'.format(self.value)}
 
 
+class AuthProblemDetails():
+    def __init__(self, code: int, detail: str, path: str,
+                 title=None, instance=None
+                 ) -> None:
+        self.status = code
+        self.detail = detail
+        self.type = path
+        self.title = title if title is not None else self.getTitle(code)
+        self.instance = instance if instance is not None else []
+
+    def getTitle(self, code):
+        return HTTPStatus(code).phrase
+
+    def serialize(self):
+        details = {}
+        for key in dir(self):
+            if key == 'ns' or key.startswith('__') or \
+                    callable(getattr(self, key)):
+                continue
+            else:
+                details[key] = getattr(self, key)
+        return json.dumps(details, indent=True)
+
+
 class AuthFailureExp(Exception):
     def __init__(self, value):
         self.value = value
@@ -37,14 +63,14 @@ class AuthFailureExp(Exception):
             'WWW-Authenticate': '{}'.format(self.value)}
 
 
-def _response_wrapper(environ, start_response, header):
+def _response_wrapper(environ, start_response, header, detail):
     res = Response(headers=header,
-                   mimetype='text/plain', status=401)
+                   mimetype='application/json', status=401, response=detail)
     return res(environ, start_response)
 
 
-def _internal_err_response_wrapper(environ, start_response):
-    res = Response(mimetype='text/plain', status=500)
+def _internal_err_response_wrapper(environ, start_response, detail):
+    res = Response(mimetype='application/json', status=500, response=detail)
     return res(environ, start_response)
 
 
@@ -61,8 +87,7 @@ class authmiddleware():
         logger.info(__name__ + 'authentication middleware')
         req = Request(environ, populate_request=True, shallow=True)
         try:
-            auth_header = req.headers['Authorization']
-
+            auth_header = req.headers.get('Authorization', None)
             if auth_header:
                 auth_token = auth_header.split(" ")[1]
 
@@ -78,19 +103,29 @@ class authmiddleware():
                         logger.error(
                             'Internal exception happend \
                             ed {}'.format(str(ex)), exc_info=True)
+                        prb = AuthProblemDetails(
+                            500, 'Internal error.', req.path)
                         return \
-                            _internal_err_response_wrapper(environ,
-                                                           start_response)
+                            _internal_err_response_wrapper(
+                                environ,
+                                start_response, prb.serialize())
                 else:
                     raise AuthFailureExp(
                         'Bearer realm="Authentication Failed"')
             else:
                 raise AuthRequiredExp('Bearer realm="Authentication Required"')
         except AuthRequiredExp as ex:
-            return _response_wrapper(environ, start_response, ex.dictize())
+            prb = AuthProblemDetails(401, ex.value, req.path)
+            return _response_wrapper(environ, start_response,
+                                     ex.dictize(), prb.serialize())
         except AuthFailureExp as ex:
-            return _response_wrapper(environ, start_response, ex.dictize())
+            prb = AuthProblemDetails(401, ex.value, req.path)
+            return _response_wrapper(environ, start_response,
+                                     ex.dictize(), prb.serialize())
         except Exception as ex:
             logger.error('Internal exception happended {}'.format(
                 str(ex)), exc_info=True)
-            return _internal_err_response_wrapper(environ, start_response)
+            prb = AuthProblemDetails(500, 'Internal error.', req.path)
+            return \
+                _internal_err_response_wrapper(environ,
+                                               start_response, prb.serialize())
index a2907c4..f488b71 100644 (file)
@@ -56,7 +56,7 @@ def get_api_url():
     port_external = 30205
     port = port_internal if host_external is None or host_external == '' \
         else port_external
-    return f"http://{host}:{port}"
+    return f"https://{host}:{port}"
 
 
 def get_root_api_base():
@@ -348,9 +348,12 @@ def get_auth_provider():
 
 def get_dms_support_profiles():
     profiles = config.conf.API.DMS_SUPPORT_PROFILES
-    profiles = profiles.replace("'", "").replace(
-        '"', "").replace('[', "").replace(']', "")
-    profiles = profiles.split(',')
+    if profiles is None or profiles == '':
+        profiles = []
+    elif "[" in profiles and "]" in profiles:
+        profiles = profiles.replace("'", "").replace(
+            '"', "").replace('[', "").replace(']', "")
+        profiles = profiles.split(',')
     if 'native_k8sapi' not in profiles:
         profiles.append('native_k8sapi')
     return profiles
diff --git a/o2common/domain/filter.py b/o2common/domain/filter.py
new file mode 100644 (file)
index 0000000..80fe322
--- /dev/null
@@ -0,0 +1,102 @@
+# Copyright (C) 2021-2022 Wind River Systems, Inc.
+#
+#  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 sqlalchemy.sql.elements import ColumnElement
+from sqlalchemy import or_
+
+from o2common.helper import o2logging
+logger = o2logging.get_logger(__name__)
+
+
+def gen_orm_filter(obj: ColumnElement, filter_str: str):
+    if not filter_str:
+        return []
+    filter_without_space = filter_str.replace(" ", "")
+    items = filter_without_space.split(';')
+
+    filter_list = list()
+    for i in items:
+        if '(' in i:
+            i = i.replace("(", "")
+        if ')' in i:
+            i = i.replace(")", "")
+        filter_expr = i.split(',')
+        if len(filter_expr) < 3:
+            continue
+        filter_op = filter_expr[0]
+        filter_key = filter_expr[1]
+        filter_vals = filter_expr[2:]
+        filter_list.extend(toFilterArgs(
+            filter_op, obj, filter_key, filter_vals))
+    logger.info('Filter list length: %d' % len(filter_list))
+    return filter_list
+
+
+def toFilterArgs(operation: str, obj: ColumnElement, key: str, values: list):
+    if not hasattr(obj, key):
+        logger.warning('Filter attrName %s not in Object %s.' %
+                       (key, str(obj)))
+        raise KeyError(
+            'Filter attrName {} not in the Object'.format(key))
+
+    if operation in ['eq', 'neq', 'gt', 'lt', 'gte', 'lte']:
+        if len(values) != 1:
+            raise KeyError(
+                'Filter operation one {} is only support one value.'.
+                format(operation))
+    elif operation in ['in', 'nin', 'cont', 'ncont']:
+        if len(values) == 0:
+            raise KeyError('Filter operation {} value is needed.'.
+                           format(operation))
+    else:
+        raise KeyError('Filter operation {} not support.'.format(operation))
+
+    ll = list()
+    if operation == 'eq':
+        val = values[0]
+        if val.lower() == 'null':
+            val = None
+        ll.append(getattr(obj, key) == val)
+    elif operation == 'neq':
+        val = values[0]
+        if val.lower() == 'null':
+            val = None
+        ll.append(getattr(obj, key) != val)
+    elif operation == 'gt':
+        val = values[0]
+        ll.append(getattr(obj, key) > val)
+    elif operation == 'lt':
+        val = values[0]
+        ll.append(getattr(obj, key) < val)
+    elif operation == 'gte':
+        val = values[0]
+        ll.append(getattr(obj, key) >= val)
+    elif operation == 'lte':
+        val = values[0]
+        ll.append(getattr(obj, key) <= val)
+    elif operation == 'in':
+        ll.append(getattr(obj, key).in_(values))
+    elif operation == 'nin':
+        ll.append(~getattr(obj, key).in_(values))
+    elif operation == 'cont':
+        val_list = list()
+        for val in values:
+            val_list.append(getattr(obj, key).contains(val))
+        ll.append(or_(*val_list))
+    elif operation == 'ncont':
+        val_list = list()
+        for val in values:
+            val_list.append(getattr(obj, key).contains(val))
+        ll.append(~or_(*val_list))
+    return ll
index 6d55b71..2b05027 100644 (file)
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import re
 from sqlalchemy.sql.elements import ColumnElement
-from sqlalchemy import or_
 
 from o2common.views.route_exception import BadRequestException
+from o2common.domain.filter import gen_orm_filter
 
 from o2common.helper import o2logging
 logger = o2logging.get_logger(__name__)
 
 
 def gen_filter(obj: ColumnElement, filter_str: str):
-    if filter_str == '':
-        return []
+    check_filter(obj, filter_str)
+    try:
+        filter_list = gen_orm_filter(obj, filter_str)
+    except KeyError as e:
+        raise BadRequestException(e.args[0])
+    return filter_list
+
+
+# The regular expressions testing example put on here
+# (neq,testkey,value-1)
+# (neq,testkey,value-1,value-2)
+# (gt,hello,1)
+# (gte,world,2)
+# (lt,testlt,notint)
+# (ncont,key1,v1,v_2)
+# (gt,hello,1);(ncont,world,val1,val-2)
+# (eq,wrong,60cba7be-e2cd-3b8c-a7ff-16e0f10573f9)
+# (eq,description,value key)
+def check_filter(obj: ColumnElement, filter_str: str):
+    if not filter_str:
+        return
+    pattern = r'^(\((eq|neq|gt|lt|gte|lte){1},\w+,[\w -\.]+\)\;?|' +\
+        r'\((in|nin|cont|ncont){1},\w*(,[\w -\.]*)*\)\;?)+'
+    result = re.match(pattern, filter_str)
+    logger.warning('filter: {} match result is {}'.format(filter_str, result))
+    if not result:
+        raise BadRequestException(
+            'filter value formater not correct.')
+    check_filter_attribute(obj, filter_str)
+
+
+def check_filter_attribute(obj: ColumnElement, filter_str: str):
     filter_without_space = filter_str.replace(" ", "")
     items = filter_without_space.split(';')
 
-    filter_list = list()
     for i in items:
         if '(' in i:
             i = i.replace("(", "")
@@ -35,67 +65,12 @@ def gen_filter(obj: ColumnElement, filter_str: str):
             i = i.replace(")", "")
         filter_expr = i.split(',')
         if len(filter_expr) < 3:
+            raise BadRequestException(
+                'Filter {} formater not correct.'.format(i))
             continue
-        filter_op = filter_expr[0]
+        filter_op = filter_expr[0]
         filter_key = filter_expr[1]
-        filter_vals = filter_expr[2:]
-        filter_list.extend(toFilterArgs(
-            filter_op, obj, filter_key, filter_vals))
-    logger.info('Filter list length: %d' % len(filter_list))
-    return filter_list
-
-
-def toFilterArgs(operation: str, obj: ColumnElement, key: str, values: list):
-    if not hasattr(obj, key):
-        logger.warning('Filter attrName %s not in Object %s.' %
-                       (key, str(obj)))
-        raise BadRequestException(
-            'Filter attrName {} not in the Object'.format(key))
-
-    if operation in ['eq', 'neq', 'gt', 'lt', 'gte', 'lte']:
-        if len(values) != 1:
-            raise KeyError('Filter operation one is only support one value.')
-    elif operation in ['in', 'nin', 'cont', 'ncont']:
-        if len(values) == 0:
-            raise KeyError('Filter operation value is needed.')
-    else:
-        raise KeyError('Filter operation value not support.')
-
-    ll = list()
-    if operation == 'eq':
-        val = values[0]
-        if val.lower() == 'null':
-            val = None
-        ll.append(getattr(obj, key) == val)
-    elif operation == 'neq':
-        val = values[0]
-        if val.lower() == 'null':
-            val = None
-        ll.append(getattr(obj, key) != val)
-    elif operation == 'gt':
-        val = values[0]
-        ll.append(getattr(obj, key) > val)
-    elif operation == 'lt':
-        val = values[0]
-        ll.append(getattr(obj, key) < val)
-    elif operation == 'gte':
-        val = values[0]
-        ll.append(getattr(obj, key) >= val)
-    elif operation == 'lte':
-        val = values[0]
-        ll.append(getattr(obj, key) <= val)
-    elif operation == 'in':
-        ll.append(getattr(obj, key).in_(values))
-    elif operation == 'nin':
-        ll.append(~getattr(obj, key).in_(values))
-    elif operation == 'cont':
-        val_list = list()
-        for val in values:
-            val_list.append(getattr(obj, key).contains(val))
-        ll.append(or_(*val_list))
-    elif operation == 'ncont':
-        val_list = list()
-        for val in values:
-            val_list.append(getattr(obj, key).contains(val))
-        ll.append(~or_(*val_list))
-    return ll
+        # filter_vals = filter_expr[2:]
+        if not hasattr(obj, filter_key):
+            raise BadRequestException(
+                'Filter attrName {} not in the Object'.format(filter_key))
index 7307bcc..9af3b7a 100644 (file)
@@ -89,7 +89,6 @@ class StxPserverClient(BaseClient):
         return self.driver.getPserver(id)
 
     def _list(self, **filters) -> List[ocloudModel.StxGenericModel]:
-        filters['resourcepoolid']
         return self.driver.getPserverList(**filters)
 
     def _set_stx_client(self):
@@ -230,7 +229,7 @@ class StxClientImp(object):
             os_client_args = config.get_stx_access_info(
                 region_name=subcloud[0].name,
                 subcloud_hostname=subcloud[0].oam_floating_ip)
-            logger.warning(os_client_args)
+            logger.info(os_client_args)
             config_client = get_stx_client(**os_client_args)
         except EndpointException as e:
             msg = e.format_message()
@@ -238,12 +237,13 @@ class StxClientImp(object):
                 os_client_args = config.get_stx_access_info(
                     region_name=subcloud[0].name, sub_is_https=True,
                     subcloud_hostname=subcloud[0].oam_floating_ip)
-                logger.warning(os_client_args)
+                logger.info(os_client_args)
                 config_client = get_stx_client(**os_client_args)
             else:
                 raise ValueError('Stx endpoint exception: %s' % msg)
-        else:
+        except Exception:
             raise ValueError('cgtsclient get subcloud client failed')
+
         return config_client
 
     def setStxClient(self, resource_pool_id):
@@ -283,7 +283,8 @@ class StxClientImp(object):
         if systems[0].distributed_cloud_role is None or \
                 systems[0].distributed_cloud_role != 'systemcontroller':
             return [ocloudModel.StxGenericModel(
-                ResourceTypeEnum.RESOURCE_POOL, systems[0])]
+                ResourceTypeEnum.RESOURCE_POOL,
+                self._respoolconverter(systems[0]))]
 
         pools = []
         if config.get_system_controller_as_respool():
@@ -305,14 +306,16 @@ class StxClientImp(object):
 
         return [ocloudModel.StxGenericModel(
             ResourceTypeEnum.RESOURCE_POOL,
-            respool) for respool in pools if respool]
+                self._respoolconverter(
+                    respool)) for respool in pools if respool]
 
     def getResourcePoolDetail(self, id):
         self.setStxClient(id)
         systems = self.stxclient.isystem.list()
         logger.debug('systems:' + str(systems[0].to_dict()))
         return ocloudModel.StxGenericModel(
-            ResourceTypeEnum.RESOURCE_POOL, systems[0]) if systems else None
+            ResourceTypeEnum.RESOURCE_POOL,
+            self._respoolconverter(systems[0])) if systems else None
 
     def getPserverList(self, **filters) -> List[ocloudModel.StxGenericModel]:
         hosts = self.stxclient.ihost.list()
@@ -320,6 +323,7 @@ class StxClientImp(object):
         return [ocloudModel.StxGenericModel(
             ResourceTypeEnum.PSERVER, self._hostconverter(host))
             for host in hosts if host and (host.availability == 'available'
+                                           or host.availability == 'online'
                                            or host.availability == 'degraded')]
 
     def getPserver(self, id) -> ocloudModel.StxGenericModel:
@@ -524,6 +528,11 @@ class StxClientImp(object):
                                 'more than one system exists in the account.')
             return isystems[0]
 
+    @ staticmethod
+    def _respoolconverter(res_pool):
+        setattr(res_pool, 'name', res_pool.region_name)
+        return res_pool
+
     @ staticmethod
     def _hostconverter(host):
         setattr(host, 'name', host.hostname)
index d1e2fa7..388013f 100644 (file)
@@ -81,7 +81,7 @@ def is_outdated(ocloud: Ocloud, stxobj: StxGenericModel):
 
 
 def create_by(stxobj: StxGenericModel) -> Ocloud:
-    imsendpoint = config.get_api_url() + config.get_o2ims_api_base() + '/'
+    imsendpoint = config.get_api_url()
     globalcloudId = conf.DEFAULT.ocloud_global_id
     description = "An ocloud"
     ocloud = Ocloud(stxobj.id, stxobj.name, imsendpoint,
index 11729a4..8ee4dd8 100644 (file)
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import json
 # import redis
 # import requests
+import ssl
+import json
 from urllib.parse import urlparse
 
 # from o2common.config import config
+from o2common.domain.filter import gen_orm_filter
 from o2common.service.unit_of_work import AbstractUnitOfWork
-from o2ims.domain import commands
-from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEvent2SMO
-import ssl
 from o2common.service.command.handler import get_https_conn_default
 from o2common.service.command.handler import get_http_conn
 from o2common.service.command.handler import get_https_conn_selfsigned
 from o2common.service.command.handler import post_data
+
+from o2ims.domain import commands
+from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEvent2SMO, \
+    AlarmEventRecord
+
 from o2common.helper import o2logging
 logger = o2logging.get_logger(__name__)
 
@@ -34,7 +38,7 @@ def notify_alarm_to_smo(
     cmd: commands.PubAlarm2SMO,
     uow: AbstractUnitOfWork,
 ):
-    logger.info('In notify_alarm_to_smo')
+    logger.debug('In notify_alarm_to_smo')
     data = cmd.data
     with uow:
         subs = uow.alarm_subscriptions.list()
@@ -43,6 +47,30 @@ def notify_alarm_to_smo(
             logger.debug('Alarm Subscription: {}'.format(
                 sub_data['alarmSubscriptionId']))
 
+            alarm = uow.alarm_event_records.get(data.id)
+            if alarm is None:
+                logger.debug('Alarm Event {} does not exists.'.format(data.id))
+                continue
+            if sub_data.get('filter', None):
+                try:
+                    args = gen_orm_filter(AlarmEventRecord, sub_data['filter'])
+                except KeyError:
+                    logger.warning(
+                        'Alarm Subscription {} filter {} has wrong attribute '
+                        'name or value. Ignore the filter'.format(
+                            sub_data['alarmSubscriptionId'],
+                            sub_data['filter']))
+                    callback_smo(sub, data)
+                    continue
+                args.append(AlarmEventRecord.alarmEventRecordId == data.id)
+                ret = uow.alarm_event_records.list_with_count(*args)
+                if ret[0] != 0:
+                    logger.debug(
+                        'Alarm Event {} skip for subscription {} because of '
+                        'the filter.'
+                        .format(data.id, sub_data['alarmSubscriptionId']))
+                    continue
+
             callback_smo(sub, data)
 
 
@@ -69,8 +97,9 @@ def callback_smo(sub: AlarmSubscription, msg: AlarmEvent2SMO):
             return
         logger.error('Notify alarm Response code is: {}'.format(status))
     except ssl.SSLCertVerificationError as e:
-        logger.info(
-            'Notify alarm post data with trusted ca failed: {}'.format(e))
+        logger.debug(
+            'Notify alarm try to post data with trusted ca \
+                failed: {}'.format(e))
         if 'self signed' in str(e):
             conn = get_https_conn_selfsigned(o.netloc)
             try:
index fe22f2a..e7b41e1 100644 (file)
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import json
 # import redis
 # import requests
+import json
+import ssl
 from urllib.parse import urlparse
 
 # from o2common.config import config
+from o2common.domain.filter import gen_orm_filter
 from o2common.service.unit_of_work import AbstractUnitOfWork
-from o2ims.domain import commands
-from o2ims.domain.subscription_obj import Subscription, Message2SMO
 from o2common.service.command.handler import get_https_conn_default
 from o2common.service.command.handler import get_http_conn
 from o2common.service.command.handler import get_https_conn_selfsigned
 from o2common.service.command.handler import post_data
-import ssl
+
+from o2ims.domain import commands, ocloud
+from o2ims.domain.subscription_obj import Subscription, Message2SMO
+
 from o2common.helper import o2logging
 logger = o2logging.get_logger(__name__)
 
@@ -38,28 +41,37 @@ def notify_change_to_smo(
     cmd: commands.PubMessage2SMO,
     uow: AbstractUnitOfWork,
 ):
-    logger.info('In notify_change_to_smo')
+    logger.debug('In notify_change_to_smo')
     data = cmd.data
     with uow:
         subs = uow.subscriptions.list()
         for sub in subs:
             sub_data = sub.serialize()
             logger.debug('Subscription: {}'.format(sub_data['subscriptionId']))
-
-            try:
-                resource_filter = json.loads(sub_data['filter'])
-                if len(resource_filter) > 0:
-                    resource = uow.resources.get(data.id)
-                    logger.debug(type(resource))
-                    if resource:  # TODO deal with resource is empty
-                        res_type_id = resource.serialize()['resourceTypeId']
-                        resourcetype = uow.resource_types.get(res_type_id)
-                        logger.debug(resourcetype.name)
-                        if resourcetype.name not in resource_filter:
-                            continue
-            except json.decoder.JSONDecodeError as err:
-                logger.warning(
-                    'subscription filter decode json failed: {}'.format(err))
+            resource = uow.resources.get(data.id)
+            if resource is None:
+                logger.debug('Resource {} does not exists.'.format(data.id))
+                continue
+            res_pool_id = resource.serialize()['resourcePoolId']
+            logger.debug('res pool id is {}'.format(res_pool_id))
+            if sub_data.get('filter', None):
+                try:
+                    args = gen_orm_filter(ocloud.Resource, sub_data['filter'])
+                except KeyError:
+                    logger.error(
+                        'Subscription {} filter {} has wrong attribute name '
+                        'or value. Ignore the filter.'.format(
+                            sub_data['subscriptionId'], sub_data['filter']))
+                    callback_smo(sub, data)
+                    continue
+                args.append(ocloud.Resource.resourceId == data.id)
+                ret = uow.resources.list_with_count(res_pool_id, *args)
+                if ret[0] != 0:
+                    logger.debug(
+                        'Resource {} skip for subscription {} because of the '
+                        'filter.'
+                        .format(data.id, sub_data['subscriptionId']))
+                    continue
 
             callback_smo(sub, data)
 
@@ -102,7 +114,8 @@ def callback_smo(sub: Subscription, msg: Message2SMO):
             return
         logger.error('Notify Response code is: {}'.format(status))
     except ssl.SSLCertVerificationError as e:
-        logger.info('Notify post data with trusted ca failed: {}'.format(e))
+        logger.debug(
+            'Notify try to post data with trusted ca failed: {}'.format(e))
         if 'self signed' in str(e):
             conn = get_https_conn_selfsigned(o.netloc)
             try:
index aead4cc..c144405 100644 (file)
@@ -108,7 +108,8 @@ def call_smo(reg_data: dict):
     try:
         return post_data(conn, o.path, callback_data)
     except ssl.SSLCertVerificationError as e:
-        logger.info('Register to smo with trusted ca failed: {}'.format(e))
+        logger.debug('Try to register to smo with \
+        trusted ca failed: {}'.format(e))
         if 'self signed' in str(e):
             conn = get_https_conn_selfsigned(o.netloc)
             try:
index 11fad03..4ca44ca 100644 (file)
@@ -17,7 +17,8 @@ from flask_restx import Resource, reqparse
 
 from o2common.service.messagebus import MessageBus
 from o2common.views.pagination_route import link_header, PAGE_PARAM
-from o2common.views.route_exception import NotFoundException
+from o2common.views.route_exception import NotFoundException, \
+    BadRequestException
 from o2ims.views import alarm_view
 from o2ims.views.api_ns import api_ims_monitoring as api_monitoring_v1
 from o2ims.views.alarm_dto import AlarmDTO, SubscriptionDTO
@@ -192,6 +193,10 @@ class SubscriptionsListRouter(Resource):
         mask='{alarmSubscriptionId,callback,consumerSubscriptionId,filter}')
     def post(self):
         data = api_monitoring_v1.payload
+        callback = data.get('callback', None)
+        if not callback:
+            raise BadRequestException('The callback parameter is required')
+
         result = alarm_view.subscription_create(data, bus.uow)
         return result, 201
 
index b3a9e4a..58920f3 100644 (file)
 import uuid as uuid
 
 from o2common.service import unit_of_work
+from o2common.views.view import gen_filter, check_filter
 from o2common.views.pagination_view import Pagination
-from o2common.views.view import gen_filter
+from o2common.views.route_exception import BadRequestException
+
 from o2ims.views.alarm_dto import SubscriptionDTO
 from o2ims.domain.alarm_obj import AlarmSubscription, AlarmEventRecord
 
@@ -61,13 +63,25 @@ def subscription_one(subscriptionId: str,
 
 def subscription_create(subscriptionDto: SubscriptionDTO.subscription_create,
                         uow: unit_of_work.AbstractUnitOfWork):
+    filter = subscriptionDto.get('filter', '')
+    consumer_subs_id = subscriptionDto.get('consumerSubscriptionId', '')
+
+    check_filter(AlarmEventRecord, filter)
 
     sub_uuid = str(uuid.uuid4())
     subscription = AlarmSubscription(
         sub_uuid, subscriptionDto['callback'],
-        subscriptionDto['consumerSubscriptionId'],
-        subscriptionDto['filter'])
+        consumer_subs_id, filter)
     with uow:
+        args = list()
+        args.append(getattr(AlarmSubscription, 'callback')
+                    == subscriptionDto['callback'])
+        args.append(getattr(AlarmSubscription, 'filter') == filter)
+        args.append(getattr(AlarmSubscription,
+                    'consumerSubscriptionId') == consumer_subs_id)
+        count, _ = uow.alarm_subscriptions.list_with_count(*args)
+        if count > 0:
+            raise BadRequestException("The value of parameters is duplicated")
         uow.alarm_subscriptions.add(subscription)
         uow.commit()
         first = uow.alarm_subscriptions.get(sub_uuid)
index 478a513..2bc836d 100644 (file)
@@ -177,6 +177,11 @@ class DeploymentManagerDTO:
         'helmcli_kubeconfig': fields.String(attribute='helmcli_kubeconfig'),
     })
 
+    extensions = api_ims_inventory_v1.model("DeploymentManagerExtensions", {
+        'profileName': fields.String,
+        'profileData': fields.Nested(profile, False, True),
+    })
+
     deployment_manager_get = api_ims_inventory_v1.model(
         "DeploymentManagerGetDto",
         {
@@ -192,12 +197,10 @@ class DeploymentManagerDTO:
             # 'supportedLocations': fields.String,
             # 'capabilities': fields.String,
             # 'capacity': fields.String,
-            'profileName': fields.String,
-            'profileData': fields.Nested(profile, False, True),
-            'extensions': fields.String
+            'extensions': fields.Nested(extensions, True, True)
         },
         mask='{deploymentManagerId,name,description,oCloudId,serviceUri,' +\
-        'profileName,profileData}'
+        'extensions/profileName,extensions/profileData}'
     )
 
 
index af5e8c6..fe0420b 100644 (file)
@@ -17,7 +17,8 @@ from flask_restx import Resource, reqparse
 
 from o2common.service.messagebus import MessageBus
 from o2common.views.pagination_route import link_header, PAGE_PARAM
-from o2common.views.route_exception import NotFoundException
+from o2common.views.route_exception import NotFoundException, \
+    BadRequestException
 from o2ims.domain import ocloud
 from o2ims.views import ocloud_view
 from o2ims.views.api_ns import api_ims_inventory as api_ims_inventory_v1
@@ -517,6 +518,10 @@ class SubscriptionsListRouter(Resource):
         mask='{subscriptionId,callback,consumerSubscriptionId,filter}')
     def post(self):
         data = api_ims_inventory_v1.payload
+        callback = data.get('callback', None)
+        if not callback:
+            raise BadRequestException('The callback parameter is required')
+
         result = ocloud_view.subscription_create(data, bus.uow)
         return result, 201
 
index 953adc9..c685898 100644 (file)
@@ -21,8 +21,10 @@ import shutil
 
 from o2common.service import unit_of_work
 from o2common.config import config
+from o2common.views.view import gen_filter, check_filter
 from o2common.views.pagination_view import Pagination
-from o2common.views.view import gen_filter
+from o2common.views.route_exception import BadRequestException
+
 from o2ims.domain import ocloud
 from o2ims.views.ocloud_dto import SubscriptionDTO
 from o2ims.domain.subscription_obj import Subscription
@@ -140,16 +142,18 @@ def deployment_manager_one(deploymentManagerId: str,
             return None
 
     profile_data = result.pop("profile", None)
-    result['profileName'] = profile
     profiles = config.get_dms_support_profiles()
     if profile not in profiles:
         return ""
 
+    extensions = {
+        'profileName': profile
+    }
     if ocloud.DeploymentManagerProfileDefault == profile \
             or ocloud.DeploymentManagerProfileSOL018 == profile:
         result['serviceUri'] = \
             profile_data['cluster_api_endpoint']
-        result['profileData'] = profile_data
+        extensions['profileData'] = profile_data
     elif ocloud.DeploymentManagerProfileSOL018HelmCLI == profile:
         result['serviceUri'] = \
             profile_data['cluster_api_endpoint']
@@ -160,10 +164,11 @@ def deployment_manager_one(deploymentManagerId: str,
             config.get_helmcli_access()
         helmcli_profile["helmcli_kubeconfig"] = _gen_kube_config(
             deploymentManagerId, profile_data)
-        result['profileData'] = helmcli_profile
+        extensions['profileData'] = helmcli_profile
     else:
         return ""
 
+    result['extensions'] = extensions
     return result
 
 
@@ -220,13 +225,25 @@ def subscription_one(subscriptionId: str,
 
 def subscription_create(subscriptionDto: SubscriptionDTO.subscription_create,
                         uow: unit_of_work.AbstractUnitOfWork):
+    filter = subscriptionDto.get('filter', '')
+    consumer_subs_id = subscriptionDto.get('consumerSubscriptionId', '')
+
+    check_filter(ocloud.Resource, filter)
 
     sub_uuid = str(uuid.uuid4())
     subscription = Subscription(
         sub_uuid, subscriptionDto['callback'],
-        subscriptionDto['consumerSubscriptionId'],
-        subscriptionDto['filter'])
+        consumer_subs_id, filter)
     with uow:
+        args = list()
+        args.append(getattr(Subscription, 'callback')
+                    == subscriptionDto['callback'])
+        args.append(getattr(Subscription, 'filter') == filter)
+        args.append(getattr(Subscription,
+                    'consumerSubscriptionId') == consumer_subs_id)
+        count, _ = uow.subscriptions.list_with_count(*args)
+        if count > 0:
+            raise BadRequestException("The value of parameters is duplicated")
         uow.subscriptions.add(subscription)
         uow.commit()
         first = uow.subscriptions.get(sub_uuid)
index 1cd48b8..0c0b3e5 100644 (file)
@@ -169,10 +169,16 @@ def test_flask_get_one(mock_flask_uow):
         assert resp.status_code == 404
 
 
-def test_flask_post(mock_flask_uow):
+def test_flask_post(mock_flask_uow, mappers):
     session, app = mock_flask_uow
     apibase = config.get_o2ims_monitoring_api_base() + '/v1'
 
+    order_by = MagicMock()
+    order_by.count.return_value = 0
+    order_by.limit.return_value.offset.return_value = []
+    session.return_value.query.return_value.filter.return_value.\
+        order_by.return_value = order_by
+
     with app.test_client() as client:
         session.return_value.execute.return_value = []
 
@@ -180,7 +186,7 @@ def test_flask_post(mock_flask_uow):
         resp = client.post(apibase+'/alarmSubscriptions', json={
             'callback': sub_callback,
             'consumerSubscriptionId': 'consumerSubId1',
-            'filter': 'empty'
+            'filter': '(eq,resourceTypeId,xxx)'
         })
         assert resp.status_code == 201
         assert 'alarmSubscriptionId' in resp.get_json()
index 3c0fde7..5b51221 100644 (file)
@@ -324,7 +324,7 @@ def test_view_deployment_manager_one(mock_uow):
         deployment_manager_id1, uow, profile=profileName)
     assert str(deployment_manager_res.get(
         'serviceUri')) == cluster_endpoint
-    assert str(deployment_manager_res.get(
+    assert str(deployment_manager_res.get("extensions").get(
         "profileName")) == profileName
 
     # profile wrong name
@@ -446,10 +446,16 @@ def test_flask_get_one(mock_flask_uow):
         assert resp.status_code == 404
 
 
-def test_flask_post(mock_flask_uow):
+def test_flask_post(mock_flask_uow, mappers):
     session, app = mock_flask_uow
     apibase = config.get_o2ims_api_base() + '/v1'
 
+    order_by = MagicMock()
+    order_by.count.return_value = 0
+    order_by.limit.return_value.offset.return_value = []
+    session.return_value.query.return_value.filter.return_value.\
+        order_by.return_value = order_by
+
     with app.test_client() as client:
         session.return_value.execute.return_value = []
 
@@ -457,7 +463,7 @@ def test_flask_post(mock_flask_uow):
         resp = client.post(apibase+'/subscriptions', json={
             'callback': sub_callback,
             'consumerSubscriptionId': 'consumerSubId1',
-            'filter': 'empty'
+            'filter': '(eq,resourceTypeId,xxx)'
         })
         assert resp.status_code == 201
         assert 'subscriptionId' in resp.get_json()