+# -*- coding: utf-8 -*-
+##
+# Copyright 2018 University of Bristol - High Performance Networks Research
+# Group
+# All Rights Reserved.
+#
+# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
+# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: <highperformance-networks@bristol.ac.uk>
+#
+# Neither the name of the University of Bristol nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# This work has been performed in the context of DCMS UK 5G Testbeds
+# & Trials Programme and in the framework of the Metro-Haul project -
+# funded by the European Commission under Grant number 761727 through the
+# Horizon 2020 and 5G-PPP programmes.
+##
+# pylint: disable=E1101
+
+from __future__ import unicode_literals, print_function
+
+import json
+import unittest
+from time import time
+
+from mock import MagicMock, patch
+
+from . import fixtures as eg
+from ...tests.db_helpers import (
+ TestCaseWithDatabasePerTest,
+ disable_foreign_keys,
+ uuid,
+)
+from ..persistence import WimPersistence
+from ..wan_link_actions import WanLinkCreate, WanLinkDelete
+from ..wimconn import WimConnectorError
+
+
+class TestActionsWithDb(TestCaseWithDatabasePerTest):
+ def setUp(self):
+ super(TestActionsWithDb, self).setUp()
+ self.persist = WimPersistence(self.db)
+ self.connector = MagicMock()
+ self.ovim = MagicMock()
+
+
+class TestCreate(TestActionsWithDb):
+ @disable_foreign_keys
+ def test_process__instance_nets_on_build(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and the local network in each datacenter is still being built
+ wan_link = eg.instance_wim_nets()
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
+ for net in instance_nets:
+ net['status'] = 'BUILD'
+ self.populate([{'instance_nets': instance_nets,
+ 'instance_wim_nets': wan_link}])
+
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ now = time()
+ action = WanLinkCreate(eg.wim_actions('CREATE')[0])
+ action.instance_scenario_id = instance_nets[0]['instance_scenario_id']
+ action.sce_net_id = instance_nets[0]['sce_net_id']
+ # -- ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record}])
+ # <-- #
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be defered
+ assert action.is_scheduled
+ self.assertEqual(action.extra['attempts'], 1)
+ self.assertGreater(action.extra['last_attempted_at'], now)
+
+ @disable_foreign_keys
+ def test_process__instance_nets_on_error(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and at least one local network is in a not good state (error, or
+ # being deleted)
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
+ instance_nets[1]['status'] = 'SCHEDULED_DELETION'
+ wan_link = eg.instance_wim_nets()
+ self.populate([{'instance_nets': instance_nets,
+ 'instance_wim_nets': wan_link}])
+
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action = WanLinkCreate(eg.wim_actions('CREATE')[0])
+ action.instance_scenario_id = instance_nets[0]['instance_scenario_id']
+ action.sce_net_id = instance_nets[0]['sce_net_id']
+ # -- ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record}])
+ # <-- #
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should fail
+ assert action.is_failed
+ self.assertIn('issue with the local networks', action.error_msg)
+ self.assertIn('SCHEDULED_DELETION', action.error_msg)
+
+ def prepare_create__sdn(self):
+ db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
+
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
+ port_mappings = [
+ eg.wim_port_mapping(0, 0),
+ eg.wim_port_mapping(0, 1)
+ ]
+ instance_action = eg.instance_action(action_id='ACTION-000')
+ for i, net in enumerate(instance_nets):
+ net['status'] = 'ACTIVE'
+ net['sdn_net_id'] = uuid('sdn-net%d' % i)
+
+ db_state += [{'instance_nets': instance_nets},
+ {'instance_wim_nets': eg.instance_wim_nets()},
+ {'wim_port_mappings': port_mappings},
+ {'instance_actions': instance_action}]
+
+ action = WanLinkCreate(
+ eg.wim_actions('CREATE', action_id='ACTION-000')[0])
+ # --> ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record}])
+
+ return db_state, action
+
+ @disable_foreign_keys
+ def test_process__sdn(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and the local network in each datacenter is already created
+ db_state, action = self.prepare_create__sdn()
+ self.populate(db_state)
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ number_done = instance_action['number_done']
+ number_failed = instance_action['number_failed']
+
+ connector_patch = patch.object(
+ self.connector, 'create_connectivity_service',
+ lambda *_, **__: (uuid('random-id'), None))
+
+ ovim_patch = patch.object(
+ self.ovim, 'get_ports', MagicMock(return_value=[{
+ 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
+ 'switch_port': 1,
+ }]))
+
+ # If the connector works fine
+ with connector_patch, ovim_patch:
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be succeeded
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': action.instance_action_id,
+ 'task_index': action.task_index})
+ self.assertEqual(db_action['status'], 'DONE')
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ self.assertEqual(instance_action['number_done'], number_done + 1)
+ self.assertEqual(instance_action['number_failed'], number_failed)
+
+ @disable_foreign_keys
+ def test_process__sdn_fail(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and the local network in each datacenter is already created
+ db_state, action = self.prepare_create__sdn()
+ self.populate(db_state)
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ number_done = instance_action['number_done']
+ number_failed = instance_action['number_failed']
+
+ connector_patch = patch.object(
+ self.connector, 'create_connectivity_service',
+ MagicMock(side_effect=WimConnectorError('foobar')))
+
+ ovim_patch = patch.object(
+ self.ovim, 'get_ports', MagicMock(return_value=[{
+ 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
+ 'switch_port': 1,
+ }]))
+
+ # If the connector throws an error
+ with connector_patch, ovim_patch:
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be fail
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': action.instance_action_id,
+ 'task_index': action.task_index})
+ self.assertEqual(db_action['status'], 'FAILED')
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ self.assertEqual(instance_action['number_done'], number_done)
+ self.assertEqual(instance_action['number_failed'], number_failed + 1)
+
+
+class TestDelete(TestActionsWithDb):
+ @disable_foreign_keys
+ def test_process__no_internal_id(self):
+ # Given no WAN link was created yet,
+ # when we try to process a DELETE action, with no wim_internal_id
+ action = WanLinkDelete(eg.wim_actions('DELETE')[0])
+ action.wim_internal_id = None
+ # -- ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record,
+ 'instance_wim_nets': eg.instance_wim_nets()}])
+ # <-- #
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should succeed
+ assert action.is_done
+
+ def prepare_delete(self):
+ db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
+
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
+ port_mappings = [
+ eg.wim_port_mapping(0, 0),
+ eg.wim_port_mapping(0, 1)
+ ]
+ instance_action = eg.instance_action(action_id='ACTION-000')
+ for i, net in enumerate(instance_nets):
+ net['status'] = 'ACTIVE'
+ net['sdn_net_id'] = uuid('sdn-net%d' % i)
+
+ db_state += [{'instance_nets': instance_nets},
+ {'instance_wim_nets': eg.instance_wim_nets()},
+ {'wim_port_mappings': port_mappings},
+ {'instance_actions': instance_action}]
+
+ action = WanLinkDelete(
+ eg.wim_actions('DELETE', action_id='ACTION-000')[0])
+ # --> ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record}])
+
+ return db_state, action
+
+ @disable_foreign_keys
+ def test_process(self):
+ # Given we want to delete 1 WAN link between 2 datacenters
+ db_state, action = self.prepare_delete()
+ self.populate(db_state)
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ number_done = instance_action['number_done']
+ number_failed = instance_action['number_failed']
+
+ connector_patch = patch.object(
+ self.connector, 'delete_connectivity_service')
+
+ # If the connector works fine
+ with connector_patch:
+ # When we try to process a DELETE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be succeeded
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': action.instance_action_id,
+ 'task_index': action.task_index})
+ self.assertEqual(db_action['status'], 'DONE')
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ self.assertEqual(instance_action['number_done'], number_done + 1)
+ self.assertEqual(instance_action['number_failed'], number_failed)
+
+ @disable_foreign_keys
+ def test_process__wan_link_error(self):
+ # Given we have a delete action that targets a wan link with an error
+ db_state, action = self.prepare_delete()
+ wan_link = [tables for tables in db_state
+ if tables.get('instance_wim_nets')][0]['instance_wim_nets']
+ from pprint import pprint
+ pprint(wan_link)
+ wan_link[0]['status'] = 'ERROR'
+ self.populate(db_state)
+
+ # When we try to process it
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then it should fail
+ assert action.is_failed
+
+ def create_action(self):
+ action = WanLinkCreate(
+ eg.wim_actions('CREATE', action_id='ACTION-000')[0])
+ # --> ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ self.populate([{'vim_wim_actions': action_record}])
+
+ return action
+
+ @disable_foreign_keys
+ def test_create_and_delete(self):
+ # Given a CREATE action was well succeeded
+ db_state, delete_action = self.prepare_delete()
+ delete_action.save(self.persist, task_index=1)
+ self.populate(db_state)
+ create_action = self.create_action()
+
+ connector_patch = patch.multiple(
+ self.connector,
+ delete_connectivity_service=MagicMock(),
+ create_connectivity_service=(
+ lambda *_, **__: (uuid('random-id'), None)))
+
+ ovim_patch = patch.object(
+ self.ovim, 'get_ports', MagicMock(return_value=[{
+ 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
+ 'switch_port': 1,
+ }]))
+
+ with connector_patch, ovim_patch:
+ create_action.process(self.connector, self.persist, self.ovim)
+
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ with connector_patch:
+ delete_action.process(self.connector, self.persist, self.ovim)
+
+ # Then the DELETE action should be successful
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': delete_action.instance_action_id,
+ 'task_index': delete_action.task_index})
+ self.assertEqual(db_action['status'], 'DONE')
+
+
+if __name__ == '__main__':
+ unittest.main()