From 5e02e76308e1677fb106572b885a366eb6c0fbec Mon Sep 17 00:00:00 2001 From: Bin Yang Date: Tue, 23 Nov 2021 09:09:36 +0800 Subject: [PATCH] Add o2dms api endpoint Signed-off-by: Bin Yang Change-Id: Ic624aeda9f9bc8aba86e81b8e43b2ee3a89a009f --- Dockerfile | 3 + Dockerfile.localtest | 3 + README-o2imsbuilder.md | 3 +- charts/resources/scripts/init/o2pubsub_start.sh | 2 +- charts/resources/scripts/init/o2watcher_start.sh | 2 +- charts/templates/deployment.yaml | 2 +- docker-compose.yml | 15 ++-- o2app/__init__.py | 13 +++ o2app/adapter/__init__.py | 13 +++ o2app/adapter/unit_of_work.py | 83 ++++++++++++++++++ o2app/bootstrap.py | 88 +++++++++++++++++++ o2app/entrypoints/__init__.py | 13 +++ o2app/entrypoints/flask_application.py | 35 ++++++++ o2app/entrypoints/redis_eventconsumer.py | 46 ++++++++++ o2app/entrypoints/resource_watcher.py | 83 ++++++++++++++++++ o2app/service/__init__.py | 13 +++ o2app/service/handlers.py | 39 +++++++++ o2app/service/messagebus.py | 72 ++++++++++++++++ o2common/adapter/__init__.py | 13 +++ o2common/adapter/notifications.py | 20 +++++ o2common/adapter/redis_eventpublisher.py | 31 +++++++ o2common/config/__init__.py | 13 +++ o2common/config/config.py | 93 ++++++++++++++++++++ o2common/domain/__init__.py | 13 +++ o2common/domain/base.py | 26 ++++++ o2common/domain/commands.py | 60 +++++++++++++ o2common/domain/events.py | 25 ++++++ o2common/service/__init__.py | 13 +++ o2common/service/unit_of_work.py | 54 ++++++++++++ o2common/service/watcher/__init__.py | 13 +++ o2common/service/watcher/base.py | 98 ++++++++++++++++++++++ o2dms/adapter/__init__.py | 13 +++ o2dms/adapter/dms_repository.py | 37 ++++++++ o2dms/adapter/orm.py | 61 ++++++++++++++ o2dms/domain/dms.py | 31 +++++++ o2dms/domain/dms_repo.py | 50 +++++++++++ o2dms/service/__init__.py | 13 +++ o2dms/views/__init__.py | 13 +++ o2dms/views/dms_dto.py | 54 ++++++++++++ o2dms/views/dms_lcm_view.py | 71 ++++++++++++++++ o2dms/views/dms_route.py | 96 +++++++++++++++++++++ o2ims/adapter/__init__.py | 13 +++ o2ims/adapter/orm.py | 1 + o2ims/config.py | 10 ++- o2ims/entrypoints/flask_application.py | 3 + o2ims/service/auditor/dms_handler.py | 3 +- o2ims/views/__init__.py | 13 +++ tests/o2app-api-entry.sh | 19 +++++ ...o2ims-watcher-entry.sh => o2app-redis-entry.sh} | 8 +- ...o2ims-redis-entry.sh => o2app-watcher-entry.sh} | 8 +- 50 files changed, 1500 insertions(+), 17 deletions(-) create mode 100644 o2app/__init__.py create mode 100644 o2app/adapter/__init__.py create mode 100644 o2app/adapter/unit_of_work.py create mode 100644 o2app/bootstrap.py create mode 100644 o2app/entrypoints/__init__.py create mode 100644 o2app/entrypoints/flask_application.py create mode 100644 o2app/entrypoints/redis_eventconsumer.py create mode 100644 o2app/entrypoints/resource_watcher.py create mode 100644 o2app/service/__init__.py create mode 100644 o2app/service/handlers.py create mode 100644 o2app/service/messagebus.py create mode 100644 o2common/adapter/__init__.py create mode 100644 o2common/adapter/notifications.py create mode 100644 o2common/adapter/redis_eventpublisher.py create mode 100644 o2common/config/__init__.py create mode 100644 o2common/config/config.py create mode 100644 o2common/domain/__init__.py create mode 100644 o2common/domain/base.py create mode 100644 o2common/domain/commands.py create mode 100644 o2common/domain/events.py create mode 100644 o2common/service/__init__.py create mode 100644 o2common/service/unit_of_work.py create mode 100644 o2common/service/watcher/__init__.py create mode 100644 o2common/service/watcher/base.py create mode 100644 o2dms/adapter/__init__.py create mode 100644 o2dms/adapter/dms_repository.py create mode 100644 o2dms/adapter/orm.py create mode 100644 o2dms/domain/dms.py create mode 100644 o2dms/domain/dms_repo.py create mode 100644 o2dms/service/__init__.py create mode 100644 o2dms/views/__init__.py create mode 100644 o2dms/views/dms_dto.py create mode 100644 o2dms/views/dms_lcm_view.py create mode 100644 o2dms/views/dms_route.py create mode 100644 o2ims/adapter/__init__.py create mode 100644 o2ims/views/__init__.py create mode 100644 tests/o2app-api-entry.sh rename tests/{o2ims-watcher-entry.sh => o2app-redis-entry.sh} (54%) rename tests/{o2ims-redis-entry.sh => o2app-watcher-entry.sh} (55%) diff --git a/Dockerfile b/Dockerfile index a4ca721..3cbe00a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,9 @@ RUN mkdir -p /src COPY o2ims/ /src/o2ims/ COPY o2dms/ /src/o2dms/ COPY o2common/ /src/o2common/ + +RUN mkdir -p /src/o2app/ +COPY o2app/ /src/o2app/ COPY setup.py /src/ RUN pip install -e /src diff --git a/Dockerfile.localtest b/Dockerfile.localtest index 16d399d..109ce75 100644 --- a/Dockerfile.localtest +++ b/Dockerfile.localtest @@ -26,6 +26,9 @@ RUN mkdir -p /src COPY o2ims/ /src/o2ims/ COPY o2dms/ /src/o2dms/ COPY o2common/ /src/o2common/ + +RUN mkdir -p /src/o2app/ +COPY o2app/ /src/o2app/ COPY setup.py /src/ COPY configs/ /etc/o2/ diff --git a/README-o2imsbuilder.md b/README-o2imsbuilder.md index e451723..a143301 100644 --- a/README-o2imsbuilder.md +++ b/README-o2imsbuilder.md @@ -94,6 +94,7 @@ kubectl -n ${NAMESPACE} get pods ### test api endpoint ```sh +curl -k http(s)://:30205 curl -k http(s)://:30205/o2ims_infrastructureInventory/v1 ``` @@ -116,7 +117,7 @@ kubectl -n ${NAMESPACE} exec -it o2api- -c postgres -- bash \d - select * from stxcache; + select * from ocloud; \q diff --git a/charts/resources/scripts/init/o2pubsub_start.sh b/charts/resources/scripts/init/o2pubsub_start.sh index e39329b..5dff1b3 100644 --- a/charts/resources/scripts/init/o2pubsub_start.sh +++ b/charts/resources/scripts/init/o2pubsub_start.sh @@ -19,6 +19,6 @@ cd /root/ git clone "https://gerrit.o-ran-sc.org/r/pti/o2" pip install -e /root/o2 -python /root/o2/o2ims/entrypoints/redis_eventconsumer.py +python /root/o2/o2app/entrypoints/redis_eventconsumer.py sleep infinity diff --git a/charts/resources/scripts/init/o2watcher_start.sh b/charts/resources/scripts/init/o2watcher_start.sh index 53fd670..643c27d 100644 --- a/charts/resources/scripts/init/o2watcher_start.sh +++ b/charts/resources/scripts/init/o2watcher_start.sh @@ -19,6 +19,6 @@ cd /root/ git clone "https://gerrit.o-ran-sc.org/r/pti/o2" pip install -e /root/o2 -python /root/o2/o2ims/entrypoints/resource_watcher.py +python /root/o2/o2app/entrypoints/resource_watcher.py sleep infinity diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index 7748f3e..cdb54d6 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -62,7 +62,7 @@ spec: - name: DB_PASSWORD value: o2ims123 - name: FLASK_APP - value: /root/o2/o2ims/entrypoints/flask_application.py + value: /root/o2/o2app/entrypoints/flask_application.py - name: FLASK_DEBUG value: {{ .Values.o2ims.logginglevel }} - name: LOGGING_CONFIG_LEVEL diff --git a/docker-compose.yml b/docker-compose.yml index 64334b7..552154d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,10 +24,11 @@ services: - ./o2ims:/o2ims - ./o2dms:/o2dms - ./o2common:/o2common + - ./o2app:/o2app - ./tests:/tests entrypoint: - /bin/sh - - /tests/o2ims-redis-entry.sh + - /tests/o2app-redis-entry.sh api: image: o2imsdms @@ -39,7 +40,7 @@ services: - API_HOST=api - REDIS_HOST=redis - PYTHONDONTWRITEBYTECODE=1 - - FLASK_APP=/o2ims/entrypoints/flask_application.py + - FLASK_APP=/o2app/entrypoints/flask_application.py - FLASK_DEBUG=1 - PYTHONUNBUFFERED=1 - OS_AUTH_URL=${OS_AUTH_URL} @@ -51,12 +52,11 @@ services: - ./o2ims:/o2ims - ./o2dms:/o2dms - ./o2common:/o2common + - ./o2app:/o2app - ./tests:/tests entrypoint: - - flask - - run - - --host=0.0.0.0 - - --port=80 + - /bin/sh + - /tests/o2app-api-entry.sh ports: - "5005:80" @@ -81,10 +81,11 @@ services: - ./o2ims:/o2ims - ./o2dms:/o2dms - ./o2common:/o2common + - ./o2app:/o2app - ./tests:/tests entrypoint: - /bin/sh - - /tests/o2ims-watcher-entry.sh + - /tests/o2app-watcher-entry.sh postgres: image: postgres:9.6 diff --git a/o2app/__init__.py b/o2app/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2app/__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/o2app/adapter/__init__.py b/o2app/adapter/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2app/adapter/__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/o2app/adapter/unit_of_work.py b/o2app/adapter/unit_of_work.py new file mode 100644 index 0000000..0faba56 --- /dev/null +++ b/o2app/adapter/unit_of_work.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. + +# pylint: disable=attribute-defined-outside-init +from __future__ import annotations +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm.session import Session + +from o2common.config import config +from o2common.service.unit_of_work import AbstractUnitOfWork + +from o2ims.adapter import ocloud_repository +from o2dms.adapter import dms_repository + +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) + self.resource_types = ocloud_repository\ + .ResouceTypeSqlAlchemyRepository(self.session) + self.resource_pools = ocloud_repository\ + .ResourcePoolSqlAlchemyRepository(self.session) + self.resources = ocloud_repository\ + .ResourceSqlAlchemyRepository(self.session) + self.deployment_managers = ocloud_repository\ + .DeploymentManagerSqlAlchemyRepository(self.session) + self.nfdeployment_descs = dms_repository\ + .NfDeploymentDescSqlAlchemyRepository(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() + + def _collect_new_events(self): + for entry in self.oclouds.seen: + while entry.events: + yield entry.events.pop(0) + for entry in self.resource_pools.seen: + while entry.events: + yield entry.events.pop(0) + for entry in self.resources.seen: + while entry.events: + yield entry.events.pop(0) + for entry in self.resource_types.seen: + while entry.events: + yield entry.events.pop(0) + for entry in self.deployment_managers.seen: + while entry.events: + yield entry.events.pop(0) + for entry in self.nfdeployment_descs.seen: + while entry.events: + yield entry.events.pop(0) diff --git a/o2app/bootstrap.py b/o2app/bootstrap.py new file mode 100644 index 0000000..7a074f6 --- /dev/null +++ b/o2app/bootstrap.py @@ -0,0 +1,88 @@ +# 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 retry import retry +import inspect +from typing import Callable + +from o2common.adapter.notifications import AbstractNotifications,\ + SmoO2Notifications +from o2common.adapter import redis_eventpublisher +from o2common.service import unit_of_work + +from o2app.service import handlers, messagebus +from o2app.adapter.unit_of_work import SqlAlchemyUnitOfWork + +from o2ims.adapter import orm as o2ims_orm +from o2dms.adapter import orm as o2dms_orm + +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + + +@retry(tries=100, delay=2, backoff=1) +def wait_for_db_ready(engine): + # wait for db up + logger.info("Wait for DB ready ...") + engine.connect() + logger.info("DB is ready") + + +def bootstrap( + start_orm: bool = True, + uow: unit_of_work.AbstractUnitOfWork = SqlAlchemyUnitOfWork(), + notifications: AbstractNotifications = None, + publish: Callable = redis_eventpublisher.publish, +) -> messagebus.MessageBus: + + if notifications is None: + notifications = SmoO2Notifications() + + if start_orm: + with uow: + # get default engine if uow is by default + engine = uow.session.get_bind() + wait_for_db_ready(engine) + o2ims_orm.start_o2ims_mappers(engine) + o2dms_orm.start_o2dms_mappers(engine) + + 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/o2app/entrypoints/__init__.py b/o2app/entrypoints/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2app/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/o2app/entrypoints/flask_application.py b/o2app/entrypoints/flask_application.py new file mode 100644 index 0000000..45c0436 --- /dev/null +++ b/o2app/entrypoints/flask_application.py @@ -0,0 +1,35 @@ +# 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 flask import Flask +from flask_restx import Api + +from o2app import bootstrap +# from o2ims import config +# from o2ims.views.ocloud_route import configure_routes +from o2ims.views import ocloud_route as ims_route +from o2dms.views import dms_route + + +# apibase = config.get_o2ims_api_base() +app = Flask(__name__) +api = Api(app, version='1.0.0', + title='O-Cloud O2 Services', + description='Swagger OpenAPI document for \ + O-Cloud O2 Services', + ) +bus = bootstrap.bootstrap() + +ims_route.configure_namespace(api, bus) +dms_route.configure_namespace(api, bus) diff --git a/o2app/entrypoints/redis_eventconsumer.py b/o2app/entrypoints/redis_eventconsumer.py new file mode 100644 index 0000000..188eb87 --- /dev/null +++ b/o2app/entrypoints/redis_eventconsumer.py @@ -0,0 +1,46 @@ +# 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 redis + +from o2app import bootstrap +from o2common.config import config +# from o2common.domain import commands + +from o2common.helper import o2logging +logger = o2logging.get_logger(__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/o2app/entrypoints/resource_watcher.py b/o2app/entrypoints/resource_watcher.py new file mode 100644 index 0000000..036596c --- /dev/null +++ b/o2app/entrypoints/resource_watcher.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. + +import cotyledon + +from o2app import bootstrap +from o2common.service.watcher.base import WatcherTree + +from o2ims.service.watcher.worker import PollWorker +from o2ims.service.watcher.ocloud_watcher import OcloudWatcher +from o2ims.service.watcher.ocloud_watcher import DmsWatcher +from o2ims.service.watcher.resourcepool_watcher import ResourcePoolWatcher +from o2ims.adapter.clients.ocloud_sa_client import StxSaDmsClient +from o2ims.adapter.clients.ocloud_sa_client import StxSaOcloudClient +from o2ims.adapter.clients.ocloud_sa_client import StxSaResourcePoolClient + +from o2ims.service.watcher.pserver_watcher import PServerWatcher +from o2ims.adapter.clients.ocloud_sa_client import StxPserverClient + +from o2ims.service.watcher.pserver_cpu_watcher import PServerCpuWatcher +from o2ims.adapter.clients.ocloud_sa_client import StxCpuClient + +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + +# r = redis.Redis(**config.get_redis_host_and_port()) + + +class WatcherService(cotyledon.Service): + def __init__(self, worker_id, args=None) -> None: + super().__init__(worker_id) + self.args = args + self.bus = bootstrap.bootstrap() + self.worker = PollWorker() + + def run(self): + try: + root = WatcherTree(OcloudWatcher( + StxSaOcloudClient(), self.bus)) + root.addchild( + DmsWatcher(StxSaDmsClient(), self.bus)) + + child_respool = root.addchild( + ResourcePoolWatcher(StxSaResourcePoolClient(), + self.bus)) + child_pserver = child_respool.addchild( + PServerWatcher(StxPserverClient(), self.bus)) + child_pserver.addchild( + PServerCpuWatcher(StxCpuClient(), self.bus)) + + self.worker.add_watcher(root) + + self.worker.start() + except Exception as ex: + logger.warning("WorkerService Exception:" + str(ex)) + finally: + self.worker.stop() + + +def start_watchers(sm: cotyledon.ServiceManager = None): + watchersm = sm if sm else cotyledon.ServiceManager() + watchersm.add(WatcherService, workers=1, args=()) + watchersm.run() + + +def main(): + logger.info("Resource watcher starting") + start_watchers() + + +if __name__ == "__main__": + main() diff --git a/o2app/service/__init__.py b/o2app/service/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2app/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/o2app/service/handlers.py b/o2app/service/handlers.py new file mode 100644 index 0000000..830e1ff --- /dev/null +++ b/o2app/service/handlers.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. + +# pylint: disable=unused-argument +from __future__ import annotations +# from dataclasses import asdict +from typing import List, Dict, Callable, Type +from o2ims.service.auditor import dms_handler +# TYPE_CHECKING +from o2ims.domain import commands, events +from o2ims.service.auditor import ocloud_handler + +# if TYPE_CHECKING: +# from . import unit_of_work + + +class InvalidResourceType(Exception): + pass + + +EVENT_HANDLERS = { +} # type: Dict[Type[events.Event], List[Callable]] + + +COMMAND_HANDLERS = { + commands.UpdateOCloud: ocloud_handler.update_ocloud, + commands.UpdateDms: dms_handler.update_dms +} # type: Dict[Type[commands.Command], Callable] diff --git a/o2app/service/messagebus.py b/o2app/service/messagebus.py new file mode 100644 index 0000000..297aa8f --- /dev/null +++ b/o2app/service/messagebus.py @@ -0,0 +1,72 @@ +# 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 +from typing import Callable, Dict, List, Union, Type, TYPE_CHECKING +from o2ims.domain import commands, events + +if TYPE_CHECKING: + from . import unit_of_work + +from o2common.helper import o2logging +logger = o2logging.get_logger(__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 not message: + continue + elif 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 as ex: + logger.exception("Exception handling command %s", command) + raise ex diff --git a/o2common/adapter/__init__.py b/o2common/adapter/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2common/adapter/__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/o2common/adapter/notifications.py b/o2common/adapter/notifications.py new file mode 100644 index 0000000..4c49c1b --- /dev/null +++ b/o2common/adapter/notifications.py @@ -0,0 +1,20 @@ +# pylint: disable=too-few-public-methods +import abc +from o2ims import config + + +SMO_O2_ENDPOINT = config.get_smo_o2endpoint() + + +class AbstractNotifications(abc.ABC): + @abc.abstractmethod + def send(self, message): + raise NotImplementedError + + +class SmoO2Notifications(AbstractNotifications): + def __init__(self, smoO2Endpoint=SMO_O2_ENDPOINT): + self.smoO2Endpoint = smoO2Endpoint + + def send(self, message): + pass diff --git a/o2common/adapter/redis_eventpublisher.py b/o2common/adapter/redis_eventpublisher.py new file mode 100644 index 0000000..4df167e --- /dev/null +++ b/o2common/adapter/redis_eventpublisher.py @@ -0,0 +1,31 @@ +# 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 +from dataclasses import asdict +import redis + +from o2ims import config +from o2ims.domain import events + +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + + +r = redis.Redis(**config.get_redis_host_and_port()) + + +def publish(channel, event: events.Event): + logger.info("publishing: channel=%s, event=%s", channel, event) + r.publish(channel, json.dumps(asdict(event))) diff --git a/o2common/config/__init__.py b/o2common/config/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2common/config/__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/o2common/config/config.py b/o2common/config/config.py new file mode 100644 index 0000000..55d4755 --- /dev/null +++ b/o2common/config/config.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 os +import sys + +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + + +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_root_api_base(): + return "/" + + +def get_o2ims_api_base(): + return get_root_api_base() + 'o2ims_infrastructureInventory/v1' + + +def get_o2dms_api_base(): + return get_root_api_base() + "o2dms/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 + + +def get_stx_access_info(): + # authurl = os.environ.get("STX_AUTH_URL", "http://192.168.204.1:5000/v3") + # username = os.environ.get("STX_USERNAME", "admin") + # pswd = os.environ.get("STX_PASSWORD", "passwd1") + # stx_access_info = (authurl, username, pswd) + try: + client_args = dict( + auth_url=os.environ.get('OS_AUTH_URL', + "http://192.168.204.1:5000/v3"), + username=os.environ.get('OS_USERNAME', "admin"), + api_key=os.environ.get('OS_PASSWORD', "fakepasswd1"), + project_name=os.environ.get('OS_PROJECT_NAME', "admin"), + ) + # dc_client_args = dict( + # auth_url=os.environ['OS_AUTH_URL'], + # username=os.environ['OS_USERNAME'], + # api_key=os.environ['OS_PASSWORD'], + # project_name=os.environ['OS_PROJECT_NAME'], + # user_domain_name=os.environ['OS_USER_DOMAIN_NAME'], + # project_domain_name=os.environ['OS_PROJECT_NAME'], + # project_domain_id=os.environ['OS_PROJECT_DOMAIN_ID'] + # ) + except KeyError: + logger.error('Please source your RC file before execution, ' + 'e.g.: `source ~/downloads/admin-rc.sh`') + sys.exit(1) + + os_client_args = {} + for key, val in client_args.items(): + os_client_args['os_{key}'.format(key=key)] = val + os_client_args['os_password'] = os_client_args.pop('os_api_key') + os_client_args['os_region_name'] = 'RegionOne' + os_client_args['api_version'] = 1 + return os_client_args diff --git a/o2common/domain/__init__.py b/o2common/domain/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2common/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/o2common/domain/base.py b/o2common/domain/base.py new file mode 100644 index 0000000..8a673da --- /dev/null +++ b/o2common/domain/base.py @@ -0,0 +1,26 @@ +# 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 typing import List +from .events import Event + + +class AgRoot: + def __init__(self) -> None: + self.hash = "" + # self.id = "" + self.updatetime = datetime.now() + self.createtime = datetime.now() + self.events = [] # type: List[Event] diff --git a/o2common/domain/commands.py b/o2common/domain/commands.py new file mode 100644 index 0000000..8383f47 --- /dev/null +++ b/o2common/domain/commands.py @@ -0,0 +1,60 @@ +# 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 +# from datetime import datetime +# from o2ims.domain.resource_type import ResourceTypeEnum +from o2ims.domain.stx_object import StxGenericModel + + +class Command: + pass + + +@dataclass +class UpdateStxObject(Command): + data: StxGenericModel + + +@dataclass +class UpdateOCloud(UpdateStxObject): + pass + + +@dataclass +class UpdateDms(UpdateStxObject): + parentid: str + + +@dataclass +class UpdateResourcePool(UpdateStxObject): + parentid: str + + +@dataclass +class UpdateResource(UpdateStxObject): + parentid: str + + +@dataclass +class UpdatePserverCpu(UpdateResource): + pass + + +@dataclass +class UpdatePserver(UpdateResource): + pass diff --git a/o2common/domain/events.py b/o2common/domain/events.py new file mode 100644 index 0000000..591662f --- /dev/null +++ b/o2common/domain/events.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 dataclasses import dataclass + + +class Event: + pass + + +@dataclass +class OcloudUpdated(Event): + oCloudId: str diff --git a/o2common/service/__init__.py b/o2common/service/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2common/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/o2common/service/unit_of_work.py b/o2common/service/unit_of_work.py new file mode 100644 index 0000000..3bef2a3 --- /dev/null +++ b/o2common/service/unit_of_work.py @@ -0,0 +1,54 @@ +# 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 o2ims.domain.ocloud_repo import OcloudRepository,\ + ResourcePoolRepository, ResourceRepository, ResourceTypeRepository,\ + DeploymentManagerRepository +from o2ims.domain.stx_repo import StxObjectRepository + + +class AbstractUnitOfWork(abc.ABC): + oclouds: OcloudRepository + resource_types: ResourceTypeRepository + resource_pools: ResourcePoolRepository + resources: ResourceRepository + deployment_managers: DeploymentManagerRepository + stxobjects: StxObjectRepository + + def __enter__(self): + return self + + def __exit__(self, *args): + self.rollback() + + def commit(self): + self._commit() + + def collect_new_events(self): + return self._collect_new_events() + + def _collect_new_events(self): + raise NotImplementedError + + @abc.abstractmethod + def _commit(self): + raise NotImplementedError + + @abc.abstractmethod + def rollback(self): + raise NotImplementedError diff --git a/o2common/service/watcher/__init__.py b/o2common/service/watcher/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2common/service/watcher/__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/o2common/service/watcher/base.py b/o2common/service/watcher/base.py new file mode 100644 index 0000000..0daf8d4 --- /dev/null +++ b/o2common/service/watcher/base.py @@ -0,0 +1,98 @@ +# 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 logging import exception +# from cgtsclient import exc +from o2ims.service.client.base_client import BaseClient +# from o2ims.domain.stx_object import StxGenericModel +# from o2ims.service.unit_of_work import AbstractUnitOfWork +from o2ims.domain import commands +from o2ims.service.messagebus import MessageBus +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + + +class BaseWatcher(object): + def __init__(self, client: BaseClient, + bus: MessageBus) -> None: + super().__init__() + self._client = client + self._bus = bus + # self._uow = bus.uow + + def targetname(self) -> str: + return self._targetname() + + def probe(self, parent: commands.UpdateStxObject = None): + try: + cmds = self._probe(parent.data if parent else None) + for cmd in cmds: + self._bus.handle(cmd) + + # return self._probe(parent) + return cmds + except Exception as ex: + logger.warning("Failed to probe resource due to: " + str(ex)) + return [] + + def _probe(self, parent: object = None) -> commands.UpdateStxObject: + raise NotImplementedError + + def _targetname(self): + raise NotImplementedError + + # def _compare_and_update(self, newmodel: StxGenericModel) -> bool: + # with self._uow: + # # localmodel = self._uow.stxobjects.get(ocloudmodel.id) + # localmodel = self._uow.stxobjects.get(str(newmodel.id)) + # if not localmodel: + # logger.info("add entry:" + newmodel.name) + # self._uow.stxobjects.add(newmodel) + # elif localmodel.is_outdated(newmodel): + # logger.info("update entry:" + newmodel.name) + # localmodel.update_by(newmodel) + # self._uow.stxobjects.update(localmodel) + # self._uow.commit() + + +# node to organize watchers in tree hierachy +class WatcherTree(object): + def __init__(self, watcher: BaseWatcher) -> None: + super().__init__() + self.watcher = watcher + self.children = {} + + def addchild(self, watcher: BaseWatcher) -> object: + child = WatcherTree(watcher) + self.children[watcher.targetname()] = child + return child + + def removechild(self, targetname: str) -> object: + return self.children.pop(targetname) + + # probe all resources by parent, depth = 0 for indefinite recursive + def probe(self, parentresource=None, depth: int = 0): + logger.debug("probe resources with watcher: " + + self.watcher.targetname()) + childdepth = depth - 1 if depth > 0 else 0 + resources = self.watcher.probe(parentresource) + logger.debug("probe returns " + str(len(resources)) + " resources") + + if depth == 1: + # stop recursive + return + + for res in resources: + for targetname in self.children.keys(): + self.children[targetname].probe(res, childdepth) diff --git a/o2dms/adapter/__init__.py b/o2dms/adapter/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2dms/adapter/__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/o2dms/adapter/dms_repository.py b/o2dms/adapter/dms_repository.py new file mode 100644 index 0000000..c69acff --- /dev/null +++ b/o2dms/adapter/dms_repository.py @@ -0,0 +1,37 @@ +# 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 typing import List +from o2dms.domain import dms, dms_repo + + +class NfDeploymentDescSqlAlchemyRepository(dms_repo + .NfDeploymentDescRepository): + + def __init__(self, session): + super().__init__() + self.session = session + + def _add(self, nfdeployment_desc: dms.NfDeploymentDesc): + self.session.add(nfdeployment_desc) + + def _get(self, nfdeployment_desc_id) -> dms.NfDeploymentDesc: + return self.session.query(dms.NfDeploymentDesc).filter_by( + nfDeploymentDescId=nfdeployment_desc_id).first() + + def _list(self) -> List[dms.NfDeploymentDesc]: + return self.session.query() + + def _update(self, nfdeployment_desc: dms.NfDeploymentDesc): + self.session.add(nfdeployment_desc) diff --git a/o2dms/adapter/orm.py b/o2dms/adapter/orm.py new file mode 100644 index 0000000..7527515 --- /dev/null +++ b/o2dms/adapter/orm.py @@ -0,0 +1,61 @@ +# 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 sqlalchemy import ( + Table, + MetaData, + Column, + Integer, + String, + # Date, + DateTime, + # ForeignKey, + # engine, + # event, +) + +from sqlalchemy.orm import mapper +from o2dms.domain import dms as dmsModel + +from o2common.helper import o2logging +logger = o2logging.get_logger(__name__) + +metadata = MetaData() + +nfDeploymentDesc = Table( + "nfDeploymentDesc", + metadata, + Column("updatetime", DateTime), + Column("createtime", DateTime), + Column("hash", String(255)), + Column("version_number", Integer), + + Column("id", String(255), primary_key=True), + Column("deploymentManagerId", String(255)), + Column("name", String(255)), + Column("description", String(255)), + Column("supportedLocations", String(255)), + Column("capabilities", String(255)), + Column("capacity", String(255)), + # Column("extensions", String(1024)) +) + + +def start_o2dms_mappers(engine=None): + logger.info("Starting O2 DMS mappers") + + mapper(dmsModel.NfDeploymentDesc, nfDeploymentDesc) + + if engine is not None: + metadata.create_all(engine) diff --git a/o2dms/domain/dms.py b/o2dms/domain/dms.py new file mode 100644 index 0000000..d80b4a6 --- /dev/null +++ b/o2dms/domain/dms.py @@ -0,0 +1,31 @@ +# 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 o2ims.domain.base import AgRoot + + +class NfDeploymentDesc(AgRoot): + def __init__(self, id: str, name: str, dmsId: str, description: str = '', + inputParams: str = '', outputParams: str = '',) -> None: + super().__init__() + self.id = id + self.version_number = 0 + self.dmsId = dmsId + self.name = name + self.description = description + self.inputParams = inputParams + self.outputParams = outputParams + self.extensions = [] diff --git a/o2dms/domain/dms_repo.py b/o2dms/domain/dms_repo.py new file mode 100644 index 0000000..f199a3c --- /dev/null +++ b/o2dms/domain/dms_repo.py @@ -0,0 +1,50 @@ +# 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 List, Set +from o2dms.domain import dms + + +class NfDeploymentDescRepository(abc.ABC): + def __init__(self): + self.seen = set() # type: Set[dms.NfDeploymentDesc] + + def add(self, nfdeployment_descriptor: dms.NfDeploymentDesc): + self._add(nfdeployment_descriptor) + self.seen.add(nfdeployment_descriptor) + + def get(self, nfdeployment_descriptor_id) -> dms.NfDeploymentDesc: + nfdeployment_descriptor = self._get(nfdeployment_descriptor_id) + if nfdeployment_descriptor: + self.seen.add(nfdeployment_descriptor) + return nfdeployment_descriptor + + def list(self) -> List[dms.NfDeploymentDesc]: + return self._list() + + def update(self, nfdeployment_descriptor: dms.NfDeploymentDesc): + self._update(nfdeployment_descriptor) + + @abc.abstractmethod + def _add(self, nfdeployment_descriptor: dms.NfDeploymentDesc): + raise NotImplementedError + + @abc.abstractmethod + def _get(self, nfdeployment_descriptor_id) -> dms.NfDeploymentDesc: + raise NotImplementedError + + @abc.abstractmethod + def _update(self, nfdeployment_descriptor: dms.NfDeploymentDesc): + raise NotImplementedError diff --git a/o2dms/service/__init__.py b/o2dms/service/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2dms/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/o2dms/views/__init__.py b/o2dms/views/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2dms/views/__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/o2dms/views/dms_dto.py b/o2dms/views/dms_dto.py new file mode 100644 index 0000000..d353137 --- /dev/null +++ b/o2dms/views/dms_dto.py @@ -0,0 +1,54 @@ +# 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 flask_restx import Namespace, fields + + +class DmsDTO: + + api = Namespace("O2DMS", + description='DMS related operations.') + + dms_get = api.model( + "Get DMS information", + { + 'deploymentManagerId': fields.String( + required=True, + description='Deployment manager ID'), + 'name': fields.String, + 'description': fields.String, + 'supportedLocations': fields.String, + 'capabilities': fields.String, + 'capacity': fields.String, + } + ) + + +class DmsLcmNfDeploymentDescriptorDTO: + + api = Namespace("O2DMS_LCM_NfDeploymentDescriptor", + description='DMS LCM NfDeploymentDescritpor operations.') + + dmslcm_NfDeploymentDescriptor_get = api.model( + "Get NfDeploymentDescriptor information", + { + 'id': fields.String( + required=True, + description='NfDeploymentDescriptor ID'), + 'name': fields.String, + 'description': fields.String, + 'inputParams': fields.String, + 'outputParams': fields.String + } + ) diff --git a/o2dms/views/dms_lcm_view.py b/o2dms/views/dms_lcm_view.py new file mode 100644 index 0000000..32f77cc --- /dev/null +++ b/o2dms/views/dms_lcm_view.py @@ -0,0 +1,71 @@ +# 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 sqlalchemy import select + +from o2common.service import unit_of_work +from o2ims.adapter.orm import deploymentmanager +from o2dms.adapter.orm import nfDeploymentDesc + + +def deployment_managers(uow: unit_of_work.AbstractUnitOfWork): + with uow: + res = uow.session.execute(select(deploymentmanager)) + return [dict(r) for r in res] + + +def deployment_manager_one(deploymentManagerId: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + res = uow.session.execute(select(deploymentmanager).where( + deploymentmanager.c.deploymentManagerId == deploymentManagerId)) + first = res.first() + return None if first is None else dict(first) + + +def lcm_nfdeploymentdesc_list(deploymentManagerID: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + res = uow.session.execute(select(nfDeploymentDesc).where( + nfDeploymentDesc.c.deploymentManagerId == deploymentManagerID)) + return [dict(r) for r in res] + + +def lcm_nfdeploymentdesc_one(nfdeploymentdescriptorid: str, + deploymentManagerID: str, + uow: unit_of_work.AbstractUnitOfWork): + with uow: + res = uow.session.execute(select(deploymentmanager).where( + nfDeploymentDesc.c.deploymentManagerId == deploymentManagerID, + nfDeploymentDesc.c.id == nfdeploymentdescriptorid)) + first = res.first() + return None if first is None else dict(first) + + +# def lcm_nfdeploymentdesc_create(nfdeploymentdescriptorid: str, +# uow: unit_of_work.AbstractUnitOfWork): +# with uow: +# res = uow.session.execute(select(deploymentmanager).where( +# deploymentmanager.c.id == nfdeploymentdescriptorid)) +# first = res.first() +# return None if first is None else dict(first) + + +# def lcm_nfdeploymentdesc_delete(nfdeploymentdescriptorid: str, +# uow: unit_of_work.AbstractUnitOfWork): +# with uow: +# res = uow.session.execute(select(deploymentmanager).where( +# deploymentmanager.c.id == nfdeploymentdescriptorid)) +# first = res.first() +# return None if first is None else dict(first) diff --git a/o2dms/views/dms_route.py b/o2dms/views/dms_route.py new file mode 100644 index 0000000..895bfcb --- /dev/null +++ b/o2dms/views/dms_route.py @@ -0,0 +1,96 @@ +# 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 flask import jsonify +from flask_restx import Resource + +from o2common.config import config +from o2dms.views.dms_dto import DmsDTO, DmsLcmNfDeploymentDescriptorDTO +from o2dms.views import dms_lcm_view + +apibase = config.get_o2dms_api_base() + + +# ---------- DeploymentManagers ---------- # +api_dms = DmsDTO.api + + +@api_dms.route("/") +@api_dms.param('deploymentManagerID', 'ID of the deployment manager') +@api_dms.response(404, 'Deployment manager not found') +class DmsGetRouter(Resource): + + model = DmsDTO.dms_get + + @api_dms.doc('Get deployment manager') + @api_dms.marshal_with(model) + def get(self, deploymentManagerID): + result = dms_lcm_view.deployment_manager_one( + deploymentManagerID, uow) + if result is not None: + return result + api_dms.abort(404, "Deployment manager {} doesn't exist".format( + deploymentManagerID)) + + +# LCM services # +api_lcm_nfdeploymentDesc = DmsLcmNfDeploymentDescriptorDTO.api + + +@api_lcm_nfdeploymentDesc\ + .route("//O2dms_DeploymentLifecycle") +@api_lcm_nfdeploymentDesc\ + .param('deploymentManagerID', 'ID of the deployment manager') +@api_lcm_nfdeploymentDesc.response(404, 'DMS LCM not found') +class DmsLcmNfDeploymentDescListRouter(Resource): + + model = DmsLcmNfDeploymentDescriptorDTO.dmslcm_NfDeploymentDescriptor_get + + @api_lcm_nfdeploymentDesc.doc('Get a list of NfDeploymentDescriptor') + @api_lcm_nfdeploymentDesc.marshal_list_with(model) + def get(self, deploymentManagerID): + return dms_lcm_view.lcm_nfdeploymentdesc_list(deploymentManagerID, uow) + + +@api_lcm_nfdeploymentDesc\ + .route("//O2dms_DeploymentLifecycle/" + "") +@api_lcm_nfdeploymentDesc\ + .param('deploymentManagerID', 'ID of the deployment manager') +@api_lcm_nfdeploymentDesc.param('nfDeploymentDescriptorId', + 'ID of the NfDeploymentDescriptor') +@api_lcm_nfdeploymentDesc.response(404, 'DMS LCM not found') +class DmsLcmNfDeploymentDescGetRouter(Resource): + + model = DmsLcmNfDeploymentDescriptorDTO.dmslcm_NfDeploymentDescriptor_get + + @api_lcm_nfdeploymentDesc.doc('Get a NfDeploymentDescriptor') + @api_lcm_nfdeploymentDesc.marshal_with(model) + def get(self, nfDeploymentDescriptorId, deploymentManagerID): + result = dms_lcm_view\ + .lcm_nfdeploymentdesc_one(nfDeploymentDescriptorId, + deploymentManagerID, uow) + if result is not None: + return result + api_dms.abort(404, "NfDeploymentDescriptor {} doesn't exist".format( + nfDeploymentDescriptorId)) + + +def configure_namespace(app, bus): + app.add_namespace(api_dms, path=apibase) + app.add_namespace(api_lcm_nfdeploymentDesc, path=apibase) + + # Set global uow + global uow + uow = bus.uow diff --git a/o2ims/adapter/__init__.py b/o2ims/adapter/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2ims/adapter/__init__.py @@ -0,0 +1,13 @@ +# Copyright (C) 2021 Wind River Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/o2ims/adapter/orm.py b/o2ims/adapter/orm.py index 61c3157..7aafde5 100644 --- a/o2ims/adapter/orm.py +++ b/o2ims/adapter/orm.py @@ -111,6 +111,7 @@ deploymentmanager = Table( Column("updatetime", DateTime), Column("createtime", DateTime), Column("hash", String(255)), + Column("version_number", Integer), Column("deploymentManagerId", String(255), primary_key=True), Column("oCloudId", ForeignKey("ocloud.oCloudId")), diff --git a/o2ims/config.py b/o2ims/config.py index 7283383..55d4755 100644 --- a/o2ims/config.py +++ b/o2ims/config.py @@ -33,12 +33,16 @@ def get_api_url(): return f"http://{host}:{port}" +def get_root_api_base(): + return "/" + + def get_o2ims_api_base(): - return '/o2ims_infrastructureInventory/v1' + return get_root_api_base() + 'o2ims_infrastructureInventory/v1' -def get_o2dms_api_base(dmsid: str): - return "/" + dmsid + '/o2dms/v1' +def get_o2dms_api_base(): + return get_root_api_base() + "o2dms/v1" def get_redis_host_and_port(): diff --git a/o2ims/entrypoints/flask_application.py b/o2ims/entrypoints/flask_application.py index fadb34a..431b2de 100644 --- a/o2ims/entrypoints/flask_application.py +++ b/o2ims/entrypoints/flask_application.py @@ -18,6 +18,7 @@ from flask_restx import Api from o2ims import bootstrap # from o2ims import config from o2ims.views.ocloud_route import configure_namespace +from o2dms.views import dms_route # apibase = config.get_o2ims_api_base() @@ -29,3 +30,5 @@ api = Api(app, version='1.0.0', ) bus = bootstrap.bootstrap() configure_namespace(api, bus) + +dms_route.configure_namespace(api, bus) diff --git a/o2ims/service/auditor/dms_handler.py b/o2ims/service/auditor/dms_handler.py index b7d8e83..207f6e2 100644 --- a/o2ims/service/auditor/dms_handler.py +++ b/o2ims/service/auditor/dms_handler.py @@ -76,7 +76,8 @@ def is_outdated(ocloud: DeploymentManager, stxobj: StxGenericModel): def create_by(stxobj: StxGenericModel, parentid: str) -> DeploymentManager: - dmsendpoint = config.get_api_url() + config.get_o2dms_api_base(stxobj.id) + dmsendpoint = config.get_api_url() +\ + config.get_o2dms_api_base() + "/" + stxobj.id description = "A DMS" ocloudid = parentid supportedLocations = '' diff --git a/o2ims/views/__init__.py b/o2ims/views/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2ims/views/__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/o2app-api-entry.sh b/tests/o2app-api-entry.sh new file mode 100644 index 0000000..ecdd768 --- /dev/null +++ b/tests/o2app-api-entry.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# pip install -e /src +# python /o2ims/entrypoints/resource_watcher.py + +mkdir -p /etc/o2 +cp -r /configs/* /etc/o2/ +mkdir -p /src/o2common +cp -r /o2common/* /src/o2common +mkdir -p /src/o2ims +cp -r /o2ims/* /src/o2ims +mkdir -p /src/o2dms +cp -r /o2dms/* /src/o2dms +mkdir -p /src/o2app +cp -r /o2app/* /src/o2app +pip install -e /src + +export FLASK_APP=/o2app/entrypoints/flask_application.py +flask run --host=0.0.0.0 --port=80 diff --git a/tests/o2ims-watcher-entry.sh b/tests/o2app-redis-entry.sh similarity index 54% rename from tests/o2ims-watcher-entry.sh rename to tests/o2app-redis-entry.sh index 1ab193e..e47ce96 100644 --- a/tests/o2ims-watcher-entry.sh +++ b/tests/o2app-redis-entry.sh @@ -3,9 +3,15 @@ # pip install -e /src # python /o2ims/entrypoints/resource_watcher.py +mkdir -p /etc/o2 cp -r /configs/* /etc/o2/ +mkdir -p /src/o2common cp -r /o2common/* /src/o2common +mkdir -p /src/o2ims cp -r /o2ims/* /src/o2ims +mkdir -p /src/o2dms cp -r /o2dms/* /src/o2dms +mkdir -p /src/o2app +cp -r /o2app/* /src/o2app pip install -e /src -python /o2ims/entrypoints/resource_watcher.py +python /o2app/entrypoints/redis_eventconsumer.py diff --git a/tests/o2ims-redis-entry.sh b/tests/o2app-watcher-entry.sh similarity index 55% rename from tests/o2ims-redis-entry.sh rename to tests/o2app-watcher-entry.sh index be148a2..f112f93 100644 --- a/tests/o2ims-redis-entry.sh +++ b/tests/o2app-watcher-entry.sh @@ -3,9 +3,15 @@ # pip install -e /src # python /o2ims/entrypoints/resource_watcher.py +mkdir -p /etc/o2 cp -r /configs/* /etc/o2/ +mkdir -p /src/o2common cp -r /o2common/* /src/o2common +mkdir -p /src/o2ims cp -r /o2ims/* /src/o2ims +mkdir -p /src/o2dms cp -r /o2dms/* /src/o2dms +mkdir -p /src/o2app +cp -r /o2app/* /src/o2app pip install -e /src -python /o2ims/entrypoints/redis_eventconsumer.py +python /o2app/entrypoints/resource_watcher.py -- 2.16.6