From 50de35d7e8f69cd26ccdb6c227094123605e2522 Mon Sep 17 00:00:00 2001 From: hosingh000 Date: Fri, 28 Jul 2017 10:46:45 -0500 Subject: [PATCH] cleanup legacy changes Change-Id: I5a29cee1ed9e3ea18d2e7e659d5e3143d122bf62 --- orm/common/client/audit/CONTRIBUTING.rst | 17 ++ orm/common/client/audit/HACKING.rst | 4 + orm/common/client/audit/MANIFEST.in | 6 + orm/common/client/audit/README.rst | 19 ++ .../client/audit/audit_client/__init__.py | 1 + .../client/audit/audit_client/api/__init__.py | 1 + .../client/audit/audit_client/api/audit.py | 219 +++++++++++++++ .../audit_client/api/exceptions/__init__.py | 1 + .../api/exceptions/audit_exception.py | 9 + .../audit/audit_client/api/model/__init__.py | 1 + .../api/model/get_audits_result.py | 14 + .../audit_client/api/model/transaction.py | 49 ++++ .../audit/audit_client/examples/__init__.py | 1 + .../audit/audit_client/examples/get_audits.py | 22 ++ .../audit/audit_client/examples/put_audit.py | 52 ++++ .../audit/audit_client/tests/__init__.py | 1 + .../audit/audit_client/tests/api/__init__.py | 1 + .../audit_client/tests/api/model/__init__.py | 1 + .../tests/api/model/test_get_audits_result.py | 16 ++ .../tests/api/model/test_transactions.py | 69 +++++ .../audit_client/tests/api/test_audit.py | 265 ++++++++++++++++++ orm/common/client/audit/babel.cfg | 2 + orm/common/client/audit/requirements.txt | 4 + orm/common/client/audit/setup.cfg | 46 +++ orm/common/client/audit/setup.py | 33 +++ orm/common/client/audit/test-requirements.txt | 9 + orm/common/client/audit/tox.ini | 22 ++ .../keystone/debian/aic-orm-keystone.install | 1 - orm/common/client/keystone/debian/changelog | 5 - orm/common/client/keystone/debian/compat | 1 - orm/common/client/keystone/debian/control | 13 - orm/common/client/keystone/debian/copyright | 34 --- orm/common/client/keystone/debian/postrm | 4 - orm/common/client/keystone/debian/rules | 8 - .../client/keystone/debian/source/format | 1 - .../keystone/keystone_utils.egg-info/PKG-INFO | 21 -- .../keystone_utils.egg-info/SOURCES.txt | 25 -- .../dependency_links.txt | 1 - .../keystone_utils.egg-info/not-zip-safe | 1 - .../keystone/keystone_utils.egg-info/pbr.json | 1 - .../keystone_utils.egg-info/top_level.txt | 2 - orm/services/audit_trail_manager/MANIFEST.in | 1 + .../audit_trail_manager/audit_server.conf | 26 ++ .../audit_trail_manager/audit_server.wsgi | 2 + .../audit_server/__init__.py | 1 + .../audit_trail_manager/audit_server/app.py | 34 +++ .../audit_server/controllers/__init__.py | 1 + .../audit_server/controllers/root.py | 10 + .../audit_server/controllers/v1/__init__.py | 1 + .../audit_server/controllers/v1/audit.py | 13 + .../audit_server/controllers/v1/base.py | 49 ++++ .../controllers/v1/configuration.py | 28 ++ .../audit_server/controllers/v1/logs.py | 65 +++++ .../audit_server/controllers/v1/root.py | 10 + .../controllers/v1/transaction.py | 203 ++++++++++++++ .../audit_server/external_mock/__init__.py} | 0 .../external_mock/orm_common/__init__.py | 0 .../orm_common/utils/__init__.py | 0 .../external_mock/orm_common/utils/utils.py | 13 + .../audit_server/model/__init__.py | 16 ++ .../audit_server/model/transaction.py | 47 ++++ .../audit_server/model/transaction_query.py | 46 +++ .../model/transaction_query_result.py | 14 + .../audit_server/services/__init__.py | 1 + .../audit_server/services/base.py | 7 + .../audit_server/services/transaction.py | 35 +++ .../audit_server/storage/__init__.py | 1 + .../audit_server/storage/factory.py | 12 + .../audit_server/storage/mysql/__init__.py | 1 + .../audit_server/storage/mysql/transaction.py | 168 +++++++++++ .../audit_server/storage/transaction.py | 17 ++ .../audit_server/templates/error.html | 12 + .../audit_server/templates/index.html | 34 +++ .../audit_server/templates/layout.html | 22 ++ .../audit_server/tests/__init__.py | 1 + .../tests/controllers/__init__.py | 1 + .../tests/controllers/functional_test.py | 154 ++++++++++ .../tests/controllers/v1/__init__.py | 1 + .../tests/controllers/v1/functional_test.py | 10 + .../tests/controllers/v1/test_base.py | 38 +++ .../controllers/v1/test_configuration.py | 14 + .../tests/controllers/v1/test_logs.py | 25 ++ .../tests/controllers/v1/test_transaction.py | 97 +++++++ .../audit_server/tests/model/__init__.py | 1 + .../model/test_transaction_query_result.py | 19 ++ .../audit_server/tests/services/__init__.py | 1 + .../audit_server/tests/services/test_base.py | 14 + .../tests/services/test_transaction.py | 56 ++++ .../audit_server/tests/storage/__init__.py | 1 + .../tests/storage/mysql/__init__.py | 1 + .../tests/storage/mysql/test_transaction.py | 226 +++++++++++++++ .../tests/storage/test_factory.py | 25 ++ .../tests/storage/test_transaction.py | 21 ++ .../audit_server_swagger.yaml | 175 ++++++++++++ .../nodejs_audit_server_docs/README.md | 24 ++ .../nodejs_audit_server_docs/api/swagger.yaml | 178 ++++++++++++ .../controllers/Default.js | 15 + .../controllers/DefaultService.js | 60 ++++ .../nodejs_audit_server_docs/index.js | 40 +++ .../nodejs_audit_server_docs/package.json | 16 ++ orm/services/audit_trail_manager/config.py | 74 +++++ .../audit_trail_manager/debug_service.py | 6 + .../audit_trail_manager/public/css/style.css | 43 +++ .../public/images/logo.png | Bin 0 -> 20596 bytes .../scripts/db_scripts/create_db.sql | 23 ++ .../scripts/shell_scripts/create_db.sh | 24 ++ .../audit_trail_manager/swagger/swagger.yaml | 179 ++++++++++++ orm/services/audit_trail_manager/tox.ini | 19 ++ .../customer_manager/cms_rest/tests/config.py | 2 +- ...create_db.sql => ranger_cms_create_db.sql} | 0 ...update_db.sql => ranger_cms_update_db.sql} | 0 .../scripts/shell_scripts/create_db.sh | 2 +- orm/services/flavor_manager/config.py | 3 +- .../flavor_manager/fms_rest/tests/config.py | 3 +- ...create_db.sql => ranger_fms_create_db.sql} | 0 ...update_db.sql => ranger_fms_update_db.sql} | 0 .../scripts/shell_scripts/create_db.sh | 2 +- orm/services/flavor_manager/tox.ini | 4 - orm/services/id_generator/MANIFEST.in | 1 + orm/services/id_generator/TODO | 5 + orm/services/id_generator/config.py | 69 +++++ .../id_generator/public/css/style.css | 43 +++ .../id_generator/public/images/logo.png | Bin 0 -> 20596 bytes orm/services/id_generator/requirements.txt | 10 + .../scripts/db_scripts/db_create.sql | 10 + .../scripts/shell_scripts/create_db.sh | 8 + .../id_generator/swagger/swagger.yaml | 106 +++++++ .../id_generator/test-requirements.txt | 10 + orm/services/id_generator/tox.ini | 18 ++ orm/services/id_generator/uuidgen.conf | 26 ++ orm/services/id_generator/uuidgen.wsgi | 2 + orm/services/id_generator/uuidgen/__init__.py | 0 orm/services/id_generator/uuidgen/app.py | 25 ++ .../uuidgen/controllers/__init__.py | 0 .../id_generator/uuidgen/controllers/root.py | 37 +++ .../uuidgen/controllers/v1/__init__.py | 0 .../uuidgen/controllers/v1/configuration.py | 28 ++ .../uuidgen/controllers/v1/logs.py | 65 +++++ .../uuidgen/controllers/v1/root.py | 10 + .../uuidgen/controllers/v1/uuid_controller.py | 97 +++++++ .../id_generator/uuidgen/db/__init__.py | 0 .../id_generator/uuidgen/db/db_manager.py | 57 ++++ .../id_generator/uuidgen/db/uuid_db.py | 20 ++ .../uuidgen/external_mock/__init__.py | 0 .../external_mock/orm_common/__init__.py | 0 .../orm_common/utils/__init__.py | 0 .../external_mock/orm_common/utils/utils.py | 13 + .../id_generator/uuidgen/model/__init__.py | 15 + .../id_generator/uuidgen/templates/error.html | 12 + .../id_generator/uuidgen/templates/index.html | 34 +++ .../uuidgen/templates/layout.html | 22 ++ .../id_generator/uuidgen/tests/__init__.py | 22 ++ .../id_generator/uuidgen/tests/config.py | 59 ++++ .../uuidgen/tests/test_configuration.py | 14 + .../uuidgen/tests/test_functional.py | 135 +++++++++ .../id_generator/uuidgen/tests/test_logs.py | 18 ++ orm/services/image_manager/config.py | 3 +- .../image_manager/ims/tests/config.py | 2 +- orm/services/image_manager/tox.ini | 12 - .../resource_distributor/rds/utils/utils.py | 2 +- orm/tests/config.py | 3 +- 161 files changed, 4357 insertions(+), 147 deletions(-) create mode 100644 orm/common/client/audit/CONTRIBUTING.rst create mode 100644 orm/common/client/audit/HACKING.rst create mode 100644 orm/common/client/audit/MANIFEST.in create mode 100644 orm/common/client/audit/README.rst create mode 100644 orm/common/client/audit/audit_client/__init__.py create mode 100644 orm/common/client/audit/audit_client/api/__init__.py create mode 100755 orm/common/client/audit/audit_client/api/audit.py create mode 100644 orm/common/client/audit/audit_client/api/exceptions/__init__.py create mode 100644 orm/common/client/audit/audit_client/api/exceptions/audit_exception.py create mode 100644 orm/common/client/audit/audit_client/api/model/__init__.py create mode 100644 orm/common/client/audit/audit_client/api/model/get_audits_result.py create mode 100755 orm/common/client/audit/audit_client/api/model/transaction.py create mode 100644 orm/common/client/audit/audit_client/examples/__init__.py create mode 100644 orm/common/client/audit/audit_client/examples/get_audits.py create mode 100644 orm/common/client/audit/audit_client/examples/put_audit.py create mode 100644 orm/common/client/audit/audit_client/tests/__init__.py create mode 100644 orm/common/client/audit/audit_client/tests/api/__init__.py create mode 100644 orm/common/client/audit/audit_client/tests/api/model/__init__.py create mode 100644 orm/common/client/audit/audit_client/tests/api/model/test_get_audits_result.py create mode 100755 orm/common/client/audit/audit_client/tests/api/model/test_transactions.py create mode 100755 orm/common/client/audit/audit_client/tests/api/test_audit.py create mode 100644 orm/common/client/audit/babel.cfg create mode 100644 orm/common/client/audit/requirements.txt create mode 100644 orm/common/client/audit/setup.cfg create mode 100644 orm/common/client/audit/setup.py create mode 100644 orm/common/client/audit/test-requirements.txt create mode 100644 orm/common/client/audit/tox.ini delete mode 100644 orm/common/client/keystone/debian/aic-orm-keystone.install delete mode 100755 orm/common/client/keystone/debian/changelog delete mode 100755 orm/common/client/keystone/debian/compat delete mode 100755 orm/common/client/keystone/debian/control delete mode 100755 orm/common/client/keystone/debian/copyright delete mode 100755 orm/common/client/keystone/debian/postrm delete mode 100755 orm/common/client/keystone/debian/rules delete mode 100755 orm/common/client/keystone/debian/source/format delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/PKG-INFO delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/SOURCES.txt delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/dependency_links.txt delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/not-zip-safe delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/pbr.json delete mode 100644 orm/common/client/keystone/keystone_utils.egg-info/top_level.txt create mode 100644 orm/services/audit_trail_manager/MANIFEST.in create mode 100644 orm/services/audit_trail_manager/audit_server.conf create mode 100644 orm/services/audit_trail_manager/audit_server.wsgi create mode 100644 orm/services/audit_trail_manager/audit_server/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/app.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/root.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/v1/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/controllers/v1/audit.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/v1/base.py create mode 100755 orm/services/audit_trail_manager/audit_server/controllers/v1/configuration.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/v1/logs.py create mode 100644 orm/services/audit_trail_manager/audit_server/controllers/v1/root.py create mode 100755 orm/services/audit_trail_manager/audit_server/controllers/v1/transaction.py rename orm/{common/client/keystone/debian/docs => services/audit_trail_manager/audit_server/external_mock/__init__.py} (100%) mode change 100755 => 100644 create mode 100644 orm/services/audit_trail_manager/audit_server/external_mock/orm_common/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/utils.py create mode 100644 orm/services/audit_trail_manager/audit_server/model/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/model/transaction.py create mode 100755 orm/services/audit_trail_manager/audit_server/model/transaction_query.py create mode 100644 orm/services/audit_trail_manager/audit_server/model/transaction_query_result.py create mode 100644 orm/services/audit_trail_manager/audit_server/services/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/services/base.py create mode 100644 orm/services/audit_trail_manager/audit_server/services/transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/storage/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/storage/factory.py create mode 100644 orm/services/audit_trail_manager/audit_server/storage/mysql/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/storage/mysql/transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/storage/transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/templates/error.html create mode 100644 orm/services/audit_trail_manager/audit_server/templates/index.html create mode 100644 orm/services/audit_trail_manager/audit_server/templates/layout.html create mode 100644 orm/services/audit_trail_manager/audit_server/tests/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/controllers/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/controllers/functional_test.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/functional_test.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_base.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_configuration.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_logs.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/model/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/model/test_transaction_query_result.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/services/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/services/test_base.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/services/test_transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/storage/__init__.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/storage/mysql/__init__.py create mode 100755 orm/services/audit_trail_manager/audit_server/tests/storage/mysql/test_transaction.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/storage/test_factory.py create mode 100644 orm/services/audit_trail_manager/audit_server/tests/storage/test_transaction.py create mode 100755 orm/services/audit_trail_manager/audit_server_docs/audit_server_swagger.yaml create mode 100644 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/README.md create mode 100755 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/api/swagger.yaml create mode 100644 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/Default.js create mode 100644 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/DefaultService.js create mode 100644 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/index.js create mode 100644 orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/package.json create mode 100644 orm/services/audit_trail_manager/config.py create mode 100644 orm/services/audit_trail_manager/debug_service.py create mode 100644 orm/services/audit_trail_manager/public/css/style.css create mode 100644 orm/services/audit_trail_manager/public/images/logo.png create mode 100644 orm/services/audit_trail_manager/scripts/db_scripts/create_db.sql create mode 100644 orm/services/audit_trail_manager/scripts/shell_scripts/create_db.sh create mode 100644 orm/services/audit_trail_manager/swagger/swagger.yaml create mode 100644 orm/services/audit_trail_manager/tox.ini rename orm/services/customer_manager/scripts/db_scripts/{aic_orm_cms_create_db.sql => ranger_cms_create_db.sql} (100%) rename orm/services/customer_manager/scripts/db_scripts/{aic_orm_cms_update_db.sql => ranger_cms_update_db.sql} (100%) rename orm/services/flavor_manager/scripts/db_scripts/{aic_orm_fms_create_db.sql => ranger_fms_create_db.sql} (100%) rename orm/services/flavor_manager/scripts/db_scripts/{aic_orm_fms_update_db.sql => ranger_fms_update_db.sql} (100%) create mode 100644 orm/services/id_generator/MANIFEST.in create mode 100644 orm/services/id_generator/TODO create mode 100755 orm/services/id_generator/config.py create mode 100644 orm/services/id_generator/public/css/style.css create mode 100644 orm/services/id_generator/public/images/logo.png create mode 100644 orm/services/id_generator/requirements.txt create mode 100755 orm/services/id_generator/scripts/db_scripts/db_create.sql create mode 100755 orm/services/id_generator/scripts/shell_scripts/create_db.sh create mode 100644 orm/services/id_generator/swagger/swagger.yaml create mode 100644 orm/services/id_generator/test-requirements.txt create mode 100755 orm/services/id_generator/tox.ini create mode 100644 orm/services/id_generator/uuidgen.conf create mode 100644 orm/services/id_generator/uuidgen.wsgi create mode 100644 orm/services/id_generator/uuidgen/__init__.py create mode 100755 orm/services/id_generator/uuidgen/app.py create mode 100644 orm/services/id_generator/uuidgen/controllers/__init__.py create mode 100755 orm/services/id_generator/uuidgen/controllers/root.py create mode 100644 orm/services/id_generator/uuidgen/controllers/v1/__init__.py create mode 100755 orm/services/id_generator/uuidgen/controllers/v1/configuration.py create mode 100644 orm/services/id_generator/uuidgen/controllers/v1/logs.py create mode 100755 orm/services/id_generator/uuidgen/controllers/v1/root.py create mode 100755 orm/services/id_generator/uuidgen/controllers/v1/uuid_controller.py create mode 100644 orm/services/id_generator/uuidgen/db/__init__.py create mode 100755 orm/services/id_generator/uuidgen/db/db_manager.py create mode 100755 orm/services/id_generator/uuidgen/db/uuid_db.py create mode 100644 orm/services/id_generator/uuidgen/external_mock/__init__.py create mode 100644 orm/services/id_generator/uuidgen/external_mock/orm_common/__init__.py create mode 100644 orm/services/id_generator/uuidgen/external_mock/orm_common/utils/__init__.py create mode 100755 orm/services/id_generator/uuidgen/external_mock/orm_common/utils/utils.py create mode 100644 orm/services/id_generator/uuidgen/model/__init__.py create mode 100644 orm/services/id_generator/uuidgen/templates/error.html create mode 100644 orm/services/id_generator/uuidgen/templates/index.html create mode 100644 orm/services/id_generator/uuidgen/templates/layout.html create mode 100644 orm/services/id_generator/uuidgen/tests/__init__.py create mode 100755 orm/services/id_generator/uuidgen/tests/config.py create mode 100755 orm/services/id_generator/uuidgen/tests/test_configuration.py create mode 100755 orm/services/id_generator/uuidgen/tests/test_functional.py create mode 100755 orm/services/id_generator/uuidgen/tests/test_logs.py diff --git a/orm/common/client/audit/CONTRIBUTING.rst b/orm/common/client/audit/CONTRIBUTING.rst new file mode 100644 index 00000000..7e9b4d63 --- /dev/null +++ b/orm/common/client/audit/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If you would like to contribute to the development of OpenStack, you must +follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +If you already have a good understanding of how the system works and your +OpenStack accounts are set up, you can skip to the development workflow +section of this documentation to learn how changes to OpenStack should be +submitted for review via the Gerrit tool: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/audit_client diff --git a/orm/common/client/audit/HACKING.rst b/orm/common/client/audit/HACKING.rst new file mode 100644 index 00000000..6d69d5e7 --- /dev/null +++ b/orm/common/client/audit/HACKING.rst @@ -0,0 +1,4 @@ +audit_client Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/orm/common/client/audit/MANIFEST.in b/orm/common/client/audit/MANIFEST.in new file mode 100644 index 00000000..c978a52d --- /dev/null +++ b/orm/common/client/audit/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/orm/common/client/audit/README.rst b/orm/common/client/audit/README.rst new file mode 100644 index 00000000..842029bf --- /dev/null +++ b/orm/common/client/audit/README.rst @@ -0,0 +1,19 @@ +=============================== +audit_client +=============================== + +This is the Audit Client project + +Please feel here a long description which must be at least 3 lines wrapped on +80 cols, so that distribution package maintainers can use it in their packages. +Note that this is a hard requirement. + +* Free software: Apache license +* Documentation: http://docs.openstack.org/developer/audit_client +* Source: http://git.openstack.org/cgit/audit_client/audit_client +* Bugs: http://bugs.launchpad.net/audit_client + +Features +-------- + +* TODO diff --git a/orm/common/client/audit/audit_client/__init__.py b/orm/common/client/audit/audit_client/__init__.py new file mode 100644 index 00000000..231bc282 --- /dev/null +++ b/orm/common/client/audit/audit_client/__init__.py @@ -0,0 +1 @@ +"""audit_client package.""" diff --git a/orm/common/client/audit/audit_client/api/__init__.py b/orm/common/client/audit/audit_client/api/__init__.py new file mode 100644 index 00000000..27806988 --- /dev/null +++ b/orm/common/client/audit/audit_client/api/__init__.py @@ -0,0 +1 @@ +"""api package.""" diff --git a/orm/common/client/audit/audit_client/api/audit.py b/orm/common/client/audit/audit_client/api/audit.py new file mode 100755 index 00000000..6c40c315 --- /dev/null +++ b/orm/common/client/audit/audit_client/api/audit.py @@ -0,0 +1,219 @@ +"""audit module.""" + +import json +import logging +import threading +import time +import urllib2 + +from audit_client.api.exceptions.audit_exception import AuditException +from audit_client.api.model.get_audits_result import AuditsResult +from audit_client.api.model.transaction import Transaction + +logger = logging.getLogger(__name__) + +config = { + 'AUDIT_SERVER_URL': None, + 'NUM_OF_SEND_RETRIES': None, + 'TIME_WAIT_BETWEEN_RETRIES': None, + 'SERVICE_NAME': None} + + +def init(audit_server_url, num_of_send_retries, time_wait_between_retries, + service_name=''): + """Initialize the audit client. + + :param audit_server_url: audit server url + :param num_of_send_retries: number of times to try and send the record + in case of failures. + :param time_wait_between_retries: how much time to wait (in seconds) + between each retry. + """ + if not audit_server_url \ + or not num_of_send_retries \ + or not time_wait_between_retries: + error_msg = "Error: Fail to initialize audit using following inputs " \ + "AUDIT_SERVER_URL={}, NUM_OF_SEND_RETRIES={}, " \ + "TIME_WAIT_BETWEEN_RETRIES={}. " \ + "One of them is possibly None" \ + .format(audit_server_url, num_of_send_retries, + time_wait_between_retries) + logger.error(error_msg) + raise AuditException(error_msg) + + config['AUDIT_SERVER_URL'] = audit_server_url + config['NUM_OF_SEND_RETRIES'] = num_of_send_retries + config['TIME_WAIT_BETWEEN_RETRIES'] = time_wait_between_retries + config['SERVICE_NAME'] = service_name + + +def _validate(): + """# Validate proper initialization of the audit client.""" + # if not AUDIT_SERVER_URL or not NUM_OF_SEND_RETRIES + # or not TIME_WAIT_BETWEEN_RETRIES: + if not config['AUDIT_SERVER_URL'] \ + or not config['NUM_OF_SEND_RETRIES'] \ + or not config['TIME_WAIT_BETWEEN_RETRIES']: + raise AuditException( + "Error: Audit was not initialize correctly. You must first " + "run audit.init(audit_server_url, num_of_send_retries, " + "time_wait_between_retries)") + + +def audit(timestamp, application_id, tracking_id, transaction_id, + transaction_type, resource_id, service_name, user_id=None, + external_id=None, event_details=None): + """The method is used to audit transactions. + + :param timestamp: + :param application_id: + :param tracking_id: + :param transaction_id: + :param transaction_type: + :param resource_id: + :param service_name: + :param user_id: + :param external_id: + :param event_details: + :return: + """ + thread = threading.Thread(target=_audit_thread, args=( + timestamp, application_id, tracking_id, transaction_id, + transaction_type, + resource_id, service_name, user_id, external_id, event_details)) + thread.start() + + +def get_audits(timestamp_from=None, timestamp_to=None, application_id=None, + tracking_id=None, transaction_id=None, transaction_type=None, + resource_id=None, service_name=None, user_id=None, + external_id=None, event_details=None, limit=None): + """The method is used to audit transactions. + + :param timestamp_from: + :param timestamp_to: + :param application_id: + :param tracking_id: + :param transaction_id: + :param transaction_type: + :param resource_id: + :param service_name: + :param user_id: + :param external_id: + :param event_details: + """ + query = "" + query = _build_query(query, "q.timestamp_from", timestamp_from) + query = _build_query(query, "q.timestamp_to", timestamp_to) + query = _build_query(query, "q.application_id", application_id) + query = _build_query(query, "q.tracking_id", tracking_id) + query = _build_query(query, "q.transaction_id", transaction_id) + query = _build_query(query, "q.transaction_type", transaction_type) + query = _build_query(query, "q.resource_id", resource_id) + query = _build_query(query, "q.service_name", service_name) + query = _build_query(query, "q.user_id", user_id) + query = _build_query(query, "q.external_id", external_id) + query = _build_query(query, "q.event_details", event_details) + query = _build_query(query, "limit", limit) + payload = _get_data(query) + data = json.load(payload) + transactions = [] + for transaction in data['transactions']: + timestamp = transaction['timestamp'] + user_id = transaction['user_id'] + application_id = transaction['application_id'] + tracking_id = transaction['tracking_id'] + external_id = transaction['external_id'] + transaction_id = transaction['transaction_id'] + resource_id = transaction['resource_id'] + service_name = transaction['service_name'] + transaction_type = transaction['transaction_type'] + event_details = transaction['event_details'] + transactions.append( + Transaction(timestamp, user_id, application_id, tracking_id, + external_id, transaction_id, transaction_type, + event_details, resource_id, service_name)) + return AuditsResult(transactions) + + +def _build_query(query, arg_name, arg_value): + if arg_value is not None: + query = query + "%s=%s&" % (arg_name, arg_value) + return query + + +# A thread method for sending data to the audit server +# This method is asynchronic in order to prevent blocking +def _audit_thread(timestamp, application_id, tracking_id, transaction_id, + transaction_type, resource_id, service_name, user_id, + external_id, event_details): + # Prepare the data for the audit server + data = { + "timestamp": timestamp, + "application_id": application_id, + "tracking_id": tracking_id, + "transaction_id": transaction_id, + "transaction_type": transaction_type, + "resource_id": resource_id, + "service_name": service_name, + "user_id": user_id, + "external_id": external_id, + "event_details": event_details, + "resource_id": resource_id, + "service_name": service_name + } + _post_data(data) + + +def _post_data(data): + # Validate that the configuration was initialized + _validate() + # Send the data + req = urllib2.Request(config['AUDIT_SERVER_URL']) + req.add_header('Content-Type', 'application/json') + # Retry to send the data to the audit server + success = False + for retry_number in range(config['NUM_OF_SEND_RETRIES']): + try: + urllib2.urlopen(req, json.dumps(data)) + success = True + break + except Exception as error: + time.sleep(config['TIME_WAIT_BETWEEN_RETRIES']) + + if not success: + error_msg = "ERROR|CON{}AUDIT001|Fail to send data to [{}]. Tried " \ + "a couple of times with no success. Last attempt " \ + "error: [{}]".format(config['SERVICE_NAME'], + config['AUDIT_SERVER_URL'], + error.message) + logger.error(error_msg) + raise AuditException(error_msg) + + +def _get_data(query): + # Validate that the configuration was initialized + _validate() + # Send the data + audit_server_url_with_query = "{}?{}".format(config['AUDIT_SERVER_URL'], + query) + req = urllib2.Request(audit_server_url_with_query) + # Retry to get the data from the audit server + success = False + response = None + for retry_number in range(config['NUM_OF_SEND_RETRIES']): + try: + response = urllib2.urlopen(req) + success = True + break + except Exception as error: + time.sleep(config['TIME_WAIT_BETWEEN_RETRIES']) + + if not success: + error_msg = "Fail to get data from [{}]. Tried a couple of times " \ + "with no success. Last attempt error: [{}]".\ + format(audit_server_url_with_query, error.message) + logger.error(error_msg) + raise AuditException(error_msg) + else: + return response diff --git a/orm/common/client/audit/audit_client/api/exceptions/__init__.py b/orm/common/client/audit/audit_client/api/exceptions/__init__.py new file mode 100644 index 00000000..148b313e --- /dev/null +++ b/orm/common/client/audit/audit_client/api/exceptions/__init__.py @@ -0,0 +1 @@ +"""exceptions package.""" diff --git a/orm/common/client/audit/audit_client/api/exceptions/audit_exception.py b/orm/common/client/audit/audit_client/api/exceptions/audit_exception.py new file mode 100644 index 00000000..d9c65393 --- /dev/null +++ b/orm/common/client/audit/audit_client/api/exceptions/audit_exception.py @@ -0,0 +1,9 @@ +"""audit exception module.""" + + +class AuditException(Exception): + """AuditException class.""" + + def __init__(self, error_msg): + """init method.""" + Exception.__init__(self, error_msg) diff --git a/orm/common/client/audit/audit_client/api/model/__init__.py b/orm/common/client/audit/audit_client/api/model/__init__.py new file mode 100644 index 00000000..6369aa7b --- /dev/null +++ b/orm/common/client/audit/audit_client/api/model/__init__.py @@ -0,0 +1 @@ +"""model package.""" diff --git a/orm/common/client/audit/audit_client/api/model/get_audits_result.py b/orm/common/client/audit/audit_client/api/model/get_audits_result.py new file mode 100644 index 00000000..32983cea --- /dev/null +++ b/orm/common/client/audit/audit_client/api/model/get_audits_result.py @@ -0,0 +1,14 @@ +"""get_audit_result module.""" + + +class AuditsResult(object): + """AuditResult class.""" + + def __init__(self, transactions): + """init method.""" + self.transactions = transactions + + def __str__(self): + """string representation of the object.""" + return "AuditsResult[ " \ + "transactions: {}]".format(self.transactions) diff --git a/orm/common/client/audit/audit_client/api/model/transaction.py b/orm/common/client/audit/audit_client/api/model/transaction.py new file mode 100755 index 00000000..9da59529 --- /dev/null +++ b/orm/common/client/audit/audit_client/api/model/transaction.py @@ -0,0 +1,49 @@ +"""transaction module.""" + + +class Transaction(object): + """transaction class.""" + + def __init__(self, timestamp, user_id, application_id, tracking_id, + external_id, transaction_id, transaction_type, event_details, + resource_id, service_name): + """init method.""" + self.timestamp = timestamp + self.user_id = user_id + self.application_id = application_id + self.tracking_id = tracking_id + self.external_id = external_id + self.transaction_id = transaction_id + self.transaction_type = transaction_type + self.event_details = event_details + self.resource_id = resource_id + self.service_name = service_name + + def __str__(self): + """string representation of the object.""" + return "Transaction:[" \ + "timestamp={}, " \ + "user_id={}, " \ + "application_id={}, " \ + "tracking_id={}, " \ + "external_id={}, " \ + "transaction_id={}, " \ + "transaction_type={}, " \ + "event_details={}, " \ + "resource_id={}, " \ + "service_name={}]" \ + .format( + self.timestamp, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + def __repr__(self): + """string representation of the object.""" + return str(self) diff --git a/orm/common/client/audit/audit_client/examples/__init__.py b/orm/common/client/audit/audit_client/examples/__init__.py new file mode 100644 index 00000000..6b0299f2 --- /dev/null +++ b/orm/common/client/audit/audit_client/examples/__init__.py @@ -0,0 +1 @@ +"""examples package.""" diff --git a/orm/common/client/audit/audit_client/examples/get_audits.py b/orm/common/client/audit/audit_client/examples/get_audits.py new file mode 100644 index 00000000..c0af2b83 --- /dev/null +++ b/orm/common/client/audit/audit_client/examples/get_audits.py @@ -0,0 +1,22 @@ +"""get_audits module.""" + +from audit_client.api import audit + +audit_server_url = "http://127.0.0.1:8776/v1/audit/transaction" +num_of_send_retries = 3 +time_wait_between_retries = 1 +audit.init(audit_server_url, num_of_send_retries, time_wait_between_retries) +response = audit.get_audits(timestamp_from=1, timestamp_to=5, + application_id="application_id_1", + tracking_id="tracking_id_1", + transaction_id="transaction_id_1", + transaction_type="transaction_type_1", + resource_id="resource_id_1", + service_name="service_name_1", user_id="user_id_1", + external_id="external_id_1", + event_details="event_details_1", status="status_1", + limit=10) +print(response) + +response = audit.get_audits(timestamp_from=1, timestamp_to=5, limit=10) +print(response) diff --git a/orm/common/client/audit/audit_client/examples/put_audit.py b/orm/common/client/audit/audit_client/examples/put_audit.py new file mode 100644 index 00000000..a8a69534 --- /dev/null +++ b/orm/common/client/audit/audit_client/examples/put_audit.py @@ -0,0 +1,52 @@ +"""put_audit module.""" + +from audit_client.api import audit + +audit_server_url = "http://127.0.0.1:8776/v1/audit/transaction" +num_of_send_retries = 3 +time_wait_between_retries = 1 +audit.init(audit_server_url, num_of_send_retries, time_wait_between_retries) +audit.audit(1, "application_id_1", "tracking_id_1", "transaction_id_1", + "transaction_type_1", "resource_id_1", "service_name_1", + "user_id_1", "external_id_1", "event_details_1", "status_1") +print("audit1") +audit.audit(2, "application_id_2", "tracking_id_2", "transaction_id_2", + "transaction_type_2", "resource_id_2", "service_name_2", + "user_id_2", "external_id_2", "event_details_2", "status_2") +print("audit2") +audit.audit(3, "application_id_3", "tracking_id_3", "transaction_id_3", + "transaction_type_3", "resource_id_3", "service_name_3", + "user_id_3", "external_id_3", "event_details_3", "status_3") +print("audit3") +audit.audit(4, "application_id_4", "tracking_id_4", "transaction_id_4", + "transaction_type_4", "resource_id_4", "service_name_4", + "user_id_4", "external_id_4", "event_details_4", "status_4") +print("audit4") +audit.audit(5, "application_id_5", "tracking_id_5", "transaction_id_5", + "transaction_type_5", "resource_id_5", "service_name_5", + "user_id_5", "external_id_5", "event_details_5", "status_5") +print("audit5") +audit.audit(6, "application_id_6", "tracking_id_6", "transaction_id_6", + "transaction_type_6", "resource_id_6", "service_name_6", + "user_id_6", "external_id_6", "event_details_6", "status_6") +print("audit6") +audit.audit(7, "application_id_7", "tracking_id_7", "transaction_id_7", + "transaction_type_7", "resource_id_7", "service_name_7", + "user_id_7", "external_id_7", "event_details_7", "status_7") +print("audit7") +audit.audit(8, "application_id_8", "tracking_id_8", "transaction_id_8", + "transaction_type_8", "resource_id_8", "service_name_8", + "user_id_8", "external_id_8", "event_details_8", "status_8") +print("audit8") +audit.audit(9, "application_id_9", "tracking_id_9", "transaction_id_9", + "transaction_type_9", "resource_id_9", "service_name_9", + "user_id_9", "external_id_9", "event_details_9", "status_9") +print("audit9") +audit.audit(10, "application_id_10", "tracking_id_10", "transaction_id_10", + "transaction_type_10", "resource_id_10", "service_name_10", + "user_id_10", "external_id_10", "event_details_10", "status_10") +print("audit10") +audit.audit(11, "application_id_11", "tracking_id_11", "transaction_id_11", + "transaction_type_11", "resource_id_11", "service_name_11", + "user_id_11", "external_id_11", "event_details_11", "status_11") +print("audit11") diff --git a/orm/common/client/audit/audit_client/tests/__init__.py b/orm/common/client/audit/audit_client/tests/__init__.py new file mode 100644 index 00000000..d2524392 --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/__init__.py @@ -0,0 +1 @@ +"""test package.""" diff --git a/orm/common/client/audit/audit_client/tests/api/__init__.py b/orm/common/client/audit/audit_client/tests/api/__init__.py new file mode 100644 index 00000000..27806988 --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/api/__init__.py @@ -0,0 +1 @@ +"""api package.""" diff --git a/orm/common/client/audit/audit_client/tests/api/model/__init__.py b/orm/common/client/audit/audit_client/tests/api/model/__init__.py new file mode 100644 index 00000000..6369aa7b --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/api/model/__init__.py @@ -0,0 +1 @@ +"""model package.""" diff --git a/orm/common/client/audit/audit_client/tests/api/model/test_get_audits_result.py b/orm/common/client/audit/audit_client/tests/api/model/test_get_audits_result.py new file mode 100644 index 00000000..8b3bc3fd --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/api/model/test_get_audits_result.py @@ -0,0 +1,16 @@ +"""test_get_audits_result module.""" + +from audit_client.api.model.get_audits_result import AuditsResult +import unittest + + +class Test(unittest.TestCase): + """test get audits result class.""" + + def test_init_AuditsResult(self): + """test init method.""" + transactions = [] + audit_result = AuditsResult(transactions) + self.assertEqual(str(audit_result), + "AuditsResult[ transactions: {}]".format( + transactions)) diff --git a/orm/common/client/audit/audit_client/tests/api/model/test_transactions.py b/orm/common/client/audit/audit_client/tests/api/model/test_transactions.py new file mode 100755 index 00000000..8a4a21f7 --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/api/model/test_transactions.py @@ -0,0 +1,69 @@ +"""test transactions module.""" + +import unittest + +from audit_client.api.model.transaction import Transaction + + +class Test(unittest.TestCase): + """test transactions class.""" + + def test_init_AuditsResult(self): + """test init method.""" + timestamp = 111 + user_id = "user_id_1" + application_id = "application_id_1" + tracking_id = "tracking_id_1" + external_id = "external_id_1" + transaction_id = "transaction_id_1" + transaction_type = "transaction_type_1" + event_details = "event_details_1" + resource_id = "resource_id_1" + service_name = "service_name_1" + transaction = Transaction(timestamp, user_id, application_id, + tracking_id, external_id, transaction_id, + transaction_type, event_details, + resource_id, service_name) + self.assertEqual(str(transaction), "Transaction:[" + "timestamp={}, " + "user_id={}, " + "application_id={}, " + "tracking_id={}, " + "external_id={}, " + "transaction_id={}, " + "transaction_type={}, " + "event_details={}, " + "resource_id={}, " + "service_name={}]".format( + timestamp, + user_id, + application_id, + tracking_id, + external_id, + transaction_id, + transaction_type, + event_details, + resource_id, + service_name)) + + self.assertEqual(transaction.__repr__(), "Transaction:[" + "timestamp={}, " + "user_id={}, " + "application_id={}, " + "tracking_id={}, " + "external_id={}, " + "transaction_id={}, " + "transaction_type={}, " + "event_details={}, " + "resource_id={}, " + "service_name={}]".format( + timestamp, + user_id, + application_id, + tracking_id, + external_id, + transaction_id, + transaction_type, + event_details, + resource_id, + service_name)) diff --git a/orm/common/client/audit/audit_client/tests/api/test_audit.py b/orm/common/client/audit/audit_client/tests/api/test_audit.py new file mode 100755 index 00000000..e1af78a8 --- /dev/null +++ b/orm/common/client/audit/audit_client/tests/api/test_audit.py @@ -0,0 +1,265 @@ +"""test_audit module.""" + +import json +import threading +import unittest +import urllib2 + +from mock import patch + +from audit_client.api import audit +from audit_client.api.exceptions.audit_exception import AuditException + + +class Test(unittest.TestCase): + """test audit class.""" + + data = { + 'transactions': [ + { + 'timestamp': 111, + 'user_id': 'user_id_1', + 'application_id': 'application_id_1', + 'tracking_id': 'tracking_id_1', + 'external_id': 'external_id_1', + 'transaction_id': 'transaction_id_1', + 'transaction_type': 'transaction_type_1', + 'event_details': 'event_details_1', + 'resource_id': "resource_id_1", + 'service_name': "service_name_1" + }, { + 'timestamp': 111, + 'user_id': 'user_id_2', + 'application_id': 'application_id_2', + 'tracking_id': 'tracking_id_2', + 'external_id': 'external_id_2', + 'transaction_id': 'transaction_id_2', + 'transaction_type': 'transaction_type_2', + 'event_details': 'event_details_2', + 'resource_id': "resource_id_2", + 'service_name': "service_name_3" + }, { + 'timestamp': 111, + 'user_id': 'user_id_3', + 'application_id': 'application_id_3', + 'tracking_id': 'tracking_id_3', + 'external_id': 'external_id_3', + 'transaction_id': 'transaction_id_3', + 'transaction_type': 'transaction_type_3', + 'event_details': 'event_details_3', + 'resource_id': "resource_id_3", + 'service_name': "service_name_3" + } + ] + } + + def test_init(self): + """test init method. + + test that init doesn't throw and exeception/error + and that the gloabl variables are set correctly + """ + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = 10 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + self.assertEqual(audit.config['AUDIT_SERVER_URL'], audit_server_url) + self.assertEqual(audit.config['NUM_OF_SEND_RETRIES'], + num_of_send_retries) + self.assertEqual(audit.config['TIME_WAIT_BETWEEN_RETRIES'], + time_wait_between_retries) + + # + def test_init_with_audit_server_url_empty(self): + """test that init throws an exception when audit_server_url is None.""" + audit_server_url = None + num_of_send_retries = 5 + time_wait_between_retries = 10 + self.assertRaises(AuditException, audit.init, audit_server_url, + num_of_send_retries, time_wait_between_retries) + + def test_init_with_num_of_send_retries_empty(self): + """test init method. + + test that init throws an exception when num_of_send_retries is None + """ + audit_server_url = "audit_server_url_1" + num_of_send_retries = None + time_wait_between_retries = 10 + self.assertRaises(AuditException, audit.init, audit_server_url, + num_of_send_retries, time_wait_between_retries) + + def test_init_with_time_wait_between_retries_empty(self): + """test init method. + + Test that init throws an exception when + time_wait_between_retries is None. + """ + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = None + self.assertRaises(AuditException, audit.init, audit_server_url, + num_of_send_retries, time_wait_between_retries) + + def test_validate(self): + """test validate method. + + test that _validate doesn't throw and exception when all variables + are populated + """ + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = 10 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + audit._validate() + + def test_validate_with_audit_server_url_empty(self): + """test validate with audit_server_url. + + test that _validate throw and exception when AUDIT_SERVER_URL + is None. + """ + audit.config['AUDIT_SERVER_URL'] = None + audit.config['NUM_OF_SEND_RETRIES'] = 5 + audit.config['TIME_WAIT_BETWEEN_RETRIES'] = 10 + self.assertRaises(AuditException, audit._validate) + + def test_validate_with_num_of_send_retries_empty(self): + """test validate with num of send_retries_empty. + + test that _validate throw and exception when NUM_OF_SEND_RETRIES + is None + """ + audit.config['AUDIT_SERVER_URL'] = "audit_server_url_1" + audit.config['NUM_OF_SEND_RETRIES'] = None + audit.config['TIME_WAIT_BETWEEN_RETRIES'] = 10 + self.assertRaises(AuditException, audit._validate) + + def test_validate_with_time_wait_between_retries_empty(self): + """test validate with ime_wait_between_retries_empty. + + test that _validate throw and exception when + TIME_WAIT_BETWEEN_RETRIES is None + """ + audit.config['AUDIT_SERVER_URL'] = "audit_server_url_1" + audit.config['NUM_OF_SEND_RETRIES'] = 5 + audit.config['TIME_WAIT_BETWEEN_RETRIES'] = None + self.assertRaises(AuditException, audit._validate) + + @patch.object(urllib2, 'urlopen') + @patch.object(json, 'load', return_value=data) + def test_get_audits(self, mock_urlopen, mock_load): + """test get_audits.""" + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = 10 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + result = audit.get_audits() + self.assertEqual(len(result.transactions), 3) + + @patch.object(urllib2, 'urlopen') + @patch.object(json, 'load') + def test_audit_thread(self, mock_urlopen, mock_load): + """Test audit_thread is executed with no exceptions.""" + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = 10 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + timestamp = 111 + application_id = 'application_id_1' + tracking_id = 'tracking_id_1' + transaction_id = 'transaction_id_1' + transaction_type = 'transaction_type_1' + user_id = 'user_id_1' + external_id = 'external_id_1' + event_details = 'event_details_1' + resource_id = 'resource_id_1' + service_name = 'service_name_1' + audit._audit_thread(timestamp, application_id, tracking_id, + transaction_id, transaction_type, + resource_id, service_name, user_id, external_id, + event_details) + + @patch.object(urllib2, 'urlopen') + @patch.object(json, 'load') + def test_post_data(self, mock_urlopen, mock_load): + """test that post_data is executed with no exceptions.""" + audit_server_url = "audit_server_url_1" + num_of_send_retries = 5 + time_wait_between_retries = 10 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + audit._post_data(None) + + @patch.object(urllib2, 'urlopen', side_effect=Exception) + @patch.object(json, 'load') + def test_post_data_with_retry_exception(self, mock_urlopen, mock_load): + """test post data with retry_exception. + + test that post_data throws an exception when trying to send + a data for num_of_send_retries + """ + audit_server_url = "audit_server_url_1" + num_of_send_retries = 1 + time_wait_between_retries = 1 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + self.assertRaises(AuditException, audit._post_data, None) + + def test_build_query(self): + """test that build_query is exceuted with no exception.""" + query = "query_1" + arg_name = "arg_1" + arg_value = "arg_value_1" + expected_query = query + "%s=%s&" % (arg_name, arg_value) + returned_query = audit._build_query(query, arg_name, arg_value) + self.assertEqual(returned_query, expected_query) + + def test_build_query_with_empty_arg_value(self): + """test build query with empty_arg_value. + + test that build_query throws an exception when called + with empty arg_value. + """ + query = "query_1" + arg_name = "arg_1" + arg_value = None + expected_query = query + returned_query = audit._build_query(query, arg_name, arg_value) + self.assertEqual(returned_query, expected_query) + + @patch.object(threading, 'Thread') + def test_audit(self, mock_thread): + """test that audit is exceuted with no exception.""" + audit_server_url = "audit_server_url_1" + num_of_send_retries = 1 + time_wait_between_retries = 1 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + timestamp = 111 + application_id = 'application_id_1' + tracking_id = 'tracking_id_1' + transaction_id = 'transaction_id_1' + transaction_type = 'transaction_type_1' + user_id = 'user_id_1' + external_id = 'external_id_1' + event_details = 'event_details_1' + resource_id = 'resource_id_1' + service_name = 'service_name_1' + audit.audit(timestamp, application_id, tracking_id, transaction_id, + transaction_type, resource_id, service_name, user_id, + external_id, event_details) + + def test_get_data(self): + """test get data.""" + audit_server_url = "audit_server_url_1" + num_of_send_retries = 1 + time_wait_between_retries = 1 + audit.init(audit_server_url, num_of_send_retries, + time_wait_between_retries) + query = "query1" + self.assertRaises(AuditException, audit._get_data, query) diff --git a/orm/common/client/audit/babel.cfg b/orm/common/client/audit/babel.cfg new file mode 100644 index 00000000..15cd6cb7 --- /dev/null +++ b/orm/common/client/audit/babel.cfg @@ -0,0 +1,2 @@ +[python: **.py] + diff --git a/orm/common/client/audit/requirements.txt b/orm/common/client/audit/requirements.txt new file mode 100644 index 00000000..2683dd48 --- /dev/null +++ b/orm/common/client/audit/requirements.txt @@ -0,0 +1,4 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + diff --git a/orm/common/client/audit/setup.cfg b/orm/common/client/audit/setup.cfg new file mode 100644 index 00000000..96589221 --- /dev/null +++ b/orm/common/client/audit/setup.cfg @@ -0,0 +1,46 @@ +[metadata] +name = audit_client +summary = This is the Audit Client project +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 + Programming Language :: Python :: 3.4 + +[files] +packages = + audit_client + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = audit_client/locale +domain = audit_client + +[update_catalog] +domain = audit_client +output_dir = audit_client/locale +input_file = audit_client/locale/audit_client.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = audit_client/locale/audit_client.pot diff --git a/orm/common/client/audit/setup.py b/orm/common/client/audit/setup.py new file mode 100644 index 00000000..17a2583b --- /dev/null +++ b/orm/common/client/audit/setup.py @@ -0,0 +1,33 @@ +"""setup.py module.""" + +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT + +from setuptools import find_packages +from setuptools import setup + +setup( + name='audit_client', + version='0.1', + description='', + author='', + author_email='', + zip_safe=False, + include_package_data=True, + packages=find_packages(), + install_requires=[] +) diff --git a/orm/common/client/audit/test-requirements.txt b/orm/common/client/audit/test-requirements.txt new file mode 100644 index 00000000..fed55c21 --- /dev/null +++ b/orm/common/client/audit/test-requirements.txt @@ -0,0 +1,9 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +# Hacking already pins down pep8, pyflakes and flake8 +hacking<0.11,>=0.10.0 +testrepository>=0.0.18 +mock<1.1.0,>=1.0 +coverage>=3.6 diff --git a/orm/common/client/audit/tox.ini b/orm/common/client/audit/tox.ini new file mode 100644 index 00000000..6eec9c02 --- /dev/null +++ b/orm/common/client/audit/tox.ini @@ -0,0 +1,22 @@ +[tox] +envlist = py27, cover, pep8 + +[testenv] +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +install_command = pip install -U {opts} {packages} + +commands = python setup.py testr + +[testenv:cover] +#omitting rds/api/app.py and rds/examples/api/functional_test.py +#since they have no need for unit test +commands = + python setup.py testr --slowest --coverage --omit=audit_client/examples/* + coverage report --omit=audit_client/examples/* + +[testenv:pep8] +#cannot handle and 'H102 Apache 2.0 license header not found' and +#'H202 assertRaises Exception too broad' +#since it requires business code changes +commands = flake8 diff --git a/orm/common/client/keystone/debian/aic-orm-keystone.install b/orm/common/client/keystone/debian/aic-orm-keystone.install deleted file mode 100644 index a98784d5..00000000 --- a/orm/common/client/keystone/debian/aic-orm-keystone.install +++ /dev/null @@ -1 +0,0 @@ -aic-orm-keystone/* opt/app/orm/keystone_utils diff --git a/orm/common/client/keystone/debian/changelog b/orm/common/client/keystone/debian/changelog deleted file mode 100755 index 86458417..00000000 --- a/orm/common/client/keystone/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -aic-orm-keystone (3.5.0) stable; urgency=low - - * this is release 3.5.0 - - -- Jenkins job Thu, 24 Jun 2016 14:48:02 +0000 diff --git a/orm/common/client/keystone/debian/compat b/orm/common/client/keystone/debian/compat deleted file mode 100755 index ec635144..00000000 --- a/orm/common/client/keystone/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/orm/common/client/keystone/debian/control b/orm/common/client/keystone/debian/control deleted file mode 100755 index 72cab6d3..00000000 --- a/orm/common/client/keystone/debian/control +++ /dev/null @@ -1,13 +0,0 @@ -Source: aic-orm-keystone -Section: unknown -Priority: optional -Maintainer: orm -Build-Depends: debhelper (>= 8.0.0) -Standards-Version: 3.9.4 -XS-Python-Version: >= 2.7 -Homepage: - -Package: aic-orm-keystone -Architecture: any -Depends: python-keystoneclient (>= 1.2.0), python-requests (>= 2.2.0) -Description: aic-orm-keystone application for ORM diff --git a/orm/common/client/keystone/debian/copyright b/orm/common/client/keystone/debian/copyright deleted file mode 100755 index 60ee39f4..00000000 --- a/orm/common/client/keystone/debian/copyright +++ /dev/null @@ -1,34 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: aic-orm-keystone -Source: - -Files: * -Copyright: - -License: GPL-3.0+ - -Files: debian/* -Copyright: 2016 root -License: GPL-3.0+ - -License: GPL-3.0+ - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". - -# Please also look if there are files or directories which have a -# different copyright/license attached and list them here. -# Please avoid to pick license terms that are more restrictive than the -# packaged work, as it may make Debian's contributions unacceptable upstream. diff --git a/orm/common/client/keystone/debian/postrm b/orm/common/client/keystone/debian/postrm deleted file mode 100755 index dc3dad32..00000000 --- a/orm/common/client/keystone/debian/postrm +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -#rm -rf /opt/app/orm/keystone_utils -#echo "Deleting /opt/app/orm/keystone_utils directory." diff --git a/orm/common/client/keystone/debian/rules b/orm/common/client/keystone/debian/rules deleted file mode 100755 index db692436..00000000 --- a/orm/common/client/keystone/debian/rules +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -%: - dh $@ --buildsystem=python_distutils -D aic-orm-keystone diff --git a/orm/common/client/keystone/debian/source/format b/orm/common/client/keystone/debian/source/format deleted file mode 100755 index 89ae9db8..00000000 --- a/orm/common/client/keystone/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/orm/common/client/keystone/keystone_utils.egg-info/PKG-INFO b/orm/common/client/keystone/keystone_utils.egg-info/PKG-INFO deleted file mode 100644 index d8d62d58..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/PKG-INFO +++ /dev/null @@ -1,21 +0,0 @@ -Metadata-Version: 1.1 -Name: keystone-utils -Version: 0.1 -Summary: keyKeystone utils -Home-page: http://www.openstack.org/ -Author: OpenStack -Author-email: openstack-dev@lists.openstack.org -License: UNKNOWN -Description: UNKNOWN -Platform: UNKNOWN -Classifier: Environment :: OpenStack -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Operating System :: POSIX :: Linux -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 diff --git a/orm/common/client/keystone/keystone_utils.egg-info/SOURCES.txt b/orm/common/client/keystone/keystone_utils.egg-info/SOURCES.txt deleted file mode 100644 index 9522de07..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/SOURCES.txt +++ /dev/null @@ -1,25 +0,0 @@ -MANIFEST.in -README.rst -setup.cfg -setup.py -keystone_utils/__init__.py -keystone_utils/tokens.py -keystone_utils.egg-info/PKG-INFO -keystone_utils.egg-info/SOURCES.txt -keystone_utils.egg-info/dependency_links.txt -keystone_utils.egg-info/not-zip-safe -keystone_utils.egg-info/pbr.json -keystone_utils.egg-info/top_level.txt -keystone_utils/tests/__init__.py -keystone_utils/tests/unit/__init__.py -keystone_utils/tests/unit/test_tokens.py -mock_keystone/__init__.py -mock_keystone/keystoneclient/__init__.py -mock_keystone/keystoneclient/exceptions.py -mock_keystone/keystoneclient/v2_0/__init__.py -mock_keystone/keystoneclient/v2_0/client.py -mock_keystone/keystoneclient/v3/__init__.py -mock_keystone/keystoneclient/v3/client.py -mock_keystone/orm_common/__init__.py -mock_keystone/orm_common/utils/__init__.py -mock_keystone/orm_common/utils/dictator.py \ No newline at end of file diff --git a/orm/common/client/keystone/keystone_utils.egg-info/dependency_links.txt b/orm/common/client/keystone/keystone_utils.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/orm/common/client/keystone/keystone_utils.egg-info/not-zip-safe b/orm/common/client/keystone/keystone_utils.egg-info/not-zip-safe deleted file mode 100644 index 8b137891..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/orm/common/client/keystone/keystone_utils.egg-info/pbr.json b/orm/common/client/keystone/keystone_utils.egg-info/pbr.json deleted file mode 100644 index 0b09e18b..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/pbr.json +++ /dev/null @@ -1 +0,0 @@ -{"is_release": false, "git_version": ""} \ No newline at end of file diff --git a/orm/common/client/keystone/keystone_utils.egg-info/top_level.txt b/orm/common/client/keystone/keystone_utils.egg-info/top_level.txt deleted file mode 100644 index b6d50ed1..00000000 --- a/orm/common/client/keystone/keystone_utils.egg-info/top_level.txt +++ /dev/null @@ -1,2 +0,0 @@ -keystone_utils -mock_keystone diff --git a/orm/services/audit_trail_manager/MANIFEST.in b/orm/services/audit_trail_manager/MANIFEST.in new file mode 100644 index 00000000..c922f11a --- /dev/null +++ b/orm/services/audit_trail_manager/MANIFEST.in @@ -0,0 +1 @@ +recursive-include public * diff --git a/orm/services/audit_trail_manager/audit_server.conf b/orm/services/audit_trail_manager/audit_server.conf new file mode 100644 index 00000000..230988de --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server.conf @@ -0,0 +1,26 @@ +Listen 8776 + + + + WSGIDaemonProcess audit_server user=orm group=orm threads=5 + WSGIScriptAlias / /opt/app/orm/audit_server/audit_server.wsgi + + + Order deny,allow + Deny from all + Allow from localhost + + + + Order deny,allow + Deny from all + Allow from localhost + + + + WSGIProcessGroup audit_server + WSGIApplicationGroup %{GLOBAL} + Require all granted + Allow from all + + diff --git a/orm/services/audit_trail_manager/audit_server.wsgi b/orm/services/audit_trail_manager/audit_server.wsgi new file mode 100644 index 00000000..eede3814 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server.wsgi @@ -0,0 +1,2 @@ +from pecan.deploy import deploy +application = deploy('/opt/app/orm/audit_server/config.py') diff --git a/orm/services/audit_trail_manager/audit_server/__init__.py b/orm/services/audit_trail_manager/audit_server/__init__.py new file mode 100644 index 00000000..13d00841 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/__init__.py @@ -0,0 +1 @@ +"""root package.""" diff --git a/orm/services/audit_trail_manager/audit_server/app.py b/orm/services/audit_trail_manager/audit_server/app.py new file mode 100644 index 00000000..846be84a --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/app.py @@ -0,0 +1,34 @@ +"""app module.""" +import logging +import os + +from pecan import make_app +from pecan.commands import CommandRunner +from audit_server import model +from audit_server.storage import factory + +logger = logging.getLogger(__name__) + + +def setup_app(config): + """setup method.""" + model.init_model() + app_conf = dict(config.app) + factory.database_url = config.database.url + factory.echo_statements = config.database.echo_statements + + app = make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + **app_conf + ) + + logger.info('Starting Audit...') + return app + +def main(): + dir_name = os.path.dirname(__file__) + drive, path_and_file = os.path.splitdrive(dir_name) + path, filename = os.path.split(path_and_file) + runner = CommandRunner() + runner.run(['serve', path+'/config.py']) diff --git a/orm/services/audit_trail_manager/audit_server/controllers/__init__.py b/orm/services/audit_trail_manager/audit_server/controllers/__init__.py new file mode 100644 index 00000000..0819f25d --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/__init__.py @@ -0,0 +1 @@ +"""controllers package.""" diff --git a/orm/services/audit_trail_manager/audit_server/controllers/root.py b/orm/services/audit_trail_manager/audit_server/controllers/root.py new file mode 100644 index 00000000..fa554e96 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/root.py @@ -0,0 +1,10 @@ +"""root controller module.""" + + +from audit_server.controllers.v1 import root as v1 + + +class RootController(object): + """root controller.""" + + v1 = v1.V1Controller() diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/__init__.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/__init__.py new file mode 100644 index 00000000..b933650e --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/__init__.py @@ -0,0 +1 @@ +"""v1 package.""" diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/audit.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/audit.py new file mode 100755 index 00000000..7f4709cb --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/audit.py @@ -0,0 +1,13 @@ +"""audit controller module.""" + +from audit_server.controllers.v1 import configuration +from audit_server.controllers.v1 import logs +from audit_server.controllers.v1 import transaction + + +class AuditController(object): + """audit controller.""" + + transaction = transaction.TransactionController() + logs = logs.LogsController() + configuration = configuration.ConfigurationController() diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/base.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/base.py new file mode 100644 index 00000000..61218964 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/base.py @@ -0,0 +1,49 @@ +"""base module for base wsme types and client side errors.""" + + +import wsme +from wsme import types as wtypes + + +class ClientSideError(wsme.exc.ClientSideError): + """base client side error.""" + + def __init__(self, error, status_code=400): + """init method.. + + :param error: error message to show to the client. + :param status_code: status code to show to the client. + """ + super(ClientSideError, self).__init__(error, status_code) + + +class InputValueError(ClientSideError): + """input value error.""" + + def __init__(self, name, value, status_code=400): + """init method.. + + :param name: the input name for which an error was raised. + :param value: the input value for which an error was raised. + :param status_code: status code to show to the client. + """ + super(InputValueError, self).__init__( + "Invalid value for input {} : {}".format(name, value), status_code) + + +class EntityNotFoundError(ClientSideError): + """entity not found error.""" + + def __init__(self, entity_id): + """init method.. + + :param entity_id: the id for which an entity was not found. + """ + super(EntityNotFoundError, self).__init__( + "Entity not found for {}".format(entity_id), status_code=404) + + +class Base(wtypes.DynamicBase): + """base class for wsme types.""" + + pass diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/configuration.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/configuration.py new file mode 100755 index 00000000..f6bdd0bd --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/configuration.py @@ -0,0 +1,28 @@ +"""Configuration rest API input module.""" + +import logging +from orm_common.utils import utils +from pecan import conf +from pecan import rest +from wsmeext.pecan import wsexpose + +logger = logging.getLogger(__name__) + + +class ConfigurationController(rest.RestController): + """Configuration controller.""" + + @wsexpose(str, str, status_code=200) + def get(self, dump_to_log='false'): + """get method. + + :param dump_to_log: A boolean string that says whether the + configuration should be written to log + :return: A pretty string that contains the service's configuration + """ + logger.info("Get configuration...") + + dump = dump_to_log.lower() == 'true' + utils.set_utils_conf(conf) + result = utils.report_config(conf, dump, logger) + return result diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/logs.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/logs.py new file mode 100644 index 00000000..e8d7b1bb --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/logs.py @@ -0,0 +1,65 @@ +import logging + +from pecan import rest +import wsme +from wsmeext.pecan import wsexpose + +logger = logging.getLogger(__name__) + + +class LogChangeResultWSME(wsme.types.DynamicBase): + """log change result wsme type.""" + + result = wsme.wsattr(str, mandatory=True, default=None) + + def __init__(self, **kwargs): + """"init method.""" + super(LogChangeResult, self).__init__(**kwargs) + + +class LogChangeResult(object): + """log change result type.""" + + def __init__(self, result): + """"init method.""" + self.result = result + + +class LogsController(rest.RestController): + """Logs Audit controller.""" + + @wsexpose(LogChangeResultWSME, str, status_code=201, + rest_content_types='json') + def put(self, level): + """update log level. + + :param level: the log level text name + :return: + """ + + logger.info("Changing log level to [{}]".format(level)) + try: + log_level = logging._levelNames.get(level.upper()) + if log_level is not None: + self._change_log_level(log_level) + result = "Log level changed to {}.".format(level) + logger.info(result) + else: + raise Exception( + "The given log level [{}] doesn't exist.".format(level)) + except Exception as e: + result = "Fail to change log_level. Reason: {}".format( + e.message) + logger.error(result) + return LogChangeResult(result) + + @staticmethod + def _change_log_level(log_level): + path = __name__.split('.') + if len(path) > 0: + root = path[0] + root_logger = logging.getLogger(root) + root_logger.setLevel(log_level) + else: + logger.info("Fail to change log_level to [{}]. " + "the given log level doesn't exist.".format(log_level)) diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/root.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/root.py new file mode 100644 index 00000000..f33a9a73 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/root.py @@ -0,0 +1,10 @@ +"""v1 controller module.""" + + +from audit_server.controllers.v1 import audit + + +class V1Controller(object): + """v1 controller.""" + + audit = audit.AuditController() diff --git a/orm/services/audit_trail_manager/audit_server/controllers/v1/transaction.py b/orm/services/audit_trail_manager/audit_server/controllers/v1/transaction.py new file mode 100755 index 00000000..0407e5fa --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/controllers/v1/transaction.py @@ -0,0 +1,203 @@ +"""transaction controller module.""" + +from audit_server.model.transaction import Model as TransactionModel +from audit_server.model.transaction_query import Model as QueryModel +from audit_server.services import transaction as transaction_service +import base +import logging +from pecan import rest +import wsme +from wsme import types as wtypes +from wsmeext.pecan import wsexpose + + +logger = logging.getLogger(__name__) + + +class Transaction(base.Base): + """transaction type.""" + + timestamp = wsme.wsattr(long, mandatory=True) + user_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + application_id = wsme.wsattr(wtypes.text, mandatory=True) + tracking_id = wsme.wsattr(wtypes.text, mandatory=True) + external_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + transaction_id = wsme.wsattr(wtypes.text, mandatory=True) + transaction_type = wsme.wsattr(wtypes.text, mandatory=True) + event_details = wsme.wsattr(wtypes.text, mandatory=False, default=None) + resource_id = wsme.wsattr(wtypes.text, mandatory=True) + service_name = wsme.wsattr(wtypes.text, mandatory=True) + + def __init__(self, **kwargs): + """init method.""" + super(Transaction, self).__init__(**kwargs) + + def to_model(self): + """transform the Transaction to a TransactionModel.""" + return TransactionModel(self.timestamp, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + def __str__(self): + """return a string representation of the object.""" + return "Transaction:[ " \ + "timestamp={}, " \ + "user_id={}, " \ + "application_id={}, " \ + "tracking_id={}, " \ + "external_id={}, " \ + "transaction_id={}," \ + "transaction_type={}, " \ + "event_details={}, " \ + "resource_id={}," \ + "service_name={}]".format(self.timestamp, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + +class Query(base.Base): + """query type.""" + + timestamp_from = wsme.wsattr(long, mandatory=False, default=None) + timestamp_to = wsme.wsattr(long, mandatory=False, default=None) + user_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + application_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + tracking_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + external_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + transaction_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + transaction_type = wsme.wsattr(wtypes.text, mandatory=False, default=None) + event_details = wsme.wsattr(wtypes.text, mandatory=False, default=None) + resource_id = wsme.wsattr(wtypes.text, mandatory=False, default=None) + service_name = wsme.wsattr(wtypes.text, mandatory=False, default=None) + + def __init__(self, **kwargs): + """init method.""" + super(Query, self).__init__(**kwargs) + + def to_model(self): + """transform the Query to a QueryModel.""" + return QueryModel(self.timestamp_from, + self.timestamp_to, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + def __str__(self): + """return a string representation of the object.""" + return "TransactionQuery:[ " \ + "timestamp_from={}, " \ + "timestamp_to={}, " \ + "user_id={}, " \ + "application_id={}, " \ + "tracking_id={}, " \ + "external_id={}, " \ + "transaction_id={}," \ + "transaction_type={}, " \ + "event_details={}, " \ + "resource_id={}," \ + "service_name={}]".format(self.timestamp_from, + self.timestamp_to, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + +class QueryResult(base.Base): + """query result type.""" + + transactions = wsme.wsattr([Transaction], mandatory=False, default=None) + + def __init__(self, **kwargs): + """"init method.""" + super(QueryResult, self).__init__(**kwargs) + + +class TransactionController(rest.RestController): + """Transaction Audit controller.""" + + @wsexpose(QueryResult, Query, int, str, rest_content_types='json') + def get_all(self, q=None, limit=10, marker=None): + """get all transactions that meet the given query. + + :param q: the query to use in order to search for relevant. + transactions. + :param limit: the maximun number of transactions to return. + :param marker: a place order for pagination (not implemented yet). + + Example of usage: + http://127.0.0.1:8777/v1/audit/transaction?q.timestamp_from=1111&q. + timestamp_to=5555&q.user_id=user1&q.application_id=SSP&limit=15& + marker=1 + """ + logger.debug("Getting audit records...start") + logger.info( + "Getting audit records for the query:[{}]...start".format(q)) + model = None + if q is not None: + model = q.to_model() + # page or marker (the last row in the previous page) + query_result = transaction_service.get_transactions(model, limit, + marker) + logger.debug("Getting audit records...end") + return query_result + + """ + + """ + + @wsexpose(None, body=Transaction, status_code=201, + rest_content_types='json') + def post(self, transaction_input): + """add a new transaction. + + :param transaction_input: the new transaction values + + Example of usage: + http://127.0.0.1:8777/v1/audit/transaction + Headers: Content-Type=application/json + Body:{ + "timestamp":111, + "user_id":"user1", + "application_id":"application_id1", + "tracking_id":"tracking_id1", + "external_id":"external_id1", + "transaction_id":"transaction_id1", + "transaction_type":"transaction_type1", + "event_details":"event_details1", + "status":"status1", + "resource_id":"resource_id1", + "service_name":"service_name1" + } + """ + logger.debug("Posting new audit record...start") + logger.info("Posting new audit record: [{}]".format( + transaction_input)) + model = transaction_input.to_model() + transaction_service.add_transaction(model) + logger.debug("Auditing record...end") diff --git a/orm/common/client/keystone/debian/docs b/orm/services/audit_trail_manager/audit_server/external_mock/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from orm/common/client/keystone/debian/docs rename to orm/services/audit_trail_manager/audit_server/external_mock/__init__.py diff --git a/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/__init__.py b/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/__init__.py b/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/utils.py b/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/utils.py new file mode 100755 index 00000000..e1fc153a --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/external_mock/orm_common/utils/utils.py @@ -0,0 +1,13 @@ +"""Utils module mock.""" + + +def report_config(conf, dump=False): + """Mock report_config function.""" + + pass + + +def set_utils_conf(conf): + """Mock set_utils_conf function.""" + + pass diff --git a/orm/services/audit_trail_manager/audit_server/model/__init__.py b/orm/services/audit_trail_manager/audit_server/model/__init__.py new file mode 100644 index 00000000..1b8f0e19 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/model/__init__.py @@ -0,0 +1,16 @@ +"""model package.""" + +from pecan import conf # noqa + + +def init_model(): + """Thi is a stub method which is called at application startup time. + + If you need to bind to a parsed database configuration, set up tables or + ORM classes, or perform any database initialization, this is the + recommended place to do it. + + For more information working with databases, and some common recipes, + see http://pecan.readthedocs.org/en/latest/databases.html + """ + pass diff --git a/orm/services/audit_trail_manager/audit_server/model/transaction.py b/orm/services/audit_trail_manager/audit_server/model/transaction.py new file mode 100755 index 00000000..ce1b6d94 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/model/transaction.py @@ -0,0 +1,47 @@ +"""transaction model module.""" + + +class Model(object): + """transaction model.""" + + def __init__(self, timestamp, user_id, application_id, tracking_id, + external_id, transaction_id, transaction_type, event_details, + resource_id, service_name): + """init method.""" + self.timestamp = timestamp + self.user_id = user_id + self.application_id = application_id + self.tracking_id = tracking_id + self.external_id = external_id + self.transaction_id = transaction_id + self.transaction_type = transaction_type + self.event_details = event_details + self.resource_id = resource_id + self.service_name = service_name + + def __str__(self): + """return a string representation of the object.""" + return "Transaction:[ " \ + "timestamp={}, " \ + "user_id={}, " \ + "application_id={}, " \ + "tracking_id={}, " \ + "external_id={}, " \ + "transaction_id={}," \ + "transaction_type={}, " \ + "event_details={}, " \ + "resource_id={}," \ + "service_name={}]".format(self.timestamp, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) + + def __repr__(self): + """return a string representation of the transaction object.""" + return str(self) diff --git a/orm/services/audit_trail_manager/audit_server/model/transaction_query.py b/orm/services/audit_trail_manager/audit_server/model/transaction_query.py new file mode 100755 index 00000000..22677b73 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/model/transaction_query.py @@ -0,0 +1,46 @@ +"""transaction_query model module.""" + + +class Model(object): + """transaction query model.""" + + def __init__(self, timestamp_from, timestamp_to, user_id, application_id, + tracking_id, external_id, transaction_id, transaction_type, + event_details, resource_id, service_name): + """init method.""" + self.timestamp_from = timestamp_from + self.timestamp_to = timestamp_to + self.user_id = user_id + self.application_id = application_id + self.tracking_id = tracking_id + self.external_id = external_id + self.transaction_id = transaction_id + self.transaction_type = transaction_type + self.event_details = event_details + self.resource_id = resource_id + self.service_name = service_name + + def __str__(self): + """return a string representation of the object.""" + return "Transaction:[ " \ + "timestamp_from={}, " \ + "timestamp_to={}" \ + "user_id={}, " \ + "application_id={}, " \ + "tracking_id={}, " \ + "external_id={}, " \ + "transaction_id={}," \ + "transaction_type={}, " \ + "event_details={}, " \ + "resource_id={}," \ + "service_name={}]".format(self.timestamp_from, + self.timestamp_to, + self.user_id, + self.application_id, + self.tracking_id, + self.external_id, + self.transaction_id, + self.transaction_type, + self.event_details, + self.resource_id, + self.service_name) diff --git a/orm/services/audit_trail_manager/audit_server/model/transaction_query_result.py b/orm/services/audit_trail_manager/audit_server/model/transaction_query_result.py new file mode 100644 index 00000000..b1a7c003 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/model/transaction_query_result.py @@ -0,0 +1,14 @@ +"""transaction_query_result model module.""" + + +class Model(object): + """transaction query result model.""" + + def __init__(self, transactions): + """init method.""" + self.transactions = transactions + + def __str__(self): + """return a string representation of the object.""" + return "TransactionQueryResult:[ " \ + "transactions={}]".format(self.transactions) diff --git a/orm/services/audit_trail_manager/audit_server/services/__init__.py b/orm/services/audit_trail_manager/audit_server/services/__init__.py new file mode 100644 index 00000000..28dd3664 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/services/__init__.py @@ -0,0 +1 @@ +"""services package.""" diff --git a/orm/services/audit_trail_manager/audit_server/services/base.py b/orm/services/audit_trail_manager/audit_server/services/base.py new file mode 100644 index 00000000..ab9e5311 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/services/base.py @@ -0,0 +1,7 @@ +"""base module for all services, holds errors.""" + + +class Error(Exception): + """base error class.""" + + pass diff --git a/orm/services/audit_trail_manager/audit_server/services/transaction.py b/orm/services/audit_trail_manager/audit_server/services/transaction.py new file mode 100644 index 00000000..f96a56b2 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/services/transaction.py @@ -0,0 +1,35 @@ +"""transaction service module.""" + +import logging + +from audit_server.model.transaction_query_result import \ + Model as QueryResultModel +from audit_server.storage import factory + +logger = logging.getLogger(__name__) + + +def add_transaction(transaction): + """add a new transaction. + + :param transaction: the new transaction object. + """ + logger.debug("Auditing record: [{}] ...start".format(transaction)) + conn = factory.get_transaction_connection() + conn.add_record(transaction) + logger.debug("Auditing record...end") + + +def get_transactions(query, limit, marker): + """get all transactions that meet the given query. + + :param q: the query to use in order to search for relevant. + transactions. + :param limit: the maximun number of transactions to return. + :param marker: a place order for pagination (not implemented yet). + """ + logger.debug("Getting records for query:{}...start".format(query)) + conn = factory.get_transaction_connection() + transactions = conn.get_records(query, limit, marker) + logger.debug("Getting records for query...end") + return QueryResultModel(transactions) diff --git a/orm/services/audit_trail_manager/audit_server/storage/__init__.py b/orm/services/audit_trail_manager/audit_server/storage/__init__.py new file mode 100644 index 00000000..aa4a391c --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/storage/__init__.py @@ -0,0 +1 @@ +"""storage package.""" diff --git a/orm/services/audit_trail_manager/audit_server/storage/factory.py b/orm/services/audit_trail_manager/audit_server/storage/factory.py new file mode 100644 index 00000000..cd4e2243 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/storage/factory.py @@ -0,0 +1,12 @@ +"""factory module.""" + + +from audit_server.storage.mysql.transaction import Connection as Transaction + +database_url = 'NA' +echo_statements = False + + +def get_transaction_connection(): + """return a transaction orm implementation.""" + return Transaction(database_url, echo_statements) diff --git a/orm/services/audit_trail_manager/audit_server/storage/mysql/__init__.py b/orm/services/audit_trail_manager/audit_server/storage/mysql/__init__.py new file mode 100644 index 00000000..3a36ad06 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/storage/mysql/__init__.py @@ -0,0 +1 @@ +"""mysql package.""" diff --git a/orm/services/audit_trail_manager/audit_server/storage/mysql/transaction.py b/orm/services/audit_trail_manager/audit_server/storage/mysql/transaction.py new file mode 100755 index 00000000..c25b31b1 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/storage/mysql/transaction.py @@ -0,0 +1,168 @@ +"""mysql transaction module.""" + +import logging + +from sqlalchemy import Column, Integer, Text, BigInteger, asc +from sqlalchemy import create_engine +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.declarative.api import declarative_base +from sqlalchemy.orm import sessionmaker + +from audit_server.model.transaction import Model +from audit_server.storage import transaction + +Base = declarative_base() +logger = logging.getLogger(__name__) + + +class Record(Base): + """record base class.""" + + __tablename__ = 'transactions' + + id = Column(Integer, autoincrement=True, primary_key=True) + timestamp = Column(BigInteger) + user_id = Column(Text) + application_id = Column(Text) + tracking_id = Column(Text) + external_id = Column(Text) + transaction_id = Column(Text) + transaction_type = Column(Text) + event_details = Column(Text) + resource_id = Column(Text) + service_name = Column(Text) + + +class Connection(transaction.Base): + """Implements mysql DB.""" + + def __init__(self, url, echo_statements): + """init method.""" + self._engine = create_engine(url, echo=echo_statements) + self._session_maker = sessionmaker(bind=self._engine) + pass + + def add_record(self, transaction_record): + """add a new transaction record. + + :param transaction_record: the new transaction record. + """ + logger.debug( + "Auditing record: [{}] ...start".format(transaction_record)) + try: + session = self._session_maker() + session.add(Record(timestamp=transaction_record.timestamp, + user_id=transaction_record.user_id, + application_id=transaction_record. + application_id, + tracking_id=transaction_record.tracking_id, + external_id=transaction_record.external_id, + transaction_id=transaction_record. + transaction_id, + transaction_type=transaction_record. + transaction_type, + event_details=transaction_record.event_details, + resource_id=transaction_record.resource_id, + service_name=transaction_record.service_name)) + session.commit() + # All other exceptions will be raised + except IntegrityError as e: + # Except Exception as e: + session.rollback() + # Raise the exception only if it's not a duplicate entry exception + if 'duplicate entry' in e.message.lower(): + logger.warning( + "Fail to audit record - Duplicate entry: {}".format( + e)) + else: + logger.warning("Fail to audit record: {}".format(e)) + raise e + + finally: + session.close() + logger.debug("Auditing record...end") + + def get_records(self, query, limit=10, marker=None): + """get all records records that meet the given query. + + :param q: the query to use in order to search for relevant. + records. + :param limit: the maximun number of records to return. + :param marker: a place order for pagination (not implemented yet). + """ + logger.debug("Getting records using query:[{}]...start".format(query)) + try: + session = self._session_maker() + records = session.query(Record) + if query is not None: + records = Connection._add_filter(records, query, marker) + records = records.order_by(asc(Record.timestamp)) + records = records.limit(limit) + records_result = records.all() + transactions = [] + for record in records_result: + timestamp = record.timestamp + user_id = record.user_id + application_id = record.application_id + tracking_id = record.tracking_id + external_id = record.external_id + transaction_id = record.transaction_id + transaction_type = record.transaction_type + event_details = record.event_details + resource_id = record.resource_id + service_name = record.service_name + model = Model(timestamp, user_id, application_id, tracking_id, + external_id, transaction_id, transaction_type, + event_details, resource_id, service_name) + transactions.append(model) + logger.debug( + "Getting records using query:[{}] " + "return the result :[{}]...start".format( + query, transactions)) + logger.debug("Getting records using query...end") + return transactions + finally: + session.close() + + @staticmethod + def _add_filter(records, query, marker): + """add filter to the select based on the given query. + + :param records: the records created so far. + :param query: the query to use in order to search for relevant. + records. + :param marker: a place order for pagination (not implemented yet). + """ + logger.debug("add_filter for query: [{}] ...start".format(query)) + if marker is not None: + records = records.filter(Record.timestamp > marker) + if query.timestamp_from is not None: + records = records.filter(Record.timestamp >= query.timestamp_from) + if query.timestamp_to is not None: + records = records.filter(Record.timestamp <= query.timestamp_to) + records = Connection._add_filter_eq(records, Record.user_id, + query.user_id) + records = Connection._add_filter_eq(records, Record.application_id, + query.application_id) + records = Connection._add_filter_eq(records, Record.tracking_id, + query.tracking_id) + records = Connection._add_filter_eq(records, Record.external_id, + query.external_id) + records = Connection._add_filter_eq(records, Record.transaction_id, + query.transaction_id) + records = Connection._add_filter_eq(records, Record.transaction_type, + query.transaction_type) + records = Connection._add_filter_eq(records, Record.event_details, + query.event_details) + records = Connection._add_filter_eq(records, Record.resource_id, + query.resource_id) + records = Connection._add_filter_eq(records, Record.service_name, + query.service_name) + logger.debug("add_filter for query: [{}] ...end".format(query)) + return records + + @staticmethod + def _add_filter_eq(records, key, value): + if value is not None: + records = records.filter(key == value) + return records diff --git a/orm/services/audit_trail_manager/audit_server/storage/transaction.py b/orm/services/audit_trail_manager/audit_server/storage/transaction.py new file mode 100644 index 00000000..9b007c6c --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/storage/transaction.py @@ -0,0 +1,17 @@ +"""transaction interface module.""" + + +class Base(object): + """transaction base class.""" + + def __init__(self, url): + """init method.""" + pass + + def add_record(self, transaction): + """add new transaction record to the db.""" + raise NotImplementedError("Please Implement this method") + + def get_records(self, query): + """get transactions that meet the given query from the db.""" + raise NotImplementedError("Please Implement this method") diff --git a/orm/services/audit_trail_manager/audit_server/templates/error.html b/orm/services/audit_trail_manager/audit_server/templates/error.html new file mode 100644 index 00000000..f2d97961 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/templates/error.html @@ -0,0 +1,12 @@ +<%inherit file="layout.html" /> + +## provide definitions for blocks we want to redefine +<%def name="title()"> + Server Error ${status} + + +## now define the body of the template +
+

