diff options
-rw-r--r-- | Dockerfile | 33 | ||||
-rw-r--r-- | README.md | 85 | ||||
-rw-r--r-- | etc_customize/customize.sh | 21 | ||||
-rw-r--r-- | policyhandler/config.py | 9 | ||||
-rw-r--r-- | policyhandler/customize/__init__.py | 32 | ||||
-rw-r--r-- | policyhandler/customize/customizer.py | 35 | ||||
-rw-r--r-- | policyhandler/customize/customizer_base.py | 62 | ||||
-rw-r--r-- | policyhandler/deploy_handler.py | 39 | ||||
-rw-r--r-- | policyhandler/discovery.py | 43 | ||||
-rw-r--r-- | policyhandler/web_server.py | 4 | ||||
-rw-r--r-- | pom.xml | 2 | ||||
-rw-r--r-- | setup.py | 4 | ||||
-rw-r--r-- | tests/test_policyhandler.py | 9 | ||||
-rw-r--r-- | version.properties | 2 |
14 files changed, 326 insertions, 54 deletions
@@ -1,4 +1,22 @@ -# Use an official Python runtime as a base image +# ================================================================================ +# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +# Use the official Python as the base image FROM python:2.7 ENV INSROOT /opt/app @@ -12,23 +30,24 @@ WORKDIR ${APPDIR} # Make port 25577 available to the world outside this container EXPOSE 25577 -# Copy the current directory contents into the container at ${APPDIR} +# Copy the current directory content into the container at ${APPDIR} COPY ./*.py ./ COPY ./*.in ./ COPY ./*.txt ./ -COPY ./*.sh ./ +COPY ./run_policy.sh ./ COPY ./policyhandler/ ./policyhandler/ COPY ./etc/ ./etc/ +COPY ./etc_customize/ ./etc_customize/ RUN mkdir -p ${APPDIR}/logs \ && chown -R ${APPUSER}:${APPUSER} ${APPDIR} \ && chmod a+w ${APPDIR}/logs \ && chmod 500 ${APPDIR}/etc \ && chmod 500 ${APPDIR}/run_policy.sh \ - && ls -la && ls -la ./policyhandler - -# Install any needed packages specified in requirements.txt -RUN pip install -r requirements.txt + && pip install -r requirements.txt \ + && (CUST_SH=./etc_customize/customize.sh && test -e ${CUST_SH} && chmod 500 ${CUST_SH} \ + && (${CUST_SH} | tee -a logs/"customize_${APPUSER}_$(date +%Y_%m%d-%H%M%S).log" 2>&1)) \ + && ls -laR ${APPDIR}/ USER ${APPUSER} @@ -1,4 +1,5 @@ -# policy_handler +# policy_handler + ## web-service for policies to be used by DCAE-Controller - GET **/policy\_latest/***\<policy-id>* -- get the latest policy from policy-engine @@ -6,7 +7,7 @@ ## manual http API -- GET **/catch_up** -- catch up with the latest state of the policy-engine +- GET **/catch_up** -- catch up with the latest state of the policy-engine - GET **/policies_latest** -- get all the latest policies in policy-engine through web-service API - GET **/shutdown** -- shutdown the server @@ -41,12 +42,12 @@ in folder `policy_handler`: - `config.json` contains - - `"scope_prefixes" : ["DCAE_alex.Config_"]` - the list of policy-scope-class values - - `"policy_engine"` - the http connect info to ECOMP **policy-engine** + - `"scope_prefixes" : ["DCAE.Config_"]` - the list of policy-scope-class values + - `"policy_engine"` - the http connect info to ONAP **policy-engine** - headers.ClientAuth : base64(<mech-id with namespace>:<password>) - headers.Authorization : base64(<policy-engine server auth>) - `"deploy_handler"` - the http connect info to _policy part_ of the **deploy-handler** - - `policy_engine.properties` contains config info for the client lib of ECOMP **policy-engine** that receives push notifications from the ECOMP **policy-engine** server + - `policy_engine.properties` contains config info for the client lib of ONAP **policy-engine** that receives push notifications from the ONAP **policy-engine** server - CLIENT_ID is the mech-id with the namespace - need to register with policy-engine team thru email - CLIENT_KEY is the base64 of the mech-id password - separate passwords for TEST versus PROD @@ -59,3 +60,77 @@ in folder `policy_handler`: `./run_policy.sh` ---------- + +## customization per company + +### ```etc_customize/``` folder + +- company is expected to place any company specific files required to be in the docker image in the folder ```etc_customize/``` + +- change the ```etc_customize/customize.sh``` script to perform company specific actions during docker image build + +- ```etc_customize/customize.sh``` script is expected to be overridden by company to customize docker image build + +### ```policyhandler/customize/``` folder + +contains ```CustomizeBase``` and ```Customize``` classes + +- ```CustomizeBase``` defines the interface and the default=ONAP behavior + +- ```CustomizeBase``` is owned by ONAP and should not be changed by the company + +- ```Customize``` inherits ```CustomizeBase``` + +- policy-handler instantiates ```Customize``` to get the customized behavior + +- ```Customize``` is owned by the company and should be changed by the company +- ONAP is not going to change ```Customize``` + +- the methods of ```Customize``` are expected to be overridden by the company to change the behavior of the policy-handler + +- samples are provided for methods in ```Customize``` class as the commented out lines + +- Company is allowed to add more files to customize/ folder if that is required for better structuring of their code as soon as it is invoked by the methods of ```Customize``` + +here is an example of ```customizer.py``` + +```python +"""contains the Customizer class with method overrides per company specification""" + +from .customizer_base import CustomizerBase + +class Customizer(CustomizerBase): + """ + the Customizer class inherits CustomizerBase that is owned by ONAP + + :Customizer: class is owned by the company that needs to customize the policy-handler + + :override: any method defined in the CustomizerBase class to customize the behavior of the policy-handler + """ + def __init__(self): + """class that contains the customization""" + super(Customizer, self).__init__() + + def get_service_url(self, audit, service_name, service): + """ + returns the service url when called from DiscoveryClient + + this is just a sample code - replace it with the real customization + """ + service_url = super(Customizer, self).get_service_url(audit, service_name, service) + audit.info("TODO: customization for service_url on {0}".format(service_name)) + return service_url + + def get_deploy_handler_kwargs(self, audit): + """ + returns the optional dict-kwargs for requests.post to deploy-handler + + this is just a sample code - replace it with the real customization + """ + kwargs = {"verify": "/usr/local/share/ca-certificates/aafcacert.crt"} + audit.info("kwargs for requests.post to deploy-handler: {0}".format(json.dumps(kwargs))) + return kwargs + +``` + +---------- diff --git a/etc_customize/customize.sh b/etc_customize/customize.sh new file mode 100644 index 0000000..cd125f2 --- /dev/null +++ b/etc_customize/customize.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# ============LICENSE_START======================================================= +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +echo "customize installation by changing $0" diff --git a/policyhandler/config.py b/policyhandler/config.py index d7768fa..8a8f418 100644 --- a/policyhandler/config.py +++ b/policyhandler/config.py @@ -18,11 +18,11 @@ """read and use the config""" -import os -import json import copy +import json import logging import logging.config +import os from .discovery import DiscoveryClient @@ -78,7 +78,7 @@ class Config(object): discovery_key, json.dumps(new_config)) Config._logger.debug("config before merge from discovery: %s", json.dumps(Config.config)) Config.merge(new_config.get(Config.SERVICE_NAME_POLICY_HANDLER)) - Config._logger.debug("merged config from discovery: %s", json.dumps(Config.config)) + Config._logger.info("merged config from discovery: %s", json.dumps(Config.config)) @staticmethod def load_from_file(file_path=None): @@ -92,7 +92,7 @@ class Config(object): loaded_config = json.load(config_json) if not loaded_config: - Config._logger.info("config not loaded from file: %s", file_path) + Config._logger.warn("config not loaded from file: %s", file_path) return Config._logger.info("config loaded from file: %s", file_path) @@ -102,4 +102,5 @@ class Config(object): Config.wservice_port = loaded_config.get(Config.FIELD_WSERVICE_PORT, Config.wservice_port) Config.merge(loaded_config.get(Config.SERVICE_NAME_POLICY_HANDLER)) + Config._logger.info("config loaded from file: %s", json.dumps(Config.config)) return True diff --git a/policyhandler/customize/__init__.py b/policyhandler/customize/__init__.py new file mode 100644 index 0000000..7449528 --- /dev/null +++ b/policyhandler/customize/__init__.py @@ -0,0 +1,32 @@ +# ================================================================================ +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +"""polymorphic customizer changes the behavior of predefined methods in policy-handler""" + +from .customizer import Customizer + +class CustomizerUser(object): + """unprotected singleton around Customizer""" + _customizer = None + + @staticmethod + def get_customizer(): + """get instance of customizer""" + if not CustomizerUser._customizer: + CustomizerUser._customizer = Customizer() + return CustomizerUser._customizer diff --git a/policyhandler/customize/customizer.py b/policyhandler/customize/customizer.py new file mode 100644 index 0000000..22bf60e --- /dev/null +++ b/policyhandler/customize/customizer.py @@ -0,0 +1,35 @@ +# ================================================================================ +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +"""contains the Customizer class with method overrides per company specification""" + +from .customizer_base import CustomizerBase + +class Customizer(CustomizerBase): + """ + the Customizer class inherits CustomizerBase that is owned by ONAP + + :Customizer: class is owned by the company that needs to customize the policy-handler + + :override: any method defined in the CustomizerBase class to customize the behavior of the policy-handler + + see README.md for the sample of the customizer.py + """ + def __init__(self): + """class that contains the customization""" + super(Customizer, self).__init__() diff --git a/policyhandler/customize/customizer_base.py b/policyhandler/customize/customizer_base.py new file mode 100644 index 0000000..97e1550 --- /dev/null +++ b/policyhandler/customize/customizer_base.py @@ -0,0 +1,62 @@ +# ================================================================================ +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +""" +contains the base class :CustomizerBase: +that defines the signatures and default behavior of the methods called by the policy-handler + +the methods are expected to be overriden by the child class Cutomizer that is company specific + +:do NOT change: this class and/or this file - it is owned by ONAP +""" + +import logging + +class CustomizerBase(object): + """ + base class for Customizer class + + do NOT change this class and/or this file - it is owned by ONAP + + policy-hanlder is using the instance of the child Customizer class to get the overriden methods + + the methods defined in this class are the placeholders and are expected to be overriden by the Customizer class + """ + + def __init__(self): + """base class for customization contains the default methods""" + self._logger = logging.getLogger("policy_handler.customizer") + self._logger.info("created customizer") + + def get_service_url(self, audit, service_name, service): + """returns the service url when called from DiscoveryClient""" + service_url = "http://{0}:{1}".format( + service.get("ServiceAddress", ""), service.get("ServicePort", "")) + + info = "no customization for service_url: {0} on {1}".format(service_url, service_name) + self._logger.info(info) + audit.info(info) + return service_url + + def get_deploy_handler_kwargs(self, audit): + """returns the optional dict-kwargs for requests.post to deploy_handler""" + info = "no optional kwargs for requests.post to deploy_handler" + self._logger.info(info) + audit.info(info) + kwargs = {} + return kwargs diff --git a/policyhandler/deploy_handler.py b/policyhandler/deploy_handler.py index 5792631..0950dfd 100644 --- a/policyhandler/deploy_handler.py +++ b/policyhandler/deploy_handler.py @@ -18,13 +18,15 @@ """ send notification to deploy-handler""" -import logging import json +import logging + import requests from .config import Config from .discovery import DiscoveryClient from .onap.audit import REQUEST_X_ECOMP_REQUESTID, Audit, AuditHttpCode +from .customize import CustomizerUser POOL_SIZE = 1 @@ -38,14 +40,21 @@ class DeployHandler(object): _url = None _url_path = None _target_entity = None + _custom_kwargs = None @staticmethod - def _lazy_init(): + def _lazy_init(audit): """ set static properties """ if DeployHandler._lazy_inited: return DeployHandler._lazy_inited = True + DeployHandler._custom_kwargs = CustomizerUser.get_customizer() \ + .get_deploy_handler_kwargs(audit) + if not DeployHandler._custom_kwargs \ + or not isinstance(DeployHandler._custom_kwargs, dict): + DeployHandler._custom_kwargs = {} + DeployHandler._requests_session = requests.Session() DeployHandler._requests_session.mount( 'https://', @@ -56,8 +65,8 @@ class DeployHandler(object): requests.adapters.HTTPAdapter(pool_connections=POOL_SIZE, pool_maxsize=POOL_SIZE) ) - DeployHandler._target_entity = Config.config["deploy_handler"] - DeployHandler._url = DiscoveryClient.get_service_url(DeployHandler._target_entity) + DeployHandler._target_entity = Config.config.get("deploy_handler", "deploy_handler") + DeployHandler._url = DiscoveryClient.get_service_url(audit, DeployHandler._target_entity) DeployHandler._url_path = (DeployHandler._url or "") + '/policy' DeployHandler._logger.info("DeployHandler url(%s)", DeployHandler._url) @@ -67,7 +76,7 @@ class DeployHandler(object): if not message: return - DeployHandler._lazy_init() + DeployHandler._lazy_init(audit) sub_aud = Audit(aud_parent=audit, targetEntity=DeployHandler._target_entity, targetServiceName=DeployHandler._url_path) headers = {REQUEST_X_ECOMP_REQUESTID : sub_aud.request_id} @@ -75,10 +84,10 @@ class DeployHandler(object): msg_str = json.dumps(message) headers_str = json.dumps(headers) - DeployHandler._logger.info("message: %s", msg_str) - log_line = "post to deployment-handler {0} msg={1} headers={2}".format( - DeployHandler._url_path, msg_str, headers_str) - + log_action = "post to {0} at {1}".format( + DeployHandler._target_entity, DeployHandler._url_path) + log_data = " msg={0} headers={1}".format(msg_str, headers_str) + log_line = log_action + log_data DeployHandler._logger.info(log_line) sub_aud.metrics_start(log_line) @@ -93,11 +102,11 @@ class DeployHandler(object): res = None try: res = DeployHandler._requests_session.post( - DeployHandler._url_path, json=message, headers=headers + DeployHandler._url_path, json=message, headers=headers, + **DeployHandler._custom_kwargs ) except requests.exceptions.RequestException as ex: - error_msg = "failed to post to deployment-handler {0} {1} msg={2} headers={3}" \ - .format(DeployHandler._url_path, str(ex), msg_str, headers_str) + error_msg = "failed to {0}: {1}{2}".format(log_action, str(ex), log_data) DeployHandler._logger.exception(error_msg) sub_aud.set_http_status_code(AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value) audit.set_http_status_code(AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value) @@ -107,10 +116,8 @@ class DeployHandler(object): sub_aud.set_http_status_code(res.status_code) audit.set_http_status_code(res.status_code) - sub_aud.metrics( - "response from deployment-handler to post {0}: {1} msg={2} text={3} headers={4}" \ - .format(DeployHandler._url_path, res.status_code, msg_str, res.text, - res.request.headers)) + sub_aud.metrics("response {0} from {1}: text={2}{3}" \ + .format(res.status_code, log_action, res.text, log_data)) if res.status_code == requests.codes.ok: return res.json() diff --git a/policyhandler/discovery.py b/policyhandler/discovery.py index a7e3c56..ce24c3d 100644 --- a/policyhandler/discovery.py +++ b/policyhandler/discovery.py @@ -18,11 +18,14 @@ """client to talk to consul at the standard port 8500""" -import logging -import json import base64 +import json +import logging + import requests +from .customize import CustomizerUser + class DiscoveryClient(object): """talking to consul at http://consul:8500 @@ -39,27 +42,43 @@ class DiscoveryClient(object): """ CONSUL_SERVICE_MASK = "http://consul:8500/v1/catalog/service/{0}" CONSUL_KV_MASK = "http://consul:8500/v1/kv/{0}" - SERVICE_MASK = "http://{0}:{1}" - SERVICE_ADDRESS = "ServiceAddress" - SERVICE_PORT = "ServicePort" _logger = logging.getLogger("policy_handler.discovery") - @staticmethod - def get_service_url(service_name): + def get_service_url(audit, service_name): """find the service record in consul""" service_path = DiscoveryClient.CONSUL_SERVICE_MASK.format(service_name) - DiscoveryClient._logger.info("discover %s", service_path) + log_line = "discover {0}".format(service_path) + DiscoveryClient._logger.info(log_line) + audit.info(log_line) response = requests.get(service_path) + + log_line = "response {0} for {1}: {2}".format( + response.status_code, service_path, response.text) + DiscoveryClient._logger.info(log_line) + audit.info(log_line) + response.raise_for_status() + service = response.json() if not service: - DiscoveryClient._logger.error("failed discover %s", service_path) + log_line = "failed discover {0}".format(service_path) + DiscoveryClient._logger.error(log_line) + audit.error(log_line) return service = service[0] - return DiscoveryClient.SERVICE_MASK.format( - service[DiscoveryClient.SERVICE_ADDRESS], service[DiscoveryClient.SERVICE_PORT] - ) + + service_url = CustomizerUser.get_customizer().get_service_url(audit, service_name, service) + if not service_url: + log_line = "failed to get service_url for {0}".format(service_name) + DiscoveryClient._logger.error(log_line) + audit.error(log_line) + return + + log_line = "got service_url: {0} for {1}".format(service_url, service_name) + DiscoveryClient._logger.info(log_line) + audit.info(log_line) + return service_url @staticmethod def get_value(key): diff --git a/policyhandler/web_server.py b/policyhandler/web_server.py index 8efb51d..20e74e3 100644 --- a/policyhandler/web_server.py +++ b/policyhandler/web_server.py @@ -104,7 +104,7 @@ class _PolicyWeb(object): { "configAttributes": { "key1":"value1" }, "configName": "alex_config_name", - "ecompName": "DCAE", + "onapName": "DCAE", "policyName": "DCAE_alex.Config_alex_.*", "unique": false } @@ -122,7 +122,7 @@ class _PolicyWeb(object): "matchingConditions": { "priority": "10", "key1": "value1", - "ECOMPName": "DCAE", + "ONAPName": "DCAE", "ConfigName": "alex_config_name" }, "property": null, @@ -30,7 +30,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. <groupId>org.onap.dcaegen2.platform</groupId> <artifactId>policy-handler</artifactId> <name>dcaegen2-platform-policy-handler</name> - <version>2.1.0-SNAPSHOT</version> + <version>2.2.0-SNAPSHOT</version> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> @@ -23,7 +23,7 @@ from setuptools import setup setup(
name='policyhandler',
description='DCAE-Controller policy-handler to communicate with policy-engine',
- version="2.1.0",
+ version="2.2.0",
author='Alex Shatov',
packages=['policyhandler'],
zip_safe=False,
@@ -31,7 +31,7 @@ setup( "CherryPy>=10.2.2,<11.0",
"enum34>=1.1.6",
"requests>=2.13.0,<3.0.0",
- "websocket-client>=0.40.0"
+ "websocket-client>=0.40.0,<1.0.0"
],
keywords='policy dcae controller',
classifiers=[
diff --git a/tests/test_policyhandler.py b/tests/test_policyhandler.py index f47d9d0..375c358 100644 --- a/tests/test_policyhandler.py +++ b/tests/test_policyhandler.py @@ -47,7 +47,7 @@ from policyhandler.policy_rest import PolicyRest from policyhandler.policy_utils import PolicyUtils from policyhandler.web_server import _PolicyWeb -POLICY_HANDLER_VERSION = "2.0.0" +POLICY_HANDLER_VERSION = "2.2.0" class MonkeyHttpResponse(object): """Monkey http reposne""" @@ -77,8 +77,8 @@ def monkeyed_discovery(full_path): res_json = {} if full_path == DiscoveryClient.CONSUL_SERVICE_MASK.format(Config.config["deploy_handler"]): res_json = [{ - DiscoveryClient.SERVICE_ADDRESS: "1.1.1.1", - DiscoveryClient.SERVICE_PORT: "123" + "ServiceAddress": "1.1.1.1", + "ServicePort": "123" }] elif full_path == DiscoveryClient.CONSUL_KV_MASK.format(Config.get_system_name()): res_json = copy.deepcopy(Settings.dicovered_config) @@ -267,7 +267,8 @@ def monkeyed_deploy_handler(full_path, json=None, headers=None): def fix_deploy_handler(monkeypatch, fix_discovery): """monkeyed discovery request.get""" Settings.logger.info("setup fix_deploy_handler") - DeployHandler._lazy_init() + audit = Audit(req_message="fix_deploy_handler") + DeployHandler._lazy_init(audit) monkeypatch.setattr('policyhandler.deploy_handler.DeployHandler._requests_session.post', monkeyed_deploy_handler) yield fix_deploy_handler # provide the fixture value diff --git a/version.properties b/version.properties index 91dd40e..5791c10 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ major=2
-minor=1
+minor=2
patch=0
base_version=${major}.${minor}.${patch}
release_version=${base_version}
|