Add framework and apiserver 09/6909/2
authorBin Yang <bin.yang@windriver.com>
Thu, 21 Oct 2021 11:10:40 +0000 (19:10 +0800)
committerJackie Huang <jackie.huang@windriver.com>
Tue, 26 Oct 2021 01:11:11 +0000 (01:11 +0000)
Issue-ID: INF-196
Signed-off-by: Bin Yang <bin.yang@windriver.com>
Change-Id: I3e3022662a8d7e0158657811c0422f3503cb7883

39 files changed:
.gitignore [new file with mode: 0644]
Dockerfile [new file with mode: 0644]
README.md [new file with mode: 0644]
docker-compose.yml [new file with mode: 0644]
mypy.ini [new file with mode: 0644]
requirements-test.txt [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
src/__init__.py [new file with mode: 0644]
src/o2common/__init__.py [new file with mode: 0644]
src/o2dms/__init__.py [new file with mode: 0644]
src/o2dms/setup.py [new file with mode: 0644]
src/o2ims/__init__.py [new file with mode: 0644]
src/o2ims/adapter/notifications.py [new file with mode: 0644]
src/o2ims/adapter/ocloud_repository.py [new file with mode: 0644]
src/o2ims/adapter/orm.py [new file with mode: 0644]
src/o2ims/adapter/redis_eventpublisher.py [new file with mode: 0644]
src/o2ims/bootstrap.py [new file with mode: 0644]
src/o2ims/config.py [new file with mode: 0644]
src/o2ims/domain/__init__.py [new file with mode: 0644]
src/o2ims/domain/commands.py [new file with mode: 0644]
src/o2ims/domain/events.py [new file with mode: 0644]
src/o2ims/domain/ocloud.py [new file with mode: 0644]
src/o2ims/domain/resource_type.py [new file with mode: 0644]
src/o2ims/entrypoints/__init__.py [new file with mode: 0644]
src/o2ims/entrypoints/flask_application.py [new file with mode: 0644]
src/o2ims/entrypoints/redis_eventconsumer.py [new file with mode: 0644]
src/o2ims/service/__init__.py [new file with mode: 0644]
src/o2ims/service/handlers.py [new file with mode: 0644]
src/o2ims/service/messagebus.py [new file with mode: 0644]
src/o2ims/service/unit_of_work.py [new file with mode: 0644]
src/o2ims/views/ocloud_view.py [new file with mode: 0644]
src/setup.py [new file with mode: 0644]
tests/__init__.py [new file with mode: 0644]
tests/conftest.py [new file with mode: 0644]
tests/integration/__init__.py [new file with mode: 0644]
tests/integration/test_ocloud_repository.py [new file with mode: 0644]
tests/pytest.ini [new file with mode: 0644]
tests/unit/__init__.py [new file with mode: 0644]
tests/unit/test_ocloud.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ea29b71
--- /dev/null
@@ -0,0 +1,4 @@
+.venv
+.mypy_cache
+__pycache__
+*.egg-info
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..e1afbcb
--- /dev/null
@@ -0,0 +1,15 @@
+FROM python:3.10-slim-buster\r
+\r
+COPY requirements.txt /tmp/\r
+RUN pip install -r /tmp/requirements.txt\r
+\r
+COPY requirements-test.txt /tmp/\r
+RUN pip install -r /tmp/requirements-test.txt\r
+\r
+RUN mkdir -p /src\r
+COPY src/ /src/\r
+RUN pip install -e /src\r
+\r
+COPY tests/ /tests/\r
+\r
+WORKDIR /src\r
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..eb6900a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,31 @@
+## Building containers\r
+\r
+\r
+```sh\r
+docker-compose build\r
+```\r
+\r
+\r
+## Creating a local virtualenv (optional)\r
+\r
+```sh\r
+python3.8 -m venv .venv && source .venv/bin/activate\r
+pip install -r requirements.txt\r
+pip install -e src/\r
+```\r
+\r
+## Running the tests\r
+\r
+```sh\r
+docker-compose up -d\r
+docker-compose run --rm --no-deps --entrypoint=pytest api /tests/unit /tests/integration\r
+pytest tests/unit\r
+pytest tests/integration\r
+pytest tests/e2e\r
+```\r
+\r
+## Tear down containers\r
+\r
+```sh\r
+docker-compose down --remove-orphans\r
+```\r
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644 (file)
index 0000000..91d3ec8
--- /dev/null
@@ -0,0 +1,60 @@
+version: "3"
+
+services:
+
+  redis_pubsub:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: o2imsdms-image
+    depends_on:
+      - postgres
+      - redis
+    environment:
+      - DB_HOST=postgres
+      - DB_PASSWORD=o2ims123
+      - REDIS_HOST=redis
+      - PYTHONDONTWRITEBYTECODE=1
+    volumes:
+      - ./src:/src
+      - ./tests:/tests
+    entrypoint:
+      - python
+      - /src/o2ims/entrypoints/redis_eventconsumer.py
+
+  api:
+    image: o2imsdms-image
+    depends_on:
+      - redis_pubsub
+    environment:
+      - DB_HOST=postgres
+      - DB_PASSWORD=o2ims123
+      - API_HOST=api
+      - REDIS_HOST=redis
+      - PYTHONDONTWRITEBYTECODE=1
+      - FLASK_APP=o2ims/entrypoints/flask_application.py
+      - FLASK_DEBUG=1
+      - PYTHONUNBUFFERED=1
+    volumes:
+      - ./src:/src
+      - ./tests:/tests
+    entrypoint:
+      - flask
+      - run
+      - --host=0.0.0.0
+      - --port=80
+    ports:
+      - "5005:80"
+
+  postgres:
+    image: postgres:9.6
+    environment:
+      - POSTGRES_USER=o2ims
+      - POSTGRES_PASSWORD=o2ims123
+    ports:
+      - "54321:5432"
+
+  redis:
+    image: redis:alpine
+    ports:
+      - "63791:6379"
diff --git a/mypy.ini b/mypy.ini
new file mode 100644 (file)
index 0000000..31ee9f6
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,7 @@
+[mypy]\r
+ignore_missing_imports = False\r
+mypy_path = ./src\r
+check_untyped_defs = True\r
+\r
+[mypy-pytest.*,sqlalchemy.*,redis.*]\r
+ignore_missing_imports = True\r
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644 (file)
index 0000000..251bb2b
--- /dev/null
@@ -0,0 +1,8 @@
+pylint\r
+mypy\r
+requests\r
+\r
+pytest\r
+pytest-icdiff\r
+\r
+tenacity\r
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..985e50e
--- /dev/null
@@ -0,0 +1,4 @@
+flask\r
+sqlalchemy\r
+redis\r
+psycopg2-binary\r
diff --git a/src/__init__.py b/src/__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
diff --git a/src/o2common/__init__.py b/src/o2common/__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
diff --git a/src/o2dms/__init__.py b/src/o2dms/__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
diff --git a/src/o2dms/setup.py b/src/o2dms/setup.py
new file mode 100644 (file)
index 0000000..feafb7a
--- /dev/null
@@ -0,0 +1,19 @@
+from setuptools import setup\r
+\r
+setup(\r
+    name="o2common",\r
+    version="1.0",\r
+    packages=["o2common"],\r
+)\r
+\r
+setup(\r
+    name="o2ims",\r
+    version="1.0",\r
+    packages=["o2ims"],\r
+)\r
+\r
+setup(\r
+    name="o2dms",\r
+    version="1.0",\r
+    packages=["o2dms"],\r
+)\r
diff --git a/src/o2ims/__init__.py b/src/o2ims/__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
diff --git a/src/o2ims/adapter/notifications.py b/src/o2ims/adapter/notifications.py
new file mode 100644 (file)
index 0000000..e2ad375
--- /dev/null
@@ -0,0 +1,20 @@
+# pylint: disable=too-few-public-methods
+import abc
+import smtplib
+from o2ims import config
+
+
+class AbstractNotifications(abc.ABC):
+    @abc.abstractmethod
+    def send(self, message):
+        raise NotImplementedError
+
+
+SMO_O2_ENDPOINT = config.get_smo_o2endpoint()
+
+class SmoO2Notifications(AbstractNotifications):
+    def __init__(self, smoO2Endpoint=SMO_O2_ENDPOINT):
+        self.smoO2Endpoint = smoO2Endpoint
+
+    def send(self, message):
+        pass
diff --git a/src/o2ims/adapter/ocloud_repository.py b/src/o2ims/adapter/ocloud_repository.py
new file mode 100644 (file)
index 0000000..6d3d221
--- /dev/null
@@ -0,0 +1,77 @@
+# 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.
+
+import abc
+from typing import Set
+from o2ims.adapter import orm
+from o2ims.domain import ocloud
+
+class OcloudRepository(abc.ABC):
+    def __init__(self):
+        self.seen = set()  # type: Set[ocloud.Ocloud]
+
+    def add(self, ocloud: ocloud.Ocloud):
+        self._add(ocloud)
+        self.seen.add(ocloud)
+
+    def get(self, ocloudid) -> ocloud.Ocloud:
+        ocloud = self._get(ocloudid)
+        if ocloud:
+            self.seen.add(ocloud)
+        return ocloud
+    
+    def update(self, ocloud: ocloud.Ocloud):
+        self._update(ocloud)
+
+    # def update_fields(self, ocloudid: str, updatefields: dict):
+    #     self._update(ocloudid, updatefields)
+
+    @abc.abstractmethod
+    def _add(self, ocloud: ocloud.Ocloud):
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def _get(self, ocloudid) -> ocloud.Ocloud:
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def _update(self, ocloud: ocloud.Ocloud):
+        raise NotImplementedError
+
+
+class OcloudSqlAlchemyRepository(OcloudRepository):
+    def __init__(self, session):
+        super().__init__()
+        self.session = session
+
+    def _add(self, ocloud: ocloud.Ocloud):
+        self.session.add(ocloud)
+        # self.session.add_all(ocloud.deploymentManagers)
+
+    def _get(self, ocloudid) -> ocloud.Ocloud:
+        return self.session.query(ocloud.Ocloud).filter_by(oCloudId=ocloudid).first()
+
+    def _update(self, ocloud: ocloud.Ocloud):
+        self.session.add(ocloud)
+
+    # def _update_fields(self, ocloudid: str, updatefields: dict):
+    #     dmslist = updatefields.pop("deploymentManagers", None)
+    #     if dmslist:
+    #         self._update_dms_list(dmslist)
+    #     if updatefields:
+    #         self.session.query(ocloud.Ocloud).filter_by(oCloudId=ocloudid).update(updatefields)
+
+    # def _update_dms_list(self, dms_list: list):
+    #     for dms in dms_list or []:
+    #         self.session.query(ocloud.DeploymentManager).filter_by(deploymentManagerId=dms.deploymentManagerId).update(dms)
diff --git a/src/o2ims/adapter/orm.py b/src/o2ims/adapter/orm.py
new file mode 100644 (file)
index 0000000..6cf9847
--- /dev/null
@@ -0,0 +1,97 @@
+# 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
+\r
+import logging\r
+\r
+from sqlalchemy import (\r
+    Table,\r
+    MetaData,\r
+    Column,\r
+    Integer,\r
+    String,\r
+    Date,\r
+    ForeignKey,\r
+    event,\r
+)\r
+\r
+from sqlalchemy.orm import mapper, relationship\r
+from sqlalchemy.sql.expression import true\r
+\r
+from o2ims.domain import ocloud as ocloudModel\r
+\r
+logger = logging.getLogger(__name__)\r
+\r
+metadata = MetaData()\r
+\r
+ocloud = Table(\r
+    "ocloud",\r
+    metadata,\r
+    Column("oCloudId", String(255), primary_key=True),\r
+    Column("name", String(255)),\r
+    Column("description", String(255)),\r
+    Column("infrastructureManagementServiceEndpoint", String(255))\r
+)\r
+\r
+resourcepool = Table(\r
+    "resourcepool",\r
+    metadata,\r
+    Column("resourcePoolId", String(255), primary_key=True),\r
+    Column("name", String(255)),\r
+    Column("location", String(255)),\r
+    Column("oCloudId", ForeignKey("ocloud.oCloudId")),\r
+    # Column("extensions", String(1024))\r
+)\r
+\r
+resourcetype = Table(\r
+    "resourcetype",\r
+    metadata,\r
+    Column("resourceTypeId", String(255), primary_key=True),\r
+    Column("oCloudId", ForeignKey("ocloud.oCloudId")),\r
+    Column("name", String(255)),\r
+)\r
+\r
+resource = Table(\r
+    "resource",\r
+    metadata,\r
+    Column("resourceId", String(255), primary_key=True),\r
+    Column("parentId", String(255)),\r
+    Column("resourceTypeId", ForeignKey("resourcetype.resourceTypeId")),\r
+    Column("resourcePoolId", ForeignKey("resourcepool.resourcePoolId")),\r
+    Column("oCloudId", ForeignKey("ocloud.oCloudId"))\r
+)\r
+\r
+deploymentmanager = Table(\r
+    "deploymentmanager",\r
+    metadata,\r
+    Column("deploymentManagerId", String(255), primary_key=True),\r
+    Column("name", String(255)),\r
+    Column("deploymentManagementServiceEndpoint", String(255)),\r
+    Column("oCloudId", ForeignKey("ocloud.oCloudId"))\r
+)\r
+\r
+\r
+def start_o2ims_mappers():\r
+    logger.info("Starting O2 IMS mappers")\r
+    dm_mapper = mapper(ocloudModel.DeploymentManager, deploymentmanager)\r
+    resourcepool_mapper = mapper(ocloudModel.ResourcePool, resourcepool)\r
+    resourcetype_mapper = mapper(ocloudModel.ResourceType, resourcetype)\r
+    resource_mapper = mapper(ocloudModel.Resource, resource)\r
+    ocloud_mapper = mapper(\r
+        ocloudModel.Ocloud,\r
+        ocloud,\r
+        properties={\r
+            "deploymentManagers": relationship(dm_mapper),\r
+            "resourceTypes": relationship(resourcetype_mapper),\r
+            "resourcePools": relationship(resourcepool_mapper)\r
+        })\r
diff --git a/src/o2ims/adapter/redis_eventpublisher.py b/src/o2ims/adapter/redis_eventpublisher.py
new file mode 100644 (file)
index 0000000..835c72c
--- /dev/null
@@ -0,0 +1,30 @@
+# 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.
+
+import json
+import logging
+from dataclasses import asdict
+import redis
+
+from o2ims import config
+from o2ims.domain import events
+
+logger = logging.getLogger(__name__)
+
+r = redis.Redis(**config.get_redis_host_and_port())
+
+
+def publish(channel, event: events.Event):
+    logging.info("publishing: channel=%s, event=%s", channel, event)
+    r.publish(channel, json.dumps(asdict(event)))
diff --git a/src/o2ims/bootstrap.py b/src/o2ims/bootstrap.py
new file mode 100644 (file)
index 0000000..00cdc4a
--- /dev/null
@@ -0,0 +1,63 @@
+# 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.
+
+import inspect
+from typing import Callable
+from o2ims.adapter import orm, redis_eventpublisher
+from o2ims.adapter.notifications import AbstractNotifications, SmoO2Notifications
+
+from o2ims.service import handlers, messagebus, unit_of_work
+
+
+def bootstrap(
+    start_orm: bool = True,
+    uow: unit_of_work.AbstractUnitOfWork = unit_of_work.SqlAlchemyUnitOfWork(),
+    notifications: AbstractNotifications = None,
+    publish: Callable = redis_eventpublisher.publish,
+) -> messagebus.MessageBus:
+
+    if notifications is None:
+        notifications = SmoO2Notifications()
+
+    if start_orm:
+        orm.start_o2ims_mappers()
+
+    dependencies = {"uow": uow, "notifications": notifications, "publish": publish}
+    injected_event_handlers = {
+        event_type: [
+            inject_dependencies(handler, dependencies)
+            for handler in event_handlers
+        ]
+        for event_type, event_handlers in handlers.EVENT_HANDLERS.items()
+    }
+    injected_command_handlers = {
+        command_type: inject_dependencies(handler, dependencies)
+        for command_type, handler in handlers.COMMAND_HANDLERS.items()
+    }
+
+    return messagebus.MessageBus(
+        uow=uow,
+        event_handlers=injected_event_handlers,
+        command_handlers=injected_command_handlers,
+    )
+
+
+def inject_dependencies(handler, dependencies):
+    params = inspect.signature(handler).parameters
+    deps = {
+        name: dependency
+        for name, dependency in dependencies.items()
+        if name in params
+    }
+    return lambda message: handler(message, **deps)
diff --git a/src/o2ims/config.py b/src/o2ims/config.py
new file mode 100644 (file)
index 0000000..798ea79
--- /dev/null
@@ -0,0 +1,41 @@
+# 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.
+
+import os
+
+def get_postgres_uri():
+    host = os.environ.get("DB_HOST", "localhost")
+    port = 54321 if host == "localhost" else 5432
+    password = os.environ.get("DB_PASSWORD", "o2ims123")
+    user, db_name = "o2ims", "o2ims"
+    return f"postgresql://{user}:{password}@{host}:{port}/{db_name}"
+
+
+def get_api_url():
+    host = os.environ.get("API_HOST", "localhost")
+    port = 5005 if host == "localhost" else 80
+    return f"http://{host}:{port}"
+
+def get_o2ims_api_base():
+    return '/o2ims_infrastructureInventory/v1'
+
+def get_redis_host_and_port():
+    host = os.environ.get("REDIS_HOST", "localhost")
+    port = 63791 if host == "localhost" else 6379
+    return dict(host=host, port=port)
+
+
+def get_smo_o2endpoint():
+    smo_o2endpoint = os.environ.get("SMO_O2_ENDPOINT", "http://localhost/smo_sim")
+    return smo_o2endpoint
diff --git a/src/o2ims/domain/__init__.py b/src/o2ims/domain/__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
diff --git a/src/o2ims/domain/commands.py b/src/o2ims/domain/commands.py
new file mode 100644 (file)
index 0000000..9df6cff
--- /dev/null
@@ -0,0 +1,25 @@
+# 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.
+
+# pylint: disable=too-few-public-methods
+from datetime import date
+from typing import Optional
+from dataclasses import dataclass
+
+
+class Command:
+    pass
+
+class UpdateDms(Command):
+    ref: str
\ No newline at end of file
diff --git a/src/o2ims/domain/events.py b/src/o2ims/domain/events.py
new file mode 100644 (file)
index 0000000..755f65e
--- /dev/null
@@ -0,0 +1,23 @@
+# 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.
+
+# pylint: disable=too-few-public-methods
+from dataclasses import dataclass
+
+class Event:
+    pass
+
+@dataclass
+class OcloudUpdated(Event):
+    oCloudId: str
diff --git a/src/o2ims/domain/ocloud.py b/src/o2ims/domain/ocloud.py
new file mode 100644 (file)
index 0000000..ddd2646
--- /dev/null
@@ -0,0 +1,83 @@
+# 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
+\r
+from __future__ import annotations\r
+from dataclasses import dataclass\r
+from datetime import date\r
+from typing import Optional, List, Set\r
+from .resource_type import ResourceTypeEnum\r
+# from uuid import UUID\r
+\r
+class Ocloud:\r
+    def __init__(\r
+        self, ocloudid: str, name: str, imsendpoint: str,\r
+        description: str = '', version_number: int = 0) -> None:\r
+\r
+        self.oCloudId = ocloudid\r
+        self.version_number = version_number\r
+        self.name = name\r
+        self.description = description\r
+        self.infrastructureManagementServiceEndpoint = imsendpoint\r
+        self.resourcePools = []\r
+        self.deploymentManagers = []\r
+        self.resourceTypes = []\r
+        self.extensions = []\r
+        self.events = []\r
+    \r
+    def addDeploymentManager(self, deploymentManager: DeploymentManager) -> None:\r
+        deploymentManager.oCloudId = self.oCloudId\r
+        old = filter(\r
+            lambda x: x.deploymentManagerId == deploymentManager.deploymentManagerId,\r
+            self.deploymentManagers)\r
+        for o in old or []:\r
+            self.deploymentManagers.remove(o)\r
+        self.deploymentManagers.append(deploymentManager)\r
+\r
+class DeploymentManager:\r
+    def __init__(self, id: str, name: str, ocloudid: str, dmsendpoint: str) -> None:\r
+        self.deploymentManagerId = id\r
+        self.name = name\r
+        self.oCloudId = ocloudid\r
+        self.deploymentManagementServiceEndpoint = dmsendpoint\r
+        self.extensions = []\r
+\r
+\r
+class ResourcePool:\r
+    def __init__(self, id: str, name: str, location: str, ocloudid: str) -> None:\r
+        self.resourcePoolId = id\r
+        self.name = name\r
+        self.location = location\r
+        self.oCloudId = ocloudid\r
+        self.extensions = []\r
+\r
+\r
+class ResourceType:\r
+    def __init__(self, typeid: str, name:str, typeEnum: ResourceTypeEnum, ocloudid: str) -> None:\r
+        self.resourceTypeId = typeid\r
+        self.resourceTypeEnum = typeEnum.value\r
+        self.name = name\r
+        self.oCloudId = ocloudid\r
+        self.extensions = []\r
+\r
+\r
+class Resource:\r
+    def __init__(self, resourceId:str, resourceTypeId: str, resourcePoolId: str) -> None:\r
+        self.resourceId = resourceId\r
+        self.oCloudId = None # tbd\r
+        self.resourceTypeId = resourceTypeId\r
+        self.resourcePoolId = resourcePoolId\r
+        self.parentId = None\r
+        self.elements = []\r
+        self.extensions = []\r
+\r
diff --git a/src/o2ims/domain/resource_type.py b/src/o2ims/domain/resource_type.py
new file mode 100644 (file)
index 0000000..faff8ea
--- /dev/null
@@ -0,0 +1,6 @@
+from enum import Enum\r
+\r
+class ResourceTypeEnum(Enum):\r
+    PSERVER = 1\r
+    PSERVER_CPU = 2\r
+    PSERVER_RAM = 3\r
diff --git a/src/o2ims/entrypoints/__init__.py b/src/o2ims/entrypoints/__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
diff --git a/src/o2ims/entrypoints/flask_application.py b/src/o2ims/entrypoints/flask_application.py
new file mode 100644 (file)
index 0000000..933301c
--- /dev/null
@@ -0,0 +1,29 @@
+# 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
+\r
+from datetime import datetime\r
+from flask import Flask, jsonify, request\r
+from o2ims.domain import commands\r
+from o2ims.service.handlers import InvalidResourceType\r
+from o2ims import bootstrap, config\r
+from o2ims.views import ocloud_view\r
+\r
+app = Flask(__name__)\r
+bus = bootstrap.bootstrap()\r
+apibase = config.get_o2ims_api_base()\r
+\r
+@app.route(apibase, methods=["GET"])\r
+def oclouds():\r
+    result = ocloud_view.oclouds(bus.uow)\r
+    return jsonify(result), 200\r
diff --git a/src/o2ims/entrypoints/redis_eventconsumer.py b/src/o2ims/entrypoints/redis_eventconsumer.py
new file mode 100644 (file)
index 0000000..a22b61c
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+import json
+import logging
+import redis
+
+from o2ims import bootstrap, config
+from o2ims.domain import commands
+
+logger = logging.getLogger(__name__)
+
+r = redis.Redis(**config.get_redis_host_and_port())
+
+
+def main():
+    logger.info("Redis pubsub starting")
+    bus = bootstrap.bootstrap()
+    pubsub = r.pubsub(ignore_subscribe_messages=True)
+    pubsub.subscribe("dms_changed")
+
+    for m in pubsub.listen():
+        handle_dms_changed(m, bus)
+
+
+def handle_dms_changed(m, bus):
+    logger.info("handling %s", m)
+    data = json.loads(m["data"])
+    cmd = commands.UpdateDms(ref=data["dmsid"])
+    bus.handle(cmd)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/o2ims/service/__init__.py b/src/o2ims/service/__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
diff --git a/src/o2ims/service/handlers.py b/src/o2ims/service/handlers.py
new file mode 100644 (file)
index 0000000..c75dc07
--- /dev/null
@@ -0,0 +1,32 @@
+# 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.
+
+# pylint: disable=unused-argument
+from __future__ import annotations
+from dataclasses import asdict
+from typing import List, Dict, Callable, Type, TYPE_CHECKING
+from o2ims.domain import commands, events, ocloud
+
+if TYPE_CHECKING:
+    from . import unit_of_work
+
+class InvalidResourceType(Exception):
+    pass
+
+
+EVENT_HANDLERS = {
+}  # type: Dict[Type[events.Event], List[Callable]]
+
+COMMAND_HANDLERS = {
+}  # type: Dict[Type[commands.Command], Callable]
diff --git a/src/o2ims/service/messagebus.py b/src/o2ims/service/messagebus.py
new file mode 100644 (file)
index 0000000..bff45ab
--- /dev/null
@@ -0,0 +1,69 @@
+# 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.
+
+# pylint: disable=broad-except, attribute-defined-outside-init
+from __future__ import annotations
+import logging
+from typing import Callable, Dict, List, Union, Type, TYPE_CHECKING
+from o2ims.domain import commands, events
+
+if TYPE_CHECKING:
+    from . import unit_of_work
+
+logger = logging.getLogger(__name__)
+
+Message = Union[commands.Command, events.Event]
+
+
+class MessageBus:
+    def __init__(
+        self,
+        uow: unit_of_work.AbstractUnitOfWork,
+        event_handlers: Dict[Type[events.Event], List[Callable]],
+        command_handlers: Dict[Type[commands.Command], Callable],
+    ):
+        self.uow = uow
+        self.event_handlers = event_handlers
+        self.command_handlers = command_handlers
+
+    def handle(self, message: Message):
+        self.queue = [message]
+        while self.queue:
+            message = self.queue.pop(0)
+            if isinstance(message, events.Event):
+                self.handle_event(message)
+            elif isinstance(message, commands.Command):
+                self.handle_command(message)
+            else:
+                raise Exception(f"{message} was not an Event or Command")
+
+    def handle_event(self, event: events.Event):
+        for handler in self.event_handlers[type(event)]:
+            try:
+                logger.debug("handling event %s with handler %s", event, handler)
+                handler(event)
+                self.queue.extend(self.uow.collect_new_events())
+            except Exception:
+                logger.exception("Exception handling event %s", event)
+                continue
+
+    def handle_command(self, command: commands.Command):
+        logger.debug("handling command %s", command)
+        try:
+            handler = self.command_handlers[type(command)]
+            handler(command)
+            self.queue.extend(self.uow.collect_new_events())
+        except Exception:
+            logger.exception("Exception handling command %s", command)
+            raise
diff --git a/src/o2ims/service/unit_of_work.py b/src/o2ims/service/unit_of_work.py
new file mode 100644 (file)
index 0000000..4714729
--- /dev/null
@@ -0,0 +1,78 @@
+# 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.
+
+# pylint: disable=attribute-defined-outside-init
+from __future__ import annotations
+import abc
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm.session import Session
+
+
+from o2ims import config
+from o2ims.adapter import ocloud_repository
+
+
+class AbstractUnitOfWork(abc.ABC):
+    oclouds: ocloud_repository.OcloudRepository
+
+    def __enter__(self) -> AbstractUnitOfWork:
+        return self
+
+    def __exit__(self, *args):
+        self.rollback()
+
+    def commit(self):
+        self._commit()
+
+    def collect_new_events(self):
+        for ocloud in self.oclouds.seen:
+            while ocloud.events:
+                yield ocloud.events.pop(0)
+
+    @abc.abstractmethod
+    def _commit(self):
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def rollback(self):
+        raise NotImplementedError
+
+
+DEFAULT_SESSION_FACTORY = sessionmaker(
+    bind=create_engine(
+        config.get_postgres_uri(),
+        isolation_level="REPEATABLE READ",
+    )
+)
+
+
+class SqlAlchemyUnitOfWork(AbstractUnitOfWork):
+    def __init__(self, session_factory=DEFAULT_SESSION_FACTORY):
+        self.session_factory = session_factory
+
+    def __enter__(self):
+        self.session = self.session_factory()  # type: Session
+        self.oclouds = ocloud_repository.OcloudSqlAlchemyRepository(self.session)
+        return super().__enter__()
+
+    def __exit__(self, *args):
+        super().__exit__(*args)
+        self.session.close()
+
+    def _commit(self):
+        self.session.commit()
+
+    def rollback(self):
+        self.session.rollback()
diff --git a/src/o2ims/views/ocloud_view.py b/src/o2ims/views/ocloud_view.py
new file mode 100644 (file)
index 0000000..7005b5a
--- /dev/null
@@ -0,0 +1,36 @@
+# 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
+\r
+from o2ims.service import unit_of_work\r
+\r
+\r
+def ocloud_one(ocloudid: str, uow: unit_of_work.SqlAlchemyUnitOfWork):\r
+    with uow:\r
+        results = uow.session.execute(\r
+            """\r
+            SELECT oCloudId, name FROM ocloud WHERE oCloudId = :ocloudid\r
+            """,\r
+            dict(ocloudid=ocloudid),\r
+        )\r
+    return dict(results[0]) if len(results) > 0 else None\r
+\r
+\r
+def oclouds(uow: unit_of_work.SqlAlchemyUnitOfWork):\r
+    with uow:\r
+        results = uow.session.execute(\r
+            """\r
+            SELECT oCloudId, name FROM ocloud\r
+            """,\r
+        )\r
+    return [dict(r) for r in results]\r
diff --git a/src/setup.py b/src/setup.py
new file mode 100644 (file)
index 0000000..7558373
--- /dev/null
@@ -0,0 +1,7 @@
+from setuptools import setup\r
+\r
+setup(\r
+    name="o2imsdms",\r
+    version="1.0",\r
+    packages=["o2ims", "o2dms", "o2common"],\r
+)\r
diff --git a/tests/__init__.py b/tests/__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
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..7744dbd
--- /dev/null
@@ -0,0 +1,88 @@
+# pylint: disable=redefined-outer-name\r
+import shutil\r
+import subprocess\r
+import time\r
+from pathlib import Path\r
+\r
+import pytest\r
+import redis\r
+import requests\r
+from sqlalchemy import create_engine\r
+from sqlalchemy.orm import sessionmaker, clear_mappers\r
+from tenacity import retry, stop_after_delay\r
+\r
+from o2ims.adapter.orm import metadata, start_o2ims_mappers\r
+from o2ims import config\r
+\r
+\r
+@pytest.fixture\r
+def in_memory_sqlite_db():\r
+    engine = create_engine("sqlite:///:memory:")\r
+    # engine = create_engine("sqlite:///:memory:", echo=True)\r
+    metadata.create_all(engine)\r
+    return engine\r
+\r
+\r
+@pytest.fixture\r
+def sqlite_session_factory(in_memory_sqlite_db):\r
+    yield sessionmaker(bind=in_memory_sqlite_db)\r
+\r
+\r
+@pytest.fixture\r
+def mappers():\r
+    start_o2ims_mappers()\r
+    yield\r
+    clear_mappers()\r
+\r
+\r
+@retry(stop=stop_after_delay(10))\r
+def wait_for_postgres_to_come_up(engine):\r
+    return engine.connect()\r
+\r
+\r
+@retry(stop=stop_after_delay(10))\r
+def wait_for_webapp_to_come_up():\r
+    return requests.get(config.get_api_url())\r
+\r
+\r
+@retry(stop=stop_after_delay(10))\r
+def wait_for_redis_to_come_up():\r
+    r = redis.Redis(**config.get_redis_host_and_port())\r
+    return r.ping()\r
+\r
+\r
+@pytest.fixture(scope="session")\r
+def postgres_db():\r
+    engine = create_engine(config.get_postgres_uri(), isolation_level="SERIALIZABLE")\r
+    wait_for_postgres_to_come_up(engine)\r
+    metadata.create_all(engine)\r
+    return engine\r
+\r
+\r
+@pytest.fixture\r
+def postgres_session_factory(postgres_db):\r
+    yield sessionmaker(bind=postgres_db)\r
+\r
+\r
+@pytest.fixture\r
+def postgres_session(postgres_session_factory):\r
+    return postgres_session_factory()\r
+\r
+\r
+@pytest.fixture\r
+def restart_api():\r
+    (Path(__file__).parent / "../src/o2ims/entrypoints/flask_application.py").touch()\r
+    time.sleep(0.5)\r
+    wait_for_webapp_to_come_up()\r
+\r
+\r
+@pytest.fixture\r
+def restart_redis_pubsub():\r
+    wait_for_redis_to_come_up()\r
+    if not shutil.which("docker-compose"):\r
+        print("skipping restart, assumes running in container")\r
+        return\r
+    subprocess.run(\r
+        ["docker-compose", "restart", "-t", "0", "redis_pubsub"],\r
+        check=True,\r
+    )\r
diff --git a/tests/integration/__init__.py b/tests/integration/__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
diff --git a/tests/integration/test_ocloud_repository.py b/tests/integration/test_ocloud_repository.py
new file mode 100644 (file)
index 0000000..36919b3
--- /dev/null
@@ -0,0 +1,93 @@
+# 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.
+
+import pytest
+from o2ims.adapter import ocloud_repository as repository
+from o2ims.domain import ocloud
+from o2ims import config
+import uuid
+
+pytestmark = pytest.mark.usefixtures("mappers")
+
+
+def setup_ocloud():
+    ocloudid1 = str(uuid.uuid4())
+    ocloud1 = ocloud.Ocloud(ocloudid1, "ocloud1", config.get_api_url(), "ocloud 1 for integration test", 1)
+    return ocloud1
+
+def setup_ocloud_and_save(sqlite_session_factory):
+    session = sqlite_session_factory()
+    repo = repository.OcloudSqlAlchemyRepository(session)
+    ocloudid1 = str(uuid.uuid4())
+    ocloud1 = ocloud.Ocloud(ocloudid1, "ocloud1", config.get_api_url(), "ocloud for integration test", 1)
+    repo.add(ocloud1)
+    assert repo.get(ocloudid1) == ocloud1
+    session.flush()
+    return ocloud1
+
+def test_add_ocloud(sqlite_session_factory):
+    session = sqlite_session_factory()
+    repo = repository.OcloudSqlAlchemyRepository(session)
+    ocloudid1 = str(uuid.uuid4())
+    ocloud1 = ocloud.Ocloud(ocloudid1, "ocloud1", config.get_api_url(), "ocloud for integration test", 1)
+    repo.add(ocloud1)
+    assert repo.get(ocloudid1) == ocloud1
+
+def test_get_ocloud(sqlite_session_factory):
+    ocloud1 = setup_ocloud_and_save(sqlite_session_factory)
+    session = sqlite_session_factory()
+    repo = repository.OcloudSqlAlchemyRepository(session)
+    ocloud2 = repo.get(ocloud1.oCloudId)
+    assert ocloud2 != ocloud1 and ocloud2.oCloudId == ocloud1.oCloudId
+
+def test_add_ocloud_with_dms(sqlite_session_factory):
+    session = sqlite_session_factory()
+    repo = repository.OcloudSqlAlchemyRepository(session)
+    ocloud1 = setup_ocloud()
+    dmsid = str(uuid.uuid4())
+    dms = ocloud.DeploymentManager(
+        dmsid, "k8s1", ocloud1.oCloudId, config.get_api_url()+"/k8s1")
+    ocloud1.addDeploymentManager(dms)
+    repo.add(ocloud1)
+    session.flush()
+    # seperate session to confirm ocloud is updated into repo
+    session2 = sqlite_session_factory()
+    repo2 = repository.OcloudSqlAlchemyRepository(session2)
+    ocloud2 = repo2.get(ocloud1.oCloudId)
+    assert ocloud2 is not None
+    assert ocloud2 != ocloud1 and ocloud2.oCloudId == ocloud1.oCloudId
+    assert len(ocloud2.deploymentManagers) == 1
+
+
+def test_update_ocloud_with_dms(sqlite_session_factory):
+    session = sqlite_session_factory()
+    repo = repository.OcloudSqlAlchemyRepository(session)
+    ocloud1 = setup_ocloud()
+    repo.add(ocloud1)
+    session.flush()
+    dmsid = str(uuid.uuid4())
+    dms = ocloud.DeploymentManager(
+        dmsid, "k8s1", ocloud1.oCloudId, config.get_api_url()+"/k8s1")
+    ocloud1.addDeploymentManager(dms)
+    repo.update(ocloud1)
+    # repo.update(ocloud1.oCloudId, {"deploymentManagers": ocloud1.deploymentManagers})
+    session.flush()
+
+    # seperate session to confirm ocloud is updated into repo
+    session2 = sqlite_session_factory()
+    repo2 = repository.OcloudSqlAlchemyRepository(session2)
+    ocloud2 = repo2.get(ocloud1.oCloudId)
+    assert ocloud2 is not None
+    assert ocloud2 != ocloud1 and ocloud2.oCloudId == ocloud1.oCloudId
+    assert len(ocloud2.deploymentManagers) == 1
diff --git a/tests/pytest.ini b/tests/pytest.ini
new file mode 100644 (file)
index 0000000..3fd8685
--- /dev/null
@@ -0,0 +1,4 @@
+[pytest]
+addopts = --tb=short
+filterwarnings =
+    ignore::DeprecationWarning
diff --git a/tests/unit/__init__.py b/tests/unit/__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
diff --git a/tests/unit/test_ocloud.py b/tests/unit/test_ocloud.py
new file mode 100644 (file)
index 0000000..baff737
--- /dev/null
@@ -0,0 +1,39 @@
+# 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.
+
+import pytest
+from o2ims.domain import ocloud
+from o2ims import config
+import uuid
+
+
+def setup_ocloud():
+    ocloudid1 = str(uuid.uuid4())
+    ocloud1 = ocloud.Ocloud(ocloudid1, "ocloud1", config.get_api_url(), "ocloud for unit test", 1)
+    return ocloud1
+
+def test_new_ocloud():
+    ocloudid1 = str(uuid.uuid4())
+    ocloud1 = ocloud.Ocloud(ocloudid1, "ocloud1", config.get_api_url(), "ocloud for unit test", 1)
+    assert ocloudid1 is not None and ocloud1.oCloudId == ocloudid1
+
+def test_add_ocloud_with_dms():
+    ocloud1 = setup_ocloud()
+    dmsid = str(uuid.uuid4())
+    dms = ocloud.DeploymentManager(
+        dmsid, "k8s1", ocloud1.oCloudId, config.get_api_url()+"/k8s1")
+    ocloud1.addDeploymentManager(dms)
+    ocloud1.addDeploymentManager(dms)
+    assert len(ocloud1.deploymentManagers) == 1
+    # repo.update(ocloud1.oCloudId, {"deploymentManagers": ocloud1.deploymentManagers})