Improve code coverage of openstack module

Added new unit tests to cover create_wait, update_wait, delete_wait,
scale and scale_wait methods. Added heat-client fixture for writing
test cases. Also removed unreachable code in openstack module.

Change-Id: I9c1b5fe2e4efb41368d53cdac8dbbd49eea4b60a
This commit is contained in:
shubham potale 2019-02-15 05:13:09 +00:00 committed by shubham
parent 6e7e9bb8e3
commit 0bb6a6bf80
10 changed files with 462 additions and 24 deletions

View File

@ -127,6 +127,7 @@ reno==2.7.0
repoze.lru==0.7 repoze.lru==0.7
requests-oauthlib==0.8.0 requests-oauthlib==0.8.0
requests==2.14.2 requests==2.14.2
requests-mock==1.2.0
requestsexceptions==1.4.0 requestsexceptions==1.4.0
restructuredtext-lint==1.1.3 restructuredtext-lint==1.1.3
rfc3986==1.1.0 rfc3986==1.1.0

View File

@ -16,6 +16,7 @@
import mock import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as config_fixture from oslo_config import fixture as config_fixture
from requests_mock.contrib import fixture as requests_mock_fixture
from tacker.tests import base from tacker.tests import base
@ -32,3 +33,15 @@ class TestCase(base.BaseTestCase):
def _mock(self, target, new=mock.DEFAULT): def _mock(self, target, new=mock.DEFAULT):
patcher = mock.patch(target, new) patcher = mock.patch(target, new)
return patcher.start() return patcher.start()
class FixturedTestCase(TestCase):
client_fixture_class = None
def setUp(self):
super(FixturedTestCase, self).setUp()
if self.client_fixture_class:
self.requests_mock = self.useFixture(requests_mock_fixture.
Fixture())
fix = self.client_fixture_class(self.requests_mock)
self.cs = self.useFixture(fix).client

View File

