summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrank Sandoval <frank.sandoval@oamtechnologies.com>2018-09-17 15:50:04 -0600
committerIkram Ikramullah <ikram@research.att.com>2018-09-21 16:54:58 -0400
commit4f5bfd835c01ccc7b79d0bce048fe0684c6c945a (patch)
tree1d05e52fab3ec0a168849ade435a1cfa454e505c
parent4a4ffec30cf335fc7ea84daea95308ea454f2376 (diff)
add AAF integration and AAF simulator
Patch (#3) addresses commentse this patch fixes unit tests. Original commit added AAF permissions checks for HAS API Issue-ID: OPTFRA-331 Change-Id: I69519beee31f57e4ac5188604d21805266a06074 Signed-off-by: Frank Sandoval <frank.sandoval@oamtechnologies.com>
-rw-r--r--conductor/conductor/api/adapters/__init__.py17
-rw-r--r--conductor/conductor/api/adapters/aaf/__init__.py18
-rw-r--r--conductor/conductor/api/adapters/aaf/aaf_authentication.py151
-rw-r--r--conductor/conductor/api/controllers/v1/plans.py59
-rwxr-xr-xconductor/conductor/tests/functional/simulators/aafsim/Dockerfile39
-rwxr-xr-xconductor/conductor/tests/functional/simulators/aafsim/aafsim.py87
-rwxr-xr-xconductor/conductor/tests/functional/simulators/aafsim/requirements.txt1
-rw-r--r--conductor/conductor/tests/functional/simulators/aafsim/responses/get_perms_user.json6
-rw-r--r--conductor/conductor/tests/functional/simulators/aafsim/responses/healthcheck.json1
-rwxr-xr-xconductor/conductor/tests/functional/simulators/build_aafsim.sh23
-rwxr-xr-xconductor/conductor/tests/functional/simulators/destroy_aafsim.sh23
-rwxr-xr-xconductor/conductor/tests/functional/simulators/run_aafsim.sh22
-rw-r--r--conductor/conductor/tests/unit/api/controller/v1/test_plans.py34
-rw-r--r--conductor/requirements.txt3
14 files changed, 454 insertions, 30 deletions
diff --git a/conductor/conductor/api/adapters/__init__.py b/conductor/conductor/api/adapters/__init__.py
new file mode 100644
index 0000000..ba46563
--- /dev/null
+++ b/conductor/conductor/api/adapters/__init__.py
@@ -0,0 +1,17 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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.
+#
+# -------------------------------------------------------------------------
+#
diff --git a/conductor/conductor/api/adapters/aaf/__init__.py b/conductor/conductor/api/adapters/aaf/__init__.py
new file mode 100644
index 0000000..4648e6c
--- /dev/null
+++ b/conductor/conductor/api/adapters/aaf/__init__.py
@@ -0,0 +1,18 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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.
+#
+# -------------------------------------------------------------------------
+# \ No newline at end of file
diff --git a/conductor/conductor/api/adapters/aaf/aaf_authentication.py b/conductor/conductor/api/adapters/aaf/aaf_authentication.py
new file mode 100644
index 0000000..fca84fd
--- /dev/null
+++ b/conductor/conductor/api/adapters/aaf/aaf_authentication.py
@@ -0,0 +1,151 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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 base64
+from datetime import datetime, timedelta
+import json
+
+from conductor.common import rest
+from conductor.i18n import _LE, _LI
+
+from oslo_log import log
+LOG = log.getLogger(__name__)
+
+from oslo_config import cfg
+CONF = cfg.CONF
+
+# TBD - read values from conductor.conf
+AAF_OPTS = [
+ cfg.BoolOpt('is_aaf_enabled',
+ default=True,
+ help='is_aaf_enabled.'),
+ cfg.IntOpt('aaf_cache_expiry_hrs',
+ default='3',
+ help='aaf_cache_expiry_hrs.'),
+ cfg.StrOpt('aaf_url',
+ default='http://aaf-service:8100/authz/perms/user/',
+ help='aaf_url.'),
+ cfg.IntOpt('aaf_retries',
+ default='3',
+ help='aaf_retries.'),
+ cfg.IntOpt('aaf_timeout',
+ default='100',
+ help='aaf_timeout.'),
+ cfg.ListOpt('aaf_user_roles',
+ default=['{"type": "org.onap.oof","instance": "plans","action": "GET"}',
+ '{"type": "org.onap.oof","instance": "plans","action": "POST"}'],
+ help='aaf_user_roles.')
+]
+
+CONF.register_opts(AAF_OPTS, group='aaf_authentication')
+
+AUTHZ_PERMS_USER = '{}/authz/perms/user/{}'
+
+EXPIRE_TIME = 'expire_time'
+
+perm_cache = {}
+
+def clear_cache():
+ perm_cache.clear()
+
+
+def authenticate(uid, passwd):
+ try:
+ perms = get_aaf_permissions(uid, passwd)
+ return has_valid_role(perms)
+ except Exception as exp:
+ LOG.error("Error Authenticating the user {} : {}: ".format(uid, exp))
+ pass
+ return False
+
+"""
+Check whether the user has valid permissions
+return True if the user has valid permissions
+else return false
+"""
+
+def has_valid_role(perms):
+ aaf_user_roles = CONF.aaf_authentication.aaf_user_roles
+
+ permObj = json.loads(perms)
+ permList = permObj["perm"]
+ for user_role in aaf_user_roles:
+ role = json.loads(user_role)
+ userType = role["type"]
+ userInstance = role["instance"]
+ userAction = role["action"]
+ for perm in permList:
+ permType = perm["type"]
+ permInstance = perm["instance"]
+ permAction = perm["action"]
+ if userType == permType and userInstance == permInstance and \
+ (userAction == permAction or userAction == "*"):
+ return True
+ return False
+
+"""
+Make the remote aaf api call if user is not in the cache.
+
+Return the perms
+"""
+def get_aaf_permissions(uid, passwd):
+ key = base64.b64encode("{}_{}".format(uid, passwd), "ascii")
+ time_delta = timedelta(hours = CONF.aaf_authentication.aaf_cache_expiry_hrs)
+
+# TBD - test cache logic
+ perms = perm_cache.get(key)
+
+ if perms and datetime.now() < perms.get(EXPIRE_TIME):
+ LOG.debug("Returning cached value")
+ return perms
+ LOG.debug("Invoking AAF authentication API")
+ response = remote_api(passwd, uid)
+ perms = {EXPIRE_TIME: datetime.now() + time_delta, 'roles': response}
+ perm_cache[key] = perms
+ return response
+
+def remote_api(passwd, uid):
+ server_url = CONF.aaf_authentication.aaf_url.rstrip('/')
+ kwargs = {
+ "server_url": server_url,
+ "retries": CONF.aaf_authentication.aaf_retries,
+ "username": uid,
+ "password": passwd,
+ "log_debug": LOG.debug,
+ "read_timeout": CONF.aaf_authentication.aaf_timeout,
+ }
+ restReq = rest.REST(**kwargs)
+
+ headers = {"Accept": "application/json"}
+ rkwargs = {
+ "method": 'GET',
+ "path": '',
+ "headers": headers,
+ }
+ response = restReq.request(**rkwargs)
+
+ if response is None:
+ LOG.error(_LE("No response from AAF "))
+ elif response.status_code != 200:
+ LOG.error(_LE("AAF request returned HTTP "
+ "status {} {}, link: {}").
+ format(response.status_code, response.reason,
+ server_url))
+ return response.content
+
diff --git a/conductor/conductor/api/controllers/v1/plans.py b/conductor/conductor/api/controllers/v1/plans.py
index 3858069..f70b998 100644
--- a/conductor/conductor/api/controllers/v1/plans.py
+++ b/conductor/conductor/api/controllers/v1/plans.py
@@ -16,7 +16,6 @@
#
# -------------------------------------------------------------------------
#
-
import six
import yaml
import base64
@@ -34,8 +33,9 @@ from conductor.api.controllers import validator
from conductor.i18n import _, _LI
from oslo_config import cfg
-CONF = cfg.CONF
+from conductor.api.adapters.aaf import aaf_authentication as aaf_auth
+CONF = cfg.CONF
LOG = log.getLogger(__name__)
@@ -44,14 +44,14 @@ CONDUCTOR_API_OPTS = [
default='',
help='Base URL for plans.'),
cfg.StrOpt('username',
- default='admin1',
+ default='',
help='username for plans.'),
cfg.StrOpt('password',
- default='plan.15',
+ default='',
help='password for plans.'),
cfg.BoolOpt('basic_auth_secure',
- default=True,
- help='auth toggling.')
+ default=True,
+ help='auth toggling.'),
]
CONF.register_opts(CONDUCTOR_API_OPTS, group='conductor_api')
@@ -61,7 +61,6 @@ CREATE_SCHEMA = (
(decorators.optional('id'), types.string),
(decorators.optional('limit'), types.integer),
(decorators.optional('name'), types.string),
- (decorators.optional('num_solution'), types.string),
('template', string_or_dict),
(decorators.optional('template_url'), types.string),
(decorators.optional('timeout'), types.integer),
@@ -86,11 +85,12 @@ class PlansBaseController(object):
def plans_get(self, plan_id=None):
- basic_auth_flag = CONF.conductor_api.basic_auth_secure
+ auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_authentication.is_aaf_enabled
+ # TBD - is healthcheck properly supported?
if plan_id == 'healthcheck' or \
- not basic_auth_flag or \
- (basic_auth_flag and check_basic_auth()):
+ not auth_flag or \
+ (auth_flag and check_auth()):
return self.plan_getid(plan_id)
def plan_getid(self, plan_id):
@@ -147,6 +147,7 @@ class PlansBaseController(object):
args.get('name')))
client = pecan.request.controller
+
transaction_id = pecan.request.headers.get('transaction-id')
if transaction_id:
args['template']['transaction-id'] = transaction_id
@@ -290,12 +291,13 @@ class PlansController(PlansBaseController):
if args and args['name']:
LOG.info('Plan name: {}'.format(args['name']))
- basic_auth_flag = CONF.conductor_api.basic_auth_secure
+ auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_authentication.is_aaf_enabled
- # Create the plan only when the basic authentication is disabled or pass the authentication check
- if not basic_auth_flag or \
- (basic_auth_flag and check_basic_auth()):
+ # Create the plan only when the basic authentication is disabled or pass the authenticaiton check
+ if not auth_flag or \
+ (auth_flag and check_auth()):
plan = self.plan_create(args)
+
if not plan:
error('/errors/server_error', _('Unable to create Plan.'))
else:
@@ -307,11 +309,13 @@ class PlansController(PlansBaseController):
"""Pecan subcontroller routing callback"""
return PlansItemController(uuid4), remainder
-def check_basic_auth():
+
+def check_auth():
"""
Returns True/False if the username/password of Basic Auth match/not match
+ Will also check role-based access controls if AAF integration configured
:return boolean value
- """
+ """
try:
if pecan.request.headers['Authorization'] and verify_user(pecan.request.headers['Authorization']):
@@ -323,19 +327,22 @@ def check_basic_auth():
user_pw = auth_str.split(' ')[1]
decode_user_pw = base64.b64decode(user_pw)
list_id_pw = decode_user_pw.split(':')
- LOG.error("Incorrect username={}/password={}".format(list_id_pw[0],list_id_pw[1]))
+ LOG.error("Incorrect username={} / password={}".format(list_id_pw[0], list_id_pw[1]))
except:
error('/errors/basic_auth_error', _('Unauthorized: The request does not '
'provide any HTTP authentication (basic authentication)'))
+ plan = False
if not plan:
error('/errors/authentication_error', _('Invalid credentials: username or password is incorrect'))
+
return plan
def verify_user(authstr):
"""
- authenticate user as per config file
+ authenticate user as per config file or AAF authentication service
+ :param authstr:
:return boolean value
"""
user_dict = dict()
@@ -347,7 +354,17 @@ def verify_user(authstr):
user_dict['password'] = list_id_pw[1]
password = CONF.conductor_api.password
username = CONF.conductor_api.username
- if username == user_dict['username'] and password == user_dict['password']:
- return True
+
+# print ("plans.verify_user(): Expected username/password: {}/{}".format(username, password))
+# print ("plans.verify_user(): Provided username/password: {}/{}".format(user_dict['username'], user_dict['password']))
+
+ retVal = False
+
+ if CONF.aaf_authentication.is_aaf_enabled:
+ retVal = aaf_auth.authenticate(user_dict['username'], user_dict['password'])
else:
- return False
+ if username == user_dict['username'] and password == user_dict['password']:
+ retVal = True
+
+ return retVal
+
diff --git a/conductor/conductor/tests/functional/simulators/aafsim/Dockerfile b/conductor/conductor/tests/functional/simulators/aafsim/Dockerfile
new file mode 100755
index 0000000..5970b3b
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/aafsim/Dockerfile
@@ -0,0 +1,39 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 AT&T 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.
+#
+# -------------------------------------------------------------------------
+#
+# Use an official Python runtime as a parent image
+FROM python:2.7
+
+# Set the working directory to /su/python/webpy-rest-dockerized
+WORKDIR /opt/aafsim
+
+# Copy the current directory contents into the container at /app
+ADD ./ /opt/aafsim
+
+# Install any needed packages specified in requirements.txt
+RUN pip install web.py
+
+# Make port 80 available to the world outside this container
+EXPOSE 8081
+
+# Define environment variable
+ENV NAME aafsim
+
+# Run aafsim.py when the container launches
+CMD ["/bin/sh", "-c", "python -u aafsim.py 8081 > /tmp/aafsim.log 2>&1"]
+
diff --git a/conductor/conductor/tests/functional/simulators/aafsim/aafsim.py b/conductor/conductor/tests/functional/simulators/aafsim/aafsim.py
new file mode 100755
index 0000000..c3cea8f
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/aafsim/aafsim.py
@@ -0,0 +1,87 @@
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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 web
+import web.webapi
+import json
+
+from subprocess import Popen, PIPE
+from oslo_log import log
+LOG = log.getLogger(__name__)
+
+urls = (
+ '/healthcheck','healthcheck',
+ '/authz/perms/user/','get_perms_user',
+)
+
+myhelp = {"/conductorsim/help":"provides help"}
+myok = {"ok":"ok"}
+json_data={}
+
+replydir = "./responses/"
+
+def replyToAafGet(web, replydir, replyfile):
+ LOG.debug("------------------------------------------------------")
+ fullreply = replydir + replyfile
+ trid=web.ctx.env.get('X_TRANSACTIONID','111111')
+ #print ("X-TransactionId : {}".format(trid))
+ LOG.debug("this is the context : {}".format(web.ctx.fullpath))
+ with open(fullreply) as json_file:
+ json_data = json.load(json_file)
+ LOG.debug(json_data)
+ web.header('Content-Type', 'application/json')
+ web.header('X-TransactionId', trid)
+ return json.dumps(json_data)
+
+class healthcheck:
+ def GET(self):
+ LOG.debug("------------------------------------------------------")
+ replyfile = "healthcheck.json"
+ #replyToAaiGet (web, replydir, replyfile)
+ fullreply = replydir + replyfile
+ trid=web.ctx.env.get('X_TRANSACTIONID','111111')
+ #print ("X-TransactionId : {}".format(trid))
+ LOG.debug("this is the context : {}".format(web.ctx.fullpath))
+ with open(fullreply) as json_file:
+ json_data = json.load(json_file)
+ LOG.debug(json_data)
+ web.header('Content-Type', 'application/json')
+ web.header('X-TransactionId', trid)
+ return json.dumps(json_data)
+
+class get_perms_user:
+ def GET(self):
+ LOG.debug("------------------------------------------------------")
+ replyfile = "get_perms_user.json"
+ #replyToAafGet (web, replydir, replyfile)
+ fullreply = replydir + replyfile
+ trid=web.ctx.env.get('X_TRANSACTIONID','111111')
+ #print ("X-TransactionId : {}".format(trid))
+ LOG.debug("this is the context : {}".format(web.ctx.fullpath))
+ with open(fullreply) as json_file:
+ json_data = json.load(json_file)
+ LOG.debug(json_data)
+ web.header('Content-Type', 'application/json')
+ web.header('X-TransactionId', trid)
+ return json.dumps(json_data)
+
+
+
+if __name__ == "__main__":
+ app = web.application(urls, globals())
+ app.run()
diff --git a/conductor/conductor/tests/functional/simulators/aafsim/requirements.txt b/conductor/conductor/tests/functional/simulators/aafsim/requirements.txt
new file mode 100755
index 0000000..c077218
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/aafsim/requirements.txt
@@ -0,0 +1 @@
+web
diff --git a/conductor/conductor/tests/functional/simulators/aafsim/responses/get_perms_user.json b/conductor/conductor/tests/functional/simulators/aafsim/responses/get_perms_user.json
new file mode 100644
index 0000000..d923ca5
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/aafsim/responses/get_perms_user.json
@@ -0,0 +1,6 @@
+{"perm": [{"instance": "plans", "action": "POST", "type": "org.onap.oof"},
+ {"instance": "plans", "action": "GET", "type": "org.onap.oof"}]
+}
+
+
+
diff --git a/conductor/conductor/tests/functional/simulators/aafsim/responses/healthcheck.json b/conductor/conductor/tests/functional/simulators/aafsim/responses/healthcheck.json
new file mode 100644
index 0000000..b0bd6da
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/aafsim/responses/healthcheck.json
@@ -0,0 +1 @@
+{"status":"success"}
diff --git a/conductor/conductor/tests/functional/simulators/build_aafsim.sh b/conductor/conductor/tests/functional/simulators/build_aafsim.sh
new file mode 100755
index 0000000..db6b4d1
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/build_aafsim.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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.
+#
+# -------------------------------------------------------------------------
+#
+# depends on execution from ../simulators directly, parent of ./aafsim
+cd ./aafsim
+docker build -t aafsim .
+
diff --git a/conductor/conductor/tests/functional/simulators/destroy_aafsim.sh b/conductor/conductor/tests/functional/simulators/destroy_aafsim.sh
new file mode 100755
index 0000000..b242dd0
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/destroy_aafsim.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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.
+#
+# -------------------------------------------------------------------------
+#
+docker stop aafsim
+docker rm aafsim
+docker rmi aafsim
+
diff --git a/conductor/conductor/tests/functional/simulators/run_aafsim.sh b/conductor/conductor/tests/functional/simulators/run_aafsim.sh
new file mode 100755
index 0000000..0039bbc
--- /dev/null
+++ b/conductor/conductor/tests/functional/simulators/run_aafsim.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# -------------------------------------------------------------------------
+# Copyright (c) 2018 OAM Technology Consulting LLC 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.
+#
+# -------------------------------------------------------------------------
+#
+# Note mapping to external port 8082. avoids conflict with aaisim
+docker run -d --name aafsim -p 8082:8081 aafsim
+
diff --git a/conductor/conductor/tests/unit/api/controller/v1/test_plans.py b/conductor/conductor/tests/unit/api/controller/v1/test_plans.py
index a0cd0c8..f0a28ec 100644
--- a/conductor/conductor/tests/unit/api/controller/v1/test_plans.py
+++ b/conductor/conductor/tests/unit/api/controller/v1/test_plans.py
@@ -35,21 +35,24 @@ class TestPlansController(base_api.BaseApiTest):
self.assertEqual(204, actual_response.status_int)
self.assertEqual("GET,POST", actual_response.headers['Allow'])
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch.object(plans.LOG, 'error')
@mock.patch.object(plans.LOG, 'debug')
@mock.patch.object(plans.LOG, 'warning')
@mock.patch.object(plans.LOG, 'info')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
def test_index_get(self, rpc_mock, info_mock, warning_mock, debug_mock,
- error_mock):
+ error_mock, aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
plan_id = str(uuid.uuid4())
params['id'] = plan_id
rpc_mock.return_value = {'plans': [params]}
+ aaf_mock.return_value = True
actual_response = self.app.get('/v1/plans', extra_environ=self.extra_environment)
self.assertEqual(200, actual_response.status_int)
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch.object(plans.LOG, 'error')
@mock.patch.object(plans.LOG, 'debug')
@mock.patch.object(plans.LOG, 'warning')
@@ -57,14 +60,17 @@ class TestPlansController(base_api.BaseApiTest):
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
def test_index_post_error(self, rpc_mock, info_mock, warning_mock,
debug_mock,
- error_mock):
+ error_mock,
+ aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = jsonutils.dumps(json.loads(open(req_json_file).read()))
rpc_mock.return_value = {}
+ aaf_mock.return_value = True
response = self.app.post('/v1/plans', params=params,
expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(500, response.status_int)
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch.object(plans.LOG, 'error')
@mock.patch.object(plans.LOG, 'debug')
@mock.patch.object(plans.LOG, 'warning')
@@ -72,7 +78,8 @@ class TestPlansController(base_api.BaseApiTest):
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
def test_index_post_success(self, rpc_mock, info_mock, warning_mock,
debug_mock,
- error_mock):
+ error_mock,
+ aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
mock_params = copy.deepcopy(params)
@@ -80,6 +87,7 @@ class TestPlansController(base_api.BaseApiTest):
mock_params['id'] = plan_id
rpc_mock.return_value = {'plan': mock_params}
+ aaf_mock.return_value = True
params = json.dumps(params)
response = self.app.post('/v1/plans', params=params,
expect_errors=True, extra_environ=self.extra_environment)
@@ -94,26 +102,30 @@ class TestPlansController(base_api.BaseApiTest):
class TestPlansItemController(base_api.BaseApiTest):
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
- def test_index_options(self, rpc_mock):
+ def test_index_options(self, rpc_mock, aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
plan_id = str(uuid.uuid4())
params['id'] = plan_id
rpc_mock.return_value = {'plans': [params]}
+ aaf_mock.return_value = True
url = '/v1/plans/' + plan_id
print(url)
actual_response = self.app.options(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(204, actual_response.status_int)
self.assertEqual("GET,DELETE", actual_response.headers['Allow'])
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
- def test_index_httpmethod_notallowed(self, rpc_mock):
+ def test_index_httpmethod_notallowed(self, rpc_mock, aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
plan_id = str(uuid.uuid4())
params['id'] = plan_id
rpc_mock.return_value = {'plans': [params]}
+ aaf_mock.return_value = True
url = '/v1/plans/' + plan_id
actual_response = self.app.put(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(405, actual_response.status_int)
@@ -122,35 +134,41 @@ class TestPlansItemController(base_api.BaseApiTest):
actual_response = self.app.post(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(405, actual_response.status_int)
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
- def test_index_get(self, rpc_mock):
+ def test_index_get(self, rpc_mock, aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
plan_id = str(uuid.uuid4())
params['id'] = plan_id
expected_response = {'plans': [params]}
rpc_mock.return_value = {'plans': [params]}
+ aaf_mock.return_value = True
url = '/v1/plans/' + plan_id
actual_response = self.app.get(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(200, actual_response.status_int)
self.assertJsonEqual(expected_response,
json.loads(actual_response.body))
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
- def test_index_get_non_exist(self, rpc_mock):
+ def test_index_get_non_exist(self, rpc_mock, aaf_mock):
rpc_mock.return_value = {'plans': []}
+ aaf_mock.return_value = True
plan_id = str(uuid.uuid4())
url = '/v1/plans/' + plan_id
actual_response = self.app.get(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(404, actual_response.status_int)
+ @mock.patch('conductor.api.adapters.aaf.aaf_authentication.authenticate')
@mock.patch('conductor.common.music.messaging.component.RPCClient.call')
- def test_index_delete(self, rpc_mock):
+ def test_index_delete(self, rpc_mock, aaf_mock):
req_json_file = './conductor/tests/unit/api/controller/v1/plans.json'
params = json.loads(open(req_json_file).read())
plan_id = str(uuid.uuid4())
params['id'] = plan_id
rpc_mock.return_value = {'plans': [params]}
+ aaf_mock.return_value = True
url = '/v1/plans/' + plan_id
actual_response = self.app.delete(url=url, expect_errors=True, extra_environ=self.extra_environment)
self.assertEqual(204, actual_response.status_int)
diff --git a/conductor/requirements.txt b/conductor/requirements.txt
index d09c960..6bc9dba 100644
--- a/conductor/requirements.txt
+++ b/conductor/requirements.txt
@@ -23,4 +23,5 @@ 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
+onapsmsclient>=0.0.3
+Flask>=0.11.1 \ No newline at end of file