From: Zhang Rong(Jon) Date: Thu, 23 Dec 2021 01:36:28 +0000 (+0800) Subject: Add registration the SMO's CRUD interface in IMS X-Git-Tag: 1.0.0~5 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=02b85e27680e1139bc8e1930af5449a26527b4de;p=pti%2Fo2.git Add registration the SMO's CRUD interface in IMS 1. Add domain of registration 2. Mapping domain and ORM 3. Create the interface of CRUD and the related action 4. Update unit test for the registration CRUD Issue-ID: INF-249 Signed-off-by: Zhang Rong(Jon) Change-Id: I1d9235c6d654fa4743417ae5e226581935f16214 --- diff --git a/o2app/adapter/unit_of_work.py b/o2app/adapter/unit_of_work.py index facfc45..1c9cae0 100644 --- a/o2app/adapter/unit_of_work.py +++ b/o2app/adapter/unit_of_work.py @@ -51,6 +51,8 @@ class SqlAlchemyUnitOfWork(AbstractUnitOfWork): .ResourceSqlAlchemyRepository(self.session) self.subscriptions = ocloud_repository\ .SubscriptionSqlAlchemyRepository(self.session) + self.registrations = ocloud_repository\ + .RegistrationSqlAlchemyRepository(self.session) self.deployment_managers = ocloud_repository\ .DeploymentManagerSqlAlchemyRepository(self.session) self.nfdeployment_descs = dms_repository\ diff --git a/o2app/service/handlers.py b/o2app/service/handlers.py index 0665cf2..833ef4e 100644 --- a/o2app/service/handlers.py +++ b/o2app/service/handlers.py @@ -27,7 +27,8 @@ from o2ims.service.auditor import ocloud_handler, dms_handler, \ resourcepool_handler, pserver_handler, pserver_cpu_handler, \ pserver_mem_handler, pserver_port_handler, pserver_if_handler,\ pserver_eth_handler -from o2ims.service.event import notify_handler, ocloud_event, \ +from o2ims.service.command import notify_handler +from o2ims.service.event import ocloud_event, \ resource_event, resource_pool_event # if TYPE_CHECKING: diff --git a/o2ims/adapter/ocloud_repository.py b/o2ims/adapter/ocloud_repository.py index 4880fa1..bd14722 100644 --- a/o2ims/adapter/ocloud_repository.py +++ b/o2ims/adapter/ocloud_repository.py @@ -17,7 +17,8 @@ from typing import List from o2ims.domain import ocloud, subscription_obj from o2ims.domain.ocloud_repo import OcloudRepository, ResourceTypeRepository,\ ResourcePoolRepository, ResourceRepository, DeploymentManagerRepository -from o2ims.domain.subscription_repo import SubscriptionRepository +from o2ims.domain.subscription_repo import SubscriptionRepository, \ + RegistrationRepository from o2common.helper import o2logging logger = o2logging.get_logger(__name__) @@ -159,3 +160,26 @@ class SubscriptionSqlAlchemyRepository(SubscriptionRepository): def _delete(self, subscription_id): self.session.query(subscription_obj.Subscription).filter_by( subscriptionId=subscription_id).delete() + + +class RegistrationSqlAlchemyRepository(RegistrationRepository): + def __init__(self, session): + super().__init__() + self.session = session + + def _add(self, registration: subscription_obj.Registration): + self.session.add(registration) + + def _get(self, registration_id) -> subscription_obj.Registration: + return self.session.query(subscription_obj.Registration).filter_by( + registrationId=registration_id).first() + + def _list(self) -> List[subscription_obj.Registration]: + return self.session.query(subscription_obj.Registration) + + def _update(self, registration: subscription_obj.Registration): + self.session.add(registration) + + def _delete(self, registration_id): + self.session.query(subscription_obj.Registration).filter_by( + registrationId=registration_id).delete() diff --git a/o2ims/adapter/orm.py b/o2ims/adapter/orm.py index bb5c984..6b290a5 100644 --- a/o2ims/adapter/orm.py +++ b/o2ims/adapter/orm.py @@ -24,6 +24,7 @@ from sqlalchemy import ( # Date, DateTime, ForeignKey, + Boolean, # engine, # event, ) @@ -144,6 +145,17 @@ subscription = Table( Column("filter", String(255)), ) +registration = Table( + "registration", + metadata, + Column("updatetime", DateTime), + Column("createtime", DateTime), + + Column("registrationId", String(255), primary_key=True), + Column("callback", String(255)), + Column("notified", Boolean), +) + def start_o2ims_mappers(engine=None): logger.info("Starting O2 IMS mappers") @@ -168,6 +180,7 @@ def start_o2ims_mappers(engine=None): } ) mapper(subModel.Subscription, subscription) + mapper(subModel.Registration, registration) if engine is not None: metadata.create_all(engine) diff --git a/o2ims/domain/subscription_obj.py b/o2ims/domain/subscription_obj.py index dc3145f..846bf95 100644 --- a/o2ims/domain/subscription_obj.py +++ b/o2ims/domain/subscription_obj.py @@ -30,6 +30,14 @@ class Subscription(AgRoot, Serializer): self.filter = filter +class Registration(AgRoot, Serializer): + def __init__(self, id: str, url: str) -> None: + super().__init__() + self.registrationId = id + self.callback = url + self.notified = False + + class NotificationEventEnum(str, Enum): CREATE = 'CREATE' MODIFY = 'MODIFY' diff --git a/o2ims/domain/subscription_repo.py b/o2ims/domain/subscription_repo.py index d12c00d..82df377 100644 --- a/o2ims/domain/subscription_repo.py +++ b/o2ims/domain/subscription_repo.py @@ -55,3 +55,43 @@ class SubscriptionRepository(abc.ABC): @abc.abstractmethod def _delete(self, subscription_id): raise NotImplementedError + + +class RegistrationRepository(abc.ABC): + def __init__(self): + self.seen = set() # type: Set[subobj.Subscription] + + def add(self, registration: subobj.Registration): + self._add(registration) + self.seen.add(registration) + + def get(self, registration_id) -> subobj.Registration: + registration = self._get(registration_id) + if registration: + self.seen.add(registration) + return registration + + def list(self) -> List[subobj.Registration]: + return self._list() + + def update(self, registration: subobj.Registration): + self._update(registration) + + def delete(self, registration_id): + self._delete(registration_id) + + @abc.abstractmethod + def _add(self, registration: subobj.Registration): + raise NotImplementedError + + @abc.abstractmethod + def _get(self, registration_id) -> subobj.Registration: + raise NotImplementedError + + @abc.abstractmethod + def _update(self, registration: subobj.Registration): + raise NotImplementedError + + @abc.abstractmethod + def _delete(self, registration_id): + raise NotImplementedError diff --git a/o2ims/service/command/__init__.py b/o2ims/service/command/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2ims/service/command/__init__.py @@ -0,0 +1,13 @@ +# Copyright (C) 2021 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. diff --git a/o2ims/service/event/notify_handler.py b/o2ims/service/command/notify_handler.py similarity index 100% rename from o2ims/service/event/notify_handler.py rename to o2ims/service/command/notify_handler.py diff --git a/o2ims/views/ocloud_dto.py b/o2ims/views/ocloud_dto.py index c1782bf..6bf994a 100644 --- a/o2ims/views/ocloud_dto.py +++ b/o2ims/views/ocloud_dto.py @@ -178,3 +178,32 @@ class SubscriptionDTO: description='Subscription ID'), } ) + + +class RegistrationDTO: + + registration_get = api_ims_inventory_v1.model( + "RegistrationGetDto", + { + 'registrationId': fields.String(required=True, + description='Registration ID'), + 'callback': fields.String, + 'notified': fields.Boolean, + } + ) + + registration = api_ims_inventory_v1.model( + "RegistrationCreateDto", + { + 'callback': fields.String( + required=True, description='Registration SMO callback address') + } + ) + + registration_post_resp = api_ims_inventory_v1.model( + "RegistrationCreatedRespDto", + { + 'registrationId': fields.String(required=True, + description='registration ID'), + } + ) diff --git a/o2ims/views/ocloud_route.py b/o2ims/views/ocloud_route.py index a01021e..c50907b 100644 --- a/o2ims/views/ocloud_route.py +++ b/o2ims/views/ocloud_route.py @@ -17,7 +17,8 @@ from flask_restx import Resource from o2ims.views import ocloud_view, api_ims_inventory_v1 from o2common.config import config from o2ims.views.ocloud_dto import OcloudDTO, ResourceTypeDTO,\ - ResourcePoolDTO, ResourceDTO, DeploymentManagerDTO, SubscriptionDTO + ResourcePoolDTO, ResourceDTO, DeploymentManagerDTO, SubscriptionDTO,\ + RegistrationDTO apibase = config.get_o2ims_api_base() @@ -208,6 +209,52 @@ class SubscriptionGetDelRouter(Resource): return result, 204 +# ---------- Registration ---------- # +@api_ims_inventory_v1.route("/registrations") +class RegistrationListRouter(Resource): + + model = RegistrationDTO.registration_get + expect = RegistrationDTO.registration + post_resp = RegistrationDTO.registration_post_resp + + @api_ims_inventory_v1.doc('List registrations') + @api_ims_inventory_v1.marshal_list_with(model) + def get(self): + return ocloud_view.registrations(bus.uow) + + @api_ims_inventory_v1.doc('Create a registration') + @api_ims_inventory_v1.expect(expect) + @api_ims_inventory_v1.marshal_with(post_resp, code=201) + def post(self): + data = api_ims_inventory_v1.payload + result = ocloud_view.registration_create(data, bus.uow) + return result, 201 + + +@api_ims_inventory_v1.route("/registrations/") +@api_ims_inventory_v1.param('registrationID', 'ID of the registration') +@api_ims_inventory_v1.response(404, 'Registration not found') +class RegistrationGetDelRouter(Resource): + + model = RegistrationDTO.registration_get + + @api_ims_inventory_v1.doc('Get registration by ID') + @api_ims_inventory_v1.marshal_with(model) + def get(self, registrationID): + result = ocloud_view.registration_one( + registrationID, bus.uow) + if result is not None: + return result + api_ims_inventory_v1.abort(404, "Registration {} doesn't exist".format( + registrationID)) + + @api_ims_inventory_v1.doc('Delete registration by ID') + @api_ims_inventory_v1.response(204, 'Registration deleted') + def delete(self, registrationID): + result = ocloud_view.registration_delete(registrationID, bus.uow) + return result, 204 + + def configure_namespace(app, bus_new): # Set global bus for resource diff --git a/o2ims/views/ocloud_view.py b/o2ims/views/ocloud_view.py index 3735298..386f8f5 100644 --- a/o2ims/views/ocloud_view.py +++ b/o2ims/views/ocloud_view.py @@ -15,8 +15,8 @@ import uuid from o2common.service import unit_of_work -from o2ims.views.ocloud_dto import SubscriptionDTO -from o2ims.domain.subscription_obj import Subscription +from o2ims.views.ocloud_dto import RegistrationDTO, SubscriptionDTO +from o2ims.domain.subscription_obj import Registration, Subscription def oclouds(uow: unit_of_work.AbstractUnitOfWork): @@ -115,3 +115,36 @@ def subscription_delete(subscriptionId: str, uow.subscriptions.delete(subscriptionId) uow.commit() return True + + +def registrations(uow: unit_of_work.AbstractUnitOfWork): + with uow: + li = uow.registrations.list() + return [r.serialize() for r in li] + + +def registration_one(registrationId: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + first = uow.registrations.get(registrationId) + return first.serialize() if first is not None else None + + +def registration_create(registrationDto: RegistrationDTO.registration, + uow: unit_of_work.AbstractUnitOfWork): + + reg_uuid = str(uuid.uuid4()) + registration = Registration( + reg_uuid, registrationDto['callback']) + with uow: + uow.registrations.add(registration) + uow.commit() + return {"registrationId": reg_uuid} + + +def registration_delete(registrationId: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + uow.registrations.delete(registrationId) + uow.commit() + return True diff --git a/tests/unit/test_ocloud.py b/tests/unit/test_ocloud.py index 13fc436..95dd372 100644 --- a/tests/unit/test_ocloud.py +++ b/tests/unit/test_ocloud.py @@ -94,6 +94,14 @@ def test_new_subscription(): subscription1.subscriptionId == subscription_id1 +def test_new_registration(): + registration_id1 = str(uuid.uuid4()) + registration1 = subscription_obj.Registration( + registration_id1, "https://callback/uri/write/here") + assert registration_id1 is not None and\ + registration1.registrationId == registration_id1 + + def test_view_olcouds(mock_uow): session, uow = mock_uow @@ -311,6 +319,44 @@ def test_view_subscription_one(mock_uow): "subscriptionId")) == subscription_id1 +def test_view_registrations(mock_uow): + session, uow = mock_uow + + registration_id1 = str(uuid.uuid4()) + reg1 = MagicMock() + reg1.serialize.return_value = { + "registrationId": registration_id1, + } + session.return_value.query.return_value = [reg1] + + registration_list = ocloud_view.registrations(uow) + assert str(registration_list[0].get( + "registrationId")) == registration_id1 + + +def test_view_registration_one(mock_uow): + session, uow = mock_uow + + registration_id1 = str(uuid.uuid4()) + session.return_value.query.return_value.filter_by.return_value.first.\ + return_value.serialize.return_value = None + + # Query return None + registration_res = ocloud_view.registration_one( + registration_id1, uow) + assert registration_res is None + + session.return_value.query.return_value.filter_by.return_value.first.\ + return_value.serialize.return_value = { + "registrationId": registration_id1, + } + + registration_res = ocloud_view.registration_one( + registration_id1, uow) + assert str(registration_res.get( + "registrationId")) == registration_id1 + + def test_flask_get_list(mock_flask_uow): session, app = mock_flask_uow session.query.return_value = [] @@ -336,6 +382,9 @@ def test_flask_get_list(mock_flask_uow): resp = client.get(apibase+"/subscriptions") assert resp.get_data() == b'[]\n' + resp = client.get(apibase+"/registrations") + assert resp.get_data() == b'[]\n' + def test_flask_get_one(mock_flask_uow): session, app = mock_flask_uow @@ -372,6 +421,10 @@ def test_flask_get_one(mock_flask_uow): resp = client.get(apibase+"/subscriptions/"+subscription_id1) assert resp.status_code == 404 + registration_id1 = str(uuid.uuid4()) + resp = client.get(apibase+"/registrations/"+registration_id1) + assert resp.status_code == 404 + def test_flask_post(mock_flask_uow): session, app = mock_flask_uow @@ -389,6 +442,13 @@ def test_flask_post(mock_flask_uow): assert resp.status_code == 201 assert 'subscriptionId' in resp.get_json() + reg_callback = 'http://registration/callback/url' + resp = client.post(apibase+'/registrations', json={ + 'callback': reg_callback, + }) + assert resp.status_code == 201 + assert 'registrationId' in resp.get_json() + def test_flask_delete(mock_flask_uow): session, app = mock_flask_uow @@ -401,6 +461,10 @@ def test_flask_delete(mock_flask_uow): resp = client.delete(apibase+"/subscriptions/"+subscription_id1) assert resp.status_code == 204 + registration_id1 = str(uuid.uuid4()) + resp = client.delete(apibase+"/registrations/"+registration_id1) + assert resp.status_code == 204 + def test_flask_not_allowed(mock_flask_uow): _, app = mock_flask_uow @@ -518,3 +582,22 @@ def test_flask_not_allowed(mock_flask_uow): assert resp.status == '405 METHOD NOT ALLOWED' resp = client.patch(uri) assert resp.status == '405 METHOD NOT ALLOWED' + + # Testing registrations not support method + ########################## + uri = apibase + "/registrations" + resp = client.put(uri) + assert resp.status == '405 METHOD NOT ALLOWED' + resp = client.patch(uri) + assert resp.status == '405 METHOD NOT ALLOWED' + resp = client.delete(uri) + assert resp.status == '405 METHOD NOT ALLOWED' + + subscription_id1 = str(uuid.uuid4()) + uri = apibase + "/registrations/" + subscription_id1 + resp = client.post(uri) + assert resp.status == '405 METHOD NOT ALLOWED' + resp = client.put(uri) + assert resp.status == '405 METHOD NOT ALLOWED' + resp = client.patch(uri) + assert resp.status == '405 METHOD NOT ALLOWED'