Add registration the SMO's CRUD interface in IMS 28/7528/1
authorZhang Rong(Jon) <rong.zhang@windriver.com>
Thu, 23 Dec 2021 01:36:28 +0000 (09:36 +0800)
committerBin Yang <bin.yang@windriver.com>
Tue, 11 Jan 2022 04:53:12 +0000 (04:53 +0000)
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) <rong.zhang@windriver.com>
Change-Id: I1d9235c6d654fa4743417ae5e226581935f16214
(cherry picked from commit 02b85e27680e1139bc8e1930af5449a26527b4de)

12 files changed:
o2app/adapter/unit_of_work.py
o2app/service/handlers.py
o2ims/adapter/ocloud_repository.py
o2ims/adapter/orm.py
o2ims/domain/subscription_obj.py
o2ims/domain/subscription_repo.py
o2ims/service/command/__init__.py [new file with mode: 0644]
o2ims/service/command/notify_handler.py [moved from o2ims/service/event/notify_handler.py with 100% similarity]
o2ims/views/ocloud_dto.py
o2ims/views/ocloud_route.py
o2ims/views/ocloud_view.py
tests/unit/test_ocloud.py

index facfc45..1c9cae0 100644 (file)
@@ -51,6 +51,8 @@ class SqlAlchemyUnitOfWork(AbstractUnitOfWork):
             .ResourceSqlAlchemyRepository(self.session)\r
         self.subscriptions = ocloud_repository\\r
             .SubscriptionSqlAlchemyRepository(self.session)\r
+        self.registrations = ocloud_repository\\r
+            .RegistrationSqlAlchemyRepository(self.session)\r
         self.deployment_managers = ocloud_repository\\r
             .DeploymentManagerSqlAlchemyRepository(self.session)\r
         self.nfdeployment_descs = dms_repository\\r
index 0665cf2..833ef4e 100644 (file)
@@ -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:
index 4880fa1..bd14722 100644 (file)
@@ -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()
index bb5c984..6b290a5 100644 (file)
@@ -24,6 +24,7 @@ from sqlalchemy import (
     # Date,\r
     DateTime,\r
     ForeignKey,\r
+    Boolean,\r
     # engine,\r
     # event,\r
 )\r
@@ -144,6 +145,17 @@ subscription = Table(
     Column("filter", String(255)),\r
 )\r
 \r
+registration = Table(\r
+    "registration",\r
+    metadata,\r
+    Column("updatetime", DateTime),\r
+    Column("createtime", DateTime),\r
+\r
+    Column("registrationId", String(255), primary_key=True),\r
+    Column("callback", String(255)),\r
+    Column("notified", Boolean),\r
+)\r
+\r
 \r
 def start_o2ims_mappers(engine=None):\r
     logger.info("Starting O2 IMS mappers")\r
@@ -168,6 +180,7 @@ def start_o2ims_mappers(engine=None):
         }\r
     )\r
     mapper(subModel.Subscription, subscription)\r
+    mapper(subModel.Registration, registration)\r
 \r
     if engine is not None:\r
         metadata.create_all(engine)\r
index dc3145f..846bf95 100644 (file)
@@ -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'
index d12c00d..82df377 100644 (file)
@@ -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 (file)
index 0000000..b514342
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright (C) 2021 Wind River Systems, Inc.\r
+#\r
+#  Licensed under the Apache License, Version 2.0 (the "License");\r
+#  you may not use this file except in compliance with the License.\r
+#  You may obtain a copy of the License at\r
+#\r
+#      http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+#  Unless required by applicable law or agreed to in writing, software\r
+#  distributed under the License is distributed on an "AS IS" BASIS,\r
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+#  See the License for the specific language governing permissions and\r
+#  limitations under the License.\r
index c1782bf..6bf994a 100644 (file)
@@ -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'),
+        }
+    )
index a01021e..c50907b 100644 (file)
@@ -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/<registrationID>")
+@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
index 3735298..386f8f5 100644 (file)
@@ -15,8 +15,8 @@
 import uuid\r
 \r
 from o2common.service import unit_of_work\r
-from o2ims.views.ocloud_dto import SubscriptionDTO\r
-from o2ims.domain.subscription_obj import Subscription\r
+from o2ims.views.ocloud_dto import RegistrationDTO, SubscriptionDTO\r
+from o2ims.domain.subscription_obj import Registration, Subscription\r
 \r
 \r
 def oclouds(uow: unit_of_work.AbstractUnitOfWork):\r
@@ -115,3 +115,36 @@ def subscription_delete(subscriptionId: str,
         uow.subscriptions.delete(subscriptionId)\r
         uow.commit()\r
     return True\r
+\r
+\r
+def registrations(uow: unit_of_work.AbstractUnitOfWork):\r
+    with uow:\r
+        li = uow.registrations.list()\r
+    return [r.serialize() for r in li]\r
+\r
+\r
+def registration_one(registrationId: str,\r
+                     uow: unit_of_work.AbstractUnitOfWork):\r
+    with uow:\r
+        first = uow.registrations.get(registrationId)\r
+        return first.serialize() if first is not None else None\r
+\r
+\r
+def registration_create(registrationDto: RegistrationDTO.registration,\r
+                        uow: unit_of_work.AbstractUnitOfWork):\r
+\r
+    reg_uuid = str(uuid.uuid4())\r
+    registration = Registration(\r
+        reg_uuid, registrationDto['callback'])\r
+    with uow:\r
+        uow.registrations.add(registration)\r
+        uow.commit()\r
+    return {"registrationId": reg_uuid}\r
+\r
+\r
+def registration_delete(registrationId: str,\r
+                        uow: unit_of_work.AbstractUnitOfWork):\r
+    with uow:\r
+        uow.registrations.delete(registrationId)\r
+        uow.commit()\r
+    return True\r
index 13fc436..95dd372 100644 (file)
@@ -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'