From 81e3575a77366f30c2049f98c48a3087db0ea992 Mon Sep 17 00:00:00 2001 From: Bin Yang Date: Tue, 26 Oct 2021 18:28:10 +0800 Subject: [PATCH] Add tox Issue-ID: INF-196 Signed-off-by: Bin Yang Change-Id: I668e72886de29f894ababa99f3ac0a84f552f87c --- .gitignore | 2 + Dockerfile | 11 +- LICENSE | 201 +++++++++++++++++++++ README.md | 25 +-- constraints.txt | 4 + docker-compose.yml | 15 +- {src => o2common}/__init__.py | 0 {src/o2common => o2dms}/__init__.py | 0 {src/o2dms => o2ims}/__init__.py | 0 {src/o2ims => o2ims/adapter/clients}/__init__.py | 0 o2ims/adapter/clients/ocloud_sa_client.py | 94 ++++++++++ o2ims/adapter/clients/orm_stx.py | 51 ++++++ {src/o2ims => o2ims}/adapter/notifications.py | 6 +- {src/o2ims => o2ims}/adapter/ocloud_repository.py | 14 +- {src/o2ims => o2ims}/adapter/orm.py | 12 +- .../adapter/redis_eventpublisher.py | 0 {src/o2ims => o2ims}/bootstrap.py | 6 +- {src/o2ims => o2ims}/config.py | 14 +- {src/o2ims => o2ims}/domain/__init__.py | 0 {src/o2ims => o2ims}/domain/commands.py | 8 +- {src/o2ims => o2ims}/domain/events.py | 2 + {src/o2ims => o2ims}/domain/ocloud.py | 71 ++++---- {src/o2ims => o2ims}/domain/resource_type.py | 1 + o2ims/domain/stx_object.py | 28 +++ {src/o2ims => o2ims}/entrypoints/__init__.py | 0 .../entrypoints/flask_application.py | 10 +- .../entrypoints/redis_eventconsumer.py | 0 {src/o2ims => o2ims}/service/__init__.py | 0 o2ims/service/client/__init__.py | 13 ++ o2ims/service/client/base_client.py | 37 ++++ {src/o2ims => o2ims}/service/handlers.py | 14 +- {src/o2ims => o2ims}/service/messagebus.py | 3 +- {src/o2ims => o2ims}/service/unit_of_work.py | 6 +- {src/o2ims => o2ims}/views/ocloud_view.py | 0 requirements-test.txt | 5 + requirements.txt | 5 + setup.py | 17 ++ src/o2dms/setup.py | 19 -- src/setup.py | 7 - tests/e2e/__init__.py | 13 ++ tox.ini | 35 ++++ 41 files changed, 640 insertions(+), 109 deletions(-) create mode 100644 LICENSE create mode 100644 constraints.txt rename {src => o2common}/__init__.py (100%) rename {src/o2common => o2dms}/__init__.py (100%) rename {src/o2dms => o2ims}/__init__.py (100%) rename {src/o2ims => o2ims/adapter/clients}/__init__.py (100%) create mode 100644 o2ims/adapter/clients/ocloud_sa_client.py create mode 100644 o2ims/adapter/clients/orm_stx.py rename {src/o2ims => o2ims}/adapter/notifications.py (96%) rename {src/o2ims => o2ims}/adapter/ocloud_repository.py (88%) rename {src/o2ims => o2ims}/adapter/orm.py (91%) rename {src/o2ims => o2ims}/adapter/redis_eventpublisher.py (100%) rename {src/o2ims => o2ims}/bootstrap.py (91%) rename {src/o2ims => o2ims}/config.py (77%) rename {src/o2ims => o2ims}/domain/__init__.py (100%) rename {src/o2ims => o2ims}/domain/commands.py (89%) rename {src/o2ims => o2ims}/domain/events.py (99%) rename {src/o2ims => o2ims}/domain/ocloud.py (68%) rename {src/o2ims => o2ims}/domain/resource_type.py (93%) create mode 100644 o2ims/domain/stx_object.py rename {src/o2ims => o2ims}/entrypoints/__init__.py (100%) rename {src/o2ims => o2ims}/entrypoints/flask_application.py (81%) rename {src/o2ims => o2ims}/entrypoints/redis_eventconsumer.py (100%) rename {src/o2ims => o2ims}/service/__init__.py (100%) create mode 100644 o2ims/service/client/__init__.py create mode 100644 o2ims/service/client/base_client.py rename {src/o2ims => o2ims}/service/handlers.py (81%) rename {src/o2ims => o2ims}/service/messagebus.py (98%) rename {src/o2ims => o2ims}/service/unit_of_work.py (94%) rename {src/o2ims => o2ims}/views/ocloud_view.py (100%) create mode 100644 setup.py delete mode 100644 src/o2dms/setup.py delete mode 100644 src/setup.py create mode 100644 tests/e2e/__init__.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index ea29b71..addeb00 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .mypy_cache __pycache__ *.egg-info +*.pyc +.tox \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e1afbcb..593e21f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,20 @@ FROM python:3.10-slim-buster +RUN apt-get update; apt-get install -y git gcc COPY requirements.txt /tmp/ -RUN pip install -r /tmp/requirements.txt +COPY constraints.txt /tmp/ + +RUN pip install -r /tmp/requirements.txt -c /tmp/constraints.txt COPY requirements-test.txt /tmp/ RUN pip install -r /tmp/requirements-test.txt RUN mkdir -p /src -COPY src/ /src/ +COPY o2ims/ /src/o2ims/ +COPY o2dms/ /src/o2dms/ +COPY o2common/ /src/o2common/ +COPY setup.py /src/ + RUN pip install -e /src COPY tests/ /tests/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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/README.md b/README.md index eb6900a..e4c75d9 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,11 @@ docker-compose build ``` - -## Creating a local virtualenv (optional) - -```sh -python3.8 -m venv .venv && source .venv/bin/activate -pip install -r requirements.txt -pip install -e src/ -``` - ## Running the tests ```sh docker-compose up -d docker-compose run --rm --no-deps --entrypoint=pytest api /tests/unit /tests/integration -pytest tests/unit -pytest tests/integration -pytest tests/e2e ``` ## Tear down containers @@ -29,3 +17,16 @@ pytest tests/e2e ```sh docker-compose down --remove-orphans ``` + +## Test with local virtualenv + +```sh +python3.8 -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt -c constraints.txt +pip install -r requirements-test.txt +pip install -e o2ims +# pip install -e o2dms -e o2common +pytest tests/unit +pytest tests/integration +pytest tests/e2e +``` diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..a1cc510 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,4 @@ +# -e git+https://opendev.org/starlingx/distcloud-client.git@master#egg=distributedcloud-client&subdirectory=distributedcloud-client +# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client +cryptography==3.3.2 +python-keystoneclient==3.21.0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 91d3ec8..255fbd9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,13 @@ services: - REDIS_HOST=redis - PYTHONDONTWRITEBYTECODE=1 volumes: - - ./src:/src + - ./o2ims:/o2ims + - ./o2dms:/o2dms + - ./o2common:/o2common - ./tests:/tests entrypoint: - python - - /src/o2ims/entrypoints/redis_eventconsumer.py + - /o2ims/entrypoints/redis_eventconsumer.py api: image: o2imsdms-image @@ -32,11 +34,16 @@ services: - API_HOST=api - REDIS_HOST=redis - PYTHONDONTWRITEBYTECODE=1 - - FLASK_APP=o2ims/entrypoints/flask_application.py + - FLASK_APP=/o2ims/entrypoints/flask_application.py - FLASK_DEBUG=1 - PYTHONUNBUFFERED=1 + - STX_AUTH_URL=http://192.168.204.1:5000/v3 + - STX_USERNAME=admin + - STX_PASSWORD=password1 volumes: - - ./src:/src + - ./o2ims:/o2ims + - ./o2dms:/o2dms + - ./o2common:/o2common - ./tests:/tests entrypoint: - flask diff --git a/src/__init__.py b/o2common/__init__.py similarity index 100% rename from src/__init__.py rename to o2common/__init__.py diff --git a/src/o2common/__init__.py b/o2dms/__init__.py similarity index 100% rename from src/o2common/__init__.py rename to o2dms/__init__.py diff --git a/src/o2dms/__init__.py b/o2ims/__init__.py similarity index 100% rename from src/o2dms/__init__.py rename to o2ims/__init__.py diff --git a/src/o2ims/__init__.py b/o2ims/adapter/clients/__init__.py similarity index 100% rename from src/o2ims/__init__.py rename to o2ims/adapter/clients/__init__.py diff --git a/o2ims/adapter/clients/ocloud_sa_client.py b/o2ims/adapter/clients/ocloud_sa_client.py new file mode 100644 index 0000000..b8607ef --- /dev/null +++ b/o2ims/adapter/clients/ocloud_sa_client.py @@ -0,0 +1,94 @@ +# 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. + +# client talking to Stx standalone + +from service.client.base_client import BaseClient +from typing import List +# Optional, Set +from o2ims.domain import stx_object as ocloudModel +from o2ims import config + + +class StxSaOcloudClient(BaseClient): + def __init__(self): + super().__init__() + self.driver = StxSaClientImp() + + # def list(self) -> List[ocloudModel.StxGenericModel]: + # return self._list() + + # def get(self, id) -> ocloudModel.StxGenericModel: + # return self._get(id) + + def _get(self, id) -> ocloudModel.StxGenericModel: + raise self.driver.getInstanceInfo() + + def _list(self): + return [self.driver.getInstanceInfo()] + + +class StxSaResourcePoolClient(BaseClient): + def __init__(self): + super().__init__() + self.driver = StxSaClientImp() + + def _get(self, id) -> ocloudModel.StxGenericModel: + return self.driver.getInstanceInfo() + + def _list(self): + return [self.driver.getInstanceInfo()] + + +class StxSaDmsClient(BaseClient): + def __init__(self): + super().__init__() + self.driver = StxSaClientImp() + + def _get(self, id) -> ocloudModel.StxGenericModel: + return self.driver.getK8sDetail(id) + + def _list(self): + return self.driver.getK8sList() + +# internal driver which implement client call to Stx Standalone instance + +# from keystoneauth1.identity import v3 +# from keystoneauth1 import session +# # from keystoneclient.v3 import ksclient +# from starlingxclient.v3 import stxclient + + +class StxSaClientImp(object): + def __init__(self, access_info=None) -> None: + super().__init__() + self.access_info = access_info + if self.access_info is None: + self.access_info = config.get_stx_access_info() + # self.auth = auth = v3.Password( + # auth_url="http://example.com:5000/v3", username="admin", + # password="password", project_name="admin", + # user_domain_id="default", project_domain_id="default") + # self.session = sess = session.Session(auth=auth) + # # self.keystone = ksclient.Client(session=sess) + # self.stx = stxclient.Client(session=sess) + + def getInstanceInfo(self) -> ocloudModel.StxGenericModel: + raise NotImplementedError + + def getK8sList(self) -> List[ocloudModel.StxGenericModel]: + raise NotImplementedError + + def getK8sDetail(self, id) -> ocloudModel.StxGenericModel: + raise NotImplementedError diff --git a/o2ims/adapter/clients/orm_stx.py b/o2ims/adapter/clients/orm_stx.py new file mode 100644 index 0000000..02f9bec --- /dev/null +++ b/o2ims/adapter/clients/orm_stx.py @@ -0,0 +1,51 @@ +# 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 +import logging + +from sqlalchemy import ( + Table, + MetaData, + Column, + # Integer, + String, + # Date, + DateTime, + # ForeignKey, + # event, +) + +from sqlalchemy.orm import mapper +# from sqlalchemy.sql.expression import true + +from o2ims.domain import stx_object as ocloudModel + +logger = logging.getLogger(__name__) + +metadata = MetaData() + +stxobject = Table( + "stxcache", + metadata, + Column("id", String(255), primary_key=True), + Column("name", String(255)), + Column("lastupdate", DateTime), + Column("content", String(255)) +) + + +def start_o2ims_stx_mappers(): + logger.info("Starting O2 IMS Stx mappers") + mapper(ocloudModel.StxGenericModel, stxobject) diff --git a/src/o2ims/adapter/notifications.py b/o2ims/adapter/notifications.py similarity index 96% rename from src/o2ims/adapter/notifications.py rename to o2ims/adapter/notifications.py index e2ad375..4c49c1b 100644 --- a/src/o2ims/adapter/notifications.py +++ b/o2ims/adapter/notifications.py @@ -1,17 +1,17 @@ # pylint: disable=too-few-public-methods import abc -import smtplib from o2ims import config +SMO_O2_ENDPOINT = config.get_smo_o2endpoint() + + 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 diff --git a/src/o2ims/adapter/ocloud_repository.py b/o2ims/adapter/ocloud_repository.py similarity index 88% rename from src/o2ims/adapter/ocloud_repository.py rename to o2ims/adapter/ocloud_repository.py index 6d3d221..8a547fe 100644 --- a/src/o2ims/adapter/ocloud_repository.py +++ b/o2ims/adapter/ocloud_repository.py @@ -14,9 +14,10 @@ import abc from typing import Set -from o2ims.adapter import orm +# from o2ims.adapter import orm from o2ims.domain import ocloud + class OcloudRepository(abc.ABC): def __init__(self): self.seen = set() # type: Set[ocloud.Ocloud] @@ -30,7 +31,7 @@ class OcloudRepository(abc.ABC): if ocloud: self.seen.add(ocloud) return ocloud - + def update(self, ocloud: ocloud.Ocloud): self._update(ocloud) @@ -60,7 +61,8 @@ class OcloudSqlAlchemyRepository(OcloudRepository): # self.session.add_all(ocloud.deploymentManagers) def _get(self, ocloudid) -> ocloud.Ocloud: - return self.session.query(ocloud.Ocloud).filter_by(oCloudId=ocloudid).first() + return self.session.query(ocloud.Ocloud).filter_by( + oCloudId=ocloudid).first() def _update(self, ocloud: ocloud.Ocloud): self.session.add(ocloud) @@ -70,8 +72,10 @@ class OcloudSqlAlchemyRepository(OcloudRepository): # if dmslist: # self._update_dms_list(dmslist) # if updatefields: - # self.session.query(ocloud.Ocloud).filter_by(oCloudId=ocloudid).update(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) + # self.session.query(ocloud.DeploymentManager).filter_by( + # deploymentManagerId=dms.deploymentManagerId).update(dms) diff --git a/src/o2ims/adapter/orm.py b/o2ims/adapter/orm.py similarity index 91% rename from src/o2ims/adapter/orm.py rename to o2ims/adapter/orm.py index 6cf9847..2028538 100644 --- a/src/o2ims/adapter/orm.py +++ b/o2ims/adapter/orm.py @@ -18,15 +18,15 @@ from sqlalchemy import ( Table, MetaData, Column, - Integer, + # Integer, String, - Date, + # Date, ForeignKey, - event, + # event, ) from sqlalchemy.orm import mapper, relationship -from sqlalchemy.sql.expression import true +# from sqlalchemy.sql.expression import true from o2ims.domain import ocloud as ocloudModel @@ -86,8 +86,8 @@ def start_o2ims_mappers(): dm_mapper = mapper(ocloudModel.DeploymentManager, deploymentmanager) resourcepool_mapper = mapper(ocloudModel.ResourcePool, resourcepool) resourcetype_mapper = mapper(ocloudModel.ResourceType, resourcetype) - resource_mapper = mapper(ocloudModel.Resource, resource) - ocloud_mapper = mapper( + # resource_mapper = mapper(ocloudModel.Resource, resource) + mapper( ocloudModel.Ocloud, ocloud, properties={ diff --git a/src/o2ims/adapter/redis_eventpublisher.py b/o2ims/adapter/redis_eventpublisher.py similarity index 100% rename from src/o2ims/adapter/redis_eventpublisher.py rename to o2ims/adapter/redis_eventpublisher.py diff --git a/src/o2ims/bootstrap.py b/o2ims/bootstrap.py similarity index 91% rename from src/o2ims/bootstrap.py rename to o2ims/bootstrap.py index 00cdc4a..524f325 100644 --- a/src/o2ims/bootstrap.py +++ b/o2ims/bootstrap.py @@ -15,7 +15,8 @@ import inspect from typing import Callable from o2ims.adapter import orm, redis_eventpublisher -from o2ims.adapter.notifications import AbstractNotifications, SmoO2Notifications +from o2ims.adapter.notifications import AbstractNotifications,\ + SmoO2Notifications from o2ims.service import handlers, messagebus, unit_of_work @@ -33,7 +34,8 @@ def bootstrap( if start_orm: orm.start_o2ims_mappers() - dependencies = {"uow": uow, "notifications": notifications, "publish": publish} + dependencies = {"uow": uow, "notifications": notifications, + "publish": publish} injected_event_handlers = { event_type: [ inject_dependencies(handler, dependencies) diff --git a/src/o2ims/config.py b/o2ims/config.py similarity index 77% rename from src/o2ims/config.py rename to o2ims/config.py index 798ea79..f42ec72 100644 --- a/src/o2ims/config.py +++ b/o2ims/config.py @@ -14,6 +14,7 @@ import os + def get_postgres_uri(): host = os.environ.get("DB_HOST", "localhost") port = 54321 if host == "localhost" else 5432 @@ -27,9 +28,11 @@ def get_api_url(): 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 @@ -37,5 +40,14 @@ def get_redis_host_and_port(): def get_smo_o2endpoint(): - smo_o2endpoint = os.environ.get("SMO_O2_ENDPOINT", "http://localhost/smo_sim") + 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) + return stx_access_info diff --git a/src/o2ims/domain/__init__.py b/o2ims/domain/__init__.py similarity index 100% rename from src/o2ims/domain/__init__.py rename to o2ims/domain/__init__.py diff --git a/src/o2ims/domain/commands.py b/o2ims/domain/commands.py similarity index 89% rename from src/o2ims/domain/commands.py rename to o2ims/domain/commands.py index 9df6cff..869bf52 100644 --- a/src/o2ims/domain/commands.py +++ b/o2ims/domain/commands.py @@ -13,13 +13,15 @@ # limitations under the License. # pylint: disable=too-few-public-methods -from datetime import date -from typing import Optional +# from datetime import date +# from typing import Optional from dataclasses import dataclass class Command: pass + +@dataclass class UpdateDms(Command): - ref: str \ No newline at end of file + ref: str diff --git a/src/o2ims/domain/events.py b/o2ims/domain/events.py similarity index 99% rename from src/o2ims/domain/events.py rename to o2ims/domain/events.py index 755f65e..591662f 100644 --- a/src/o2ims/domain/events.py +++ b/o2ims/domain/events.py @@ -15,9 +15,11 @@ # pylint: disable=too-few-public-methods from dataclasses import dataclass + class Event: pass + @dataclass class OcloudUpdated(Event): oCloudId: str diff --git a/src/o2ims/domain/ocloud.py b/o2ims/domain/ocloud.py similarity index 68% rename from src/o2ims/domain/ocloud.py rename to o2ims/domain/ocloud.py index ddd2646..68e304e 100644 --- a/src/o2ims/domain/ocloud.py +++ b/o2ims/domain/ocloud.py @@ -13,39 +13,16 @@ # limitations under the License. from __future__ import annotations -from dataclasses import dataclass -from datetime import date -from typing import Optional, List, Set +# from dataclasses import dataclass +# from datetime import date +# from typing import Optional, List, Set from .resource_type import ResourceTypeEnum # from uuid import UUID -class Ocloud: - def __init__( - self, ocloudid: str, name: str, imsendpoint: str, - description: str = '', version_number: int = 0) -> None: - - self.oCloudId = ocloudid - self.version_number = version_number - self.name = name - self.description = description - self.infrastructureManagementServiceEndpoint = imsendpoint - self.resourcePools = [] - self.deploymentManagers = [] - self.resourceTypes = [] - self.extensions = [] - self.events = [] - - def addDeploymentManager(self, deploymentManager: DeploymentManager) -> None: - deploymentManager.oCloudId = self.oCloudId - old = filter( - lambda x: x.deploymentManagerId == deploymentManager.deploymentManagerId, - self.deploymentManagers) - for o in old or []: - self.deploymentManagers.remove(o) - self.deploymentManagers.append(deploymentManager) class DeploymentManager: - def __init__(self, id: str, name: str, ocloudid: str, dmsendpoint: str) -> None: + def __init__(self, id: str, name: str, ocloudid: str, + dmsendpoint: str) -> None: self.deploymentManagerId = id self.name = name self.oCloudId = ocloudid @@ -54,7 +31,8 @@ class DeploymentManager: class ResourcePool: - def __init__(self, id: str, name: str, location: str, ocloudid: str) -> None: + def __init__(self, id: str, name: str, location: str, + ocloudid: str) -> None: self.resourcePoolId = id self.name = name self.location = location @@ -63,7 +41,8 @@ class ResourcePool: class ResourceType: - def __init__(self, typeid: str, name:str, typeEnum: ResourceTypeEnum, ocloudid: str) -> None: + def __init__(self, typeid: str, name: str, typeEnum: ResourceTypeEnum, + ocloudid: str) -> None: self.resourceTypeId = typeid self.resourceTypeEnum = typeEnum.value self.name = name @@ -72,12 +51,40 @@ class ResourceType: class Resource: - def __init__(self, resourceId:str, resourceTypeId: str, resourcePoolId: str) -> None: + def __init__(self, resourceId: str, resourceTypeId: str, + resourcePoolId: str) -> None: self.resourceId = resourceId - self.oCloudId = None # tbd + self.oCloudId = None # tbd self.resourceTypeId = resourceTypeId self.resourcePoolId = resourcePoolId self.parentId = None self.elements = [] self.extensions = [] + +class Ocloud: + def __init__(self, ocloudid: str, name: str, imsendpoint: str, + description: str = '', version_number: int = 0) -> None: + + self.oCloudId = ocloudid + self.version_number = version_number + self.name = name + self.description = description + self.infrastructureManagementServiceEndpoint = imsendpoint + self.resourcePools = [] + self.deploymentManagers = [] + self.resourceTypes = [] + self.extensions = [] + self.events = [] + + def addDeploymentManager(self, + deploymentManager: DeploymentManager): + + deploymentManager.oCloudId = self.oCloudId + old = filter( + lambda x: x.deploymentManagerId == + deploymentManager.deploymentManagerId, + self.deploymentManagers) + for o in old or []: + self.deploymentManagers.remove(o) + self.deploymentManagers.append(deploymentManager) diff --git a/src/o2ims/domain/resource_type.py b/o2ims/domain/resource_type.py similarity index 93% rename from src/o2ims/domain/resource_type.py rename to o2ims/domain/resource_type.py index faff8ea..72f0db0 100644 --- a/src/o2ims/domain/resource_type.py +++ b/o2ims/domain/resource_type.py @@ -1,5 +1,6 @@ from enum import Enum + class ResourceTypeEnum(Enum): PSERVER = 1 PSERVER_CPU = 2 diff --git a/o2ims/domain/stx_object.py b/o2ims/domain/stx_object.py new file mode 100644 index 0000000..a3adaf2 --- /dev/null +++ b/o2ims/domain/stx_object.py @@ -0,0 +1,28 @@ +# 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 dataclasses import dataclass +import datetime + + +class StxGenericModel: + def __init__(self, id: str, name: str, + lastupdate: datetime, content: str) -> None: + self.id = id + self.name = name + self.lastupdate = lastupdate + self.content = content + + def isChanged(self, updatetime: datetime) -> bool: + return True if self.lastupdate > updatetime else False diff --git a/src/o2ims/entrypoints/__init__.py b/o2ims/entrypoints/__init__.py similarity index 100% rename from src/o2ims/entrypoints/__init__.py rename to o2ims/entrypoints/__init__.py diff --git a/src/o2ims/entrypoints/flask_application.py b/o2ims/entrypoints/flask_application.py similarity index 81% rename from src/o2ims/entrypoints/flask_application.py rename to o2ims/entrypoints/flask_application.py index 933301c..8965a30 100644 --- a/src/o2ims/entrypoints/flask_application.py +++ b/o2ims/entrypoints/flask_application.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime -from flask import Flask, jsonify, request -from o2ims.domain import commands -from o2ims.service.handlers import InvalidResourceType +# from datetime import datetime +from flask import Flask, jsonify +# request +# from o2ims.domain import commands +# from o2ims.service.handlers import InvalidResourceType from o2ims import bootstrap, config from o2ims.views import ocloud_view @@ -23,6 +24,7 @@ app = Flask(__name__) bus = bootstrap.bootstrap() apibase = config.get_o2ims_api_base() + @app.route(apibase, methods=["GET"]) def oclouds(): result = ocloud_view.oclouds(bus.uow) diff --git a/src/o2ims/entrypoints/redis_eventconsumer.py b/o2ims/entrypoints/redis_eventconsumer.py similarity index 100% rename from src/o2ims/entrypoints/redis_eventconsumer.py rename to o2ims/entrypoints/redis_eventconsumer.py diff --git a/src/o2ims/service/__init__.py b/o2ims/service/__init__.py similarity index 100% rename from src/o2ims/service/__init__.py rename to o2ims/service/__init__.py diff --git a/o2ims/service/client/__init__.py b/o2ims/service/client/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/o2ims/service/client/__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/service/client/base_client.py b/o2ims/service/client/base_client.py new file mode 100644 index 0000000..6057ab3 --- /dev/null +++ b/o2ims/service/client/base_client.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. + +import abc +# from typing import Optional, List, Set +from typing import List +from o2ims.domain import stx_object as ocloudModel + + +class BaseClient(abc.ABC): + def __init__(self): + pass + + def list(self) -> List[ocloudModel.StxGenericModel]: + return self._list() + + def get(self, id) -> ocloudModel.StxGenericModel: + return self._get(id) + + @abc.abstractmethod + def _get(self, id) -> ocloudModel.StxGenericModel: + raise NotImplementedError + + @abc.abstractmethod + def _list(self): + raise NotImplementedError diff --git a/src/o2ims/service/handlers.py b/o2ims/service/handlers.py similarity index 81% rename from src/o2ims/service/handlers.py rename to o2ims/service/handlers.py index c75dc07..c80ea6f 100644 --- a/src/o2ims/service/handlers.py +++ b/o2ims/service/handlers.py @@ -14,12 +14,15 @@ # 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 +# 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 -if TYPE_CHECKING: - from . import unit_of_work class InvalidResourceType(Exception): pass @@ -28,5 +31,6 @@ class InvalidResourceType(Exception): EVENT_HANDLERS = { } # type: Dict[Type[events.Event], List[Callable]] + COMMAND_HANDLERS = { } # type: Dict[Type[commands.Command], Callable] diff --git a/src/o2ims/service/messagebus.py b/o2ims/service/messagebus.py similarity index 98% rename from src/o2ims/service/messagebus.py rename to o2ims/service/messagebus.py index bff45ab..0758529 100644 --- a/src/o2ims/service/messagebus.py +++ b/o2ims/service/messagebus.py @@ -51,7 +51,8 @@ class MessageBus: 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) + logger.debug("handling event %s with handler %s", + event, handler) handler(event) self.queue.extend(self.uow.collect_new_events()) except Exception: diff --git a/src/o2ims/service/unit_of_work.py b/o2ims/service/unit_of_work.py similarity index 94% rename from src/o2ims/service/unit_of_work.py rename to o2ims/service/unit_of_work.py index 4714729..40e0f76 100644 --- a/src/o2ims/service/unit_of_work.py +++ b/o2ims/service/unit_of_work.py @@ -19,7 +19,6 @@ 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 @@ -27,7 +26,7 @@ from o2ims.adapter import ocloud_repository class AbstractUnitOfWork(abc.ABC): oclouds: ocloud_repository.OcloudRepository - def __enter__(self) -> AbstractUnitOfWork: + def __enter__(self): return self def __exit__(self, *args): @@ -64,7 +63,8 @@ class SqlAlchemyUnitOfWork(AbstractUnitOfWork): def __enter__(self): self.session = self.session_factory() # type: Session - self.oclouds = ocloud_repository.OcloudSqlAlchemyRepository(self.session) + self.oclouds = ocloud_repository\ + .OcloudSqlAlchemyRepository(self.session) return super().__enter__() def __exit__(self, *args): diff --git a/src/o2ims/views/ocloud_view.py b/o2ims/views/ocloud_view.py similarity index 100% rename from src/o2ims/views/ocloud_view.py rename to o2ims/views/ocloud_view.py diff --git a/requirements-test.txt b/requirements-test.txt index 251bb2b..e22107b 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,8 +1,13 @@ +flake8 pylint mypy requests +tox pytest pytest-icdiff tenacity + +# -e git+https://opendev.org/starlingx/distcloud-client.git@master#egg=distributedcloud-client&subdirectory=distributedcloud-client +# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client diff --git a/requirements.txt b/requirements.txt index 985e50e..acbfe10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,8 @@ flask sqlalchemy redis psycopg2-binary + +Cython>=3.0a1 + +# -e git+https://opendev.org/starlingx/distcloud-client.git@master#egg=distributedcloud-client&subdirectory=distributedcloud-client +# -e git+https://opendev.org/starlingx/config.git@master#egg=cgtsclient&subdirectory=sysinv/cgts-client/cgts-client# diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..553dd19 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup +from setuptools import find_packages + +setup( + name="o2imsdms", + version="1.0", + packages=find_packages(), + license="LICENSE", + description="Represent O2 IMS and O2 DMS", + install_requires=[ + 'httplib2', + # 'distributedcloud-client', + # 'cgtsclient', + 'babel', # Required by distributedcloud-client + 'PrettyTable<0.8,>=0.7.2', # Required by distributedcloud-client + ] +) diff --git a/src/o2dms/setup.py b/src/o2dms/setup.py deleted file mode 100644 index feafb7a..0000000 --- a/src/o2dms/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup - -setup( - name="o2common", - version="1.0", - packages=["o2common"], -) - -setup( - name="o2ims", - version="1.0", - packages=["o2ims"], -) - -setup( - name="o2dms", - version="1.0", - packages=["o2dms"], -) diff --git a/src/setup.py b/src/setup.py deleted file mode 100644 index 7558373..0000000 --- a/src/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -from setuptools import setup - -setup( - name="o2imsdms", - version="1.0", - packages=["o2ims", "o2dms", "o2common"], -) diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..b514342 --- /dev/null +++ b/tests/e2e/__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/tox.ini b/tox.ini new file mode 100644 index 0000000..3480ef8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,35 @@ +# content of: tox.ini , put in same dir as setup.py +[tox] +envlist=flake8,code + +minversion = 1.6 +skipsdist = True + +[testenv] +basepython = + code: python3.8 + flake8: python3.8 +setenv = + VIRTUAL_ENV={envdir} + +# NOTE: relative paths were used due to '-w' flag for nosetests util + +usedevelop = True +install_command = pip install -U {opts} {packages} +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/requirements-test.txt +whitelist_externals = bash, flake8, pytest + +[testenv:flake8] +commands = + flake8 o2ims + flake8 o2dms + flake8 o2common + +[testenv:code] +commands = + pytest tests + +[testenv:nosetests] +commands = + pytest tests -- 2.16.6