Server Error ${status}

+
+

${message}

diff --git a/orm/services/audit_trail_manager/audit_server/templates/index.html b/orm/services/audit_trail_manager/audit_server/templates/index.html new file mode 100644 index 00000000..f17c3862 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/templates/index.html @@ -0,0 +1,34 @@ +<%inherit file="layout.html" /> + +## provide definitions for blocks we want to redefine +<%def name="title()"> + Welcome to Pecan! + + +## now define the body of the template +
+

+
+ +
+ +

This is a sample Pecan project.

+ +

+ Instructions for getting started can be found online at pecanpy.org +

+ +

+ ...or you can search the documentation here: +

+ +
+
+ + +
+ Enter search terms or a module, class or function name. +
+ +
diff --git a/orm/services/audit_trail_manager/audit_server/templates/layout.html b/orm/services/audit_trail_manager/audit_server/templates/layout.html new file mode 100644 index 00000000..40908591 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/templates/layout.html @@ -0,0 +1,22 @@ + + + ${self.title()} + ${self.style()} + ${self.javascript()} + + + ${self.body()} + + + +<%def name="title()"> + Default Title + + +<%def name="style()"> + + + +<%def name="javascript()"> + + diff --git a/orm/services/audit_trail_manager/audit_server/tests/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/__init__.py new file mode 100644 index 00000000..d2524392 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/__init__.py @@ -0,0 +1 @@ +"""test package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/__init__.py new file mode 100644 index 00000000..0819f25d --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/__init__.py @@ -0,0 +1 @@ +"""controllers package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/functional_test.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/functional_test.py new file mode 100755 index 00000000..6bf55bb6 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/functional_test.py @@ -0,0 +1,154 @@ +"""Base classes for API tests.""" + +import pecan +import pecan.testing +import unittest + + +class FunctionalTest(unittest.TestCase): + """Used for functional tests of Pecan controllers. + + Used in case when you need to test your literal application and its + integration with the framework. + """ + + PATH_PREFIX = '' + + def setUp(self): + """set up method.""" + self.app = self._make_app() + + def _make_app(self, enable_acl=False): + """make app method.""" + self.config = { + 'app': { + 'root': 'audit_server.controllers.root.RootController', + 'modules': ['audit_server'] + }, + 'wsme': { + 'debug': True, + }, + 'database': { + 'url': 'mysql://dummy:dummy@1.1.1.1/orm_audit?charset=utf8', + 'echo_statements': False + } + } + + return pecan.testing.load_test_app(self.config) + + def tearDown(self): + """tear down method.""" + super(FunctionalTest, self).tearDown() + pecan.set_config({}, overwrite=True) + + def put_json(self, path, params, expect_errors=False, headers=None, + extra_environ=None, status=None): + """Send simulated HTTP PUT request to Pecan test app. + + :param path: url path of target service + :param params: content for wsgi.input of request + :param expect_errors: boolean value whether an error is expected based + on request + :param headers: A dictionary of headers to send along with the request + :param extra_environ: A dictionary of environ variables to send along + with the request + :param status: Expected status code of response + """ + return self.post_json(path=path, params=params, + expect_errors=expect_errors, + headers=headers, extra_environ=extra_environ, + status=status, method="put") + + def post_json(self, path, params, expect_errors=False, headers=None, + method="post", extra_environ=None, status=None): + """Send simulated HTTP POST request to Pecan test app. + + :param path: url path of target service + :param params: content for wsgi.input of request + :param expect_errors: boolean value whether an error is expected based + on request + :param headers: A dictionary of headers to send along with the request + :param method: Request method type. Appropriate method function call + should be used rather than passing attribute in. + :param extra_environ: A dictionary of environ variables to send along + with the request + :param status: Expected status code of response + """ + full_path = self.PATH_PREFIX + path + response = getattr(self.app, "%s_json" % method)( + str(full_path), + params=params, + headers=headers, + status=status, + extra_environ=extra_environ, + expect_errors=expect_errors + ) + return response + + def delete(self, path, expect_errors=False, headers=None, + extra_environ=None, status=None): + """Send simulated HTTP DELETE request to Pecan test app. + + :param path: url path of target service + :param expect_errors: boolean value whether an error is expected based + on request + :param headers: A dictionary of headers to send along with the request + :param extra_environ: A dictionary of environ variables to send along + with the request + :param status: Expected status code of response + """ + full_path = self.PATH_PREFIX + path + response = self.app.delete(str(full_path), + headers=headers, + status=status, + extra_environ=extra_environ, + expect_errors=expect_errors) + return response + + def get_json(self, path, expect_errors=False, headers=None, + extra_environ=None, q=None, groupby=None, status=None, + override_params=None, **params): + """Send simulated HTTP GET request to Pecan test app. + + :param path: url path of target service + :param expect_errors: boolean value whether an error is expected based + on request + :param headers: A dictionary of headers to send along with the request + :param extra_environ: A dictionary of environ variables to send along + with the request + :param q: list of queries consisting of: field, value, op, and type + keys + :param groupby: list of fields to group by + :param status: Expected status code of response + :param override_params: literally encoded query param string + :param params: content for wsgi.input of request + """ + q = q or [] + groupby = groupby or [] + full_path = self.PATH_PREFIX + path + if override_params: + all_params = override_params + else: + query_params = {'q.field': [], + 'q.value': [], + 'q.op': [], + 'q.type': [], + } + for query in q: + for name in ['field', 'op', 'value', 'type']: + query_params['q.%s' % name].append(query.get(name, '')) + all_params = {} + all_params.update(params) + if q: + all_params.update(query_params) + if groupby: + all_params.update({'groupby': groupby}) + response = self.app.get(full_path, + params=all_params, + headers=headers, + extra_environ=extra_environ, + expect_errors=expect_errors, + status=status) + if not expect_errors: + response = response.json + return response diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/__init__.py new file mode 100644 index 00000000..b933650e --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/__init__.py @@ -0,0 +1 @@ +"""v1 package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/functional_test.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/functional_test.py new file mode 100644 index 00000000..0ad6b225 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/functional_test.py @@ -0,0 +1,10 @@ +"""functional_test module.""" + + +from audit_server.tests.controllers.functional_test import FunctionalTest + + +class FunctionalTest(FunctionalTest): + """base functional test class.""" + + PATH_PREFIX = '/v1' diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_base.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_base.py new file mode 100644 index 00000000..b2c62ca2 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_base.py @@ -0,0 +1,38 @@ +"""test_base module.""" + + +from audit_server.controllers.v1.base import ClientSideError +from audit_server.controllers.v1.base import EntityNotFoundError +from audit_server.controllers.v1.base import InputValueError +import unittest + + +class Test(unittest.TestCase): + """test case class.""" + + def test_init_ClientSideError(self): + """test the init method.""" + expected_msg = "This is an error" + expected_code = 400 + error = ClientSideError(expected_msg) + self.assertEqual(error.msg, expected_msg) + self.assertEqual(error.code, expected_code) + + def test_init_InputValueError(self): + """test the init method.""" + name = "name1" + value = "value1" + expected_msg = "Invalid value for input {} : {}".format(name, value) + expected_code = 400 + error = InputValueError(name, value) + self.assertEqual(error.msg, expected_msg) + self.assertEqual(error.code, expected_code) + + def test_init_EntityNotFoundError(self): + """test the init method.""" + id = "id1" + expected_msg = "Entity not found for {}".format(id) + expected_code = 404 + error = EntityNotFoundError(id) + self.assertEqual(error.msg, expected_msg) + self.assertEqual(error.code, expected_code) diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_configuration.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_configuration.py new file mode 100755 index 00000000..aa94c050 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_configuration.py @@ -0,0 +1,14 @@ +"""Get configuration module unittests.""" +from audit_server.controllers.v1 import configuration as root +from audit_server.tests.controllers.v1.functional_test import FunctionalTest +from mock import patch + + +class TestGetConfiguration(FunctionalTest): + """Main get configuration test case.""" + + @patch.object(root.utils, 'report_config', return_value='12345') + def test_get_configuration_success(self, input): + """Test get_configuration returns the expected value on success.""" + response = self.app.get('/v1/audit/configuration') + self.assertEqual(response.json, '12345') diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_logs.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_logs.py new file mode 100755 index 00000000..80a7377e --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_logs.py @@ -0,0 +1,25 @@ +"""Logs module unittests.""" +from audit_server.tests.controllers.v1.functional_test import FunctionalTest + + +class TestLogs(FunctionalTest): + """logs tests.""" + + def test_change_log_level_fail(self): + response = self.app.put('/v1/audit/logs/1') + expected_result = { + "result": "Fail to change log_level. Reason: " + "The given log level [1] doesn't exist."} + self.assertEqual(expected_result, response.json) + + def test_change_log_level_none(self): + response = self.app.put('/v1/audit/logs', expect_errors=True) + expected_result = 'Missing argument: "level"' + self.assertEqual(response.json["faultstring"], expected_result) + self.assertEqual(response.status_code, 400) + + def test_change_log_level_success(self): + response = self.app.put('/v1/audit/logs/debug') + expected_result = {'result': 'Log level changed to debug.'} + self.assertEqual(response.json, expected_result) + self.assertEqual(response.status_code, 201) diff --git a/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_transaction.py b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_transaction.py new file mode 100755 index 00000000..3e2a550e --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/controllers/v1/test_transaction.py @@ -0,0 +1,97 @@ +"""test_transaction module.""" + + +from mock import patch + +from audit_server.controllers.v1.transaction import QueryResult +from audit_server.model.transaction import Model as TransactionModel +from audit_server.model.transaction_query_result import \ + Model as TransactionQueryResultModel +from audit_server.services import transaction as transaction_service +from audit_server.tests.controllers.v1.functional_test import FunctionalTest + + +class Test(FunctionalTest): + """test transaction class.""" + + transaction_model = TransactionModel(timestamp=111, user_id="user_id_1", + application_id="application_id_1", + tracking_id="tracking_id_1", + external_id="external_id_1", + transaction_id="transaction_id_1", + transaction_type="transaction_type_1", + event_details="event_details_1", + resource_id="resource_id_1", + service_name="service_name_1") + + @patch.object(transaction_service, 'get_transactions', + return_value=TransactionQueryResultModel( + [transaction_model])) + def test_get_all(self, mock_get_transactions): + """test that get_one returns an appropriate json result.""" + url = "/audit/transaction?q.timestamp_from=1111&q.timestamp_to=5555&" \ + "q.user_id=user1&q.application_id=SSP&limit=15&marker=1" + output = self.get_json(url) + returned_transactions = output['transactions'] + self.assertIsNotNone(returned_transactions) + self.assertEqual(len(returned_transactions), 1) + transaction = returned_transactions[0] + self._assert_returned_transaction(transaction) + + @patch.object(transaction_service, 'get_transactions', + return_value=TransactionQueryResultModel( + [transaction_model])) + def test_get_all_with_empty_query(self, mock_get_transactions): + """Test that get_one accepts an empty query and returns a response.""" + url = "/audit/transaction?limit=15&marker=1" + output = self.get_json(url) + returned_transactions = output['transactions'] + self.assertIsNotNone(returned_transactions) + self.assertEqual(len(returned_transactions), 1) + transaction = returned_transactions[0] + self._assert_returned_transaction(transaction) + + @patch.object(transaction_service, 'add_transaction') + def test_post(self, mock_transaction_service): + """Test that post is executed with no exceptions.""" + url = "/audit/transaction" + body = { + "timestamp": 111, + "user_id": "user_1", + "application_id": "application_id_1", + "tracking_id": "tracking_id_1", + "external_id": "external_id_1", + "transaction_id": "transaction_id_1", + "transaction_type": "transaction_type_1", + "event_details": "event_details_1", + "resource_id": "resource_id_1", + "service_name": "service_name_1" + } + self.post_json(url, body) + + def test_init_QueryResult(self): + """test the init method.""" + QueryResult(**{"prop1": "value1", "prop2": "value2"}) + + def _assert_returned_transaction(self, transaction): + """Check the returned trasaction.""" + self.assertEqual(transaction['timestamp'], + self.transaction_model.timestamp) + self.assertEqual(transaction['user_id'], + self.transaction_model.user_id) + self.assertEqual(transaction['application_id'], + self.transaction_model.application_id) + self.assertEqual(transaction['tracking_id'], + self.transaction_model.tracking_id) + self.assertEqual(transaction['external_id'], + self.transaction_model.external_id) + self.assertEqual(transaction['transaction_id'], + self.transaction_model.transaction_id) + self.assertEqual(transaction['transaction_type'], + self.transaction_model.transaction_type) + self.assertEqual(transaction['event_details'], + self.transaction_model.event_details) + self.assertEqual(transaction['resource_id'], + self.transaction_model.resource_id) + self.assertEqual(transaction['service_name'], + self.transaction_model.service_name) diff --git a/orm/services/audit_trail_manager/audit_server/tests/model/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/model/__init__.py new file mode 100644 index 00000000..6369aa7b --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/model/__init__.py @@ -0,0 +1 @@ +"""model package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/model/test_transaction_query_result.py b/orm/services/audit_trail_manager/audit_server/tests/model/test_transaction_query_result.py new file mode 100644 index 00000000..718fda2f --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/model/test_transaction_query_result.py @@ -0,0 +1,19 @@ +"""test_transaction_query_result module.""" + + +import unittest + +from audit_server.model.transaction_query_result import \ + Model as TransactionQueryResultModel + + +class Test(unittest.TestCase): + """test transaction query result class.""" + + def test_str(self): + """test str method.""" + transactions = [] + model = TransactionQueryResultModel(transactions) + self.assertEqual(str(model), + "TransactionQueryResult:[ transactions={}]".format( + transactions)) diff --git a/orm/services/audit_trail_manager/audit_server/tests/services/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/services/__init__.py new file mode 100644 index 00000000..28dd3664 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/services/__init__.py @@ -0,0 +1 @@ +"""services package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/services/test_base.py b/orm/services/audit_trail_manager/audit_server/tests/services/test_base.py new file mode 100644 index 00000000..a94506cd --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/services/test_base.py @@ -0,0 +1,14 @@ +"""test_base module.""" + + +from audit_server.services.base import Error +import unittest + + +class Test(unittest.TestCase): + """test base class.""" + + def test_init_Error(self): + """Test that init of Error succeeded.""" + Error("test") + pass diff --git a/orm/services/audit_trail_manager/audit_server/tests/services/test_transaction.py b/orm/services/audit_trail_manager/audit_server/tests/services/test_transaction.py new file mode 100755 index 00000000..3916e56b --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/services/test_transaction.py @@ -0,0 +1,56 @@ +"""test_transaction module.""" + +import unittest + +from mock import patch + +from audit_server.model.transaction import Model as TransactionModel +from audit_server.model.transaction_query import Model as TransactionQuery +from audit_server.services import transaction as TransactionService +from audit_server.storage import factory + + +class Test(unittest.TestCase): + """test transaction class.""" + + @patch.object(factory, 'get_transaction_connection') + def test_add_transaction(self, mock_factory): + """test that add_transaction doesn't throws any exception.""" + timestamp = 111 + user_id = "user_id_1" + application_id = "application_id_1" + tracking_id = "tracking_id_1" + external_id = "external_id_1" + transaction_id = "transaction_id_1" + transaction_type = "transaction_type_1" + event_details = "event_details_1" + resource_id = "resource_id_1" + service_name = "service_name_1" + transaction = TransactionModel(timestamp, user_id, application_id, + tracking_id, external_id, + transaction_id, transaction_type, + event_details, resource_id, + service_name) + TransactionService.add_transaction(transaction) + + @patch.object(factory, 'get_transaction_connection') + def test_get_transaction(query, mock_factory): + """test that get_transaction doesn't throws any exception.""" + timestamp_from = 111 + timestamp_to = 555 + user_id = "user_id_1" + application_id = "application_id_1" + tracking_id = "tracking_id_1" + external_id = "external_id_1" + transaction_id = "transaction_id_1" + transaction_type = "transaction_type_1" + event_details = "event_details_1" + resource_id = "resource_id_1" + service_name = "service_name_1" + query = TransactionQuery(timestamp_from, timestamp_to, user_id, + application_id, tracking_id, external_id, + transaction_id, transaction_type, + event_details, resource_id, service_name) + limit = 10 + marker = 1 + TransactionService.get_transactions(query, limit, marker) diff --git a/orm/services/audit_trail_manager/audit_server/tests/storage/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/storage/__init__.py new file mode 100644 index 00000000..aa4a391c --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/storage/__init__.py @@ -0,0 +1 @@ +"""storage package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/__init__.py b/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/__init__.py new file mode 100644 index 00000000..3a36ad06 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/__init__.py @@ -0,0 +1 @@ +"""mysql package.""" diff --git a/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/test_transaction.py b/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/test_transaction.py new file mode 100755 index 00000000..87d38b23 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/storage/mysql/test_transaction.py @@ -0,0 +1,226 @@ +"""test_transaction module.""" + +import unittest + +from mock import patch +from sqlalchemy import create_engine +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import sessionmaker + + +from audit_server.model.transaction import Model as TransactionModel +from audit_server.model.transaction_query import Model as TransactionQueryModel +from audit_server.storage.mysql.transaction import Connection +from audit_server.storage.mysql.transaction import Record + + +class Test(unittest.TestCase): + """test transaction class.""" + + transaction = TransactionModel(timestamp=111, user_id="user_id_1", + application_id="application_id_1", + tracking_id="tracking_id_1", + external_id="external_id_1", + transaction_id="transaction_id_1", + transaction_type="transaction_type_1", + event_details="event_details_1", + resource_id="resource_id_1", + service_name="service_name_1") + + transaction_query = TransactionQueryModel(111, 222, "user_id1", + "application_id1", + "tracking_id1", + "external_id1", + "transaction_id1", + "transaction_type1", + "event_details1", + "resource_id_1", + "service_name_1") + + @patch.object(create_engine, '__init__') + @patch.object(sessionmaker, '__init__') + def setUp(self, mock_engine, mock_sessionmaker): + """setup method.""" + self.mock_engine = mock_engine + self.mock_sessionmaker = mock_sessionmaker + self.conn = object.__new__(Connection) + self.conn._engine = self.mock_engine + self.conn._session_maker = self.mock_sessionmaker + + def test_add_record(self): + """test add record. + + test that no exception/error is thrown when calling the + add_record execution. + """ + self.conn.add_record(self.transaction) + + def test_add_record_duplicate_entry(self): + """test duplicate entries to add record. + + test that when session.add throws Duplicate Entry exception, + no expcetion is thrown back. + """ + side_effect = IntegrityError(None, None, None, None) + side_effect.message = "Duplicate entry" + mock_session = self.mock_sessionmaker.return_value.__call__ + mock_add = mock_session.im_self.add + mock_add.side_effect = side_effect + self.conn.add_record(self.transaction) + + def test_add_record_integrity_entry(self): + """test add record that throws integrity error. + + test that when session.add throws a different exception + than Duplicate Entry it will be rasied up. + """ + side_effect = IntegrityError(None, None, None, None) + side_effect.message = "Some other integrity error" + mock_session = self.mock_sessionmaker.return_value.__call__ + mock_add = mock_session.im_self.add + mock_add.side_effect = IntegrityError("Some other integrity error", + None, None, None) + self.assertRaises(IntegrityError, self.conn.add_record, + self.transaction) + + def test_get_records(self): + """test get records. + + test that get_records returns a list and that + no exception/error is thrown. + """ + # mock record + record = Record() + record.id = 1 + record.timestamp = 111 + record.user_id = "user_id1" + record.application_id = "application_id1" + record.tracking_id = "tracking_id1" + record.external_id = "external_id1" + record.transaction_id = "transaction_id1" + record.transaction_type = "transaction_type1" + record.event_details = "event_details1" + + mock_returned_records = [record] + + # The transaction query above will result in the following method call: + # EngineFacade().get_session().query().filter().filter().filter(). + # filter().filter().filter().filter().filter().filter().filter(). + # order_by().limit() Therefore we will need to mock the entire + # call tree in order to set the required returned value + mock_session = self.mock_sessionmaker.return_value.__call__ + mock_query = mock_session.im_self.query + mock_filter = mock_query.return_value.filter + for key in range(len(self.transaction_query.__dict__.keys()) - 1): + mock_filter = mock_filter.return_value.filter + mock_order_by = mock_filter.return_value.order_by + mock_limit = mock_order_by.return_value.limit + mock_all = mock_limit.return_value.all + mock_all.return_value = mock_returned_records + + returned_records = self.conn.get_records(self.transaction_query) + + self.assertIsNotNone(returned_records) + self.assertIsInstance(returned_records, list) + # Check that the list is not empty + self.assertTrue(returned_records) + # Compare the return record value with the expected value + self.assertTrue(len(returned_records), len(mock_returned_records)) + self.assertEqual(returned_records[0].timestamp, + mock_returned_records[0].timestamp) + self.assertEqual(returned_records[0].user_id, + mock_returned_records[0].user_id) + self.assertEqual(returned_records[0].application_id, + mock_returned_records[0].application_id) + self.assertEqual(returned_records[0].tracking_id, + mock_returned_records[0].tracking_id) + self.assertEqual(returned_records[0].external_id, + mock_returned_records[0].external_id) + self.assertEqual(returned_records[0].transaction_id, + mock_returned_records[0].transaction_id) + self.assertEqual(returned_records[0].transaction_type, + mock_returned_records[0].transaction_type) + self.assertEqual(returned_records[0].event_details, + mock_returned_records[0].event_details) + + def test_get_records_returns_none(self): + """test get records return none. + + Test get_records will return empty list when session + returns empty list. + """ + mock_session = self.mock_engine.return_value.get_session + mock_query = mock_session.return_value.query + mock_filter_by = mock_query.return_value.filter_by + mock_order_by = mock_filter_by.return_value.order_by + mock_first = mock_order_by.return_value.first + mock_first.return_value = [] + returned_records = self.conn.get_records(self.transaction_query) + self.assertIsNotNone(returned_records) + self.assertIsInstance(returned_records, list) + # Check that the list is empty + self.assertTrue(not returned_records) + + def test_get_records_with_empty_query(self): + """test get record with empty query. + + test get_records with empty query will not throw an exception + and will returns a result. + """ + transaction_query = None + self.conn.get_records(transaction_query) + + def test_add_filter(self): + """test that add_filter is executed with no exceptions/error.""" + marker = 5 + session = self.mock_engine.get_session() + records = session.query(Record) + records = self.conn._add_filter(records, self.transaction_query, + marker) + + def test_add_filter_with_timestamp_empty(self): + """test add filter with empty timestamp. + + test that add_filter is executed with no exceptions/error + when timestamp_from and timestamp_to are empty. + """ + timestamp_from = None + timestamp_to = None + user_id = "user_id_1" + application_id = "application_id_1" + tracking_id = "tracking_id_1" + external_id = "external_id_1" + transaction_id = "transaction_id_1" + transaction_type = "transaction_type_1" + event_details = "event_details_1" + resource_id = "resource_id_1" + service_name = "service_name_1" + query = TransactionQueryModel(timestamp_from, timestamp_to, user_id, + application_id, tracking_id, + external_id, transaction_id, + transaction_type, event_details, + resource_id, service_name) + marker = 5 + session = self.mock_engine.get_session() + records = session.query(Record) + records = self.conn._add_filter(records, query, marker) + + def test_add_filter_eq(self): + """test that add_filter_eq will not throw an exception.""" + session = self.mock_engine.get_session() + records = session.query(Record) + key = "key1" + value = "value1" + records = self.conn._add_filter_eq(records, key, value) + + def test_add_filter_eq_with_empty_value(self): + """test add filter with empty value. + + test that add_filter_eq will not throw an exception + when value is None. + """ + session = self.mock_engine.get_session() + records = session.query(Record) + key = "key1" + value = None + records = self.conn._add_filter_eq(records, key, value) diff --git a/orm/services/audit_trail_manager/audit_server/tests/storage/test_factory.py b/orm/services/audit_trail_manager/audit_server/tests/storage/test_factory.py new file mode 100644 index 00000000..0ca8d81e --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/storage/test_factory.py @@ -0,0 +1,25 @@ +"""test_factory module.""" + +from audit_server.storage import factory +from audit_server.storage.mysql.transaction import Connection +from mock import patch +from sqlalchemy import create_engine +import unittest + + +class Test(unittest.TestCase): + """test factory class.""" + + @patch.object(create_engine, '__init__') + def test_get_get_transaction_connection(self, mock_engine): + """test get_zone_resource_type_status. + + test that get_zone_resource_type_status_connection returns + an instance of type ZoneResourceTypeStatusConnection. + """ + factory.database_url = 'mysql://root:stack@127.0.0.1/orm_audit?' \ + 'charset=utf8' + factory.echo_statements = False + mock_engine.get_session.return_value = None + conn = factory.get_transaction_connection() + self.assertIsInstance(conn, Connection) diff --git a/orm/services/audit_trail_manager/audit_server/tests/storage/test_transaction.py b/orm/services/audit_trail_manager/audit_server/tests/storage/test_transaction.py new file mode 100644 index 00000000..cd360a42 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server/tests/storage/test_transaction.py @@ -0,0 +1,21 @@ +"""test_transaction module.""" + + +from audit_server.storage.transaction import Base +import unittest + + +class Test(unittest.TestCase): + """test transaction class.""" + + def test_add_record(self): + """test that add_record throws an NotImplementedError exception.""" + baseConn = Base("test_url") + self.assertRaises(NotImplementedError, baseConn.add_record, + transaction=None) + + def test_get_latest_record(self): + """test that add_record throws an NotImplementedError exception.""" + baseConn = Base("test_url") + self.assertRaises(NotImplementedError, baseConn.get_records, + query=None) diff --git a/orm/services/audit_trail_manager/audit_server_docs/audit_server_swagger.yaml b/orm/services/audit_trail_manager/audit_server_docs/audit_server_swagger.yaml new file mode 100755 index 00000000..30d10b9f --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/audit_server_swagger.yaml @@ -0,0 +1,175 @@ +# This is the Audit Server API +swagger: '2.0' +info: + title: Audit Server API + description: Audit Server service + version: "1.0.0" +# the domain of the service +host: server +# array of all schemes that your API supports +schemes: + - http +# will be prefixed to all paths +basePath: /v1 +produces: + - application/json +paths: + /audit/transaction: + get: + summary: get audit transactions + description: | + Returns transactions that meet the query. + parameters: + - name: q.timestamp_from + in: query + description: Transaction from timestamp. + required: false + type: number + format: long + - name: q.timestamp_to + in: query + description: Transaction to timestamp. + required: false + type: number + format: long + - name: q.user_id + in: query + description: Transaction user id. + required: false + type: string + - name: q.application_id + in: query + description: Transaction application id. + required: false + type: string + - name: q.tracking_id + in: query + description: Transaction tracking id. + required: false + type: string + - name: q.external_id + in: query + description: Transaction external id. + required: false + type: string + - name: q.transaction_id + in: query + description: Transaction transaction id. + required: false + type: string + - name: q.transaction_type + in: query + description: Transaction transaction type. + required: false + type: string + - name: q.event_details + in: query + description: Transaction event details. + required: false + type: string + - name: q.status + in: query + description: Transaction status. + required: false + type: string + - name: q.resource_id + in: query + description: Transaction resource id. + required: false + type: string + - name: q.service_name + in: query + description: Transaction service name. + required: false + type: string + - name: limit + in: query + description: Max number of records to return + required: false + type: string + #tags: + # - Transactions + responses: + 200: + description: An array of transactions + schema: + $ref: '#/definitions/Transactions' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + post: + summary: put audit transactions + description: | + Insert audit transactions + parameters: + - name: transaction + in: body + description: The audit transaction to add + required: true + schema: + $ref: "#/definitions/Transaction" + #tags: + # - Transactions + responses: + "201": + description: Null response + default: + description: unexpected error + schema: + $ref: '#/definitions/Error' +definitions: + Transactions: + type: object + properties: + transactions: + type: array + items: + $ref: '#/definitions/Transaction' + Transaction: + type: object + properties: + timestamp: + type: number + format: long + description: Time of audit record in milliseconds since 1/1/1970. + user_id: + type: string + description: The concrete user which initiated this transaction (i.e the SSP user for which a create action was initiated). + application_id: + type: string + description: (SSP, eCOMP, etc). + tracking_id: + type: string + description: The “session” identifier for a set of transactions, and will be the same all through the flow. So if SSP calls CMS with its own transaction id, it should also be used as the tracking id. If it does not include some identifier, the CMS generated transaction id can serve as the tracking id. Either way, that tracking id will be preserved and identify all actions along the flow – on RDS, by the ORD, etc. + external_id: + type: string + description: Anything we get from outside the ORM. For example, if the SSP has its own transaction ID, it may forward it to us. + transaction_id: + type: string + description: The transaction ID of the component creating the current record. + transaction_type: + type: string + description: The type of transaction (depends on the application business logic). + event_details: + type: string + description: Free description, may be transaction type specific data. + status: + type: string + description: The transaction status (depends on the application business logic). + resource_id: + type: string + description: The UUID of the resource on which this action is operating. + service_name: + type: string + description: The service which initiated the log ie- CMS, RDS, RMS.... + Error: + type: object + properties: + faultcode: + type: string + faultstring: + type: string + debuginfo: + type: string + diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/README.md b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/README.md new file mode 100644 index 00000000..d94aa385 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/README.md @@ -0,0 +1,24 @@ +# Swagger generated server + +## Overview +This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project. By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. This is an example of building a node.js server. + +This example uses the [expressjs](http://expressjs.com/) framework. To see how to make this your own, look here: + +[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md) + +### Running the server +To run the server, follow these simple steps: + +``` +npm install +node . +``` + +To view the Swagger UI interface: + +``` +open http://localhost:8080/docs +``` + +This project leverages the mega-awesome [swagger-tools](https://github.com/apigee-127/swagger-tools) middleware which does most all the work. diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/api/swagger.yaml b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/api/swagger.yaml new file mode 100755 index 00000000..1a6cf850 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/api/swagger.yaml @@ -0,0 +1,178 @@ +--- +swagger: "2.0" +info: + description: "Audit Server service" + version: "1.0.0" + title: "Audit Server API" +host: "server" +basePath: "/v1" +schemes: +- "http" +produces: +- "application/json" +paths: + /audit/transaction: + get: + summary: "get audit transactions" + description: "Returns transactions that meet the query.\n" + operationId: "auditTransactionGET" + parameters: + - name: "q.timestamp_from" + in: "query" + description: "Transaction from timestamp." + required: false + type: "number" + format: "long" + - name: "q.timestamp_to" + in: "query" + description: "Transaction to timestamp." + required: false + type: "number" + format: "long" + - name: "q.user_id" + in: "query" + description: "Transaction user id." + required: false + type: "string" + - name: "q.application_id" + in: "query" + description: "Transaction application id." + required: false + type: "string" + - name: "q.tracking_id" + in: "query" + description: "Transaction tracking id." + required: false + type: "string" + - name: "q.external_id" + in: "query" + description: "Transaction external id." + required: false + type: "string" + - name: "q.transaction_id" + in: "query" + description: "Transaction transaction id." + required: false + type: "string" + - name: "q.transaction_type" + in: "query" + description: "Transaction transaction type." + required: false + type: "string" + - name: "q.event_details" + in: "query" + description: "Transaction event details." + required: false + type: "string" + - name: "q.status" + in: "query" + description: "Transaction status." + required: false + type: "string" + - name: "q.resource_id" + in: "query" + description: "Transaction resource id." + required: false + type: "string" + - name: "q.service_name" + in: "query" + description: "Transaction service name." + required: false + type: "string" + - name: "limit" + in: "query" + description: "Max number of records to return" + required: false + type: "string" + responses: + 200: + description: "An array of transactions" + schema: + $ref: "#/definitions/Transactions" + default: + description: "Unexpected error" + schema: + $ref: "#/definitions/Error" + x-swagger-router-controller: "Default" + post: + summary: "put audit transactions" + description: "Insert audit transactions\n" + operationId: "auditTransactionPOST" + parameters: + - in: "body" + name: "transaction" + description: "The audit transaction to add" + required: true + schema: + $ref: "#/definitions/Transaction" + responses: + 201: + description: "Null response" + default: + description: "unexpected error" + schema: + $ref: "#/definitions/Error" + x-swagger-router-controller: "Default" +definitions: + Transactions: + type: "object" + properties: + transactions: + type: "array" + items: + $ref: "#/definitions/Transaction" + Transaction: + type: "object" + properties: + timestamp: + type: "number" + format: "long" + description: "Time of audit record in milliseconds since 1/1/1970." + user_id: + type: "string" + description: "The concrete user which initiated this transaction (i.e the\ + \ SSP user for which a create action was initiated)." + application_id: + type: "string" + description: "(SSP, eCOMP, etc)." + tracking_id: + type: "string" + description: "The “session” identifier for a set of transactions, and will\ + \ be the same all through the flow. So if SSP calls CMS with its own transaction\ + \ id, it should also be used as the tracking id. If it does not include\ + \ some identifier, the CMS generated transaction id can serve as the tracking\ + \ id. Either way, that tracking id will be preserved and identify all actions\ + \ along the flow – on RDS, by the ORD, etc." + external_id: + type: "string" + description: "Anything we get from outside the ORM. For example, if the SSP\ + \ has its own transaction ID, it may forward it to us." + transaction_id: + type: "string" + description: "The transaction ID of the component creating the current record." + transaction_type: + type: "string" + description: "The type of transaction (depends on the application business\ + \ logic)." + event_details: + type: "string" + description: "Free description, may be transaction type specific data." + status: + type: "string" + description: "The transaction status (depends on the application business\ + \ logic)." + resource_id: + type: "string" + description: "The UUID of the resource on which this action is operating." + service_name: + type: "string" + description: "The service which initiated the log ie- CMS, RDS, RMS...." + Error: + type: "object" + properties: + faultcode: + type: "string" + faultstring: + type: "string" + debuginfo: + type: "string" diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/Default.js b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/Default.js new file mode 100644 index 00000000..f5f85571 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/Default.js @@ -0,0 +1,15 @@ +'use strict'; + +var url = require('url'); + + +var Default = require('./DefaultService'); + + +module.exports.auditTransactionGET = function auditTransactionGET (req, res, next) { + Default.auditTransactionGET(req.swagger.params, res, next); +}; + +module.exports.auditTransactionPOST = function auditTransactionPOST (req, res, next) { + Default.auditTransactionPOST(req.swagger.params, res, next); +}; diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/DefaultService.js b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/DefaultService.js new file mode 100644 index 00000000..cc02e203 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/controllers/DefaultService.js @@ -0,0 +1,60 @@ +'use strict'; + +exports.auditTransactionGET = function(args, res, next) { + /** + * parameters expected in the args: + * q.timestampFrom (BigDecimal) + * q.timestampTo (BigDecimal) + * q.userId (String) + * q.applicationId (String) + * q.trackingId (String) + * q.externalId (String) + * q.transactionId (String) + * q.transactionType (String) + * q.eventDetails (String) + * q.status (String) + * q.resourceId (String) + * q.serviceName (String) + * limit (String) + **/ + + + var examples = {}; + examples['application/json'] = { + "transactions" : [ { + "transaction_id" : "aeiou", + "user_id" : "aeiou", + "service_name" : "aeiou", + "resource_id" : "aeiou", + "external_id" : "aeiou", + "event_details" : "aeiou", + "transaction_type" : "aeiou", + "application_id" : "aeiou", + "tracking_id" : "aeiou", + "timestamp" : 1.3579000000000001069366817318950779736042022705078125, + "status" : "aeiou" + } ] +}; + + if(Object.keys(examples).length > 0) { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(examples[Object.keys(examples)[0]] || {}, null, 2)); + } + else { + res.end(); + } + + +} + +exports.auditTransactionPOST = function(args, res, next) { + /** + * parameters expected in the args: + * transaction (Transaction) + **/ + // no response value expected for this operation + + + res.end(); +} + diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/index.js b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/index.js new file mode 100644 index 00000000..aaa873a1 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/index.js @@ -0,0 +1,40 @@ +'use strict'; + +var app = require('connect')(); +var http = require('http'); +var swaggerTools = require('swagger-tools'); +var jsyaml = require('js-yaml'); +var fs = require('fs'); +var serverPort = 8080; + +// swaggerRouter configuration +var options = { + swaggerUi: '/swagger.json', + controllers: './controllers', + useStubs: process.env.NODE_ENV === 'development' ? true : false // Conditionally turn on stubs (mock mode) +}; + +// The Swagger document (require it, build it programmatically, fetch it from a URL, ...) +var spec = fs.readFileSync('./api/swagger.yaml', 'utf8'); +var swaggerDoc = jsyaml.safeLoad(spec); + +// Initialize the Swagger middleware +swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) { + // Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain + app.use(middleware.swaggerMetadata()); + + // Validate Swagger requests + app.use(middleware.swaggerValidator()); + + // Route validated requests to appropriate controller + app.use(middleware.swaggerRouter(options)); + + // Serve the Swagger documents and Swagger UI + app.use(middleware.swaggerUi()); + + // Start the server + http.createServer(app).listen(serverPort, function () { + console.log('Your server is listening on port %d (http://localhost:%d)', serverPort, serverPort); + console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort); + }); +}); diff --git a/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/package.json b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/package.json new file mode 100644 index 00000000..2cbb5d27 --- /dev/null +++ b/orm/services/audit_trail_manager/audit_server_docs/nodejs_audit_server_docs/package.json @@ -0,0 +1,16 @@ +{ + "name": "audit-server-api", + "version": "1.0.0", + "description": "Audit Server service", + "main": "index.js", + "keywords": [ + "swagger" + ], + "license": "MIT", + "private": true, + "dependencies": { + "connect": "^3.2.0", + "js-yaml": "^3.3.0", + "swagger-tools": "0.9.*" + } +} diff --git a/orm/services/audit_trail_manager/config.py b/orm/services/audit_trail_manager/config.py new file mode 100644 index 00000000..98ad0be6 --- /dev/null +++ b/orm/services/audit_trail_manager/config.py @@ -0,0 +1,74 @@ +"""config module.""" + + +# Server Specific Configurations +server = { + 'port': '8776', + 'host': '0.0.0.0' +} + +# DB configurations +database = { + 'url': 'mysql://root:stack@127.0.0.1/orm_audit?charset=utf8', + # 'url': 'mysql://root:root@127.0.0.1/orm_audit?charset=utf8', + 'echo_statements': True +} + +# Pecan Application Configurations +app = { + 'root': 'audit_server.controllers.root.RootController', + 'modules': ['audit_server'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/audit_server/templates', + 'debug': True, + 'errors': { + 404: '/error/404', + '__force_dict__': True + } +} + +logging = { + 'root': {'level': 'INFO', 'handlers': ['console']}, + 'loggers': { + 'audit_server': {'level': 'DEBUG', 'handlers': ['console', 'logfile'], + 'propagate': False}, + 'pecan': {'level': 'DEBUG', 'handlers': ['console'], + 'propagate': False}, + 'py.warnings': {'handlers': ['console']}, + '__force_dict__': True + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'color' + }, + 'logfile': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'formatter': 'color', + 'filename': '/opt/app/orm/audit_server/audit_server.log' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + }, + 'color': { + '()': 'pecan.log.ColorFormatter', + 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]' + '[%(threadName)s] %(message)s'), + '__force_dict__': True + } + } +} + +verify = False + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/orm/services/audit_trail_manager/debug_service.py b/orm/services/audit_trail_manager/debug_service.py new file mode 100644 index 00000000..e0f90b89 --- /dev/null +++ b/orm/services/audit_trail_manager/debug_service.py @@ -0,0 +1,6 @@ +"""Used for running the pecan application directly from pycharm.""" + +from pecan.commands import CommandRunner + +runner = CommandRunner() +runner.run(['serve', 'config.py']) diff --git a/orm/services/audit_trail_manager/public/css/style.css b/orm/services/audit_trail_manager/public/css/style.css new file mode 100644 index 00000000..55c9db54 --- /dev/null +++ b/orm/services/audit_trail_manager/public/css/style.css @@ -0,0 +1,43 @@ +body { + background: #311F00; + color: white; + font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif; + padding: 1em 2em; +} + +a { + color: #FAFF78; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +div#content { + width: 800px; + margin: 0 auto; +} + +form { + margin: 0; + padding: 0; + border: 0; +} + +fieldset { + border: 0; +} + +input.error { + background: #FAFF78; +} + +header { + text-align: center; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif; + text-transform: uppercase; +} diff --git a/orm/services/audit_trail_manager/public/images/logo.png b/orm/services/audit_trail_manager/public/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a8f403e4a4f3ce69a4577a46ae37f5633abb79fa GIT binary patch literal 20596 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 ZD6?|u)*zSG3-Sw<9?1AXo%Yig{{dC~0l)wN literal 0 HcmV?d00001 diff --git a/orm/services/audit_trail_manager/scripts/db_scripts/create_db.sql b/orm/services/audit_trail_manager/scripts/db_scripts/create_db.sql new file mode 100644 index 00000000..e8299eb4 --- /dev/null +++ b/orm/services/audit_trail_manager/scripts/db_scripts/create_db.sql @@ -0,0 +1,23 @@ +create database if not exists orm_audit; + +use orm_audit; + +create table if not exists transactions( + id integer not null auto_increment, + timestamp bigint not null, + user_id varchar(64) binary null, + application_id varchar(64) binary not null, + tracking_id varchar(64) binary not null, + external_id varchar(64) binary null, + transaction_id varchar(64) binary not null, + transaction_type varchar(64) binary not null, + event_details varchar(255) binary null, + status varchar(64) binary null, + resource_id varchar(64) binary not null, + service_name varchar(64) binary not null, + primary key (id), + key tracking_id_index (tracking_id), + key transaction_id_index (transaction_id), + key resource_id_index (resource_id), + unique(timestamp, user_id, application_id, tracking_id, external_id, transaction_id, + transaction_type, event_details, status, resource_id, service_name)); diff --git a/orm/services/audit_trail_manager/scripts/shell_scripts/create_db.sh b/orm/services/audit_trail_manager/scripts/shell_scripts/create_db.sh new file mode 100644 index 00000000..d462b787 --- /dev/null +++ b/orm/services/audit_trail_manager/scripts/shell_scripts/create_db.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +echo Creating database: orm_audit +echo Creating table: transactions + +mysql -uroot -pstack < ../db_scripts/create_db.sql + +echo Done ! + + + + + + + + + + + + + + + + diff --git a/orm/services/audit_trail_manager/swagger/swagger.yaml b/orm/services/audit_trail_manager/swagger/swagger.yaml new file mode 100644 index 00000000..5c608cae --- /dev/null +++ b/orm/services/audit_trail_manager/swagger/swagger.yaml @@ -0,0 +1,179 @@ +swagger: '2.0' +info: + version: 3.5.0 + title: Audit API + description: Audit trail API. + + + termsOfService: terms + contact: + url: www.orm.com + email: zb593m@att.com + license: + name: MIT + url: http://opensource.org/licenses/MIT + +# the domain of the service +host: 135.76.2.229 +# array of all schemes that your API supports +schemes: + - https +# will be prefixed to all paths +basePath: /v1/audit +produces: + - application/json + +paths: + /transaction: + + post: + summary: Add a new transaction + description: | + Add a new transaction record to Audit trail. + + parameters: + - name: transaction + in: body + description: Full transaction to add. + schema: + $ref: "#/definitions/Transaction" + required: true + + tags: + - Audit + + responses: + 201: + description: Success (no body) + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + get: + summary: Get a list of Audit records by criteria + description: | + The get Audit endpoint retrieves all flavors that match the specified criteria + parameters: + - name: q.timestamp_from + in: query + type: "integer" + required: false + description: Get records starting from this time + - name: q.timestamp_to + in: query + type: "integer" + description: Get records up to this time + required: false + - name: q.user_id + in: query + type: "string" + description: User ID + required: false + - name: q.application_id + in: query + type: "string" + description: Application ID + required: false + - name: q.tracking_id + in: query + type: "string" + description: Tracking ID + required: false + - name: q.external_id + in: query + type: "string" + description: External ID + required: false + - name: q.transaction_id + in: query + type: "string" + description: Transaction ID + required: false + - name: q.transaction_type + in: query + type: "string" + description: Transaction type (e.g., 'create customer') + required: false + - name: q.event_details + in: query + type: "string" + description: Event details + required: false + - name: q.resource_id + in: query + type: "string" + description: Get records that involved this resource + required: false + - name: q.service_name + in: query + type: "string" + description: Get records that originated by this service + required: false + tags: + - Audit + responses: + 200: + description: List of Audit records that matched all criteria + schema: + $ref: '#/definitions/Transactions' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + +definitions: + Transaction: + type: object + properties: + timestamp: + type: integer + description: Transaction timestamp (long integer) + user_id: + type: string + description: User ID (not mandatory) + application_id: + type: string + description: Application ID + tracking_id: + type: string + description: Tracking ID for the operation + external_id: + type: string + description: External ID (not mandatory) + transaction_id: + type: string + description: Transaction ID + transaction_type: + type: string + description: Transaction type (e.g., 'create customer') + event_details: + type: string + description: All information about the resource and the operation (not mandatory) + resource_id: + type: string + description: Resource ID + service_name: + type: string + description: Service name (e.g., 'CMS') + + Transactions: + type: array + items: + $ref: '#/definitions/Transaction' + + Error: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + transaction_id: + type: string + message: + type: string + details: + type: string + diff --git a/orm/services/audit_trail_manager/tox.ini b/orm/services/audit_trail_manager/tox.ini new file mode 100644 index 00000000..0fe24ecf --- /dev/null +++ b/orm/services/audit_trail_manager/tox.ini @@ -0,0 +1,19 @@ +[tox] +#envlist = py27, cover +envlist = py27, cover, pep8 + +[testenv] +setenv= PYTHONPATH={toxinidir}:{toxinidir}/audit_server/external_mock/ +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +install_command = pip install -U {opts} {packages} + +commands = python setup.py testr + +[testenv:cover] +commands = python setup.py testr --slowest --coverage + coverage report --omit=audit_server/tests/* + +[testenv:pep8] +#commands = flake8 --ignore=D100,D101,D102,D103,D104,D105 +commands = flake8 diff --git a/orm/services/customer_manager/cms_rest/tests/config.py b/orm/services/customer_manager/cms_rest/tests/config.py index 3eb0c526..5a9c2367 100755 --- a/orm/services/customer_manager/cms_rest/tests/config.py +++ b/orm/services/customer_manager/cms_rest/tests/config.py @@ -126,5 +126,5 @@ authentication = { "token_role": "admin", "role_location": {"tenant": "admin"}, "keystone_version": "2.0", - "policy_file": "/opt/app/orm/aic-orm-cms/cms_rest/etc/policy.json" + "policy_file": "cms_rest/etc/policy.json" } diff --git a/orm/services/customer_manager/scripts/db_scripts/aic_orm_cms_create_db.sql b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql similarity index 100% rename from orm/services/customer_manager/scripts/db_scripts/aic_orm_cms_create_db.sql rename to orm/services/customer_manager/scripts/db_scripts/ranger_cms_create_db.sql diff --git a/orm/services/customer_manager/scripts/db_scripts/aic_orm_cms_update_db.sql b/orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql similarity index 100% rename from orm/services/customer_manager/scripts/db_scripts/aic_orm_cms_update_db.sql rename to orm/services/customer_manager/scripts/db_scripts/ranger_cms_update_db.sql diff --git a/orm/services/customer_manager/scripts/shell_scripts/create_db.sh b/orm/services/customer_manager/scripts/shell_scripts/create_db.sh index e46e6d24..705b2182 100755 --- a/orm/services/customer_manager/scripts/shell_scripts/create_db.sh +++ b/orm/services/customer_manager/scripts/shell_scripts/create_db.sh @@ -2,6 +2,6 @@ echo Creating database: orm_cms_db -mysql -uroot -pstack < ../db_scripts/aic_orm_cms_create_db.sql +mysql -uroot -pstack < ../db_scripts/ranger_cms_create_db.sql echo Done ! diff --git a/orm/services/flavor_manager/config.py b/orm/services/flavor_manager/config.py index 604ea8b2..4e5fcd2e 100755 --- a/orm/services/flavor_manager/config.py +++ b/orm/services/flavor_manager/config.py @@ -158,6 +158,5 @@ authentication = { "token_role": "admin", # The Keystone version currently in use. Can be either "2.0" or "3" "keystone_version": "2.0", - "policy_file": "/opt/app/orm/aic-orm-fms/fms_rest/etc/policy.json", - # "policy_file": "/orm/aic-orm-fms/fms_rest/etc/policy.json" + "policy_file": "fms_rest/etc/policy.json", } diff --git a/orm/services/flavor_manager/fms_rest/tests/config.py b/orm/services/flavor_manager/fms_rest/tests/config.py index 721165d3..6f8be58d 100755 --- a/orm/services/flavor_manager/fms_rest/tests/config.py +++ b/orm/services/flavor_manager/fms_rest/tests/config.py @@ -132,6 +132,5 @@ authentication = { "rms_url": "http://127.0.0.1:8080", "tenant_name": "admin", "keystone_version": "2.0", - # "policy_file": "/opt/app/orm/aic-orm-fms/fms_rest/etc/policy.json", - "policy_file": "/orm/aic-orm-fms/fms_rest/etc/policy.json" + "policy_file": "fms_rest/etc/policy.json" } diff --git a/orm/services/flavor_manager/scripts/db_scripts/aic_orm_fms_create_db.sql b/orm/services/flavor_manager/scripts/db_scripts/ranger_fms_create_db.sql similarity index 100% rename from orm/services/flavor_manager/scripts/db_scripts/aic_orm_fms_create_db.sql rename to orm/services/flavor_manager/scripts/db_scripts/ranger_fms_create_db.sql diff --git a/orm/services/flavor_manager/scripts/db_scripts/aic_orm_fms_update_db.sql b/orm/services/flavor_manager/scripts/db_scripts/ranger_fms_update_db.sql similarity index 100% rename from orm/services/flavor_manager/scripts/db_scripts/aic_orm_fms_update_db.sql rename to orm/services/flavor_manager/scripts/db_scripts/ranger_fms_update_db.sql diff --git a/orm/services/flavor_manager/scripts/shell_scripts/create_db.sh b/orm/services/flavor_manager/scripts/shell_scripts/create_db.sh index 2cf28dba..3dc4e379 100644 --- a/orm/services/flavor_manager/scripts/shell_scripts/create_db.sh +++ b/orm/services/flavor_manager/scripts/shell_scripts/create_db.sh @@ -3,6 +3,6 @@ echo Creating database: orm_rds echo Creating table: resource_status -mysql -uroot -pstack < ../db_scripts/aic_orm_fms_create_db.sql +mysql -uroot -pstack < ../db_scripts/ranger_fms_create_db.sql echo Done ! diff --git a/orm/services/flavor_manager/tox.ini b/orm/services/flavor_manager/tox.ini index b550d1ba..8e9d9942 100755 --- a/orm/services/flavor_manager/tox.ini +++ b/orm/services/flavor_manager/tox.ini @@ -10,14 +10,10 @@ deps= -r{toxinidir}/requirements.txt [testenv:pep8] commands = -# pip install git+ssh://jenkins@gerrit.mtn5.cci.att.com:29418/aic-orm-common@dev -# pip install ../aic-orm-common py.test --pep8 -m pep8 [testenv:cover] commands= -# pip install git+ssh://jenkins@gerrit.mtn5.cci.att.com:29418/aic-orm-common@dev -# pip install ../aic-orm-common coverage run setup.py test coverage report coverage html --omit=fms_rest/data/sql_alchemy/*,fms_rest/utils/utils.py,.tox/* diff --git a/orm/services/id_generator/MANIFEST.in b/orm/services/id_generator/MANIFEST.in new file mode 100644 index 00000000..c922f11a --- /dev/null +++ b/orm/services/id_generator/MANIFEST.in @@ -0,0 +1 @@ +recursive-include public * diff --git a/orm/services/id_generator/TODO b/orm/services/id_generator/TODO new file mode 100644 index 00000000..2a0ed9b4 --- /dev/null +++ b/orm/services/id_generator/TODO @@ -0,0 +1,5 @@ +- move db connection string to config +- choose better name for Uuid database class + +future refactoring? +- lose class for database, just use module level functions - so we can connect once. need to test resetting database server diff --git a/orm/services/id_generator/config.py b/orm/services/id_generator/config.py new file mode 100755 index 00000000..317b554e --- /dev/null +++ b/orm/services/id_generator/config.py @@ -0,0 +1,69 @@ +# Server Specific Configurations +server = { + 'port': '8090', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'uuidgen.controllers.root.RootController', + 'modules': ['uuidgen'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/uuidgen/templates', + 'debug': True, + 'errors': { + 404: '/error/404', + '__force_dict__': True + } +} + +logging = { + 'root': {'level': 'INFO', 'handlers': ['console']}, + 'loggers': { + 'uuidgen': {'level': 'DEBUG', 'handlers': ['console', 'Logfile'], + 'propagate': False}, + 'pecan': {'level': 'DEBUG', 'handlers': ['console'], + 'propagate': False}, + 'py.warnings': {'handlers': ['console']}, + '__force_dict__': True + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'color' + }, + 'Logfile': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'maxBytes': 50000000, + 'backupCount': 10, + 'filename': '/opt/app/orm/uuidgen/uuidgen.log', + 'formatter': 'simple' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + }, + 'color': { + '()': 'pecan.log.ColorFormatter', + 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]' + '[%(threadName)s] %(message)s'), + '__force_dict__': True + } + } +} + +verify = False + +database = { + 'connection_string': 'mysql://root:stack@localhost:3306/orm' +} +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/orm/services/id_generator/public/css/style.css b/orm/services/id_generator/public/css/style.css new file mode 100644 index 00000000..55c9db54 --- /dev/null +++ b/orm/services/id_generator/public/css/style.css @@ -0,0 +1,43 @@ +body { + background: #311F00; + color: white; + font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif; + padding: 1em 2em; +} + +a { + color: #FAFF78; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +div#content { + width: 800px; + margin: 0 auto; +} + +form { + margin: 0; + padding: 0; + border: 0; +} + +fieldset { + border: 0; +} + +input.error { + background: #FAFF78; +} + +header { + text-align: center; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif; + text-transform: uppercase; +} diff --git a/orm/services/id_generator/public/images/logo.png b/orm/services/id_generator/public/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a8f403e4a4f3ce69a4577a46ae37f5633abb79fa GIT binary patch literal 20596 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 ZD6?|u)*zSG3-Sw<9?1AXo%Yig{{dC~0l)wN literal 0 HcmV?d00001 diff --git a/orm/services/id_generator/requirements.txt b/orm/services/id_generator/requirements.txt new file mode 100644 index 00000000..6d5e36ac --- /dev/null +++ b/orm/services/id_generator/requirements.txt @@ -0,0 +1,10 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pecan==1.0.2 +SQLAlchemy==0.9.7 +WSME>=0.6 +MySQL-python==1.2.5 +requests==2.2.1 +oslo.db==1.7.2 diff --git a/orm/services/id_generator/scripts/db_scripts/db_create.sql b/orm/services/id_generator/scripts/db_scripts/db_create.sql new file mode 100755 index 00000000..4d48283f --- /dev/null +++ b/orm/services/id_generator/scripts/db_scripts/db_create.sql @@ -0,0 +1,10 @@ +CREATE DATABASE if not exists orm; +USE orm; + +CREATE TABLE if not exists `uuids` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uuid` varchar(36) NOT NULL, + `uuid_type` varchar(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uuid_idx` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/orm/services/id_generator/scripts/shell_scripts/create_db.sh b/orm/services/id_generator/scripts/shell_scripts/create_db.sh new file mode 100755 index 00000000..a7d51793 --- /dev/null +++ b/orm/services/id_generator/scripts/shell_scripts/create_db.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo Creating database: orm_rds +echo Creating table: resource_status + +mysql -uroot -pstack < ../db_scripts/db_create.sql + +echo Done ! diff --git a/orm/services/id_generator/swagger/swagger.yaml b/orm/services/id_generator/swagger/swagger.yaml new file mode 100644 index 00000000..b4aee343 --- /dev/null +++ b/orm/services/id_generator/swagger/swagger.yaml @@ -0,0 +1,106 @@ +swagger: '2.0' +info: + version: 3.5.0 + title: Uuidgen API + description: ORM UUID generator API. + contact: + url: www.orm.com + email: zb593m@att.com + + +# the domain of the service +host: 135.76.2.229 +# array of all schemes that your API supports +schemes: + - https +# will be prefixed to all paths +basePath: /v1 +produces: + - application/json + +paths: + /uuids: + + post: + summary: Generate a new UUID or save an existing one + + parameters: + - name: uuid + in: body + description: Information about the UUID (if available) + schema: + $ref: "#/definitions/Uuid" + required: false + + tags: + - UUID + + responses: + 200: + description: UUID generated successfully (or it already exists) + schema: + $ref: '#/definitions/UuidResponse' + 400: + description: Invalid parameters received + schema: + $ref: '#/definitions/UnknownParametersError' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + +definitions: + Uuid: + type: object + properties: + uuid: + type: string + description: The existing UUID to add (do not specify if you want to generate a new UUID) + uuid_type: + type: string + description: UUID type (e.g., transaction) + + UuidResponse: + type: object + properties: + uuid: + type: string + description: The existing UUID to add (do not specify if you want to generate a new UUID) + uuid_type: + type: string + description: UUID type (e.g., transaction) + issued_at: + type: string + description: Response timestamp + + BadRequest: + type: object + properties: + code: + type: integer + description: Error code + message: + type: string + description: Error message + + UnknownParametersError: + type: object + properties: + badRequest: + $ref: '#/definitions/BadRequest' + + Error: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + transaction_id: + type: string + message: + type: string + details: + type: string + diff --git a/orm/services/id_generator/test-requirements.txt b/orm/services/id_generator/test-requirements.txt new file mode 100644 index 00000000..21922369 --- /dev/null +++ b/orm/services/id_generator/test-requirements.txt @@ -0,0 +1,10 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +# Hacking already pins down pep8, pyflakes and flake8 +mock +coverage +testfixtures +pytest-pep8 +oslo.db==1.7.2 diff --git a/orm/services/id_generator/tox.ini b/orm/services/id_generator/tox.ini new file mode 100755 index 00000000..a6706fbe --- /dev/null +++ b/orm/services/id_generator/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist=py27,cover + +[testenv] +setenv= PYTHONPATH={toxinidir}:{toxinidir}/uuidgen/external_mock/ +deps= -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:pep8] +commands= + py.test --pep8 -m pep8 + +[testenv:cover] +commands= + coverage run setup.py test + coverage report --omit=uuidgen/tests/* + coverage html --omit=uuidgen/tests/* +#commands={envpython} setup.py test -v {posargs} diff --git a/orm/services/id_generator/uuidgen.conf b/orm/services/id_generator/uuidgen.conf new file mode 100644 index 00000000..6e68bdfe --- /dev/null +++ b/orm/services/id_generator/uuidgen.conf @@ -0,0 +1,26 @@ +Listen 8090 + + + + WSGIDaemonProcess uuidgen user=orm group=orm threads=5 + WSGIScriptAlias / /opt/app/orm/uuidgen/uuidgen.wsgi + + + Order deny,allow + Deny from all + Allow from localhost + + + + Order deny,allow + Deny from all + Allow from localhost + + + + WSGIProcessGroup uuidgen + WSGIApplicationGroup %{GLOBAL} + Require all granted + Allow from all + + diff --git a/orm/services/id_generator/uuidgen.wsgi b/orm/services/id_generator/uuidgen.wsgi new file mode 100644 index 00000000..1bba938d --- /dev/null +++ b/orm/services/id_generator/uuidgen.wsgi @@ -0,0 +1,2 @@ +from pecan.deploy import deploy +application = deploy('/opt/app/orm/uuidgen/config.py') diff --git a/orm/services/id_generator/uuidgen/__init__.py b/orm/services/id_generator/uuidgen/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/app.py b/orm/services/id_generator/uuidgen/app.py new file mode 100755 index 00000000..d55860ed --- /dev/null +++ b/orm/services/id_generator/uuidgen/app.py @@ -0,0 +1,25 @@ +import os +import logging +from pecan import make_app +from pecan.commands import CommandRunner +from uuidgen import model + +logger = logging.getLogger(__name__) + + +def setup_app(config): + model.init_model() + app_conf = dict(config.app) + + app = make_app(app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + **app_conf) + logger.info('Starting uuidgen...') + return app + +def main(): + dir_name = os.path.dirname(__file__) + drive, path_and_file = os.path.splitdrive(dir_name) + path, filename = os.path.split(path_and_file) + runner = CommandRunner() + runner.run(['serve', path+'/config.py']) diff --git a/orm/services/id_generator/uuidgen/controllers/__init__.py b/orm/services/id_generator/uuidgen/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/controllers/root.py b/orm/services/id_generator/uuidgen/controllers/root.py new file mode 100755 index 00000000..555d6bd6 --- /dev/null +++ b/orm/services/id_generator/uuidgen/controllers/root.py @@ -0,0 +1,37 @@ +from pecan import expose +from pecan.rest import RestController +from webob.exc import status_map + +import uuidgen.controllers.v1.root as root +import logging + +LOG = logging.getLogger(__name__) + + +class RootController(RestController): + # url/v1/ + v1 = root.RootController() + + @expose(template='json') + def get(self): + """ + Method to handle GET / + prameters: None + return: dict describing uuid command version information + """ + LOG.info("root -get versions") + return { + "versions": { + "values": [ + { + "status": "stable", + "id": "v1", + "links": [ + { + "href": "http://localhost:8090/" + } + ] + } + ] + } + } diff --git a/orm/services/id_generator/uuidgen/controllers/v1/__init__.py b/orm/services/id_generator/uuidgen/controllers/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/controllers/v1/configuration.py b/orm/services/id_generator/uuidgen/controllers/v1/configuration.py new file mode 100755 index 00000000..f6bdd0bd --- /dev/null +++ b/orm/services/id_generator/uuidgen/controllers/v1/configuration.py @@ -0,0 +1,28 @@ +"""Configuration rest API input module.""" + +import logging +from orm_common.utils import utils +from pecan import conf +from pecan import rest +from wsmeext.pecan import wsexpose + +logger = logging.getLogger(__name__) + + +class ConfigurationController(rest.RestController): + """Configuration controller.""" + + @wsexpose(str, str, status_code=200) + def get(self, dump_to_log='false'): + """get method. + + :param dump_to_log: A boolean string that says whether the + configuration should be written to log + :return: A pretty string that contains the service's configuration + """ + logger.info("Get configuration...") + + dump = dump_to_log.lower() == 'true' + utils.set_utils_conf(conf) + result = utils.report_config(conf, dump, logger) + return result diff --git a/orm/services/id_generator/uuidgen/controllers/v1/logs.py b/orm/services/id_generator/uuidgen/controllers/v1/logs.py new file mode 100644 index 00000000..e8d7b1bb --- /dev/null +++ b/orm/services/id_generator/uuidgen/controllers/v1/logs.py @@ -0,0 +1,65 @@ +import logging + +from pecan import rest +import wsme +from wsmeext.pecan import wsexpose + +logger = logging.getLogger(__name__) + + +class LogChangeResultWSME(wsme.types.DynamicBase): + """log change result wsme type.""" + + result = wsme.wsattr(str, mandatory=True, default=None) + + def __init__(self, **kwargs): + """"init method.""" + super(LogChangeResult, self).__init__(**kwargs) + + +class LogChangeResult(object): + """log change result type.""" + + def __init__(self, result): + """"init method.""" + self.result = result + + +class LogsController(rest.RestController): + """Logs Audit controller.""" + + @wsexpose(LogChangeResultWSME, str, status_code=201, + rest_content_types='json') + def put(self, level): + """update log level. + + :param level: the log level text name + :return: + """ + + logger.info("Changing log level to [{}]".format(level)) + try: + log_level = logging._levelNames.get(level.upper()) + if log_level is not None: + self._change_log_level(log_level) + result = "Log level changed to {}.".format(level) + logger.info(result) + else: + raise Exception( + "The given log level [{}] doesn't exist.".format(level)) + except Exception as e: + result = "Fail to change log_level. Reason: {}".format( + e.message) + logger.error(result) + return LogChangeResult(result) + + @staticmethod + def _change_log_level(log_level): + path = __name__.split('.') + if len(path) > 0: + root = path[0] + root_logger = logging.getLogger(root) + root_logger.setLevel(log_level) + else: + logger.info("Fail to change log_level to [{}]. " + "the given log level doesn't exist.".format(log_level)) diff --git a/orm/services/id_generator/uuidgen/controllers/v1/root.py b/orm/services/id_generator/uuidgen/controllers/v1/root.py new file mode 100755 index 00000000..46affcb9 --- /dev/null +++ b/orm/services/id_generator/uuidgen/controllers/v1/root.py @@ -0,0 +1,10 @@ +import uuidgen.controllers.v1.configuration as configuration +import uuidgen.controllers.v1.uuid_controller as v1 +import uuidgen.controllers.v1.logs as logs + + +class RootController(object): + # url/v1/ + uuids = v1.UUIDController() + logs = logs.LogsController() + configuration = configuration.ConfigurationController() diff --git a/orm/services/id_generator/uuidgen/controllers/v1/uuid_controller.py b/orm/services/id_generator/uuidgen/controllers/v1/uuid_controller.py new file mode 100755 index 00000000..bc9ba278 --- /dev/null +++ b/orm/services/id_generator/uuidgen/controllers/v1/uuid_controller.py @@ -0,0 +1,97 @@ +import datetime +import logging +import uuid + +from pecan import expose, response +from pecan.rest import RestController + +from uuidgen.db.db_manager import DBManager + +LOG = logging.getLogger(__name__) + + +def respond(reason, code, message): + """ + A helper function to create a response dict with the given values + """ + return { + reason: { + "code": code, + "message": message + } + } + + +class UUIDController(RestController): + @expose(template='json') + def post(self, **kw): + """ + Method to handle POST /v1/uuids - create and return a new uuid + prameters: + uuid_type (optional) + return: dict describing success or failure of post command + """ + messageToReturn = None + uuid_type = '' + customer_id = None + if 'uuid_type' in kw: + uuid_type = kw['uuid_type'] + del kw['uuid_type'] + if 'uuid' in kw: + customer_id = kw['uuid'] + del kw['uuid'] + LOG.info("UUIDController.post (url: /v1/uuids) uuid_type=" + uuid_type) + + if len(kw): + response.status = 400 + messageToReturn = respond("badRequest", 400, 'Unknown parameter(s):' + ', '.join(kw.keys())) + LOG.info("UUIDController.post - " + str(messageToReturn)) + return messageToReturn + + if not customer_id: + return self.create_new_uuid(uuid_type) + + return self.validate_and_add_uuid(customer_id) + + def validate_and_add_uuid(self, customer_id): + try: + uuid_type = "custId" + db_manager = DBManager() + db_manager.create_uuid(customer_id, uuid_type) + return { + "uuid": customer_id, + "issued_at": datetime.datetime.utcnow().isoformat() + 'Z', + "uuid_type": uuid_type + } + except Exception as e: + # ignore case of uuid already exist, this can append when creating customer with specific uuid, + # we just need to save it in the database that we will not give this value in the future requests + # but we don't need to throw exception if already exist, this is not our responsible + # if it is duplicate uuid it will fail in the service which uses this uuid (cms, fms) + if e.orig.args[0] != 1062: + response.status = 500 + messageToReturn = respond("badRequest", 500, 'Database error') + LOG.error(str(messageToReturn) + "Exception: " + str(e)) + return messageToReturn + return { + "uuid": customer_id, + "issued_at": datetime.datetime.utcnow().isoformat() + 'Z', + "uuid_type": uuid_type + } + + def create_new_uuid(self, uuid_type): + uu = uuid.uuid4().hex + + try: + db_manager = DBManager() + db_manager.create_uuid(uu, uuid_type) + return { + "uuid": uu, + "issued_at": datetime.datetime.utcnow().isoformat() + 'Z', + "uuid_type": uuid_type + } + except Exception as e: + response.status = 500 + messageToReturn = respond("badRequest", 500, 'Database error') + LOG.error(str(messageToReturn) + "Exception: " + str(e)) + return messageToReturn diff --git a/orm/services/id_generator/uuidgen/db/__init__.py b/orm/services/id_generator/uuidgen/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/db/db_manager.py b/orm/services/id_generator/uuidgen/db/db_manager.py new file mode 100755 index 00000000..c43cfc7d --- /dev/null +++ b/orm/services/id_generator/uuidgen/db/db_manager.py @@ -0,0 +1,57 @@ +from pecan import conf +from oslo_db.sqlalchemy import session as db_session +from uuid_db import UUID +import logging + +logger = logging.getLogger(__name__) + + +class DBManager(object): + + def __init__(self, connection_string=None): + + if not connection_string: + connection_string = conf.database.connection_string + + self._engine_facade = db_session.EngineFacade(connection_string, autocommit=False) + self._session = None + + def get_session(self): + if not self._session: + self._session = self._engine_facade.get_session() + return self._session + + @property + def session(self): + return self.get_session() + + def begin_transaction(self): + # self.session.begin() + # no need to begin transaction - the transaction is open automatically + pass + + def get_engine(self): + return self._engine_facade.get_engine() + + @property + def engine(self): + return self.get_engine() + + def close(self): + self.session.close() + self.engine.dispose() + + def create_uuid(self, _uuid, _uuid_type): + uuid = UUID() + uuid.uuid = _uuid + uuid.uuid_type = _uuid_type + + try: + self.begin_transaction() + self.session.add(uuid) + self.session.commit() + self.session.close() + self.engine.dispose() + except SystemError as ex: + logger.exception(ex) + raise ex diff --git a/orm/services/id_generator/uuidgen/db/uuid_db.py b/orm/services/id_generator/uuidgen/db/uuid_db.py new file mode 100755 index 00000000..134ea96c --- /dev/null +++ b/orm/services/id_generator/uuidgen/db/uuid_db.py @@ -0,0 +1,20 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, String, Integer +from oslo_db.sqlalchemy import models +import logging + +LOG = logging.getLogger(__name__) + + +class UUIDBaseModel(models.ModelBase): + + """Base class from UUID models.""" + + __table_args__ = {'mysql_engine': 'InnoDB'} + + +class UUID(declarative_base(), UUIDBaseModel): + __tablename__ = 'uuids' + id = Column(Integer, primary_key=True) + uuid = Column(String, nullable=False, unique=True) + uuid_type = Column(String, nullable=True, unique=True) diff --git a/orm/services/id_generator/uuidgen/external_mock/__init__.py b/orm/services/id_generator/uuidgen/external_mock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/external_mock/orm_common/__init__.py b/orm/services/id_generator/uuidgen/external_mock/orm_common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/external_mock/orm_common/utils/__init__.py b/orm/services/id_generator/uuidgen/external_mock/orm_common/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orm/services/id_generator/uuidgen/external_mock/orm_common/utils/utils.py b/orm/services/id_generator/uuidgen/external_mock/orm_common/utils/utils.py new file mode 100755 index 00000000..e1fc153a --- /dev/null +++ b/orm/services/id_generator/uuidgen/external_mock/orm_common/utils/utils.py @@ -0,0 +1,13 @@ +"""Utils module mock.""" + + +def report_config(conf, dump=False): + """Mock report_config function.""" + + pass + + +def set_utils_conf(conf): + """Mock set_utils_conf function.""" + + pass diff --git a/orm/services/id_generator/uuidgen/model/__init__.py b/orm/services/id_generator/uuidgen/model/__init__.py new file mode 100644 index 00000000..d983f7bc --- /dev/null +++ b/orm/services/id_generator/uuidgen/model/__init__.py @@ -0,0 +1,15 @@ +from pecan import conf # noqa + + +def init_model(): + """ + This is a stub method which is called at application startup time. + + If you need to bind to a parsed database configuration, set up tables or + ORM classes, or perform any database initialization, this is the + recommended place to do it. + + For more information working with databases, and some common recipes, + see http://pecan.readthedocs.org/en/latest/databases.html + """ + pass diff --git a/orm/services/id_generator/uuidgen/templates/error.html b/orm/services/id_generator/uuidgen/templates/error.html new file mode 100644 index 00000000..f2d97961 --- /dev/null +++ b/orm/services/id_generator/uuidgen/templates/error.html @@ -0,0 +1,12 @@ +<%inherit file="layout.html" /> + +## provide definitions for blocks we want to redefine +<%def name="title()"> + Server Error ${status} + + +## now define the body of the template +

