Merge "Begin converting baremetal node tests"

This commit is contained in:
Jenkins 2017-09-06 05:50:26 +00:00 committed by Gerrit Code Review
commit 3e07162bff
7 changed files with 210 additions and 53 deletions

View File

@ -12,9 +12,11 @@
import importlib
import warnings
from keystoneauth1 import plugin
from os_client_config import constructors
from shade import _utils
from shade import exc
class LegacyClientFactoryMixin(object):
@ -144,8 +146,47 @@ class LegacyClientFactoryMixin(object):
def _get_legacy_ironic_microversion(self):
return '1.6'
def _join_ksa_version(self, version):
return ".".join([str(x) for x in version])
@property
def ironic_client(self):
# Trigger discovery from ksa. This will make ironicclient and
# keystoneauth1.adapter.Adapter code paths both go through discovery.
# ironicclient does its own magic with discovery, so we won't
# pass an endpoint_override here like we do for keystoneclient.
# Just so it's not wasted though, make sure we can handle the
# min microversion we need.
needed = self._get_legacy_ironic_microversion()
# TODO(mordred) Bug in ksa - don't do microversion matching for
# auth_type = admin_token. Remove this if when the fix lands.
if (hasattr(plugin.BaseAuthPlugin, 'get_endpoint_data') or
self.cloud_config.config['auth_type'] not in (
'admin_token', 'none')):
# TODO(mordred) once we're on REST properly, we need a better
# method for matching requested and available microversion
endpoint_data = self._baremetal_client.get_endpoint_data()
if not endpoint_data.min_microversion:
raise exc.OpenStackCloudException(
"shade needs an ironic that supports microversions")
if endpoint_data.min_microversion[1] > int(needed[-1]):
raise exc.OpenStackCloudException(
"shade needs an ironic that supports microversion {needed}"
" but the ironic found has a minimum microversion"
" of {found}".format(
needed=needed,
found=self._join_ksa_version(
endpoint_data.min_microversion)))
if endpoint_data.max_microversion[1] < int(needed[-1]):
raise exc.OpenStackCloudException(
"shade needs an ironic that supports microversion {needed}"
" but the ironic found has a maximum microversion"
" of {found}".format(
needed=needed,
found=self._join_ksa_version(
endpoint_data.max_microversion)))
return self._create_legacy_client(
'ironic', 'baremetal', deprecated=False,
module_name='ironicclient.client.Client',

View File

@ -418,7 +418,8 @@ class OpenStackCloud(
# data.api_version can be None if no version was detected, such
# as with neutron
api_version = adapter.get_api_major_version()
api_version = adapter.get_api_major_version(
endpoint_override=self.cloud_config.get_endpoint(service_type))
api_major = self._get_major_version_id(api_version)
# If we detect a different version that was configured, warn the user.
@ -468,11 +469,13 @@ class OpenStackCloud(
def _baremetal_client(self):
if 'baremetal' not in self._raw_clients:
client = self._get_raw_client('baremetal')
# TODO(mordred) Fix this once we've migrated all the way to REST
# Don't bother with version discovery - there is only one version
# of ironic. This is what ironicclient does, fwiw.
client.endpoint_override = urllib.parse.urljoin(
client.get_endpoint(), 'v1')
# Do this to force version discovery. We need to do that, because
# the endpoint-override trick we do for neutron because
# ironicclient just appends a /v1 won't work and will break
# keystoneauth - because ironic's versioned discovery endpoint
# is non-compliant and doesn't return an actual version dict.
client = self._get_versioned_client(
'baremetal', min_version=1, max_version='1.latest')
self._raw_clients['baremetal'] = client
return self._raw_clients['baremetal']

View File

@ -466,6 +466,12 @@ class RequestsMockTestCase(BaseTestCase):
return dict(method='GET', uri="https://dns.example.com/",
text=open(discovery_fixture, 'r').read())
def get_ironic_discovery_mock_dict(self):
discovery_fixture = os.path.join(
self.fixtures_directory, "baremetal.json")
return dict(method='GET', uri="https://bare-metal.example.com/",
text=open(discovery_fixture, 'r').read())
def use_glance(self, image_version_json='image-version.json'):
# NOTE(notmorgan): This method is only meant to be used in "setUp"
# where the ordering of the url being registered is tightly controlled
@ -484,6 +490,15 @@ class RequestsMockTestCase(BaseTestCase):
self.__do_register_uris([
self.get_designate_discovery_mock_dict()])
def use_ironic(self):
# NOTE(TheJulia): This method is only meant to be used in "setUp"
# where the ordering of the url being registered is tightly controlled
# if the functionality of .use_ironic is meant to be used during an
# actual test case, use .get_ironic_discovery_mock and apply to the
# right location in the mock_uris when calling .register_uris
self.__do_register_uris([
self.get_ironic_discovery_mock_dict()])
def register_uris(self, uri_mock_list=None):
"""Mock a list of URIs and responses via requests mock.
@ -613,3 +628,17 @@ class RequestsMockTestCase(BaseTestCase):
if do_count:
self.assertEqual(
len(self.calls), len(self.adapter.request_history))
class IronicTestCase(RequestsMockTestCase):
def setUp(self):
super(IronicTestCase, self).setUp()
self.use_ironic()
self.uuid = str(uuid.uuid4())
self.name = self.getUniqueString('name')
def get_mock_url(self, resource=None, append=None, qs_elements=None):
return super(IronicTestCase, self).get_mock_url(
service_type='baremetal', interface='public', resource=resource,
append=append, base_url_append='v1', qs_elements=qs_elements)

View File

@ -0,0 +1,30 @@
{
"default_version": {
"id": "v1",
"links": [
{
"href": "https://bare-metal.example.com/v1/",
"rel": "self"
}
],
"min_version": "1.1",
"status": "CURRENT",
"version": "1.33"
},
"description": "Ironic is an OpenStack project which aims to provision baremetal machines.",
"name": "OpenStack Ironic API",
"versions": [
{
"id": "v1",
"links": [
{
"href": "https://bare-metal.example.com/v1/",
"rel": "self"
}
],
"min_version": "1.1",
"status": "CURRENT",
"version": "1.33"
}
]
}

View File

@ -0,0 +1,59 @@
# 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.
"""
test_baremetal_node
----------------------------------
Tests for baremetal node related operations
"""
import uuid
from shade.tests import fakes
from shade.tests.unit import base
class TestBaremetalNode(base.IronicTestCase):
def setUp(self):
super(TestBaremetalNode, self).setUp()
self.fake_baremetal_node = fakes.make_fake_machine(
self.name, self.uuid)
def test_list_machines(self):
fake_baremetal_two = fakes.make_fake_machine('two', str(uuid.uuid4()))
self.register_uris([
dict(method='GET',
uri=self.get_mock_url(resource='nodes'),
json={'nodes': [self.fake_baremetal_node,
fake_baremetal_two]}),
])
machines = self.op_cloud.list_machines()
self.assertEqual(2, len(machines))
self.assertEqual(self.fake_baremetal_node, machines[0])
self.assert_calls()
def test_get_machine(self):
self.register_uris([
dict(method='GET',
uri=self.get_mock_url(
resource='nodes',
append=[self.fake_baremetal_node['uuid']]),
json=self.fake_baremetal_node),
])
machine = self.op_cloud.get_machine(self.fake_baremetal_node['uuid'])
self.assertEqual(machine['uuid'],
self.fake_baremetal_node['uuid'])
self.assert_calls()

View File

@ -12,15 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from keystoneauth1 import plugin
import ironicclient
from os_client_config import cloud_config
import shade
from shade.tests import base
from shade.tests.unit import base
class TestShadeOperatorNoAuth(base.TestCase):
class TestShadeOperatorNoAuth(base.RequestsMockTestCase):
def setUp(self):
"""Setup Noauth OperatorCloud tests
@ -28,32 +26,49 @@ class TestShadeOperatorNoAuth(base.TestCase):
URL in the auth data. This is permits testing of the basic
mechanism that enables Ironic noauth mode to be utilized with
Shade.
Uses base.RequestsMockTestCase instead of IronicTestCase because
we need to do completely different things with discovery.
"""
super(TestShadeOperatorNoAuth, self).setUp()
self.cloud_noauth = shade.operator_cloud(
auth_type='admin_token',
auth=dict(endpoint="http://localhost:6385"),
validate=False,
)
# By clearing the URI registry, we remove all calls to a keystone
# catalog or getting a token
self._uri_registry.clear()
# TODO(mordred) Remove this if with next KSA release
if hasattr(plugin.BaseAuthPlugin, 'get_endpoint_data'):
self.use_ironic()
self.register_uris([
dict(method='GET',
uri=self.get_mock_url(
service_type='baremetal', base_url_append='v1',
resource='nodes'),
json={'nodes': []}),
])
@mock.patch.object(cloud_config.CloudConfig, 'get_session')
@mock.patch.object(ironicclient.client, 'Client')
def test_ironic_noauth_selection_using_a_task(
self, mock_client, get_session_mock):
def test_ironic_noauth_none_auth_type(self):
"""Test noauth selection for Ironic in OperatorCloud
Utilize a task to trigger the client connection attempt
and evaluate if get_session_endpoint was called while the client
was still called.
We want session_endpoint to be called because we're storing the
endpoint in a noauth token Session object now.
The new way of doing this is with the keystoneauth none plugin.
"""
session_mock = mock.Mock()
session_mock.get_endpoint.return_value = None
session_mock.get_token.return_value = 'yankee'
get_session_mock.return_value = session_mock
self.cloud_noauth = shade.operator_cloud(
auth_type='none',
baremetal_endpoint_override="https://bare-metal.example.com")
self.cloud_noauth.patch_machine('name', {})
self.assertTrue(get_session_mock.called)
self.assertTrue(mock_client.called)
self.cloud_noauth.list_machines()
self.assert_calls()
def test_ironic_noauth_admin_token_auth_type(self):
"""Test noauth selection for Ironic in OperatorCloud
The old way of doing this was to abuse admin_token.
"""
self.cloud_noauth = shade.operator_cloud(
auth_type='admin_token',
auth=dict(
endpoint='https://bare-metal.example.com',
token='ignored'))
self.cloud_noauth.list_machines()
self.assert_calls()

View File

@ -37,29 +37,9 @@ class TestShadeOperator(base.RequestsMockTestCase):
machine_id=self.machine_id,
machine_name=self.machine_name)
def get_ironic_mock_url(self, append=None, *args, **kwargs):
if append:
# TODO(mordred): Remove when we do version discovery
# properly everywhere
append.insert(0, 'v1')
return self.get_mock_url('baremetal', append=append, *args, **kwargs)
def test_operator_cloud(self):
self.assertIsInstance(self.op_cloud, shade.OperatorCloud)
def test_get_machine(self):
self.register_uris([
dict(method='GET',
uri=self.get_ironic_mock_url(
append=['nodes', self.machine_name]),
json=self.node),
])
machine = self.op_cloud.get_machine(self.machine_name)
self.assertEqual(self.node, machine)
self.assert_calls()
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
def test_get_machine_by_mac(self, mock_client):
class port_value(object):