summaryrefslogtreecommitdiffstats
path: root/conductor
diff options
context:
space:
mode:
authorDileep Ranganathan <dileep.ranganathan@intel.com>2018-09-15 11:05:19 -0700
committerDileep Ranganathan <dileep.ranganathan@intel.com>2018-09-15 16:42:14 -0700
commit69d5fbb7b4bbf234f38542bca6134f167b929aa8 (patch)
tree0c53a80819c4900860d3419d7db4ca1307748755 /conductor
parentb3c8241bfeebac1a7e1fd99091e89fab514973c2 (diff)
Secret Management Service feature
Added supporting library required for enabling SMS integration. Added Unit tests and manual tests for store/retrieve/delete secrets. Updated conductor.conf with aaf_sms group. Added preload_secrets config for testing. Integration with application NOT Done in this patch. Change-Id: Idf7e4249a88a39c586d893226a9110e9d5180787 Issue-ID: OPTFRA-345 Signed-off-by: Dileep Ranganathan <dileep.ranganathan@intel.com>
Diffstat (limited to 'conductor')
-rw-r--r--conductor/conductor/common/config_loader.py38
-rw-r--r--conductor/conductor/common/sms.py120
-rw-r--r--conductor/conductor/opts.py2
-rw-r--r--conductor/conductor/tests/unit/test_sms.py89
-rw-r--r--conductor/requirements.txt1
-rw-r--r--conductor/test-requirements.txt1
6 files changed, 251 insertions, 0 deletions
diff --git a/conductor/conductor/common/config_loader.py b/conductor/conductor/common/config_loader.py
new file mode 100644
index 0000000..60e05f1
--- /dev/null
+++ b/conductor/conductor/common/config_loader.py
@@ -0,0 +1,38 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 Intel Corporation Intellectual Property
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+
+import json
+
+import yaml
+
+
+def load_config_file(config_file, child_name="dockerConfiguration"):
+ """
+ Load YAML/JSON configuration from a file
+ :param config_file: path to config file (.yaml or .json).
+ :param child_name: if present, return only that child node
+ :return: config (all or specific child node)
+ """
+ with open(config_file, 'r') as fid:
+ res = {}
+ if config_file.endswith(".yaml"):
+ res = yaml.load(fid)
+ elif config_file.endswith(".json") or config_file.endswith("json"):
+ res = json.load(fid)
+ return res.get(child_name, res) if child_name else res
diff --git a/conductor/conductor/common/sms.py b/conductor/conductor/common/sms.py
new file mode 100644
index 0000000..43b9522
--- /dev/null
+++ b/conductor/conductor/common/sms.py
@@ -0,0 +1,120 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 Intel Corporation Intellectual Property
+#
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Secret Management Service Integration'''
+from conductor.common import config_loader
+from onapsmsclient import Client
+
+from oslo_config import cfg
+from oslo_log import log
+
+LOG = log.getLogger(__name__)
+
+CONF = cfg.CONF
+
+AAF_SMS_OPTS = [
+ cfg.StrOpt('aaf_sms_url',
+ default='https://aaf-sms.onap:10443',
+ help='Base URL for SMS, up to and not including '
+ 'the version, and without a trailing slash.'),
+ cfg.IntOpt('aaf_sms_timeout',
+ default=30,
+ help='Timeout for SMS API Call'),
+ cfg.StrOpt('aaf_ca_certs',
+ default='AAF_RootCA.cer',
+ help='Path to the cacert that will be used to verify '
+ 'If this is None, verify will be False and the server cert'
+ 'is not verified by the client.'),
+ cfg.StrOpt('secret_domain',
+ default='has',
+ help='Domain UUID - A unique UUID generated when the domain'
+ 'for HAS is created by administrator during deployment')
+]
+
+CONF.register_opts(AAF_SMS_OPTS, group='aaf_sms')
+config_spec = {
+ "preload_secrets": "../preload_secrets.yaml"
+}
+
+secret_cache = {}
+
+
+def preload_secrets():
+ """ This is intended to load the secrets required for testing Application
+ Actual deployment will have a preload script. Make sure the config is
+ in sync"""
+ preload_config = config_loader.load_config_file(
+ config_spec.get("preload_secrets"))
+ domain = preload_config.get("domain")
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ domain = sms_client.createDomain(domain)
+ config.secret_domain = domain # uuid
+ secrets = preload_config.get("secrets")
+ for secret in secrets:
+ sms_client.storeSecret(domain, secret.get('name'),
+ secret.get('values'))
+ LOG.debug("Preload secrets complete")
+
+
+def retrieve_secrets():
+ """Get all secrets under the domain name"""
+ secret_dict = dict()
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ domain = config.secret_domain
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ secrets = sms_client.getSecretNames(domain)
+ for secret in secrets:
+ values = sms_client.getSecret(domain, secret)
+ secret_dict[secret] = values
+ LOG.debug("Secret Dictionary Retrieval Success")
+ return secret_dict
+
+
+def delete_secrets():
+ """ This is intended to delete the secrets for a clean initialization for
+ testing Application. Actual deployment will have a preload script.
+ Make sure the config is in sync"""
+ config = CONF.aaf_sms
+ sms_url = config.aaf_sms_url
+ timeout = config.aaf_sms_timeout
+ cacert = config.aaf_ca_certs
+ domain = config.secret_domain
+ sms_client = Client(url=sms_url, timeout=timeout, cacert=cacert)
+ ret_val = sms_client.deleteDomain(domain)
+ LOG.debug("Clean up complete")
+ return ret_val
+
+
+if __name__ == "__main__":
+ # Initialize Secrets from SMS
+ preload_secrets()
+
+ # Retrieve Secrets from SMS and load to secret cache
+ # Use the secret_cache instead of config files
+ secret_cache = retrieve_secrets()
+
+ # Clean up Delete secrets and domain
+ delete_secrets()
diff --git a/conductor/conductor/opts.py b/conductor/conductor/opts.py
index e2ace38..52624cf 100644
--- a/conductor/conductor/opts.py
+++ b/conductor/conductor/opts.py
@@ -22,6 +22,7 @@ import itertools
import conductor.api.app
import conductor.common.music.api
import conductor.common.music.messaging.component
+import conductor.common.sms
import conductor.conf.inventory_provider
import conductor.conf.service_controller
import conductor.conf.vim_controller
@@ -68,4 +69,5 @@ def list_opts():
('music_api', conductor.common.music.api.MUSIC_API_OPTS),
('solver', conductor.solver.service.SOLVER_OPTS),
('reservation', conductor.reservation.service.reservation_OPTS),
+ ('aaf_sms', conductor.common.sms.AAF_SMS_OPTS),
]
diff --git a/conductor/conductor/tests/unit/test_sms.py b/conductor/conductor/tests/unit/test_sms.py
new file mode 100644
index 0000000..b04111e
--- /dev/null
+++ b/conductor/conductor/tests/unit/test_sms.py
@@ -0,0 +1,89 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 Intel Corporation Intellectual Property
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# -------------------------------------------------------------------------
+#
+import unittest
+from uuid import uuid4
+
+import requests_mock
+
+import conductor.common.sms as SMS
+from oslo_config import cfg
+
+
+class TestSMS(unittest.TestCase):
+
+ def setUp(self):
+ self.config = cfg.CONF.aaf_sms
+ self.base_domain_url = '{}/v1/sms/domain'
+ self.domain_url = '{}/v1/sms/domain/{}'
+ self.secret_url = self.domain_url + '/secret'
+
+ @requests_mock.mock()
+ def test_sms(self, mock_sms):
+ ''' NOTE: preload_secret generate the uuid for the domain
+ Create Domain API is called during the deployment using a
+ preload script. So the application oly knows the domain_uuid.
+ All sub-sequent SMS API calls needs the uuid.
+ For test purposes we need to do preload ourselves'''
+ sms_url = self.config.aaf_sms_url
+
+ # JSON Data responses
+ secretnames = {'secretnames': ['s1', 's2', 's3', 's4']}
+ secretvalues = {'values': {'Password': '', 'UserName': ''}}
+ expecect_secret_dict = dict()
+ for secret in secretnames['secretnames']:
+ expecect_secret_dict[secret] = secretvalues['values']
+
+ # Part 1 : Preload Secrets ONLY FOR TEST
+ # Mock requests for preload_secret
+ cd_url = self.base_domain_url.format(sms_url)
+ domain_uuid1 = str(uuid4())
+ s_url = self.secret_url.format(sms_url, domain_uuid1)
+ mock_sms.post(cd_url, status_code=200, json={'uuid': domain_uuid1})
+ mock_sms.post(s_url, status_code=200)
+ # Initialize Secrets from SMS
+ SMS.preload_secrets()
+
+ # Part 2: Retrieve Secret Test
+ # Mock requests for retrieve_secrets
+ # IMPORTANT: Read the config again as the preload_secrets has
+ # updated the config with uuid
+ domain_uuid2 = self.config.secret_domain
+ self.assertEqual(domain_uuid1, domain_uuid2)
+
+ d_url = self.domain_url.format(sms_url, domain_uuid2)
+ s_url = self.secret_url.format(sms_url, domain_uuid2)
+
+ # Retrieve Secrets from SMS and load to secret cache
+ # Use the secret_cache instead of config files
+ mock_sms.get(s_url, status_code=200, json=secretnames)
+ for secret in secretnames['secretnames']:
+ mock_sms.get('{}/{}'.format(s_url, secret),
+ status_code=200, json=secretvalues)
+ secret_cache = SMS.retrieve_secrets()
+ self.assertDictEqual(expecect_secret_dict, secret_cache,
+ 'Failed to retrieve secrets')
+
+ # Part 3: Clean up Delete secrets and domain
+ # Mock requests for delete_secrets
+ mock_sms.delete(d_url, status_code=200)
+ self.assertTrue(SMS.delete_secrets())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/conductor/requirements.txt b/conductor/requirements.txt
index 9359e26..d09c960 100644
--- a/conductor/requirements.txt
+++ b/conductor/requirements.txt
@@ -23,3 +23,4 @@ requests[security]!=2.9.0,>=2.8.1 # Apache-2.0
six>=1.9.0 # MIT, also required by futurist
stevedore>=1.9.0 # Apache-2.0, also required by oslo.config
WebOb>=1.2.3 # MIT
+onapsmsclient>=0.0.3 \ No newline at end of file
diff --git a/conductor/test-requirements.txt b/conductor/test-requirements.txt
index c0e68d0..7466c9d 100644
--- a/conductor/test-requirements.txt
+++ b/conductor/test-requirements.txt
@@ -18,3 +18,4 @@ os-testr>=1.0.0 # Apache-2.0
tempest>=11.0.0 # Apache-2.0
pifpaf>=0.0.11
junitxml>=0.7
+requests-mock>=1.5.2