@ -129,8 +129,9 @@ def get_dummy_vnf_config_obj():
'config': {'firewall': 'dummy_firewall_values'}}}}}}} 'config': {'firewall': 'dummy_firewall_values'}}}}}}}
def get_dummy_vnf(): def get_dummy_vnf(status='PENDING_CREATE', scaling_group=False,
return {'status': 'PENDING_CREATE', 'instance_id': None, 'name': instance_id=None):
dummy_vnf = {'status': status, 'instance_id': instance_id, 'name':
u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437', u'test_openwrt', 'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e', 'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'vnfd': { 'vnfd': {
@ -146,6 +147,11 @@ def get_dummy_vnf():
'attributes': {u'param_values': u''}, 'attributes': {u'param_values': u''},
'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123', 'id': 'eb84260e-5ff7-4332-b032-50a14d6c1123',
'description': u'OpenWRT with services'} 'description': u'OpenWRT with services'}
if scaling_group:
dummy_vnf['attributes'].update({'scaling_group_names':
'{"SP1": "SP1_group"}',
'heat_template': 'test'})
return dummy_vnf
def get_dummy_vnf_config_attr(): def get_dummy_vnf_config_attr():

View File

@ -0,0 +1,56 @@
# Copyright 2019 NTT DATA
#
# 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 fixtures
from heatclient import client
from keystoneauth1 import fixture
from keystoneauth1 import loading
from keystoneauth1 import session
IDENTITY_URL = 'http://identityserver:5000/v3'
HEAT_URL = 'http://heat-api'
class ClientFixture(fixtures.Fixture):
def __init__(self, requests_mock, heat_url=HEAT_URL,
identity_url=IDENTITY_URL):
super(ClientFixture, self).__init__()
self.identity_url = identity_url
self.client = None
self.token = fixture.V2Token()
self.token.set_scope()
self.requests_mock = requests_mock
self.discovery = fixture.V2Discovery(href=self.identity_url)
s = self.token.add_service('orchestration')
s.add_endpoint(heat_url)
def setUp(self):
super(ClientFixture, self).setUp()
auth_url = '%s/tokens' % self.identity_url
headers = {'X-Content-Type': 'application/json'}
self.requests_mock.post(auth_url,
json=self.token, headers=headers)
self.requests_mock.get(self.identity_url,
json=self.discovery, headers=headers)
self.client = self.new_client()
def new_client(self):
self.session = session.Session()
loader = loading.get_plugin_loader('password')
self.session.auth = loader.load_from_options(
auth_url=self.identity_url, username='xx', password='xx')
return client.Client("1", session=self.session)

View File

@ -0,0 +1,70 @@
# Copyright 2019 NTT DATA
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tacker.tests import uuidsentinel
def get_dummy_stack(outputs=True, status='CREATE_COMPELETE'):
outputs_value = [{}]
if outputs:
outputs_value = [{'output_value': '192.168.120.216',
'output_key': 'mgmt_ip-VDU1',
'description': 'No description given'}]
dummy_stack = {'parent': None, 'disable_rollback': True,
'description': 'Demo example\n',
'deletion_time': None, 'stack_name':
'vnf-6_3f089d15-0000-4dc0-8519-a613d577a07b',
'stack_status_reason': 'Stack CREATE completed successfully',
'creation_time': '2019-02-28T15:17:48Z',
'outputs': outputs_value,
'timeout_mins': 10, 'stack_status': status,
'stack_owner': None,
'updated_time': None,
'id': uuidsentinel.instance_id}
return dummy_stack
def get_dummy_resource(resource_status='CREATE_COMPLETE'):
return {'resource_name': 'SP1_group',
'logical_resource_id': 'SP1_group',
'creation_time': '2019-03-06T08:57:47Z',
'resource_status_reason': 'state changed',
'updated_time': '2019-03-06T08:57:47Z',
'required_by': ['SP1_scale_out', 'SP1_scale_in'],
'resource_status': resource_status,
'physical_resource_id': uuidsentinel.stack_id,
'attributes': {'outputs_list': None, 'refs': None,
'refs_map': None, 'outputs': None,
'current_size': None, 'mgmt_ip-vdu1': 'test1'},
'resource_type': 'OS::Heat::AutoScalingGroup'}
def get_dummy_event(resource_status='CREATE_COMPLETE'):
return {'resource_name': 'SP1_scale_out',
'event_time': '2019-03-06T05:44:27Z',
'logical_resource_id': 'SP1_scale_out',
'resource_status': resource_status,
'resource_status_reason': 'state changed',
'id': uuidsentinel.event_id}
def get_dummy_policy_dict():
return {'instance_id': uuidsentinel.instance_id,
'vnf': {'attributes': {'scaling_group_names': '{"SP1": "G1"}'},
'id': uuidsentinel.vnf_id},
'name': 'SP1',
'action': 'out',
'type': 'tosca.policies.tacker.Scaling',
'properties': {}}

View File

@ -13,29 +13,293 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import mock import mock
from tacker import context
from tacker.extensions import vnfm from tacker.extensions import vnfm
from tacker.tests.unit import base from tacker.tests.unit import base
from tacker.tests.unit.db import utils
from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import client
from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import \
fixture_data_utils as fd_utils
from tacker.tests import uuidsentinel
from tacker.vnfm.infra_drivers.openstack import openstack from tacker.vnfm.infra_drivers.openstack import openstack
class TestOpenStack(base.TestCase): @ddt.ddt
class TestOpenStack(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
@mock.patch("tacker.vnfm.infra_drivers.openstack.heat_client.HeatClient") def setUp(self):
def test_create_wait_with_heat_connection_exception(self, mocked_hc): super(TestOpenStack, self).setUp()
stack = {"stack_status", "CREATE_IN_PROGRESS"} self.openstack = openstack.OpenStack()
mocked_hc.get.side_effect = [stack, Exception("any stuff")] self.context = context.get_admin_context()
openstack_driver = openstack.OpenStack() self.url = client.HEAT_URL
self.instance_uuid = uuidsentinel.instance_id
self.stack_id = uuidsentinel.stack_id
self.json_headers = {'content-type': 'application/json',
'location': 'http://heat-api/stacks/'
+ self.instance_uuid + '/myStack/60f83b5e'}
self._mock('tacker.common.clients.OpenstackClients.heat', self.cs)
self.mock_log = mock.patch('tacker.vnfm.infra_drivers.openstack.'
'openstack.LOG').start()
mock.patch('time.sleep', return_value=None).start()
def _response_in_wait_until_stack_ready(self, status_list,
stack_outputs=True):
# response for heat_client's get()
for status in status_list:
url = self.url + '/stacks/' + self.instance_uuid
json = {'stack': fd_utils.get_dummy_stack(stack_outputs,
status=status)}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def _response_in_resource_get(self, id, res_name=None):
# response for heat_client's resource_get()
if res_name:
url = self.url + '/stacks/' + id + ('/myStack/60f83b5e/'
'resources/') + res_name
else:
url = self.url + '/stacks/' + id
json = {'resource': fd_utils.get_dummy_resource()}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def test_create_wait(self):
self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS",
"CREATE_COMPLETE"])
vnf_dict = utils.get_dummy_vnf(instance_id=self.instance_uuid)
self.openstack.create_wait(None, None,
vnf_dict, self.instance_uuid, None)
self.mock_log.debug.assert_called_with('outputs %s',
fd_utils.get_dummy_stack()['outputs'])
self.assertEqual('{"VDU1": "192.168.120.216"}',
vnf_dict['mgmt_ip_address'])
def test_create_wait_without_mgmt_ips(self):
self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS",
"CREATE_COMPLETE"],
stack_outputs=False)
vnf_dict = utils.get_dummy_vnf(instance_id=self.instance_uuid)
self.openstack.create_wait(None, None,
vnf_dict, self.instance_uuid, None)
self.mock_log.debug.assert_called_with('outputs %s',
fd_utils.get_dummy_stack(outputs=False)['outputs'])
self.assertIsNone(vnf_dict['mgmt_ip_address'])
def test_create_wait_with_scaling_group_names(self):
self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS",
"CREATE_COMPLETE"])
self._response_in_resource_get(self.instance_uuid,
res_name='SP1_group')
url = self.url + '/stacks/' + self.stack_id + '/resources'
json = {'resources': [fd_utils.get_dummy_resource()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
self._response_in_resource_get(self.stack_id)
vnf_dict = utils.get_dummy_vnf(scaling_group=True)
self.openstack.create_wait(None, None, vnf_dict, self.instance_uuid,
None)
self.assertEqual('{"vdu1": ["test1"]}', vnf_dict['mgmt_ip_address'])
def test_create_wait_failed_with_stack_retries_0(self):
self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS"])
vnf_dict = utils.get_dummy_vnf(instance_id=self.instance_uuid)
self.assertRaises(vnfm.VNFCreateWaitFailed, self.assertRaises(vnfm.VNFCreateWaitFailed,
openstack_driver.create_wait, self.openstack.create_wait,
None, None, {}, 'vnf_id', None) None, None, vnf_dict, self.instance_uuid, None)
@mock.patch("tacker.vnfm.infra_drivers.openstack.heat_client.HeatClient") def test_create_wait_failed_with_stack_retries_not_0(self):
def test_delete_wait_with_heat_connection_exception(self, mocked_hc): self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS",
stack = {"stack_status", "DELETE_IN_PROGRESS"} "FAILED"])
mocked_hc.get.side_effect = [stack, Exception("any stuff")] vnf_dict = utils.get_dummy_vnf(instance_id=self.instance_uuid)
openstack_driver = openstack.OpenStack() self.assertRaises(vnfm.VNFCreateWaitFailed,
self.openstack.create_wait,
None, None, vnf_dict, self.instance_uuid, {})
def _exception_response(self):
url = self.url + '/stacks/' + self.instance_uuid
body = {"error": Exception("any stuff")}
self.requests_mock.register_uri('GET', url, body=body,
status_code=404, headers=self.json_headers)
def test_create_wait_with_exception(self):
self._exception_response()
vnf_dict = utils.get_dummy_vnf(instance_id=self.instance_uuid)
self.assertRaises(vnfm.VNFCreateWaitFailed,
self.openstack.create_wait,
None, None, vnf_dict, self.instance_uuid, None)
def test_delete_wait_failed_with_stack_retries_0(self):
self._response_in_wait_until_stack_ready(["DELETE_IN_PROGRESS"])
self.assertRaises(vnfm.VNFDeleteWaitFailed, self.assertRaises(vnfm.VNFDeleteWaitFailed,
openstack_driver.delete_wait, self.openstack.delete_wait,
None, None, 'vnf_id', None, None) None, None, self.instance_uuid, None, None)
def test_delete_wait_stack_retries_not_0(self):
self._response_in_wait_until_stack_ready(["DELETE_IN_PROGRESS",
"FAILED"])
self.assertRaises(vnfm.VNFDeleteWaitFailed,
self.openstack.delete_wait,
None, None, self.instance_uuid, None, None)
self.mock_log.warning.assert_called_once()
def test_update_wait(self):
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS",
"UPDATE_COMPLETE"])
vnf_dict = utils.get_dummy_vnf(status='PENDING_UPDATE',
instance_id=self.instance_uuid)
self.openstack.update_wait(None, None, vnf_dict, None)
self.mock_log.debug.assert_called_with('outputs %s',
fd_utils.get_dummy_stack()['outputs'])
self.assertEqual('{"VDU1": "192.168.120.216"}',
vnf_dict['mgmt_ip_address'])
def test_update_wait_without_mgmt_ips(self):
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS",
"UPDATE_COMPLETE"],
stack_outputs=False)
vnf_dict = utils.get_dummy_vnf(status='PENDING_UPDATE',
instance_id=self.instance_uuid)
self.openstack.update_wait(None, None, vnf_dict, None)
self.mock_log.debug.assert_called_with('outputs %s',
fd_utils.get_dummy_stack(outputs=False)['outputs'])
self.assertIsNone(vnf_dict['mgmt_ip_address'])
def test_update_wait_failed_with_retries_0(self):
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS"])
vnf_dict = utils.get_dummy_vnf(status='PENDING_UPDATE',
instance_id=self.instance_uuid)
self.assertRaises(vnfm.VNFUpdateWaitFailed,
self.openstack.update_wait,
None, None, vnf_dict,
None)
def test_update_wait_failed_stack_retries_not_0(self):
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS",
"FAILED"])
vnf_dict = utils.get_dummy_vnf(status='PENDING_UPDATE',
instance_id=self.instance_uuid)
self.assertRaises(vnfm.VNFUpdateWaitFailed,
self.openstack.update_wait,
None, None, vnf_dict,
None)
def _responses_in_resource_event_list(self, dummy_event):
# response for heat_client's resource_event_list()
url = self.url + '/stacks/' + self.instance_uuid
json = {'stack': [fd_utils.get_dummy_stack()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
url = self.url + '/stacks/' + self.instance_uuid + ('/myStack/60f83b5e'
'/resources/SP1_scale_out/events?limit=1&sort_dir=desc&sort_keys='
'event_time')
json = {'events': [dummy_event]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def test_scale(self):
dummy_event = fd_utils.get_dummy_event()
self._responses_in_resource_event_list(dummy_event)
# response for heat_client's resource_signal()
url = self.url + '/stacks/' + self.instance_uuid + ('/myStack/60f83b5e'
'/resources/SP1_scale_out/signal')
self.requests_mock.register_uri('POST', url, json={},
headers=self.json_headers)
event_id = self.openstack.scale(plugin=self, context=self.context,
auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None)
self.assertEqual(dummy_event['id'], event_id)
def _response_in_resource_get_list(self):
# response for heat_client's resource_get_list()
url = self.url + '/stacks/' + self.stack_id + '/resources'
json = {'resources': [fd_utils.get_dummy_resource()]}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def _test_scale(self, resource_status):
dummy_event = fd_utils.get_dummy_event(resource_status)
self._responses_in_resource_event_list(dummy_event)
self._response_in_resource_get(self.instance_uuid, res_name='G1')
self._response_in_resource_get_list()
self._response_in_resource_get(self.stack_id)
self._response_in_resource_get(self.instance_uuid,
res_name='SP1_group')
def test_scale_wait_with_different_last_event_id(self):
self._test_scale("SIGNAL_COMPLETE")
mgmt_ip = self.openstack.scale_wait(plugin=self, context=self.context,
auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None,
last_event_id=uuidsentinel.
non_last_event_id)
self.assertEqual('{"vdu1": ["test1"]}', mgmt_ip)
@ddt.data("SIGNAL_COMPLETE", "CREATE_COMPLETE")
def test_scale_wait_with_same_last_event_id(self, resource_status):
self._test_scale(resource_status)
mgmt_ip = self.openstack.scale_wait(plugin=self,
context=self.context,
auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None,
last_event_id=fd_utils.get_dummy_event()['id'])
self.assertEqual('{"vdu1": ["test1"]}', mgmt_ip)
@mock.patch('tacker.vnfm.infra_drivers.openstack.openstack.LOG')
def test_scale_wait_failed_with_exception(self, mock_log):
self._exception_response()
self.assertRaises(vnfm.VNFScaleWaitFailed,
self.openstack.scale_wait,
plugin=self, context=self.context, auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None,
last_event_id=fd_utils.get_dummy_event()['id'])
mock_log.warning.assert_called_once()
def _response_in_resource_metadata(self, metadata=None):
# response for heat_client's resource_metadata()
url = self.url + '/stacks/' + self.instance_uuid + \
'/myStack/60f83b5e/resources/SP1_scale_out/metadata'
json = {'metadata': {'scaling_in_progress': metadata}}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def test_scale_wait_failed_with_stack_retries_0(self):
dummy_event = fd_utils.get_dummy_event("CREATE_IN_PROGRESS")
self._responses_in_resource_event_list(dummy_event)
self._response_in_resource_metadata(True)
self.assertRaises(vnfm.VNFScaleWaitFailed,
self.openstack.scale_wait,
plugin=self, context=self.context, auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None,
last_event_id=dummy_event['id'])
self.mock_log.warning.assert_called_once()
def test_scale_wait_without_resource_metadata(self):
dummy_event = fd_utils.get_dummy_event("CREATE_IN_PROGRESS")
self._responses_in_resource_event_list(dummy_event)
self._response_in_resource_metadata()
self._response_in_resource_get(self.instance_uuid, res_name='G1')
self._response_in_resource_get_list()
self._response_in_resource_get(self.stack_id)
self._response_in_resource_get(self.instance_uuid,
res_name='SP1_group')
mgmt_ip = self.openstack.scale_wait(plugin=self, context=self.context,
auth_attr=None,
policy=fd_utils.get_dummy_policy_dict(),
region_name=None,
last_event_id=fd_utils.get_dummy_event()
['id'])
error_reason = ('When signal occurred within cool down '
'window, no events generated from heat, '
'so ignore it')
self.mock_log.warning.assert_called_once_with(error_reason)
self.assertEqual('{"vdu1": ["test1"]}', mgmt_ip)

View File

@ -0,0 +1,33 @@
# Copyright 2019 NTT DATA
# 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.
import sys
class UUIDSentinels(object):
def __init__(self):
from oslo_utils import uuidutils
self._uuid_module = uuidutils
self._sentinels = {}
def __getattr__(self, name):
if name.startswith('_'):
raise ValueError('Sentinels must not start with _')
if name not in self._sentinels:
self._sentinels[name] = self._uuid_module.generate_uuid()
return self._sentinels[name]
sys.modules[__name__] = UUIDSentinels()

View File

@ -180,13 +180,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
stack=vnf_id) stack=vnf_id)
raise exception_class(reason=error_reason) raise exception_class(reason=error_reason)
elif stack_retries != 0 and status != wait_status: elif stack_retries != 0 and status != wait_status:
if stack: error_reason = stack.stack_status_reason
error_reason = stack.stack_status_reason
else:
error_reason = _("action on VNF %(vnf_id)s is not "
"completed. Current status of stack is "
"%(stack_status)s") % {'vnf_id': vnf_id,
'stack_status': status}
LOG.warning(error_reason) LOG.warning(error_reason)
raise exception_class(reason=error_reason) raise exception_class(reason=error_reason)

View File

@ -19,3 +19,4 @@ testtools>=2.2.0 # MIT
WebTest>=2.0.27 # MIT WebTest>=2.0.27 # MIT
python-barbicanclient>=4.5.2 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0
python-blazarclient>=1.0.1 # Apache-2.0 python-blazarclient>=1.0.1 # Apache-2.0
requests-mock>=1.2.0 # Apache-2.0