From 53320ee72b63f4b0a6b460d7fa7a23b6916e2877 Mon Sep 17 00:00:00 2001 From: Ben Nemec Date: Wed, 26 Apr 2017 12:55:22 -0500 Subject: [PATCH] Re-enable uefi support with glanceclient Use glanceclient to examine image metadata instead of the nova image proxy, which seems to have been removed in recent versions of novaclient. --- openstack_virtual_baremetal/auth.py | 29 +++++++++ .../build_nodes_json.py | 27 ++++---- openstack_virtual_baremetal/deploy.py | 30 +-------- .../tests/test_auth.py | 54 +++++++++++++++ .../tests/test_build_nodes_json.py | 65 ++++++++++++++----- .../tests/test_deploy.py | 54 +-------------- requirements.txt | 1 + 7 files changed, 153 insertions(+), 107 deletions(-) diff --git a/openstack_virtual_baremetal/auth.py b/openstack_virtual_baremetal/auth.py index 3e96769..2a7e9c3 100644 --- a/openstack_virtual_baremetal/auth.py +++ b/openstack_virtual_baremetal/auth.py @@ -127,3 +127,32 @@ def _get_keystone_token(): project_domain_name=project_domain) +def _get_token_and_endpoint(name): + """Return a token id and endpoint url for the specified service + + :param name: The name of the service. heat, glance, etc. + :returns: A tuple of (token_id, service_endpoint) + """ + auth_data = _create_auth_parameters() + auth_url = auth_data['os_auth_url'] + # Get token for service to use + if '/v3' not in auth_url: + token_data = _get_keystone_token() + token_id = token_data['token']['id'] + catalog_key = 'serviceCatalog' + else: + token_data = _get_keystone_token() + token_id = token_data['auth_token'] + catalog_key = 'catalog' + + # Get service endpoint + for endpoint in token_data[catalog_key]: + if endpoint['name'] == name: + try: + # TODO: What if there's more than one endpoint? + service_endpoint = endpoint['endpoints'][0]['publicURL'] + except KeyError: + # Keystone v3 endpoint data looks different + service_endpoint = [e for e in endpoint['endpoints'] + if e['interface'] == 'public'][0]['url'] + return token_id, service_endpoint diff --git a/openstack_virtual_baremetal/build_nodes_json.py b/openstack_virtual_baremetal/build_nodes_json.py index 99a1984..d9cb161 100755 --- a/openstack_virtual_baremetal/build_nodes_json.py +++ b/openstack_virtual_baremetal/build_nodes_json.py @@ -19,6 +19,7 @@ import os import sys import yaml +import glanceclient from neutronclient.v2_0 import client as neutronclient import novaclient as nc from novaclient import client as novaclient @@ -91,6 +92,7 @@ def _get_clients(): import os_client_config nova = os_client_config.make_client('compute', cloud=cloud) neutron = os_client_config.make_client('network', cloud=cloud) + glance = os_client_config.make_client('image', cloud=cloud) else: auth_data = auth._create_auth_parameters() @@ -124,7 +126,10 @@ def _get_clients(): project_domain_name=project_domain) sess = auth._get_keystone_session(auth_data) neutron = neutronclient.Client(session=sess) - return nova, neutron + token, glance_endpoint = auth._get_token_and_endpoint('glance') + glance = glanceclient.Client('2', token=token, + endpoint=glance_endpoint) + return nova, neutron, glance def _get_ports(neutron, bmc_base, baremetal_base): @@ -140,8 +145,8 @@ def _get_ports(neutron, bmc_base, baremetal_base): return bmc_ports, bm_ports -def _build_nodes(nova, bmc_ports, bm_ports, provision_net, baremetal_base, - undercloud_name): +def _build_nodes(nova, glance, bmc_ports, bm_ports, provision_net, + baremetal_base, undercloud_name): node_template = { 'pm_type': 'pxe_ipmitool', 'mac': '', @@ -177,12 +182,11 @@ def _build_nodes(nova, bmc_ports, bm_ports, provision_net, baremetal_base, # If a node has uefi firmware ironic needs to be aware of this, in nova # this is set using a image property called "hw_firmware_type" - # TODO(bnemec): Re-enable uefi support - #if not cache.get(baremetal.image['id']): - #cache[baremetal.image['id']] = nova.images.get(baremetal.image['id']) - #image = cache.get(baremetal.image['id']) - #if image.metadata.get('hw_firmware_type') == 'uefi': - #node['capabilities'] += ",boot_mode:uefi" + if not cache.get(baremetal.image['id']): + cache[baremetal.image['id']] = glance.images.get(baremetal.image['id']) + image = cache.get(baremetal.image['id']) + if image.get('hw_firmware_type') == 'uefi': + node['capabilities'] += ",boot_mode:uefi" bm_name_end = baremetal.name[len(baremetal_base):] if '-' in bm_name_end: @@ -240,9 +244,10 @@ def _write_pairs(bmc_bm_pairs): def main(): args = _parse_args() bmc_base, baremetal_base, provision_net, undercloud_name = _get_names(args) - nova, neutron = _get_clients() + nova, neutron, glance = _get_clients() bmc_ports, bm_ports = _get_ports(neutron, bmc_base, baremetal_base) - nodes, bmc_bm_pairs, extra_nodes = _build_nodes(nova, bmc_ports, bm_ports, + nodes, bmc_bm_pairs, extra_nodes = _build_nodes(nova, glance, bmc_ports, + bm_ports, provision_net, baremetal_base, undercloud_name) diff --git a/openstack_virtual_baremetal/deploy.py b/openstack_virtual_baremetal/deploy.py index d584e98..f53d704 100755 --- a/openstack_virtual_baremetal/deploy.py +++ b/openstack_virtual_baremetal/deploy.py @@ -140,35 +140,7 @@ def _get_heat_client(): import os_client_config return os_client_config.make_client('orchestration', cloud=cloud) else: - auth_data = auth._create_auth_parameters() - username = auth_data['os_user'] - password = auth_data['os_password'] - tenant = auth_data['os_tenant'] - auth_url = auth_data['os_auth_url'] - project = auth_data['os_project'] - user_domain = auth_data['os_user_domain'] - project_domain = auth_data['os_project_domain'] - # Get token for Heat to use - if '/v3' not in auth_url: - token_data = auth._get_keystone_token() - token_id = token_data['token']['id'] - catalog_key = 'serviceCatalog' - else: - token_data = auth._get_keystone_token() - token_id = token_data['auth_token'] - catalog_key = 'catalog' - - # Get Heat endpoint - for endpoint in token_data[catalog_key]: - if endpoint['name'] == 'heat': - try: - # TODO: What if there's more than one endpoint? - heat_endpoint = endpoint['endpoints'][0]['publicURL'] - except KeyError: - # Keystone v3 endpoint data looks different - heat_endpoint = [e for e in endpoint['endpoints'] - if e['interface'] == 'public'][0]['url'] - + token_id, heat_endpoint = auth._get_token_and_endpoint('heat') return heat_client.Client('1', endpoint=heat_endpoint, token=token_id) def _deploy(stack_name, stack_template, env_path, poll): diff --git a/openstack_virtual_baremetal/tests/test_auth.py b/openstack_virtual_baremetal/tests/test_auth.py index beb9f3e..04bd8cb 100644 --- a/openstack_virtual_baremetal/tests/test_auth.py +++ b/openstack_virtual_baremetal/tests/test_auth.py @@ -190,6 +190,26 @@ class TestCreateAuthParameters(testtools.TestCase): self.assertEqual(expected, result) +V2_TOKEN_DATA = {'token': {'id': 'fake_token'}, + 'serviceCatalog': [{'name': 'nova'}, + {'name': 'heat', + 'endpoints': [ + {'publicURL': 'heat_endpoint'} + ] + } + ]} +V3_TOKEN_DATA = {'auth_token': 'fake_v3_token', + 'catalog': [{'name': 'nova'}, + {'name': 'heat', + 'endpoints': [ + {'interface': 'private'}, + {'interface': 'public', + 'url': 'heat_endpoint'} + ] + } + ]} + + class TestKeystoneAuth(testtools.TestCase): @mock.patch('keystoneauth1.session.Session') @mock.patch('keystoneauth1.identity.v3.Password') @@ -289,3 +309,37 @@ class TestKeystoneAuth(testtools.TestCase): user_domain_name='default', project_domain_name='default' ) + + @mock.patch('openstack_virtual_baremetal.auth._get_keystone_token') + @mock.patch('openstack_virtual_baremetal.auth._create_auth_parameters') + def test_get_token_and_endpoint_v2(self, mock_cap, mock_gkt): + fake_auth_data = {'os_user': 'admin', + 'os_password': 'password', + 'os_tenant': 'admin', + 'os_auth_url': 'auth', + 'os_project': '', + 'os_user_domain': '', + 'os_project_domain': '', + } + mock_cap.return_value = fake_auth_data + mock_gkt.return_value = V2_TOKEN_DATA + token, endpoint = auth._get_token_and_endpoint('heat') + self.assertEqual('fake_token', token) + self.assertEqual('heat_endpoint', endpoint) + + @mock.patch('openstack_virtual_baremetal.auth._get_keystone_token') + @mock.patch('openstack_virtual_baremetal.auth._create_auth_parameters') + def test_get_token_and_endpoint_v3(self, mock_cap, mock_gkt): + fake_auth_data = {'os_user': 'admin', + 'os_password': 'password', + 'os_tenant': '', + 'os_auth_url': 'auth/v3', + 'os_project': 'admin', + 'os_user_domain': 'default', + 'os_project_domain': 'default', + } + mock_cap.return_value = fake_auth_data + mock_gkt.return_value = V3_TOKEN_DATA + token, endpoint = auth._get_token_and_endpoint('heat') + self.assertEqual('fake_v3_token', token) + self.assertEqual('heat_endpoint', endpoint) diff --git a/openstack_virtual_baremetal/tests/test_build_nodes_json.py b/openstack_virtual_baremetal/tests/test_build_nodes_json.py index b5edb11..43a0904 100644 --- a/openstack_virtual_baremetal/tests/test_build_nodes_json.py +++ b/openstack_virtual_baremetal/tests/test_build_nodes_json.py @@ -161,10 +161,12 @@ class TestBuildNodesJson(testtools.TestCase): self.useFixture(fixtures.EnvironmentVariable('OS_CLOUD', 'foo')) build_nodes_json._get_clients() calls = [mock.call('compute', cloud='foo'), - mock.call('network', cloud='foo')] + mock.call('network', cloud='foo'), + mock.call('image', cloud='foo')] self.assertEqual(calls, mock_make_client.mock_calls) - def _test_get_clients_env(self, mock_nova, mock_neutron): + def _test_get_clients_env(self, mock_nova, mock_neutron, mock_glance, + mock_gtae): self.useFixture(fixtures.EnvironmentVariable('OS_USERNAME', 'admin')) self.useFixture(fixtures.EnvironmentVariable('OS_PASSWORD', 'pw')) self.useFixture(fixtures.EnvironmentVariable('OS_TENANT_NAME', @@ -174,28 +176,45 @@ class TestBuildNodesJson(testtools.TestCase): mock_nova.return_value = mock_nova_client mock_neutron_client = mock.Mock() mock_neutron.return_value = mock_neutron_client - nova, neutron = build_nodes_json._get_clients() + mock_glance_client = mock.Mock() + mock_glance.return_value = mock_glance_client + mock_token = 'abc-123' + mock_endpoint = 'glance://endpoint' + mock_gtae.return_value = (mock_token, mock_endpoint) + nova, neutron, glance = build_nodes_json._get_clients() self.assertEqual(mock_nova_client, nova) self.assertEqual(mock_neutron_client, neutron) + self.assertEqual(mock_glance_client, glance) + @mock.patch('openstack_virtual_baremetal.auth._get_token_and_endpoint') @mock.patch('openstack_virtual_baremetal.build_nodes_json.nc.__version__', ('6', '0', '0')) + @mock.patch('glanceclient.Client') @mock.patch('neutronclient.v2_0.client.Client') @mock.patch('novaclient.client.Client') - def test_get_clients_env_6(self, mock_nova, mock_neutron): - self._test_get_clients_env(mock_nova, mock_neutron) + def test_get_clients_env_6(self, mock_nova, mock_neutron, mock_glance, + mock_gtae): + self._test_get_clients_env(mock_nova, mock_neutron, mock_glance, + mock_gtae) + @mock.patch('openstack_virtual_baremetal.auth._get_token_and_endpoint') @mock.patch('openstack_virtual_baremetal.build_nodes_json.nc.__version__', ('7', '0', '0')) + @mock.patch('glanceclient.Client') @mock.patch('neutronclient.v2_0.client.Client') @mock.patch('novaclient.client.Client') - def test_get_clients_env_7(self, mock_nova, mock_neutron): - self._test_get_clients_env(mock_nova, mock_neutron) + def test_get_clients_env_7(self, mock_nova, mock_neutron, mock_glance, + mock_gtae): + self._test_get_clients_env(mock_nova, mock_neutron, mock_glance, + mock_gtae) + @mock.patch('openstack_virtual_baremetal.auth._get_token_and_endpoint') @mock.patch('openstack_virtual_baremetal.auth._get_keystone_session') + @mock.patch('glanceclient.Client') @mock.patch('neutronclient.v2_0.client.Client') @mock.patch('novaclient.client.Client') - def test_get_clients_env_v3(self, mock_nova, mock_neutron, mock_gks): + def test_get_clients_env_v3(self, mock_nova, mock_neutron, mock_glance, + mock_gks, mock_gtae): self.useFixture(fixtures.EnvironmentVariable('OS_USERNAME', 'admin')) self.useFixture(fixtures.EnvironmentVariable('OS_PASSWORD', 'pw')) self.useFixture(fixtures.EnvironmentVariable('OS_PROJECT_NAME', @@ -209,12 +228,20 @@ class TestBuildNodesJson(testtools.TestCase): mock_nova.return_value = mock_nova_client mock_neutron_client = mock.Mock() mock_neutron.return_value = mock_neutron_client + mock_glance_client = mock.Mock() + mock_glance.return_value = mock_glance_client mock_session_inst = mock.Mock() mock_gks.return_value = mock_session_inst - nova, neutron = build_nodes_json._get_clients() + mock_token = 'abc-123' + mock_endpoint = 'glance://endpoint' + mock_gtae.return_value = (mock_token, mock_endpoint) + nova, neutron, glance = build_nodes_json._get_clients() mock_neutron.assert_called_once_with(session=mock_session_inst) + mock_glance.assert_called_once_with('2', token=mock_token, + endpoint=mock_endpoint) self.assertEqual(mock_nova_client, nova) self.assertEqual(mock_neutron_client, neutron) + self.assertEqual(mock_glance_client, glance) def test_get_ports(self): neutron = mock.Mock() @@ -296,8 +323,10 @@ class TestBuildNodesJson(testtools.TestCase): ips_return_val = 'ips call value' nova.servers.ips.return_value = ips_return_val + glance = mock.Mock() + nodes, bmc_bm_pairs, extra_nodes = build_nodes_json._build_nodes( - nova, bmc_ports, bm_ports, 'provision', 'bm', 'undercloud') + nova, glance, bmc_ports, bm_ports, 'provision', 'bm', 'undercloud') expected_nodes = TEST_NODES self.assertEqual(expected_nodes, nodes) self.assertEqual([('1.1.1.1', 'bm_0'), ('1.1.1.2', 'bm_1')], @@ -317,12 +346,14 @@ class TestBuildNodesJson(testtools.TestCase): servers[1].name = 'bm-foo-control_1' ips_return_val = 'ips call value' nova.servers.ips.return_value = ips_return_val + + glance = mock.Mock() mock_image_get = mock.Mock() - nova.images.get.return_value = mock_image_get - mock_image_get.metadata.get.return_value = 'uefi' + mock_image_get.get.return_value = 'uefi' + glance.images.get.return_value = mock_image_get nodes, bmc_bm_pairs, extra_nodes = build_nodes_json._build_nodes( - nova, bmc_ports, bm_ports, 'provision', 'bm-foo', None) + nova, glance, bmc_ports, bm_ports, 'provision', 'bm-foo', None) expected_nodes = copy.deepcopy(TEST_NODES) expected_nodes[0]['name'] = 'bm-foo-control-0' expected_nodes[0]['capabilities'] = ('boot_option:local,' @@ -396,7 +427,8 @@ class TestBuildNodesJson(testtools.TestCase): undercloud_name) nova = mock.Mock() neutron = mock.Mock() - mock_get_clients.return_value = (nova, neutron) + glance = mock.Mock() + mock_get_clients.return_value = (nova, neutron, glance) bmc_ports = mock.Mock() bm_ports = mock.Mock() mock_get_ports.return_value = (bmc_ports, bm_ports) @@ -412,8 +444,9 @@ class TestBuildNodesJson(testtools.TestCase): mock_get_clients.assert_called_once_with() mock_get_ports.assert_called_once_with(neutron, bmc_base, baremetal_base) - mock_build_nodes.assert_called_once_with(nova, bmc_ports, bm_ports, - provision_net, baremetal_base, + mock_build_nodes.assert_called_once_with(nova, glance, bmc_ports, + bm_ports, provision_net, + baremetal_base, undercloud_name) mock_write_nodes.assert_called_once_with(nodes, extra_nodes, args) mock_write_pairs.assert_called_once_with(pairs) diff --git a/openstack_virtual_baremetal/tests/test_deploy.py b/openstack_virtual_baremetal/tests/test_deploy.py index 37fc7a3..16b1254 100755 --- a/openstack_virtual_baremetal/tests/test_deploy.py +++ b/openstack_virtual_baremetal/tests/test_deploy.py @@ -371,26 +371,6 @@ class TestDeploy(testtools.TestCase): deploy._validate_env(args, 'foo.yaml') -V2_TOKEN_DATA = {'token': {'id': 'fake_token'}, - 'serviceCatalog': [{'name': 'nova'}, - {'name': 'heat', - 'endpoints': [ - {'publicURL': 'heat_endpoint'} - ] - } - ]} -V3_TOKEN_DATA = {'auth_token': 'fake_v3_token', - 'catalog': [{'name': 'nova'}, - {'name': 'heat', - 'endpoints': [ - {'interface': 'private'}, - {'interface': 'public', - 'url': 'heat_endpoint'} - ] - } - ]} - - class TestGetHeatClient(testtools.TestCase): @mock.patch('os_client_config.make_client') def test_os_cloud(self, mock_make_client): @@ -399,41 +379,13 @@ class TestGetHeatClient(testtools.TestCase): mock_make_client.assert_called_once_with('orchestration', cloud='foo') @mock.patch('heatclient.client.Client') - @mock.patch('openstack_virtual_baremetal.auth._get_keystone_token') - @mock.patch('openstack_virtual_baremetal.auth._create_auth_parameters') - def test_keystone_v2(self, mock_cap, mock_gkt, mock_hc): - fake_auth_data = {'os_user': 'admin', - 'os_password': 'password', - 'os_tenant': 'admin', - 'os_auth_url': 'auth', - 'os_project': '', - 'os_user_domain': '', - 'os_project_domain': '', - } - mock_cap.return_value = fake_auth_data - mock_gkt.return_value = V2_TOKEN_DATA + @mock.patch('openstack_virtual_baremetal.auth._get_token_and_endpoint') + def test_heatclient(self, mock_gtae, mock_hc): + mock_gtae.return_value = ('fake_token', 'heat_endpoint') deploy._get_heat_client() mock_hc.assert_called_once_with('1', endpoint='heat_endpoint', token='fake_token') - @mock.patch('heatclient.client.Client') - @mock.patch('openstack_virtual_baremetal.auth._get_keystone_token') - @mock.patch('openstack_virtual_baremetal.auth._create_auth_parameters') - def test_keystone_v3(self, mock_cap, mock_gkt, mock_hc): - fake_auth_data = {'os_user': 'admin', - 'os_password': 'password', - 'os_tenant': 'admin', - 'os_auth_url': 'auth/v3', - 'os_project': 'admin', - 'os_user_domain': 'default', - 'os_project_domain': 'default', - } - mock_cap.return_value = fake_auth_data - mock_gkt.return_value = V3_TOKEN_DATA - deploy._get_heat_client() - mock_hc.assert_called_once_with('1', endpoint='heat_endpoint', - token='fake_v3_token') - if __name__ == '__main__': unittest.main() diff --git a/requirements.txt b/requirements.txt index d4d87ab..835e99d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ os-client-config pyghmi PyYAML +python-glanceclient python-heatclient python-keystoneclient python-neutronclient