# # Copyright 2014 OpenStack Foundation # 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. from unittest import mock from neutronclient.common import exceptions as neutron_client_exc from oslo_utils import uuidutils from ironic.common import dhcp_factory from ironic.common import exception from ironic.common import pxe_utils from ironic.conductor import task_manager from ironic.dhcp import neutron from ironic.tests.unit.db import base as db_base from ironic.tests.unit.objects import utils as object_utils class TestNeutron(db_base.DbTestCase): def setUp(self): super(TestNeutron, self).setUp() self.config( cleaning_network='00000000-0000-0000-0000-000000000000', group='neutron') self.config(dhcp_provider='neutron', group='dhcp') self.node = object_utils.create_test_node(self.context) self.ports = [ object_utils.create_test_port( self.context, node_id=self.node.id, id=2, uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c782', address='52:54:00:cf:2d:32')] # Very simple neutron port representation self.neutron_port = {'id': '132f871f-eaec-4fed-9475-0d54465e0f00', 'mac_address': '52:54:00:cf:2d:32'} dhcp_factory.DHCPFactory._dhcp_provider = None @mock.patch('ironic.common.neutron.get_client', autospec=True) @mock.patch('ironic.common.neutron.update_neutron_port', autospec=True) def test_update_port_dhcp_opts(self, update_mock, client_mock): opts = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'}, {'opt_name': 'tftp-server', 'opt_value': '1.1.1.1'}, {'opt_name': 'server-ip-address', 'opt_value': '1.1.1.1'}] port_id = 'fake-port-id' expected = {'port': {'extra_dhcp_opts': opts}} port_data = { "id": port_id, "fixed_ips": [ { "ip_address": "192.168.1.3", } ], } client_mock.return_value.show_port.return_value = {'port': port_data} api = dhcp_factory.DHCPFactory() with task_manager.acquire(self.context, self.node.uuid) as task: api.provider.update_port_dhcp_opts(port_id, opts, context=task.context) update_mock.assert_called_once_with( self.context, port_id, expected) @mock.patch('ironic.common.neutron.get_client', autospec=True) @mock.patch('ironic.common.neutron.update_neutron_port', autospec=True) def test_update_port_dhcp_opts_v6(self, update_mock, client_mock): opts = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0', 'ip_version': 4}, {'opt_name': 'tftp-server', 'opt_value': '1.1.1.1', 'ip_version': 4}, {'opt_name': 'server-ip-address', 'opt_value': '1.1.1.1', 'ip_version': 4}, {'opt_name': 'bootfile-url', 'opt_value': 'tftp://::1/file.name', 'ip_version': 6}] port_id = 'fake-port-id' expected = { 'port': { 'extra_dhcp_opts': [{ 'opt_name': 'bootfile-url', 'opt_value': 'tftp://::1/file.name', 'ip_version': 6}] } } port_data = { "id": port_id, "fixed_ips": [ { "ip_address": "2001:db8::201", } ], } client_mock.return_value.show_port.return_value = {'port': port_data} api = dhcp_factory.DHCPFactory() with task_manager.acquire(self.context, self.node.uuid) as task: api.provider.update_port_dhcp_opts(port_id, opts, context=task.context) update_mock.assert_called_once_with( task.context, port_id, expected) @mock.patch('ironic.common.neutron.get_client', autospec=True) @mock.patch('ironic.common.neutron.update_neutron_port', autospec=True) def test_update_port_dhcp_opts_with_exception(self, update_mock, client_mock): opts = [{}] port_id = 'fake-port-id' port_data = { "id": port_id, "fixed_ips": [ { "ip_address": "192.168.1.3", } ], } client_mock.return_value.show_port.return_value = {'port': port_data} update_mock.side_effect = ( neutron_client_exc.NeutronClientException()) api = dhcp_factory.DHCPFactory() with task_manager.acquire(self.context, self.node.uuid) as task: self.assertRaises( exception.FailedToUpdateDHCPOptOnPort, api.provider.update_port_dhcp_opts, port_id, opts, context=task.context) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() api.update_dhcp(task, opts) mock_updo.assert_called_once_with(mock.ANY, 'vif-uuid', opts, context=task.context) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp_no_vif_data(self, mock_gnvi, mock_updo): mock_gnvi.return_value = {'portgroups': {}, 'ports': {}} with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory() self.assertRaises(exception.FailedToUpdateDHCPOptOnPort, api.update_dhcp, task, self.node) self.assertFalse(mock_updo.called) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp_some_failures(self, mock_gnvi, mock_updo): # confirm update is called twice, one fails, but no exception raised mock_gnvi.return_value = {'ports': {'p1': 'v1', 'p2': 'v2'}, 'portgroups': {}} exc = exception.FailedToUpdateDHCPOptOnPort('fake exception') mock_updo.side_effect = [None, exc] with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory() api.update_dhcp(task, self.node) mock_gnvi.assert_called_once_with(task) self.assertEqual(2, mock_updo.call_count) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp_fails(self, mock_gnvi, mock_updo): # confirm update is called twice, both fail, and exception is raised mock_gnvi.return_value = {'ports': {'p1': 'v1', 'p2': 'v2'}, 'portgroups': {}} exc = exception.FailedToUpdateDHCPOptOnPort('fake exception') mock_updo.side_effect = [exc, exc] with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory() self.assertRaises(exception.FailedToUpdateDHCPOptOnPort, api.update_dhcp, task, self.node) mock_gnvi.assert_called_once_with(task) self.assertEqual(2, mock_updo.call_count) @mock.patch.object(neutron, 'LOG', autospec=True) @mock.patch('time.sleep', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp_set_sleep_and_fake(self, mock_gnvi, mock_ts, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} self.config(port_setup_delay=30, group='neutron') with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() with mock.patch.object(api.provider, 'update_port_dhcp_opts', autospec=True) as mock_updo: api.update_dhcp(task, opts) mock_log.debug.assert_called_once_with( "Waiting %d seconds for Neutron.", 30) mock_ts.assert_called_with(30) mock_updo.assert_called_once_with('vif-uuid', opts, context=task.context) @mock.patch.object(neutron, 'LOG', autospec=True) @mock.patch('ironic.common.network.get_node_vif_ids', autospec=True) def test_update_dhcp_unset_sleep_and_fake(self, mock_gnvi, mock_log): mock_gnvi.return_value = {'ports': {'port-uuid': 'vif-uuid'}, 'portgroups': {}} with task_manager.acquire(self.context, self.node.uuid) as task: opts = pxe_utils.dhcp_options_for_instance(task) api = dhcp_factory.DHCPFactory() with mock.patch.object(api.provider, 'update_port_dhcp_opts', autospec=True) as mock_updo: api.update_dhcp(task, opts) mock_log.debug.assert_not_called() mock_updo.assert_called_once_with('vif-uuid', opts, context=task.context) def test__get_fixed_ip_address(self): port_id = 'fake-port-id' expected = "192.168.1.3" api = dhcp_factory.DHCPFactory().provider port_data = { "id": port_id, "network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6", "admin_state_up": True, "status": "ACTIVE", "mac_address": "fa:16:3e:4c:2c:30", "fixed_ips": [ { "ip_address": "192.168.1.3", "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" } ], "device_id": 'bece68a3-2f8b-4e66-9092-244493d6aba7', } fake_client = mock.Mock() fake_client.show_port.return_value = {'port': port_data} result = api._get_fixed_ip_address(port_id, fake_client) self.assertEqual(expected, result) fake_client.show_port.assert_called_once_with(port_id) def test__get_fixed_ip_address_invalid_ip(self): port_id = 'fake-port-id' api = dhcp_factory.DHCPFactory().provider port_data = { "id": port_id, "network_id": "3cb9bc59-5699-4588-a4b1-b87f96708bc6", "admin_state_up": True, "status": "ACTIVE", "mac_address": "fa:16:3e:4c:2c:30", "fixed_ips": [ { "ip_address": "invalid.ip", "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" } ], "device_id": 'bece68a3-2f8b-4e66-9092-244493d6aba7', } fake_client = mock.Mock() fake_client.show_port.return_value = {'port': port_data} self.assertRaises(exception.InvalidIPv4Address, api._get_fixed_ip_address, port_id, fake_client) fake_client.show_port.assert_called_once_with(port_id) def test__get_fixed_ip_address_with_exception(self): port_id = 'fake-port-id' api = dhcp_factory.DHCPFactory().provider fake_client = mock.Mock() fake_client.show_port.side_effect = ( neutron_client_exc.NeutronClientException()) self.assertRaises(exception.NetworkError, api._get_fixed_ip_address, port_id, fake_client) fake_client.show_port.assert_called_once_with(port_id) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def _test__get_port_ip_address(self, mock_gfia, network): expected = "192.168.1.3" fake_vif = 'test-vif-%s' % network port = object_utils.create_test_port( self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid(), internal_info={ 'cleaning_vif_port_id': (fake_vif if network == 'cleaning' else None), 'provisioning_vif_port_id': (fake_vif if network == 'provisioning' else None), 'tenant_vif_port_id': (fake_vif if network == 'tenant' else None), } ) mock_gfia.return_value = expected with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider result = api._get_port_ip_address(task, port, mock.sentinel.client) self.assertEqual(expected, result) mock_gfia.assert_called_once_with(mock.ANY, fake_vif, mock.sentinel.client) def test__get_port_ip_address_tenant(self): self._test__get_port_ip_address(network='tenant') def test__get_port_ip_address_cleaning(self): self._test__get_port_ip_address(network='cleaning') def test__get_port_ip_address_provisioning(self): self._test__get_port_ip_address(network='provisioning') @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def test__get_port_ip_address_for_portgroup(self, mock_gfia): expected = "192.168.1.3" pg = object_utils.create_test_portgroup( self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid(), internal_info={'tenant_vif_port_id': 'test-vif-A'}) mock_gfia.return_value = expected with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider result = api._get_port_ip_address(task, pg, mock.sentinel.client) self.assertEqual(expected, result) mock_gfia.assert_called_once_with(mock.ANY, 'test-vif-A', mock.sentinel.client) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def test__get_port_ip_address_with_exception(self, mock_gfia): expected = "192.168.1.3" port = object_utils.create_test_port(self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid()) mock_gfia.return_value = expected with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider self.assertRaises(exception.FailedToGetIPAddressOnPort, api._get_port_ip_address, task, port, mock.sentinel.client) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def test__get_port_ip_address_for_portgroup_with_exception( self, mock_gfia): expected = "192.168.1.3" pg = object_utils.create_test_portgroup(self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid()) mock_gfia.return_value = expected with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider self.assertRaises(exception.FailedToGetIPAddressOnPort, api._get_port_ip_address, task, pg, mock.sentinel.client) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def _test__get_ip_addresses_ports(self, key, mock_gfia): if key == "extra": kwargs1 = {key: {'vif_port_id': 'test-vif-A'}} else: kwargs1 = {key: {'tenant_vif_port_id': 'test-vif-A'}} ip_address = '10.10.0.1' expected = [ip_address] port = object_utils.create_test_port(self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid(), **kwargs1) mock_gfia.return_value = ip_address with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider result = api._get_ip_addresses(task, [port], mock.sentinel.client) self.assertEqual(expected, result) def test__get_ip_addresses_ports_extra(self): self._test__get_ip_addresses_ports('extra') def test__get_ip_addresses_ports_int_info(self): self._test__get_ip_addresses_ports('internal_info') @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_fixed_ip_address', autospec=True) def _test__get_ip_addresses_portgroup(self, key, mock_gfia): if key == "extra": kwargs1 = {key: {'vif_port_id': 'test-vif-A'}} else: kwargs1 = {key: {'tenant_vif_port_id': 'test-vif-A'}} ip_address = '10.10.0.1' expected = [ip_address] pg = object_utils.create_test_portgroup( self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid(), **kwargs1) mock_gfia.return_value = ip_address with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider result = api._get_ip_addresses(task, [pg], mock.sentinel.client) self.assertEqual(expected, result) def test__get_ip_addresses_portgroup_extra(self): self._test__get_ip_addresses_portgroup('extra') def test__get_ip_addresses_portgroup_int_info(self): self._test__get_ip_addresses_portgroup('internal_info') @mock.patch('ironic.common.neutron.get_client', autospec=True) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_port_ip_address', autospec=True) def test_get_ip_addresses(self, get_ip_mock, client_mock): ip_address = '10.10.0.1' expected = [ip_address] get_ip_mock.return_value = ip_address with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider result = api.get_ip_addresses(task) get_ip_mock.assert_called_once_with(mock.ANY, task, task.ports[0], client_mock.return_value) self.assertEqual(expected, result) @mock.patch('ironic.common.neutron.get_client', autospec=True) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi._get_port_ip_address', autospec=True) def test_get_ip_addresses_for_port_and_portgroup(self, get_ip_mock, client_mock): object_utils.create_test_portgroup( self.context, node_id=self.node.id, address='aa:bb:cc:dd:ee:ff', uuid=uuidutils.generate_uuid(), internal_info={'tenant_vif_port_id': 'test-vif-A'}) with task_manager.acquire(self.context, self.node.uuid) as task: api = dhcp_factory.DHCPFactory().provider api.get_ip_addresses(task) get_ip_mock.assert_has_calls( [mock.call(mock.ANY, task, task.ports[0], client_mock.return_value), mock.call(mock.ANY, task, task.portgroups[0], client_mock.return_value)] )