diff --git a/lower-constraints.txt b/lower-constraints.txt index c55a685aa..c918b5456 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -108,6 +108,7 @@ pyparsing==2.2.0 pyperclip==1.6.0 pyroute2==0.4.21 python-barbicanclient==4.5.2 +python-blazarclient==1.0.1 python-dateutil==2.7.2 python-editor==1.0.3 python-glanceclient==2.9.1 diff --git a/tacker/tests/constants.py b/tacker/tests/constants.py index 992bb7854..d82ce4a97 100644 --- a/tacker/tests/constants.py +++ b/tacker/tests/constants.py @@ -20,6 +20,7 @@ VNF_CIRROS_DEAD_TIMEOUT = 500 ACTIVE_SLEEP_TIME = 3 DEAD_SLEEP_TIME = 1 SCALE_WINDOW_SLEEP_TIME = 120 +SCALE_SLEEP_TIME = 120 NS_CREATE_TIMEOUT = 400 NS_DELETE_TIMEOUT = 300 NOVA_CLIENT_VERSION = 2 @@ -29,3 +30,9 @@ VDU_AUTOHEALING_TIMEOUT = 500 VDU_AUTOHEALING_SLEEP_TIME = 3 VNF_CIRROS_PENDING_HEAL_TIMEOUT = 300 PENDING_SLEEP_TIME = 3 + +# Blazar related +LEASE_EVENT_STATUS = 'DONE' +START_LEASE_EVET_TYPE = 'start_lease' +LEASE_CHECK_EVENT_TIMEOUT = 300 +LEASE_CHECK_SLEEP_TIME = 3 diff --git a/tacker/tests/etc/samples/sample-tosca-vnfd-instance-reservation.yaml b/tacker/tests/etc/samples/sample-tosca-vnfd-instance-reservation.yaml new file mode 100644 index 000000000..4c1de3c73 --- /dev/null +++ b/tacker/tests/etc/samples/sample-tosca-vnfd-instance-reservation.yaml @@ -0,0 +1,90 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: VNF TOSCA template with flavor input parameters + +metadata: + template_name: sample-tosca-vnfd-instance-reservation + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + properties: + image: cirros-0.4.0-x86_64-disk + flavor: { get_input: flavor } + reservation_metadata: + resource_type: {get_input: resource_type} + id: { get_input: server_group_id } + + CP1: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + order: 0 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL1 + - virtualBinding: + node: VDU1 + + CP2: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 1 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL2 + - virtualBinding: + node: VDU1 + + CP3: + type: tosca.nodes.nfv.CP.Tacker + properties: + order: 2 + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL3 + - virtualBinding: + node: VDU1 + + VL1: + type: tosca.nodes.nfv.VL + properties: + network_name: net_mgmt + vendor: Tacker + + VL2: + type: tosca.nodes.nfv.VL + + properties: + network_name: net0 + vendor: Tacker + + VL3: + type: tosca.nodes.nfv.VL + properties: + network_name: net1 + vendor: Tacker + + + policies: + - RSV: + type: tosca.policies.tacker.Reservation + reservation: + start_actions: [SP_RSV] + before_end_actions: [SP_RSV] + end_actions: [noop] + properties: + lease_id: { get_input: lease_id } + - SP_RSV: + type: tosca.policies.tacker.Scaling + properties: + increment: 2 + cooldown: 60 + min_instances: 0 + max_instances: 2 + default_instances: 0 + targets: [VDU1] diff --git a/tacker/tests/functional/base.py b/tacker/tests/functional/base.py index 2f816439b..8678f16cc 100644 --- a/tacker/tests/functional/base.py +++ b/tacker/tests/functional/base.py @@ -15,6 +15,7 @@ import time import yaml +from blazarclient import client as blazar_client from glanceclient.v2 import client as glance_client from keystoneauth1.identity import v3 from keystoneauth1 import session @@ -106,6 +107,21 @@ class BaseTackerTest(base.BaseTestCase): data['project_domain_name'] = domain_name return clients.OpenstackClients(auth_attr=data).heat + def blazarclient(cls): + data = cls.get_credentials() + domain_name = data.pop('domain_name') + data['user_domain_name'] = domain_name + data['project_domain_name'] = domain_name + auth_ses = clients.OpenstackClients(auth_attr=data).keystone_session + args = { + 'session': auth_ses, + 'service_type': 'reservation', + 'interface': 'public', + 'region_name': 'RegionOne', + } + client = blazar_client.Client(**args) + return client + @classmethod def glanceclient(cls): vim_params = cls.get_credentials() @@ -252,3 +268,14 @@ class BaseTackerTest(base.BaseTestCase): resource_name=resource_name) resource_dict = resource_details.to_dict() self.assertTrue(resource_dict['attributes']['port_security_enabled']) + + def trigger_vnf(self, vnf, policy_name, policy_action): + credential = 'g0jtsxu9' + body = {"trigger": {'policy_name': policy_name, + 'action_name': policy_action, + 'params': { + 'data': {'alarm_id': '35a80852-e24f-46ed-bd34-e2f831d00172', 'current': 'alarm'}, # noqa + 'credential': credential} + } + } + self.client.post('/vnfs/%s/triggers' % vnf, body) diff --git a/tacker/tests/functional/vnfm/test_tosca_vnf_alarm.py b/tacker/tests/functional/vnfm/test_tosca_vnf_alarm.py index 0bada9545..1c3cf80c7 100644 --- a/tacker/tests/functional/vnfm/test_tosca_vnf_alarm.py +++ b/tacker/tests/functional/vnfm/test_tosca_vnf_alarm.py @@ -26,7 +26,6 @@ import yaml class VnfTestAlarmMonitor(base.BaseTackerTest): def _test_vnf_tosca_alarm(self, vnfd_file, vnf_name): - vnf_trigger_path = '/vnfs/%s/triggers' input_yaml = read_file(vnfd_file) tosca_dict = yaml.safe_load(input_yaml) tosca_arg = {'vnfd': {'name': vnf_name, @@ -55,17 +54,6 @@ class VnfTestAlarmMonitor(base.BaseTackerTest): self.assertEqual(count, len(jsonutils.loads(vnf[ 'mgmt_ip_address'])['VDU1'])) - def trigger_vnf(vnf, policy_name, policy_action): - credential = 'g0jtsxu9' - body = {"trigger": {'policy_name': policy_name, - 'action_name': policy_action, - 'params': { - 'data': {'alarm_id': '35a80852-e24f-46ed-bd34-e2f831d00172', 'current': 'alarm'}, # noqa - 'credential': credential} - } - } - self.client.post(vnf_trigger_path % vnf, body) - def _inject_monitoring_policy(vnfd_dict): polices = vnfd_dict['topology_template'].get('policies', []) mon_policy = dict() @@ -92,14 +80,15 @@ class VnfTestAlarmMonitor(base.BaseTackerTest): vnf_id, constants.VNF_CIRROS_CREATE_TIMEOUT, constants.ACTIVE_SLEEP_TIME) - trigger_vnf(vnf_id, mon_policy_name, mon_policy_action) + self.trigger_vnf(vnf_id, mon_policy_name, mon_policy_action) else: if 'scaling_out' in mon_policy_name: _waiting_time(2) time.sleep(constants.SCALE_WINDOW_SLEEP_TIME) # scaling-out backend action scaling_out_action = mon_policy_action + '-out' - trigger_vnf(vnf_id, mon_policy_name, scaling_out_action) + self.trigger_vnf( + vnf_id, mon_policy_name, scaling_out_action) _waiting_time(3) @@ -109,7 +98,8 @@ class VnfTestAlarmMonitor(base.BaseTackerTest): time.sleep(constants.SCALE_WINDOW_SLEEP_TIME) # scaling-in backend action scaling_in_action = mon_policy_action + '-in' - trigger_vnf(vnf_id, scaling_in_name, scaling_in_action) + self.trigger_vnf( + vnf_id, scaling_in_name, scaling_in_action) _waiting_time(2) diff --git a/tacker/tests/functional/vnfm/test_tosca_vnf_reservation.py b/tacker/tests/functional/vnfm/test_tosca_vnf_reservation.py new file mode 100644 index 000000000..7ab0e5297 --- /dev/null +++ b/tacker/tests/functional/vnfm/test_tosca_vnf_reservation.py @@ -0,0 +1,244 @@ +# +# +# 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 ast +import time + +import datetime +import json +import testtools +import yaml + +from blazarclient import exception +from tacker.plugins.common import constants as evt_constants +from tacker.tests import constants +from tacker.tests.functional import base +from tacker.tests.utils import read_file + + +HYPERVISORS = [] + + +def hypervisors(): + global HYPERVISORS + client = base.BaseTackerTest.novaclient() + hypervisor_list = client.hypervisors.list() + for hypervisor in hypervisor_list: + if hypervisor.running_vms == 0: + HYPERVISORS.append(hypervisor) + return HYPERVISORS + + +class VnfTestReservationMonitor(base.BaseTackerTest): + + def _test_vnf_tosca_reservation(self, vnfd_file, vnf_name, + lease_id, param_values=None): + input_yaml = read_file(vnfd_file) + tosca_dict = yaml.safe_load(input_yaml) + + # TODO(niraj-singh): It's not possible to pass parameters through + # parameter file due to Bug #1799683. Once this bug is fixed, no need + # to update vnfd yaml. + vdu_prop = tosca_dict['topology_template']['node_templates']['VDU1'] + vdu_prop['properties']['flavor'] = param_values.get('flavor') + vdu_prop['properties']['reservation_metadata']['id'] =\ + param_values.get('server_group_id') + vdu_prop['properties']['reservation_metadata']['resource_type'] =\ + param_values.get('resource_type') + policies = tosca_dict['topology_template']['policies'] + policies[0]['RSV']['reservation']['properties']['lease_id'] =\ + param_values.get('lease_id') + + tosca_arg = {'vnfd': {'name': vnf_name, + 'attributes': {'vnfd': tosca_dict}}} + blazarclient = self.blazarclient() + + # Create vnfd with tosca template + vnfd_instance = self.client.create_vnfd(body=tosca_arg) + self.assertIsNotNone(vnfd_instance) + + # Create vnf with vnfd_id + vnfd_id = vnfd_instance['vnfd']['id'] + vnf_arg = {'vnf': {'vnfd_id': vnfd_id, 'name': vnf_name}} + vnf_instance = self.client.create_vnf(body=vnf_arg) + + self.validate_vnf_instance(vnfd_instance, vnf_instance) + + vnf_id = vnf_instance['vnf']['id'] + + def _get_reservation_policy(vnfd_dict): + policies = vnfd_dict['topology_template'].get('policies', []) + res_policy = dict() + for policy_dict in policies: + for name, policy in policy_dict.items(): + if policy['type'] == constants.POLICY_RESERVATION: + reservations = policy['reservation'] + for reserv_key, reserv_value in reservations.items(): + if reserv_key == 'properties': + continue + for policy_action in reserv_value: + res_policy[reserv_key] = policy_action + return res_policy + + def _check_lease_event_status(): + lease_event_status = _verify_and_get_lease_event_status() + self.assertEqual(lease_event_status, constants.LEASE_EVENT_STATUS, + "Lease %(lease_id)s with status %(status)s is" + " expected to be %(target)s" % + {"lease_id": lease_id, + "status": lease_event_status, + "target": constants.LEASE_EVENT_STATUS}) + + def _verify_and_get_lease_event_status(): + start_time = int(time.time()) + while ((int(time.time()) - start_time) < + constants.LEASE_CHECK_EVENT_TIMEOUT): + lease_detail = blazarclient.lease.get(lease_id) + lease_events = lease_detail.get('events') + for event in lease_events: + lease_event_status = event.get('status') + if ((event.get('event_type') == + constants.START_LEASE_EVET_TYPE) and ( + lease_event_status == + constants.LEASE_EVENT_STATUS)): + return lease_event_status + time.sleep(constants.LEASE_CHECK_SLEEP_TIME) + + def _wait_vnf_active_and_assert_vdu_count(vdu_count, scale_type=None): + self.wait_until_vnf_active( + vnf_id, + constants.VNF_CIRROS_CREATE_TIMEOUT, + constants.ACTIVE_SLEEP_TIME) + + vnf = self.client.show_vnf(vnf_id)['vnf'] + # {"VDU1": ["10.0.0.14", "10.0.0.5"]} + if scale_type == 'scaling-in' and vdu_count == 0: + try: + # After sacling-in the vnf['mgmt_ip_address'] will be the + # list containg null values. As vnf['mgmt_ip_address'] + # is string so we can not access ip address list for VDU1 + # so converting that into the dict using ast lib. + # If the list contains the null value then it will raise + # ValueError so on the basis of that we are confirming the + # scaling-in is successful. + ast.literal_eval(vnf['mgmt_ip_address']) + self.fail("Scaling-in should not contain " + "mgmt_ip_address") + except ValueError: + assert True, ("Management Ip address list for VDU1" + "contains null values.") + elif scale_type == 'scaling-out': + self.assertEqual(vdu_count, len(json.loads( + vnf['mgmt_ip_address'])['VDU1'])) + elif vdu_count == 0 and scale_type is None: + self.assertIsNone(vnf['mgmt_ip_address']) + + reservation_policy = _get_reservation_policy(tosca_dict) + _wait_vnf_active_and_assert_vdu_count(0) + + # trigger alarm for start action + start_action = reservation_policy.get('start_actions') + scaling_out_action = start_action + '-out' + _check_lease_event_status() + # scaling-out action + self.trigger_vnf(vnf_id, 'start_actions', scaling_out_action) + time.sleep(constants.SCALE_SLEEP_TIME) + # checking VDU's count after scaling out + _wait_vnf_active_and_assert_vdu_count(2, scale_type='scaling-out') + time.sleep(constants.SCALE_WINDOW_SLEEP_TIME) + + # trigger alarm for before end action + before_end_action = reservation_policy.get('before_end_actions') + scaling_in_action = before_end_action + '-in' + + # scaling-in action + self.trigger_vnf(vnf_id, 'before_end_actions', scaling_in_action) + time.sleep(constants.SCALE_SLEEP_TIME) + # checking VDU's count after scaling in + _wait_vnf_active_and_assert_vdu_count(0, scale_type='scaling-in') + + self.verify_vnf_crud_events( + vnf_id, evt_constants.RES_EVT_SCALE, + evt_constants.ACTIVE, cnt=2) + self.verify_vnf_crud_events( + vnf_id, evt_constants.RES_EVT_SCALE, + evt_constants.PENDING_SCALE_OUT, cnt=1) + self.verify_vnf_crud_events( + vnf_id, evt_constants.RES_EVT_SCALE, + evt_constants.PENDING_SCALE_IN, cnt=1) + + try: + self.client.delete_vnf(vnf_id) + except Exception: + assert False, ( + "Failed to delete vnf %s after the reservation test" % vnf_id) + # Delete vnfd_instance + self.addCleanup(self.client.delete_vnfd, vnfd_id) + self.addCleanup(self.wait_until_vnf_delete, vnf_id, + constants.VNF_CIRROS_DELETE_TIMEOUT) + + def _get_instance_reservation(self): + blazarclient = self.blazarclient() + reservations = [{'disk_gb': 0, + 'vcpus': 1, 'memory_mb': 1, + 'amount': 1, 'affinity': False, + 'resource_properties': '', + 'resource_type': 'virtual:instance'}] + events = [] + + start_date = (datetime.datetime.utcnow() + datetime.timedelta( + minutes=2)).strftime("%Y-%m-%d %H:%M") + end_date = (datetime.datetime.utcnow() + datetime.timedelta( + minutes=30)).strftime("%Y-%m-%d %H:%M") + host_added = False + for hypervisor in HYPERVISORS: + try: + blazar_host = blazarclient.host.create( + hypervisor.hypervisor_hostname) + host_added = True + except exception.BlazarClientException: + pass + + if not host_added: + self.skipTest("Skip test as Blazar failed to create host from one" + " of available hypervisors '%s' as it found some" + " instances were running on them" % + ",".join([hypervisor.hypervisor_hostname + for hypervisor in HYPERVISORS])) + instance_reservation = blazarclient.lease.create( + 'test-reservation', start_date, end_date, reservations, events) + + self.addCleanup( + blazarclient.host.delete, blazar_host.get('id')) + self.addCleanup( + blazarclient.lease.delete, instance_reservation['id']) + + return instance_reservation, blazar_host + + @testtools.skipIf(len(hypervisors()) == 0, + 'Skip test as there are no' + ' hypervisors available in nova') + def test_vnf_alarm_scale_with_instance_reservation(self): + instance_reservation, blazar_host = self._get_instance_reservation() + lease_id = str(instance_reservation['reservations'][0]['lease_id']) + flavor_id = str(instance_reservation['reservations'][0]['flavor_id']) + server_group_id = str( + instance_reservation['reservations'][0]['server_group_id']) + param_to_create_lease = {'lease_id': lease_id, + 'flavor': flavor_id, + 'server_group_id': server_group_id, + 'resource_type': 'virtual_instance'} + self._test_vnf_tosca_reservation( + 'sample-tosca-vnfd-instance-reservation.yaml', + 'VNFD1', lease_id, param_to_create_lease) diff --git a/test-requirements.txt b/test-requirements.txt index c5513f25f..442e5a463 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -18,3 +18,4 @@ tempest>=17.1.0 # Apache-2.0 testtools>=2.2.0 # MIT WebTest>=2.0.27 # MIT python-barbicanclient>=4.5.2 # Apache-2.0 +python-blazarclient>=1.0.1 # Apache-2.0