From 8339c9a882a586578b37f44a504e21c5208611c0 Mon Sep 17 00:00:00 2001 From: Bin Yang Date: Thu, 21 Oct 2021 19:10:40 +0800 Subject: [PATCH] Add framework and apiserver Issue-ID: INF-196 Signed-off-by: Bin Yang Change-Id: I3e3022662a8d7e0158657811c0422f3503cb7883 --- .gitignore | 4 ++ Dockerfile | 15 +++++ README.md | 31 +++++++++ docker-compose.yml | 60 +++++++++++++++++ mypy.ini | 7 ++ requirements-test.txt | 8 +++ requirements.txt | 4 ++ src/__init__.py | 13 ++++ src/o2common/__init__.py | 13 ++++ src/o2dms/__init__.py | 13 ++++ src/o2dms/setup.py | 19 ++++++ src/o2ims/__init__.py | 13 ++++ src/o2ims/adapter/notifications.py | 20 ++++++ src/o2ims/adapter/ocloud_repository.py | 77 ++++++++++++++++++++++ src/o2ims/adapter/orm.py | 97 ++++++++++++++++++++++++++++ src/o2ims/adapter/redis_eventpublisher.py | 30 +++++++++ src/o2ims/bootstrap.py | 63 ++++++++++++++++++ src/o2ims/config.py | 41 ++++++++++++ src/o2ims/domain/__init__.py | 13 ++++ src/o2ims/domain/commands.py | 25 +++++++ src/o2ims/domain/events.py | 23 +++++++ src/o2ims/domain/ocloud.py | 83 ++++++++++++++++++++++++ src/o2ims/domain/resource_type.py | 6 ++ src/o2ims/entrypoints/__init__.py | 13 ++++ src/o2ims/entrypoints/flask_application.py | 29 +++++++++ src/o2ims/entrypoints/redis_eventconsumer.py | 45 +++++++++++++ src/o2ims/service/__init__.py | 13 ++++ src/o2ims/service/handlers.py | 32 +++++++++ src/o2ims/service/messagebus.py | 69 ++++++++++++++++++++ src/o2ims/service/unit_of_work.py | 78 ++++++++++++++++++++++ src/o2ims/views/ocloud_view.py | 36 +++++++++++ src/setup.py | 7 ++ tests/__init__.py | 13 ++++ tests/conftest.py | 88 +++++++++++++++++++++++++ tests/integration/__init__.py | 13 ++++ tests/integration/test_ocloud_repository.py | 93 ++++++++++++++++++++++++++ tests/pytest.ini | 4 ++ tests/unit/__init__.py | 13 ++++ tests/unit/test_ocloud.py | 39 +++++++++++ 39 files changed, 1263 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 mypy.ini create mode 100644 requirements-test.txt create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 src/o2common/__init__.py create mode 100644 src/o2dms/__init__.py create mode 100644 src/o2dms/setup.py create mode 100644 src/o2ims/__init__.py create mode 100644 src/o2ims/adapter/notifications.py create mode 100644 src/o2ims/adapter/ocloud_repository.py create mode 100644 src/o2ims/adapter/orm.py create mode 100644 src/o2ims/adapter/redis_eventpublisher.py create mode 100644 src/o2ims/bootstrap.py create mode 100644 src/o2ims/config.py create mode 100644 src/o2ims/domain/__init__.py create mode 100644 src/o2ims/domain/commands.py create mode 100644 src/o2ims/domain/events.py create mode 100644 src/o2ims/domain/ocloud.py create mode 100644 src/o2ims/domain/resource_type.py create mode 100644 src/o2ims/entrypoints/__init__.py create mode 100644 src/o2ims/entrypoints/flask_application.py create mode 100644 src/o2ims/entrypoints/redis_eventconsumer.py create mode 100644 src/o2ims/service/__init__.py create mode 100644 src/o2ims/service/handlers.py create mode 100644 src/o2ims/service/messagebus.py create mode 100644 src/o2ims/service/unit_of_work.py create mode 100644 src/o2ims/views/ocloud_view.py create mode 100644 src/setup.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_ocloud_repository.py create mode 100644 tests/pytest.ini create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_ocloud.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea29b71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv +.mypy_cache +__pycache__ +*.egg-info diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e1afbcb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.10-slim-buster + +COPY requirements.txt /tmp/ +RUN pip install -r /tmp/requirements.txt + +COPY requirements-test.txt /tmp/ +RUN pip install -r /tmp/requirements-test.txt + +RUN mkdir -p /src +COPY src/ /src/ +RUN pip install -e /src + +COPY tests/ /tests/ + +WORKDIR /src diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb6900a --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +## Building containers + + +```sh +docker-compose build +``` + + +## Creating a local virtualenv (optional) + +```sh +python3.8 -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +pip install -e src/ +``` + +## Running the tests + +```sh +docker-compose up -d +docker-compose run --rm --no-deps --entrypoint=pytest api /tests/unit /tests/integration +pytest tests/unit +pytest tests/integration +pytest tests/e2e +``` + +## Tear down containers + +```sh +docker-compose down --remove-orphans +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..91d3ec8 --- /dev/null +++ b/docker-compose.yml @@ -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 index 0000000..31ee9f6 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,7 @@ +[mypy] +ignore_missing_imports = False +mypy_path = ./src +check_untyped_defs = True + +[mypy-pytest.*,sqlalchemy.*,redis.*] +ignore_missing_imports = True diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..251bb2b --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,8 @@ +pylint +mypy +requests + +pytest +pytest-icdiff + +tenacity diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..985e50e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask +sqlalchemy +redis +psycopg2-binary diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/src/__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/src/o2common/__init__.py b/src/o2common/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/src/o2common/__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/src/o2dms/__init__.py b/src/o2dms/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/src/o2dms/__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/src/o2dms/setup.py b/src/o2dms/setup.py new file mode 100644 index 0000000..feafb7a --- /dev/null +++ b/src/o2dms/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup + +setup( + name="o2common", + version="1.0", + packages=["o2common"], +) + +setup( + name="o2ims", + version="1.0", + packages=["o2ims"], +) + +setup( + name="o2dms", + version="1.0", + packages=["o2dms"], +) diff --git a/src/o2ims/__init__.py b/src/o2ims/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/src/o2ims/__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/src/o2ims/adapter/notifications.py b/src/o2ims/adapter/notifications.py new file mode 100644 index 0000000..e2ad375 --- /dev/null +++ b/src/o2ims/adapter/notifications.py @@ -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 index 0000000..6d3d221 --- /dev/null +++ b/src/o2ims/adapter/ocloud_repository.py @@ -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 index 0000000..6cf9847 --- /dev/null +++ b/src/o2ims/adapter/orm.py @@ -0,0 +1,97 @@ +# 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 logging + +from sqlalchemy import ( + Table, + MetaData, + Column, + Integer, + String, + Date, + ForeignKey, + event, +) + +from sqlalchemy.orm import mapper, relationship +from sqlalchemy.sql.expression import true + +from o2ims.domain import ocloud as ocloudModel + +logger = logging.getLogger(__name__) + +metadata = MetaData() + +ocloud = Table( + "ocloud", + metadata, + Column("oCloudId", String(255), primary_key=True), + Column("name", String(255)), + Column("description", String(255)), + Column("infrastructureManagementServiceEndpoint", String(255)) +) + +resourcepool = Table( + "resourcepool", + metadata, + Column("resourcePoolId", String(255), primary_key=True), + Column("name", String(255)), + Column("location", String(255)), + Column("oCloudId", ForeignKey("ocloud.oCloudId")), + # Column("extensions", String(1024)) +) + +resourcetype = Table( + "resourcetype", + metadata, + Column("resourceTypeId", String(255), primary_key=True), + Column("oCloudId", ForeignKey("ocloud.oCloudId")), + Column("name", String(255)), +) + +resource = Table( + "resource", + metadata, + Column("resourceId", String(255), primary_key=True), + Column("parentId", String(255)), + Column("resourceTypeId", ForeignKey("resourcetype.resourceTypeId")), + Column("resourcePoolId", ForeignKey("resourcepool.resourcePoolId")), + Column("oCloudId", ForeignKey("ocloud.oCloudId")) +) + +deploymentmanager = Table( + "deploymentmanager", + metadata, + Column("deploymentManagerId", String(255), primary_key=True), + Column("name", String(255)), + Column("deploymentManagementServiceEndpoint", String(255)), + Column("oCloudId", ForeignKey("ocloud.oCloudId")) +) + + +def start_o2ims_mappers(): + logger.info("Starting O2 IMS mappers") + dm_mapper = mapper(ocloudModel.DeploymentManager, deploymentmanager) + resourcepool_mapper = mapper(ocloudModel.ResourcePool, resourcepool) + resourcetype_mapper = mapper(ocloudModel.ResourceType, resourcetype) + resource_mapper = mapper(ocloudModel.Resource, resource) + ocloud_mapper = mapper( + ocloudModel.Ocloud, + ocloud, + properties={ + "deploymentManagers": relationship(dm_mapper), + "resourceTypes": relationship(resourcetype_mapper), + "resourcePools": relationship(resourcepool_mapper) + }) diff --git a/src/o2ims/adapter/redis_eventpublisher.py b/src/o2ims/adapter/redis_eventpublisher.py new file mode 100644 index 0000000..835c72c --- /dev/null +++ b/src/o2ims/adapter/redis_eventpublisher.py @@ -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 index 0000000..00cdc4a --- /dev/null +++ b/src/o2ims/bootstrap.py @@ -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 index 0000000..798ea79 --- /dev/null +++ b/src/o2ims/config.py @@ -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 index 0000000..b514342 --- /dev/null +++ b/src/o2ims/domain/__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/src/o2ims/domain/commands.py b/src/o2ims/domain/commands.py new file mode 100644 index 0000000..9df6cff --- /dev/null +++ b/src/o2ims/domain/commands.py @@ -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 index 0000000..755f65e --- /dev/null +++ b/src/o2ims/domain/events.py @@ -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 index 0000000..ddd2646 --- /dev/null +++ b/src/o2ims/domain/ocloud.py @@ -0,0 +1,83 @@ +# 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. + +from __future__ import annotations +from dataclasses import dataclass +from datetime import date +from typing import Optional, List, Set +from .resource_type import ResourceTypeEnum +# from uuid import UUID + +class Ocloud: + def __init__( + self, ocloudid: str, name: str, imsendpoint: str, + description: str = '', version_number: int = 0) -> None: + + self.oCloudId = ocloudid + self.version_number = version_number + self.name = name + self.description = description + self.infrastructureManagementServiceEndpoint = imsendpoint + self.resourcePools = [] + self.deploymentManagers = [] + self.resourceTypes = [] + self.extensions = [] + self.events = [] + + def addDeploymentManager(self, deploymentManager: DeploymentManager) -> None: + deploymentManager.oCloudId = self.oCloudId + old = filter( + lambda x: x.deploymentManagerId == deploymentManager.deploymentManagerId, + self.deploymentManagers) + for o in old or []: + self.deploymentManagers.remove(o) + self.deploymentManagers.append(deploymentManager) + +class DeploymentManager: + def __init__(self, id: str, name: str, ocloudid: str, dmsendpoint: str) -> None: + self.deploymentManagerId = id + self.name = name + self.oCloudId = ocloudid + self.deploymentManagementServiceEndpoint = dmsendpoint + self.extensions = [] + + +class ResourcePool: + def __init__(self, id: str, name: str, location: str, ocloudid: str) -> None: + self.resourcePoolId = id + self.name = name + self.location = location + self.oCloudId = ocloudid + self.extensions = [] + + +class ResourceType: + def __init__(self, typeid: str, name:str, typeEnum: ResourceTypeEnum, ocloudid: str) -> None: + self.resourceTypeId = typeid + self.resourceTypeEnum = typeEnum.value + self.name = name + self.oCloudId = ocloudid + self.extensions = [] + + +class Resource: + def __init__(self, resourceId:str, resourceTypeId: str, resourcePoolId: str) -> None: + self.resourceId = resourceId + self.oCloudId = None # tbd + self.resourceTypeId = resourceTypeId + self.resourcePoolId = resourcePoolId + self.parentId = None + self.elements = [] + self.extensions = [] + diff --git a/src/o2ims/domain/resource_type.py b/src/o2ims/domain/resource_type.py new file mode 100644 index 0000000..faff8ea --- /dev/null +++ b/src/o2ims/domain/resource_type.py @@ -0,0 +1,6 @@ +from enum import Enum + +class ResourceTypeEnum(Enum): + PSERVER = 1 + PSERVER_CPU = 2 + PSERVER_RAM = 3 diff --git a/src/o2ims/entrypoints/__init__.py b/src/o2ims/entrypoints/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/src/o2ims/entrypoints/__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/src/o2ims/entrypoints/flask_application.py b/src/o2ims/entrypoints/flask_application.py new file mode 100644 index 0000000..933301c --- /dev/null +++ b/src/o2ims/entrypoints/flask_application.py @@ -0,0 +1,29 @@ +# 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. + +from datetime import datetime +from flask import Flask, jsonify, request +from o2ims.domain import commands +from o2ims.service.handlers import InvalidResourceType +from o2ims import bootstrap, config +from o2ims.views import ocloud_view + +app = Flask(__name__) +bus = bootstrap.bootstrap() +apibase = config.get_o2ims_api_base() + +@app.route(apibase, methods=["GET"]) +def oclouds(): + result = ocloud_view.oclouds(bus.uow) + return jsonify(result), 200 diff --git a/src/o2ims/entrypoints/redis_eventconsumer.py b/src/o2ims/entrypoints/redis_eventconsumer.py new file mode 100644 index 0000000..a22b61c --- /dev/null +++ b/src/o2ims/entrypoints/redis_eventconsumer.py @@ -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 index 0000000..b514342 --- /dev/null +++ b/src/o2ims/service/__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/src/o2ims/service/handlers.py b/src/o2ims/service/handlers.py new file mode 100644 index 0000000..c75dc07 --- /dev/null +++ b/src/o2ims/service/handlers.py @@ -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 index 0000000..bff45ab --- /dev/null +++ b/src/o2ims/service/messagebus.py @@ -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 index 0000000..4714729 --- /dev/null +++ b/src/o2ims/service/unit_of_work.py @@ -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 index 0000000..7005b5a --- /dev/null +++ b/src/o2ims/views/ocloud_view.py @@ -0,0 +1,36 @@ +# 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. + +from o2ims.service import unit_of_work + + +def ocloud_one(ocloudid: str, uow: unit_of_work.SqlAlchemyUnitOfWork): + with uow: + results = uow.session.execute( + """ + SELECT oCloudId, name FROM ocloud WHERE oCloudId = :ocloudid + """, + dict(ocloudid=ocloudid), + ) + return dict(results[0]) if len(results) > 0 else None + + +def oclouds(uow: unit_of_work.SqlAlchemyUnitOfWork): + with uow: + results = uow.session.execute( + """ + SELECT oCloudId, name FROM ocloud + """, + ) + return [dict(r) for r in results] diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..7558373 --- /dev/null +++ b/src/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + +setup( + name="o2imsdms", + version="1.0", + packages=["o2ims", "o2dms", "o2common"], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/tests/__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/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..7744dbd --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,88 @@ +# pylint: disable=redefined-outer-name +import shutil +import subprocess +import time +from pathlib import Path + +import pytest +import redis +import requests +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, clear_mappers +from tenacity import retry, stop_after_delay + +from o2ims.adapter.orm import metadata, start_o2ims_mappers +from o2ims import config + + +@pytest.fixture +def in_memory_sqlite_db(): + engine = create_engine("sqlite:///:memory:") + # engine = create_engine("sqlite:///:memory:", echo=True) + metadata.create_all(engine) + return engine + + +@pytest.fixture +def sqlite_session_factory(in_memory_sqlite_db): + yield sessionmaker(bind=in_memory_sqlite_db) + + +@pytest.fixture +def mappers(): + start_o2ims_mappers() + yield + clear_mappers() + + +@retry(stop=stop_after_delay(10)) +def wait_for_postgres_to_come_up(engine): + return engine.connect() + + +@retry(stop=stop_after_delay(10)) +def wait_for_webapp_to_come_up(): + return requests.get(config.get_api_url()) + + +@retry(stop=stop_after_delay(10)) +def wait_for_redis_to_come_up(): + r = redis.Redis(**config.get_redis_host_and_port()) + return r.ping() + + +@pytest.fixture(scope="session") +def postgres_db(): + engine = create_engine(config.get_postgres_uri(), isolation_level="SERIALIZABLE") + wait_for_postgres_to_come_up(engine) + metadata.create_all(engine) + return engine + + +@pytest.fixture +def postgres_session_factory(postgres_db): + yield sessionmaker(bind=postgres_db) + + +@pytest.fixture +def postgres_session(postgres_session_factory): + return postgres_session_factory() + + +@pytest.fixture +def restart_api(): + (Path(__file__).parent / "../src/o2ims/entrypoints/flask_application.py").touch() + time.sleep(0.5) + wait_for_webapp_to_come_up() + + +@pytest.fixture +def restart_redis_pubsub(): + wait_for_redis_to_come_up() + if not shutil.which("docker-compose"): + print("skipping restart, assumes running in container") + return + subprocess.run( + ["docker-compose", "restart", "-t", "0", "redis_pubsub"], + check=True, + ) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/tests/integration/__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/tests/integration/test_ocloud_repository.py b/tests/integration/test_ocloud_repository.py new file mode 100644 index 0000000..36919b3 --- /dev/null +++ b/tests/integration/test_ocloud_repository.py @@ -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 index 0000000..3fd8685 --- /dev/null +++ b/tests/pytest.ini @@ -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 index 0000000..b514342 --- /dev/null +++ b/tests/unit/__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/tests/unit/test_ocloud.py b/tests/unit/test_ocloud.py new file mode 100644 index 0000000..baff737 --- /dev/null +++ b/tests/unit/test_ocloud.py @@ -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}) -- 2.16.6