+

Server Error ${status}

+
+

${message}

diff --git a/orm/services/id_generator/uuidgen/templates/index.html b/orm/services/id_generator/uuidgen/templates/index.html new file mode 100644 index 00000000..f17c3862 --- /dev/null +++ b/orm/services/id_generator/uuidgen/templates/index.html @@ -0,0 +1,34 @@ +<%inherit file="layout.html" /> + +## provide definitions for blocks we want to redefine +<%def name="title()"> + Welcome to Pecan! + + +## now define the body of the template +
+

+
+ +
+ +

This is a sample Pecan project.

+ +

+ Instructions for getting started can be found online at pecanpy.org +

+ +

+ ...or you can search the documentation here: +

+ +
+
+ + +
+ Enter search terms or a module, class or function name. +
+ +
diff --git a/orm/services/id_generator/uuidgen/templates/layout.html b/orm/services/id_generator/uuidgen/templates/layout.html new file mode 100644 index 00000000..40908591 --- /dev/null +++ b/orm/services/id_generator/uuidgen/templates/layout.html @@ -0,0 +1,22 @@ + + + ${self.title()} + ${self.style()} + ${self.javascript()} + + + ${self.body()} + + + +<%def name="title()"> + Default Title + + +<%def name="style()"> + + + +<%def name="javascript()"> + + diff --git a/orm/services/id_generator/uuidgen/tests/__init__.py b/orm/services/id_generator/uuidgen/tests/__init__.py new file mode 100644 index 00000000..78ea5274 --- /dev/null +++ b/orm/services/id_generator/uuidgen/tests/__init__.py @@ -0,0 +1,22 @@ +import os +from unittest import TestCase +from pecan import set_config +from pecan.testing import load_test_app + +__all__ = ['FunctionalTest'] + + +class FunctionalTest(TestCase): + """ + Used for functional tests where you need to test your + literal application and its integration with the framework. + """ + + def setUp(self): + self.app = load_test_app(os.path.join( + os.path.dirname(__file__), + 'config.py' + )) + + def tearDown(self): + set_config({}, overwrite=True) diff --git a/orm/services/id_generator/uuidgen/tests/config.py b/orm/services/id_generator/uuidgen/tests/config.py new file mode 100755 index 00000000..760c00c6 --- /dev/null +++ b/orm/services/id_generator/uuidgen/tests/config.py @@ -0,0 +1,59 @@ +# Server Specific Configurations +server = { + 'port': '8080', + 'host': '0.0.0.0' +} + +logging = { + 'root': {'level': 'INFO', 'handlers': ['console']}, + 'loggers': { + 'uuidgen': {'level': 'DEBUG', 'handlers': ['console'], + 'propagate': False}, + 'pecan': {'level': 'DEBUG', 'handlers': ['console'], + 'propagate': False}, + 'py.warnings': {'handlers': ['console']}, + '__force_dict__': True + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'color' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + }, + 'color': { + '()': 'pecan.log.ColorFormatter', + 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]' + '[%(threadName)s] %(message)s'), + '__force_dict__': True + } + } +} +# Pecan Application Configurations +app = { + 'root': 'uuidgen.controllers.root.RootController', + 'modules': ['uuidgen'], + 'static_root': '%(confdir)s/../../public', + 'template_path': '%(confdir)s/../templates', + 'debug': True, + 'errors': { + '404': '/error/404', + '__force_dict__': True + } +} + +database = { + 'connection_string': 'mysql://root:stack@localhost:3306/orm' +} + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/orm/services/id_generator/uuidgen/tests/test_configuration.py b/orm/services/id_generator/uuidgen/tests/test_configuration.py new file mode 100755 index 00000000..58434c4e --- /dev/null +++ b/orm/services/id_generator/uuidgen/tests/test_configuration.py @@ -0,0 +1,14 @@ +"""Get configuration module unittests.""" +from mock import patch +from uuidgen.controllers.v1 import configuration as root +from uuidgen.tests import FunctionalTest + + +class TestGetConfiguration(FunctionalTest): + """Main get configuration test case.""" + + @patch.object(root.utils, 'report_config', return_value='12345') + def test_get_configuration_success(self, input): + """Test get_configuration returns the expected value on success.""" + response = self.app.get('/v1/configuration') + self.assertEqual(response.json, '12345') diff --git a/orm/services/id_generator/uuidgen/tests/test_functional.py b/orm/services/id_generator/uuidgen/tests/test_functional.py new file mode 100755 index 00000000..2ef82327 --- /dev/null +++ b/orm/services/id_generator/uuidgen/tests/test_functional.py @@ -0,0 +1,135 @@ +import mock +from uuidgen.tests import FunctionalTest +import re +import uuidgen.controllers.v1.uuid_controller as uuid_controller +import uuidgen.controllers.v1.uuid_controller +import json + + +class MyException(Exception): + def __init__(self, args=None): + self.orig = mock.MagicMock() + if args is not None: + self.orig.args = args + + +class TestRootController(FunctionalTest): + def test_get_not_found(self): + resp = self.app.get('/a/bogus/url', expect_errors=True) + self.assertEqual(resp.status_int, 404) + + def test_get(self): + resp = self.app.get('/') + self.assertEqual(resp.status_int, 200) + self.assertIn('versions', resp.body) + + def test_bad_request(self): + resp = self.app.post('/v1/uuids', params={'user_type': 'milkshake'}, expect_errors=True) + self.assertEqual(resp.status_int, 400) + self.assertIn('badRequest', resp.body) + self.assertIn('"code": 400', resp.body) + self.assertIn('Unknown parameter(s):user_type', resp.body) + + @mock.patch.object(uuidgen.db.db_manager.DBManager, 'create_uuid') + def test_uuid(self, db_manager_mock): + resp = self.app.post('/v1/uuids') + self.assertEqual(resp.status_int, 200) + body = json.loads(resp.body) + self.assertTrue(re.search('^[\dTZ.:-]+$', body['issued_at'])) + self.assertEqual('', body['uuid_type']) + self.assertTrue(re.search('^[0-9a-f]+$', body['uuid'])) + + @mock.patch.object(uuidgen.db.db_manager.DBManager, 'create_uuid') + def test_uuid_with_param(self, db_manager_mock): + resp = self.app.post('/v1/uuids', + params={'uuid_type': 'milkshake'}) + self.assertEqual(resp.status_int, 200) + body = json.loads(resp.body) + self.assertTrue(re.search('^[\dTZ.:-]+$', body['issued_at'])) + self.assertEqual('milkshake', body['uuid_type']) + # Same format as with no parameter + self.assertTrue(re.search('^[0-9a-f-]+$', body['uuid'])) + + resp = self.app.post('/v1/uuids', + params={'uuid': 1337, 'uuid_type': 'milkshake'}) + self.assertEqual(resp.status_int, 200) + body = json.loads(resp.body) + self.assertTrue(re.search('^[\dTZ.:-]+$', body['issued_at'])) + self.assertEqual('custId', body['uuid_type']) + # Same format as with no parameter + self.assertTrue(re.search('^[0-9a-f-]+$', body['uuid'])) + + @mock.patch.object(uuidgen.db.db_manager.DBManager, 'create_uuid', side_effect=Exception('boom')) + def test_uuid_no_connection(self, db_manager_mock): + resp = self.app.post('/v1/uuids', expect_errors=True) + self.assertEqual(resp.status_int, 500) + self.assertIn('badRequest', resp.body) + self.assertIn('"code": 500', resp.body) + + @mock.patch.object(uuidgen.db.db_manager.DBManager, 'create_uuid', + side_effect=MyException()) + def test_uuid_with_param_db_error(self, db_manager_mock): + resp = self.app.post('/v1/uuids', params={'uuid': '1337'}, + expect_errors=True) + self.assertEqual(resp.status_int, 500) + self.assertIn('badRequest', resp.body) + self.assertIn('"code": 500', resp.body) + + # @mock.patch.object(uuidgen.db.db_manager.DBManager, 'create_uuid', + # side_effect=MyException([1062])) + # def test_uuid_with_param_duplicate_key(self, db_manager_mock): + # resp = self.app.post('/v1/uuids', params={'uuid': '1337'}, + # expect_errors=True) + # self.assertEqual(resp.status_int, 400) + # self.assertIn('badRequest', resp.body) + # self.assertIn('"code": 400', resp.body) + + @mock.patch.object(uuid_controller, 'DBManager') + def test_uuid_already_exists(self, db_manager_mock): + my_create_uuid = mock.MagicMock(side_effect=MyException([1062])) + create_uuid_wrapper = mock.MagicMock() + create_uuid_wrapper.create_uuid = my_create_uuid + db_manager_mock.return_value = create_uuid_wrapper + + resp = self.app.post('/v1/uuids', + params={'uuid': 1337, 'uuid_type': 'milkshake'}, + expect_errors=True) + self.assertEqual(resp.status_int, 200) + body = json.loads(resp.body) + self.assertTrue(re.search('^[\dTZ.:-]+$', body['issued_at'])) + self.assertEqual('custId', body['uuid_type']) + # Same format as with no parameter + self.assertTrue(re.search('^[0-9a-f-]+$', body['uuid'])) + + @mock.patch.object(uuidgen.db.db_manager, 'sessionmaker') + def test_uuid_commit_error(self, mock_sessionmaker): + mock_session = mock.MagicMock() + mock_session.commit = mock.MagicMock(side_effect=SystemError('test')) + mock_sessionmaker.return_value = mock.MagicMock( + return_value=mock_session) + + resp = self.app.post('/v1/uuids', params={'uuid': '1337'}, + expect_errors=True) + self.assertEqual(resp.status_int, 500) + self.assertIn('1337', resp.body) + + # TODO: apply the following 2 tests when we understand mocking better: now we don't get the 'inner' exceptions + # @mock.patch.object('uuidgen.controllers.v1.uuid_controller.Uuid', 'insert', autospec=True) + # @mock.patch('uuidgen.controllers.v1.uuid_controller.Uuid') + # def test_uuid_insert_database_error(self, mock_insert, mock_Uuid): + # mock_insert.side_effect = sqlalchemy.exc.IntegrityError('boom', 'boom', 'boom') + # resp = self.app.post('/v1/uuids', expect_errors=True) + # self.assertEqual(resp.status_int, 500) + # self.assertIn('badRequest', resp.body) + # self.assertIn('"code": 500', resp.body) + # self.assertIn('Database error', resp.body) + + # @mock.patch.object('uuidgen.controllers.v1.uuid_controller.Uuid', 'insert', autospec=True) + # @mock.patch('uuidgen.controllers.v1.uuid_controller.Uuid') + # def test_uuid_duplicate_uuid(self, mock_insert, mock_Uuid): + # mock_insert.side_effect = sqlalchemy.exc.IntegrityError('boom', 'boom', 'boom') + # resp = self.app.post('/v1/uuids', expect_errors=True) + # self.assertEqual(resp.status_int, 500) + # self.assertIn('badRequest', resp.body) + # self.assertIn('"code": 500', resp.body) + # self.assertIn('Duplicate key error', resp.body) diff --git a/orm/services/id_generator/uuidgen/tests/test_logs.py b/orm/services/id_generator/uuidgen/tests/test_logs.py new file mode 100755 index 00000000..ce344f83 --- /dev/null +++ b/orm/services/id_generator/uuidgen/tests/test_logs.py @@ -0,0 +1,18 @@ +"""Set log level module unittests.""" +# import logging +# import mock +# from mock import patch +from uuidgen.controllers.v1 import logs +from uuidgen.tests import FunctionalTest + + +class TestLogs(FunctionalTest): + """Main test case.""" + + def test_change_log_level_sanity(self): + logs.__name__ = 'test.test' + response = self.app.put('/v1/logs', 'level=DEBUG') + + def test_change_log_level_bad_log_level(self): + logs.__name__ = 'test' + response = self.app.put('/v1/logs', 'level') diff --git a/orm/services/image_manager/config.py b/orm/services/image_manager/config.py index c2cd1d53..a7bbf522 100755 --- a/orm/services/image_manager/config.py +++ b/orm/services/image_manager/config.py @@ -116,7 +116,6 @@ authentication = { "rms_url": "http://127.0.0.1:8080", "tenant_name": "admin", "token_role": "admin", - # The Keystone version currently in use. Can be either "2.0" or "3" "keystone_version": "2.0", - "policy_file": "/opt/app/orm/aic-orm-ims/ims/etc/policy.json" + "policy_file": "ims/etc/policy.json" } diff --git a/orm/services/image_manager/ims/tests/config.py b/orm/services/image_manager/ims/tests/config.py index 6884da21..369e24be 100755 --- a/orm/services/image_manager/ims/tests/config.py +++ b/orm/services/image_manager/ims/tests/config.py @@ -101,5 +101,5 @@ authentication = { "tenant_name": "admin", "keystone_version": "2.0", "token_role": "admin", - "policy_file": "/opt/app/orm/aic-orm-ims/ims/etc/policy.json" + "policy_file": "ims/etc/policy.json" } diff --git a/orm/services/image_manager/tox.ini b/orm/services/image_manager/tox.ini index 0b65030e..04ce4ec0 100755 --- a/orm/services/image_manager/tox.ini +++ b/orm/services/image_manager/tox.ini @@ -6,24 +6,12 @@ setenv= PYTHONPATH={toxinidir}:{toxinidir}/ims/external_mock/ deps= -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -;commands= -; pip install git+ssh://jenkins@gerrit.mtn5.cci.att.com:29418/aic-orm-common@dev -;# pip install ../aic-orm-common/ -; -; python setup.py testr --coverage --slowest -; coverage report --omit=ims/persistency/sql_alchemy/*,ims/persistency/wsme/* -; coverage html --omit=ims/persistency/sql_alchemy/*,ims/persistency/wsme/* - [testenv:pep8] commands= -# pip install git+ssh://jenkins@gerrit.mtn5.cci.att.com:29418/aic-orm-common@master -# pip install ../aic-orm-common/ py.test --pep8 -m pep8 [testenv:cover] commands= -# pip install git+ssh://jenkins@gerrit.mtn5.cci.att.com:29418/aic-orm-common@master -# pip install ../aic-orm-common/ coverage run setup.py test coverage report coverage html diff --git a/orm/services/resource_distributor/rds/utils/utils.py b/orm/services/resource_distributor/rds/utils/utils.py index 3529d9b1..7f9c5795 100755 --- a/orm/services/resource_distributor/rds/utils/utils.py +++ b/orm/services/resource_distributor/rds/utils/utils.py @@ -40,7 +40,7 @@ def _get_all_rms_regions(): def _validate_version(region, resource_type): version = None if 'ranger_agent' in region['version'].lower(): - version = region['version'].lower().split('aic')[1].strip().split('.') + version = region['version'].lower().split('ranger_agent')[1].strip().split('.') version = version[0] + '.' + ''.join(version[1:]) if not version or float(version) < 3: return False diff --git a/orm/tests/config.py b/orm/tests/config.py index 721165d3..74cf0784 100644 --- a/orm/tests/config.py +++ b/orm/tests/config.py @@ -132,6 +132,5 @@ authentication = { "rms_url": "http://127.0.0.1:8080", "tenant_name": "admin", "keystone_version": "2.0", - # "policy_file": "/opt/app/orm/aic-orm-fms/fms_rest/etc/policy.json", - "policy_file": "/orm/aic-orm-fms/fms_rest/etc/policy.json" + "policy_file": "orm/services/flavor_manager/fms_rest/etc/policy.json" }