diff --git a/lower-constraints.txt b/lower-constraints.txt index b843242fd..e5e75ff8c 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -57,7 +57,7 @@ munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 networkx==1.10 -openstacksdk==0.11.2 +openstacksdk==0.48.0 os-client-config==1.28.0 os-service-types==1.2.0 osc-lib==1.8.0 diff --git a/requirements.txt b/requirements.txt index 01b9baa13..e8360fed6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,4 @@ tripleo-common>=12.6.0 # Apache-2.0 cryptography>=2.1 # BSD/Apache-2.0 ansible-runner>=1.4.5 # Apache 2.0 validations-libs>=1.0.0 +openstacksdk>=0.48.0 # Apache-2.0 diff --git a/tripleoclient/tests/v1/overcloud_deploy/fakes.py b/tripleoclient/tests/v1/overcloud_deploy/fakes.py index 33cd79bd1..38b6323c0 100644 --- a/tripleoclient/tests/v1/overcloud_deploy/fakes.py +++ b/tripleoclient/tests/v1/overcloud_deploy/fakes.py @@ -108,3 +108,84 @@ class TestDeployOvercloud(fakes.FakePlaybookExecution): def setUp(self): super(TestDeployOvercloud, self).setUp(ansible_mock=False) + + +class FakeNeutronNetwork(dict): + def __init__(self, **attrs): + NETWORK_ATTRS = ['id', + 'name', + 'status', + 'tenant_id', + 'is_admin_state_up', + 'mtu', + 'segments', + 'is_shared', + 'subnet_ids', + 'provider:network_type', + 'provider:physical_network', + 'provider:segmentation_id', + 'router:external', + 'availability_zones', + 'availability_zone_hints', + 'is_default', + 'tags'] + + raw = dict.fromkeys(NETWORK_ATTRS) + raw.update(attrs) + raw.update({ + 'provider_physical_network': attrs.get( + 'provider:physical_network', None), + 'provider_network_type': attrs.get( + 'provider:network_type', None), + 'provider_segmentation_id': attrs.get( + 'provider:segmentation_id', None) + }) + super(FakeNeutronNetwork, self).__init__(raw) + + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) + + def __setattr__(self, key, value): + if key in self: + self[key] = value + else: + raise AttributeError(key) + + +class FakeNeutronSubnet(dict): + def __init__(self, **attrs): + SUBNET_ATTRS = ['id', + 'name', + 'network_id', + 'cidr', + 'tenant_id', + 'is_dhcp_enabled', + 'dns_nameservers', + 'allocation_pools', + 'host_routes', + 'ip_version', + 'gateway_ip', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'subnetpool_id', + 'segment_id', + 'tags'] + + raw = dict.fromkeys(SUBNET_ATTRS) + raw.update(attrs) + super(FakeNeutronSubnet, self).__init__(raw) + + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) + + def __setattr__(self, key, value): + if key in self: + self[key] = value + else: + raise AttributeError(key) diff --git a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py index 8c6d2c386..0fbbe0214 100644 --- a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py +++ b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py @@ -22,6 +22,7 @@ import yaml from heatclient import exc as hc_exc import mock +import openstack from osc_lib import exceptions as oscexc from osc_lib.tests import utils from swiftclient.exceptions import ClientException as ObjectClientException @@ -170,6 +171,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): self.cmd._download_missing_files_from_plan = self.real_download_missing shutil.rmtree = self.real_shutil + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_get_undercloud_host_entry', autospec=True, @@ -189,7 +192,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_create_parameters_env, mock_breakpoints_cleanup, mock_events, mock_stack_network_check, - mock_get_undercloud_host_entry, mock_copy): + mock_get_undercloud_host_entry, mock_copy, + mock_get_ctlplane_attrs): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) clients = self.app.client_manager @@ -241,7 +245,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'SnmpdReadonlyUserPassword': 'PASSWORD', 'StackAction': 'UPDATE', 'UndercloudHostsEntries': [ - '192.168.0.1 uc.ctlplane.localhost uc.ctlplane'] + '192.168.0.1 uc.ctlplane.localhost uc.ctlplane'], + 'CtlplaneNetworkAttributes': {}, } def _custom_create_params_env(_self, parameters, tht_root, @@ -266,6 +271,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): object_client.put_container.assert_called_once_with( 'overcloud', headers={'x-container-meta-usage-tripleo': 'plan'}) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('tripleoclient.workflows.deployment.create_overcloudrc', autospec=True) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @@ -290,7 +297,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_breakpoints_cleanup, mock_postconfig, mock_invoke_plan_env_wf, mock_get_undercloud_host_entry, - mock_copy, mock_overcloudrc): + mock_copy, mock_overcloudrc, mock_get_ctlplane_attrs): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) @@ -337,7 +344,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'parameter_defaults': { 'StackAction': 'CREATE', 'UndercloudHostsEntries': - ['192.168.0.1 uc.ctlplane.localhost uc.ctlplane']}} + ['192.168.0.1 uc.ctlplane.localhost uc.ctlplane'], + 'CtlplaneNetworkAttributes': {}}} mock_open_context = mock.mock_open() with mock.patch('six.moves.builtins.open', mock_open_context): @@ -378,6 +386,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): object_client = clients.tripleoclient.object_store object_client.put_object.assert_has_calls(calls) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('os.chdir') @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' @@ -410,7 +420,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_postconfig, mock_shutil_rmtree, mock_invoke_plan_env_wf, mock_stack_network_check, - mock_get_undercloud_host_entry, mock_copy, mock_chdir): + mock_get_undercloud_host_entry, mock_copy, mock_chdir, + mock_get_ctlplane_attrs): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) plane_management_fixture = deployment.PlanManagementFixture() @@ -468,7 +479,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'StackAction': 'CREATE', 'UndercloudHostsEntries': [ '192.168.0.1 uc.ctlplane.localhost uc.ctlplane' - ] + ], + 'CtlplaneNetworkAttributes': {}, } testcase = self @@ -1119,6 +1131,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): object_client.put_container.assert_called_once_with( 'overcloud', headers={'x-container-meta-usage-tripleo': 'plan'}) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_get_undercloud_host_entry', autospec=True, @@ -1145,7 +1159,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): mock_breakpoints_cleanup, mock_deploy_post_config, mock_stack_network_check, - mock_get_undercloud_host_entry, mock_copy): + mock_get_undercloud_host_entry, mock_copy, + mock_get_ctlplane_attrs): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) plane_management_fixture = deployment.PlanManagementFixture() @@ -1204,7 +1219,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'NtpServer': 'ntp', 'UndercloudHostsEntries': [ '192.168.0.1 uc.ctlplane.localhost uc.ctlplane' - ] + ], + 'CtlplaneNetworkAttributes': {}, } def _custom_create_params_env(_self, parameters, tht_root, @@ -1475,6 +1491,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): self.assertTrue(fixture.mock_config_download.called) mock_copy.assert_called_once() + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('tripleoclient.utils.copy_clouds_yaml') @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' '_get_undercloud_host_entry', autospec=True, @@ -1489,7 +1507,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'create_plan_from_templates', autospec=True) def test_config_download_timeout( self, mock_plan_man, mock_hc, mock_stack_network_check, mock_hd, - mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy): + mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy, + mock_get_ctlplane_attrs): fixture = deployment.DeploymentWorkflowFixture() self.useFixture(fixture) utils_fixture = deployment.UtilsOvercloudFixture() @@ -1512,9 +1531,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): self.assertIn( [mock.call(mock.ANY, mock.ANY, 'overcloud', mock.ANY, {'StackAction': 'UPDATE', 'UndercloudHostsEntries': - ['192.168.0.1 uc.ctlplane.localhost uc.ctlplane']}, {}, - 451, mock.ANY, {}, False, False, False, None, - deployment_options={})], + ['192.168.0.1 uc.ctlplane.localhost uc.ctlplane'], + 'CtlplaneNetworkAttributes': {}}, {}, 451, mock.ANY, + {}, False, False, False, None, deployment_options={})], mock_hd.mock_calls) self.assertIn( [mock.call(mock.ANY, mock.ANY, mock.ANY, 'ctlplane', None, None, @@ -1757,6 +1776,67 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud): 'specified.') mock_warning.assert_called_once_with(expected_message) + @mock.patch('openstack.connect', autospec=True) + def test__get_ctlplane_attrs_no_config(self, mock_connect): + mock_connect.side_effect = openstack.exceptions.ConfigException + function = overcloud_deploy.DeployOvercloud._get_ctlplane_attrs + + expected = dict() + self.assertEqual(expected, function(mock.ANY)) + + @mock.patch('openstack.connect', autospec=True) + @mock.patch.object(openstack.connection, 'Connection', autospec=True) + def test__get_ctlplane_attrs_no_network(self, mock_conn, mock_connect): + mock_connect.return_value = mock_conn + function = overcloud_deploy.DeployOvercloud._get_ctlplane_attrs + + mock_conn.network.find_network.return_value = None + expected = dict() + self.assertEqual(expected, function(mock.ANY)) + + @mock.patch('openstack.connect', autospec=True) + @mock.patch.object(openstack.connection, 'Connection', autospec=True) + def test__get_ctlplane_attrs(self, mock_conn, mock_connect): + mock_connect.return_value = mock_conn + function = overcloud_deploy.DeployOvercloud._get_ctlplane_attrs + + fake_network = fakes.FakeNeutronNetwork( + name='net_name', + mtu=1440, + dns_domain='ctlplane.localdomain.', + tags=[], + subnet_ids=['subnet_id']) + fake_subnet = fakes.FakeNeutronSubnet( + id='subnet_id', + name='subnet_name', + cidr='192.168.24.0/24', + gateway_ip='192.168.24.1', + host_routes=[ + {'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}], + dns_nameservers=['192.168.24.254'], + ip_version=4 + ) + mock_conn.network.find_network.return_value = fake_network + mock_conn.network.get_subnet.return_value = fake_subnet + expected = { + 'network': { + 'dns_domain': 'ctlplane.localdomain.', + 'mtu': 1440, + 'name': 'net_name', + 'tags': []}, + 'subnets': { + 'subnet_name': { + 'cidr': '192.168.24.0/24', + 'dns_nameservers': ['192.168.24.254'], + 'gateway_ip': '192.168.24.1', + 'host_routes': [{'destination': '192.168.25.0/24', + 'nexthop': '192.168.24.1'}], + 'ip_version': 4, + 'name': 'subnet_name'} + } + } + self.assertEqual(expected, function(mock.ANY)) + class TestArgumentValidation(fakes.TestDeployOvercloud): diff --git a/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py b/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py index 9fd334cf7..8cd2f8175 100644 --- a/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py +++ b/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py @@ -36,6 +36,8 @@ class TestOvercloudUpdatePrepare(fakes.TestOvercloudUpdatePrepare): self.mock_uuid4 = uuid4_patcher.start() self.addCleanup(self.mock_uuid4.stop) + @mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' + '_get_ctlplane_attrs', autospec=True, return_value={}) @mock.patch('tripleoclient.utils.ensure_run_as_normal_user') @mock.patch('tripleoclient.utils.prompt_user_for_confirmation', return_value=True) @@ -57,7 +59,7 @@ class TestOvercloudUpdatePrepare(fakes.TestOvercloudUpdatePrepare): def test_update_out(self, mock_deploy, mock_open, mock_copy, mock_yaml, mock_abspath, mock_update, mock_logger, mock_get_stack, mock_get_undercloud_host_entry, - mock_confirm, mock_usercheck): + mock_confirm, mock_usercheck, mock_get_ctlplane_attrs): mock_stack = mock.Mock(parameters={'DeployIdentifier': ''}) mock_stack.stack_name = 'overcloud' mock_get_stack.return_value = mock_stack diff --git a/tripleoclient/tests/v1/undercloud/test_config.py b/tripleoclient/tests/v1/undercloud/test_config.py index b06b46896..b243e0158 100644 --- a/tripleoclient/tests/v1/undercloud/test_config.py +++ b/tripleoclient/tests/v1/undercloud/test_config.py @@ -923,6 +923,32 @@ class TestNetworkSettings(TestBaseNetworkSettings): self.assertRaises(exceptions.DeploymentError, undercloud_config._generate_inspection_subnets) + def test__env_set_undercloud_ctlplane_networks_attribues(self): + self.conf.config(local_subnet='ctlplane-subnet', + local_mtu=1444, + undercloud_nameservers=['192.168.24.253', + '192.168.24.252']) + self.conf.config(cidr='192.168.24.0/24', + gateway='192.168.24.254', + host_routes=[{'destination': '10.10.10.254/32', + 'nexthop': '192.168.24.1'}], + group='ctlplane-subnet') + env = {} + undercloud_config._env_set_undercloud_ctlplane_networks_attribues(env) + expected = { + 'CtlplaneNetworkAttributes': { + 'network': {'mtu': 1444}, + 'subnets': { + 'ctlplane-subnet': { + 'cidr': '192.168.24.0/24', + 'dns_nameservers': ['192.168.24.253', + '192.168.24.252'], + 'gateway_ip': '192.168.24.254', + 'host_routes': [{'destination': '10.10.10.254/32', + 'nexthop': '192.168.24.1'}], + 'tags': []}}}} + self.assertEqual(expected, env) + class TestChronySettings(TestBaseNetworkSettings): def test_default(self): diff --git a/tripleoclient/v1/overcloud_deploy.py b/tripleoclient/v1/overcloud_deploy.py index a5a4ed85f..fbcaf8806 100644 --- a/tripleoclient/v1/overcloud_deploy.py +++ b/tripleoclient/v1/overcloud_deploy.py @@ -29,6 +29,7 @@ import time import yaml from heatclient.common import template_utils +import openstack from osc_lib import exceptions as oscexc from osc_lib.i18n import _ from swiftclient.exceptions import ClientException @@ -100,8 +101,44 @@ class DeployOvercloud(command.Command): parameters[ 'UndercloudHostsEntries'] = [self._get_undercloud_host_entry()] + parameters['CtlplaneNetworkAttributes'] = self._get_ctlplane_attrs() + return parameters + def _get_ctlplane_attrs(self): + try: + conn = openstack.connect('undercloud') + except openstack.exceptions.ConfigException: + return dict() + + network = conn.network.find_network('ctlplane') + if network is None: + return dict() + + net_attributes_map = {'network': dict(), 'subnets': dict()} + + net_attributes_map['network'].update({ + 'name': network.name, + 'mtu': network.mtu, + 'dns_domain': network.dns_domain, + 'tags': network.tags, + }) + + for subnet_id in network.subnet_ids: + subnet = conn.network.get_subnet(subnet_id) + net_attributes_map['subnets'].update({ + subnet.name: { + 'name': subnet.name, + 'cidr': subnet.cidr, + 'gateway_ip': subnet.gateway_ip, + 'host_routes': subnet.host_routes, + 'dns_nameservers': subnet.dns_nameservers, + 'ip_version': subnet.ip_version, + } + }) + + return net_attributes_map + def _cleanup_host_entry(self, entry): # remove any tab or space excess entry_stripped = re.sub('[ \t]+', ' ', str(entry).rstrip()) diff --git a/tripleoclient/v1/tripleo_deploy.py b/tripleoclient/v1/tripleo_deploy.py index f2f0ec01a..42eb4b019 100644 --- a/tripleoclient/v1/tripleo_deploy.py +++ b/tripleoclient/v1/tripleo_deploy.py @@ -703,6 +703,18 @@ class Deploy(command.Command): stack_name=parsed_args.stack, role_name=self._get_primary_role_name( roles_file_path, parsed_args.templates))) + tmp_env.update( + { + 'CtlplaneNetworkAttributes': { + 'subnets': { + 'ctlplane-subnet': { + 'cidr': str(ip_nw.cidr), + 'host_routes': [] + } + } + } + } + ) with open(maps_file, 'w') as env_file: yaml.safe_dump({'parameter_defaults': tmp_env}, env_file, diff --git a/tripleoclient/v1/undercloud_config.py b/tripleoclient/v1/undercloud_config.py index 97bfa237f..89302fcae 100644 --- a/tripleoclient/v1/undercloud_config.py +++ b/tripleoclient/v1/undercloud_config.py @@ -414,6 +414,18 @@ def _process_network_args(env): raise exceptions.InvalidConfiguration(msg) +def _env_set_undercloud_ctlplane_networks_attribues(env): + env['CtlplaneNetworkAttributes'] = dict(network=dict(), subnets=dict()) + env['CtlplaneNetworkAttributes']['network']['mtu'] = CONF.local_mtu + env['CtlplaneNetworkAttributes']['subnets']['ctlplane-subnet'] = { + 'cidr': CONF.get(CONF.local_subnet).cidr, + 'gateway_ip': CONF.get(CONF.local_subnet).gateway, + 'dns_nameservers': CONF.undercloud_nameservers, + 'host_routes': CONF.get(CONF.local_subnet).host_routes, + 'tags': [], + } + + def _process_chrony_acls(env): """Populate ACL rules for chrony to allow ctlplane subnets""" acl_rules = [] @@ -468,6 +480,7 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=True, # Set up parameters for undercloud networking _process_network_args(env_data) + _env_set_undercloud_ctlplane_networks_attribues(env_data) # Setup parameter for Chrony ACL rules _process_chrony_acls(env_data)