--- /dev/null
+.venv
+.mypy_cache
+__pycache__
+*.egg-info
--- /dev/null
+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
--- /dev/null
+## 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
--- /dev/null
+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"
--- /dev/null
+[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
--- /dev/null
+pylint\r
+mypy\r
+requests\r
+\r
+pytest\r
+pytest-icdiff\r
+\r
+tenacity\r
--- /dev/null
+flask\r
+sqlalchemy\r
+redis\r
+psycopg2-binary\r
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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)))
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+from enum import Enum\r
+\r
+class ResourceTypeEnum(Enum):\r
+ PSERVER = 1\r
+ PSERVER_CPU = 2\r
+ PSERVER_RAM = 3\r
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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()
--- /dev/null
+# 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
--- /dev/null
+# 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]
--- /dev/null
+# 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
--- /dev/null
+# 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()
--- /dev/null
+# 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
--- /dev/null
+from setuptools import setup\r
+\r
+setup(\r
+ name="o2imsdms",\r
+ version="1.0",\r
+ packages=["o2ims", "o2dms", "o2common"],\r
+)\r
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+[pytest]
+addopts = --tb=short
+filterwarnings =
+ ignore::DeprecationWarning
--- /dev/null
+# 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
--- /dev/null
+# 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})