Split ironic-related functions from utils to separate common.ironic module

Change-Id: I56c1a5eececb555c14847aecfc153ed40e680863
This commit is contained in:
Dmitry Tantsur 2016-02-29 18:58:44 +01:00
parent e03cbe8c8b
commit 0f8b5de248
18 changed files with 274 additions and 212 deletions

View File

@ -0,0 +1,110 @@
# 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.
import socket
from ironicclient import client
from keystoneclient import client as keystone_client
from oslo_config import cfg
from ironic_inspector.common.i18n import _
from ironic_inspector import utils
CONF = cfg.CONF
# See http://specs.openstack.org/openstack/ironic-specs/specs/kilo/new-ironic-state-machine.html # noqa
VALID_STATES = {'enroll', 'manageable', 'inspecting', 'inspectfail'}
SET_CREDENTIALS_VALID_STATES = {'enroll'}
# 1.11 is API version, which support 'enroll' state
DEFAULT_IRONIC_API_VERSION = '1.11'
def get_ipmi_address(node):
ipmi_fields = ['ipmi_address'] + CONF.ipmi_address_fields
# NOTE(sambetts): IPMI Address is useless to us if bridging is enabled so
# just ignore it and return None
if node.driver_info.get("ipmi_bridging", "no") != "no":
return
for name in ipmi_fields:
value = node.driver_info.get(name)
if value:
try:
ip = socket.gethostbyname(value)
return ip
except socket.gaierror:
msg = ('Failed to resolve the hostname (%s) for node %s')
raise utils.Error(msg % (value, node.uuid), node_info=node)
def get_client(token=None,
api_version=DEFAULT_IRONIC_API_VERSION): # pragma: no cover
"""Get Ironic client instance."""
# NOTE: To support standalone ironic without keystone
if CONF.ironic.auth_strategy == 'noauth':
args = {'os_auth_token': 'noauth',
'ironic_url': CONF.ironic.ironic_url}
elif token is None:
args = {'os_password': CONF.ironic.os_password,
'os_username': CONF.ironic.os_username,
'os_auth_url': CONF.ironic.os_auth_url,
'os_tenant_name': CONF.ironic.os_tenant_name,
'os_service_type': CONF.ironic.os_service_type,
'os_endpoint_type': CONF.ironic.os_endpoint_type}
else:
keystone_creds = {'password': CONF.ironic.os_password,
'username': CONF.ironic.os_username,
'auth_url': CONF.ironic.os_auth_url,
'tenant_name': CONF.ironic.os_tenant_name}
keystone = keystone_client.Client(**keystone_creds)
# FIXME(sambetts): Work around for Bug 1539839 as client.authenticate
# is not called.
keystone.authenticate()
ironic_url = keystone.service_catalog.url_for(
service_type=CONF.ironic.os_service_type,
endpoint_type=CONF.ironic.os_endpoint_type)
args = {'os_auth_token': token,
'ironic_url': ironic_url}
args['os_ironic_api_version'] = api_version
args['max_retries'] = CONF.ironic.max_retries
args['retry_interval'] = CONF.ironic.retry_interval
return client.get_client(1, **args)
def check_provision_state(node, with_credentials=False):
state = node.provision_state.lower()
if with_credentials and state not in SET_CREDENTIALS_VALID_STATES:
msg = _('Invalid provision state for setting IPMI credentials: '
'"%(state)s", valid states are %(valid)s')
raise utils.Error(msg % {'state': state,
'valid': list(SET_CREDENTIALS_VALID_STATES)},
node_info=node)
elif not with_credentials and state not in VALID_STATES:
msg = _('Invalid provision state for introspection: '
'"%(state)s", valid states are "%(valid)s"')
raise utils.Error(msg % {'state': state, 'valid': list(VALID_STATES)},
node_info=node)
def capabilities_to_dict(caps):
"""Convert the Node's capabilities into a dictionary."""
if not caps:
return {}
return dict([key.split(':', 1) for key in caps.split(',')])
def dict_to_capabilities(caps_dict):
"""Convert a dictionary into a string with the capabilities syntax."""
return ','.join(["%s:%s" % (key, value)
for key, value in caps_dict.items()
if value is not None])

View File

@ -19,8 +19,8 @@ from oslo_config import cfg
from oslo_log import log
from ironic_inspector.common.i18n import _LE, _LW
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import node_cache
from ironic_inspector import utils
CONF = cfg.CONF
@ -125,7 +125,7 @@ def update_filters(ironic=None):
return
assert INTERFACE is not None
ironic = utils.get_client() if ironic is None else ironic
ironic = ir_utils.get_client() if ironic is None else ironic
with LOCK:
macs_active = set(p.address for p in ironic.port.list(limit=0))

View File

@ -22,6 +22,7 @@ from ironicclient import exceptions
from oslo_config import cfg
from ironic_inspector.common.i18n import _, _LI, _LW
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector import utils
@ -71,7 +72,7 @@ def introspect(uuid, new_ipmi_credentials=None, token=None):
:param token: authentication token
:raises: Error
"""
ironic = utils.get_client(token)
ironic = ir_utils.get_client(token)
try:
node = ironic.node.get(uuid)
@ -81,7 +82,7 @@ def introspect(uuid, new_ipmi_credentials=None, token=None):
raise utils.Error(_("Cannot get node %(node)s: %(exc)s") %
{'node': uuid, 'exc': exc})
utils.check_provision_state(node, with_credentials=new_ipmi_credentials)
ir_utils.check_provision_state(node, with_credentials=new_ipmi_credentials)
if new_ipmi_credentials:
new_ipmi_credentials = (
@ -93,8 +94,9 @@ def introspect(uuid, new_ipmi_credentials=None, token=None):
raise utils.Error(msg % validation.power['reason'],
node_info=node)
bmc_address = ir_utils.get_ipmi_address(node)
node_info = node_cache.add_node(node.uuid,
bmc_address=utils.get_ipmi_address(node),
bmc_address=bmc_address,
ironic=ironic)
node_info.set_option('new_ipmi_credentials', new_ipmi_credentials)
@ -184,7 +186,7 @@ def abort(uuid, token=None):
:raises: Error
"""
LOG.debug('Aborting introspection for node %s', uuid)
ironic = utils.get_client(token)
ironic = ir_utils.get_client(token)
node_info = node_cache.get_node(uuid, ironic=ironic, locked=False)
# check pending operations

View File

@ -28,6 +28,7 @@ import werkzeug
from ironic_inspector import db
from ironic_inspector.common.i18n import _, _LC, _LE, _LI, _LW
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector.common import swift
from ironic_inspector import conf # noqa
from ironic_inspector import firewall
@ -308,7 +309,7 @@ def periodic_clean_up(period): # pragma: no cover
def sync_with_ironic():
ironic = utils.get_client()
ironic = ir_utils.get_client()
# TODO(yuikotakada): pagination
ironic_nodes = ironic.node.list(limit=0)
ironic_node_uuids = {node.uuid for node in ironic_nodes}

View File

@ -26,6 +26,7 @@ from sqlalchemy import text
from ironic_inspector import db
from ironic_inspector.common.i18n import _, _LE, _LW, _LI
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import utils
CONF = cfg.CONF
@ -139,7 +140,7 @@ class NodeInfo(object):
def ironic(self):
"""Ironic client instance."""
if self._ironic is None:
self._ironic = utils.get_client()
self._ironic = ir_utils.get_client()
return self._ironic
def set_option(self, name, value):
@ -303,11 +304,11 @@ class NodeInfo(object):
:param props: capabilities to update
"""
existing = utils.capabilities_to_dict(
existing = ir_utils.capabilities_to_dict(
self.node().properties.get('capabilities'))
existing.update(caps)
self.update_properties(
capabilities=utils.dict_to_capabilities(existing))
capabilities=ir_utils.dict_to_capabilities(existing))
def delete_port(self, port):
"""Delete port.
@ -583,7 +584,7 @@ def create_node(driver, ironic=None, **attributes):
:return: NodeInfo, or None in case error happened.
"""
if ironic is None:
ironic = utils.get_client()
ironic = ir_utils.get_client()
try:
node = ironic.node.create(driver=driver, **attributes)
except exceptions.InvalidAttribute as e:

View File

@ -16,6 +16,7 @@
from oslo_config import cfg
from ironic_inspector.common.i18n import _, _LW
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import node_cache
from ironic_inspector import utils
@ -73,7 +74,7 @@ def _check_existing_nodes(introspection_data, node_driver_info, ironic):
# impact on performance on big clusters
nodes = ironic.node.list(fields=('uuid', 'driver_info'), limit=0)
for node in nodes:
if ipmi_address == utils.get_ipmi_address(node):
if ipmi_address == ir_utils.get_ipmi_address(node):
raise utils.Error(
_('Node %(uuid)s already has BMC address '
'%(ipmi_address)s, not enrolling') %
@ -83,7 +84,7 @@ def _check_existing_nodes(introspection_data, node_driver_info, ironic):
def enroll_node_not_found_hook(introspection_data, **kwargs):
node_attr = {}
ironic = utils.get_client()
ironic = ir_utils.get_client()
node_driver_info = _extract_node_driver_info(introspection_data)
node_attr['driver_info'] = node_driver_info

View File

@ -18,6 +18,7 @@ from ironicclient import exceptions
from oslo_config import cfg
from ironic_inspector.common.i18n import _, _LE, _LI
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector.common import swift
from ironic_inspector import firewall
from ironic_inspector import node_cache
@ -142,7 +143,7 @@ def _run_post_hooks(node_info, introspection_data):
def _process_node(node, introspection_data, node_info):
# NOTE(dtantsur): repeat the check in case something changed
utils.check_provision_state(node)
ir_utils.check_provision_state(node)
node_info.create_ports(introspection_data.get('macs') or ())
@ -165,7 +166,7 @@ def _process_node(node, introspection_data, node_info):
'won\'t be stored',
node_info=node_info, data=introspection_data)
ironic = utils.get_client()
ironic = ir_utils.get_client()
firewall.update_filters(ironic)
node_info.invalidate_cache()
@ -194,7 +195,7 @@ def _finish_set_ipmi_credentials(ironic, node, node_info, introspection_data,
'value': new_username},
{'op': 'add', 'path': '/driver_info/ipmi_password',
'value': new_password}]
if (not utils.get_ipmi_address(node) and
if (not ir_utils.get_ipmi_address(node) and
introspection_data.get('ipmi_address')):
patch.append({'op': 'add', 'path': '/driver_info/ipmi_address',
'value': introspection_data['ipmi_address']})

View File

@ -26,11 +26,11 @@ from oslo_config import cfg
from oslo_utils import units
import requests
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import dbsync
from ironic_inspector import main
from ironic_inspector import rules
from ironic_inspector.test import base
from ironic_inspector import utils
CONF = """
@ -61,7 +61,7 @@ class Base(base.NodeTest):
super(Base, self).setUp()
rules.delete_all()
self.cli = utils.get_client()
self.cli = ir_utils.get_client()
self.cli.reset_mock()
self.cli.node.get.return_value = self.node
self.cli.node.update.return_value = self.node
@ -442,7 +442,7 @@ def mocked_server():
content = CONF % {'db_file': db_file}
fp.write(content.encode('utf-8'))
with mock.patch.object(utils, 'get_client'):
with mock.patch.object(ir_utils, 'get_client'):
dbsync.main(args=['--config-file', conf_file, 'upgrade'])
cfg.CONF.reset()

View File

@ -0,0 +1,118 @@
# 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.
import mock
import socket
import unittest
from ironicclient import client
from keystoneclient import client as keystone_client
from oslo_config import cfg
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector.test import base
from ironic_inspector import utils
CONF = cfg.CONF
class TestGetClient(base.BaseTest):
def setUp(self):
super(TestGetClient, self).setUp()
CONF.set_override('auth_strategy', 'keystone')
@mock.patch.object(client, 'get_client')
@mock.patch.object(keystone_client, 'Client')
def test_get_client_with_auth_token(self, mock_keystone_client,
mock_client):
fake_token = 'token'
fake_ironic_url = 'http://127.0.0.1:6385'
mock_keystone_client().service_catalog.url_for.return_value = (
fake_ironic_url)
ir_utils.get_client(fake_token)
args = {'os_auth_token': fake_token,
'ironic_url': fake_ironic_url,
'os_ironic_api_version': '1.11',
'max_retries': CONF.ironic.max_retries,
'retry_interval': CONF.ironic.retry_interval}
mock_client.assert_called_once_with(1, **args)
@mock.patch.object(client, 'get_client')
def test_get_client_without_auth_token(self, mock_client):
ir_utils.get_client(None)
args = {'os_password': CONF.ironic.os_password,
'os_username': CONF.ironic.os_username,
'os_auth_url': CONF.ironic.os_auth_url,
'os_tenant_name': CONF.ironic.os_tenant_name,
'os_endpoint_type': CONF.ironic.os_endpoint_type,
'os_service_type': CONF.ironic.os_service_type,
'os_ironic_api_version': '1.11',
'max_retries': CONF.ironic.max_retries,
'retry_interval': CONF.ironic.retry_interval}
mock_client.assert_called_once_with(1, **args)
class TestGetIpmiAddress(base.BaseTest):
def test_ipv4_in_resolves(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': '192.168.1.1'})
ip = ir_utils.get_ipmi_address(node)
self.assertEqual(ip, '192.168.1.1')
@mock.patch('socket.gethostbyname')
def test_good_hostname_resolves(self, mock_socket):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'www.example.com'})
mock_socket.return_value = '192.168.1.1'
ip = ir_utils.get_ipmi_address(node)
mock_socket.assert_called_once_with('www.example.com')
self.assertEqual(ip, '192.168.1.1')
@mock.patch('socket.gethostbyname')
def test_bad_hostname_errors(self, mock_socket):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'meow'},
uuid='uuid1')
mock_socket.side_effect = socket.gaierror('Boom')
self.assertRaises(utils.Error, ir_utils.get_ipmi_address, node)
def test_additional_fields(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'foo': '192.168.1.1'})
self.assertIsNone(ir_utils.get_ipmi_address(node))
CONF.set_override('ipmi_address_fields', ['foo', 'bar', 'baz'])
ip = ir_utils.get_ipmi_address(node)
self.assertEqual(ip, '192.168.1.1')
def test_ipmi_bridging_enabled(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'www.example.com',
'ipmi_bridging': 'single'})
self.assertIsNone(ir_utils.get_ipmi_address(node))
class TestCapabilities(unittest.TestCase):
def test_capabilities_to_dict(self):
capabilities = 'cat:meow,dog:wuff'
expected_output = {'cat': 'meow', 'dog': 'wuff'}
output = ir_utils.capabilities_to_dict(capabilities)
self.assertEqual(expected_output, output)
def test_dict_to_capabilities(self):
capabilities_dict = {'cat': 'meow', 'dog': 'wuff'}
output = ir_utils.dict_to_capabilities(capabilities_dict)
self.assertIn('cat:meow', output)
self.assertIn('dog:wuff', output)

View File

@ -18,17 +18,17 @@ import subprocess
import mock
from oslo_config import cfg
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
CONF = cfg.CONF
@mock.patch.object(firewall, '_iptables')
@mock.patch.object(utils, 'get_client')
@mock.patch.object(ir_utils, 'get_client')
@mock.patch.object(subprocess, 'check_call')
class TestFirewall(test_base.NodeTest):
def test_update_filters_without_manage_firewall(self, mock_call,

View File

@ -19,6 +19,7 @@ from ironicclient import exceptions
import mock
from oslo_config import cfg
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import node_cache
@ -52,7 +53,7 @@ class BaseTest(test_base.NodeTest):
lambda f, *a, **kw: f(*a, **kw) and None)
@mock.patch.object(firewall, 'update_filters', autospec=True)
@mock.patch.object(node_cache, 'add_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestIntrospect(BaseTest):
def test_ok(self, client_mock, add_mock, filters_mock):
cli = self._prepare(client_mock)
@ -337,7 +338,7 @@ class TestIntrospect(BaseTest):
lambda f, *a, **kw: f(*a, **kw) and None)
@mock.patch.object(firewall, 'update_filters', autospec=True)
@mock.patch.object(node_cache, 'add_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestSetIpmiCredentials(BaseTest):
def setUp(self):
super(TestSetIpmiCredentials, self).setUp()
@ -422,7 +423,7 @@ class TestSetIpmiCredentials(BaseTest):
lambda f, *a, **kw: f(*a, **kw) and None)
@mock.patch.object(firewall, 'update_filters', autospec=True)
@mock.patch.object(node_cache, 'get_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestAbort(BaseTest):
def setUp(self):
super(TestAbort, self).setUp()

View File

@ -19,6 +19,7 @@ import unittest
import mock
from oslo_utils import uuidutils
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import db
from ironic_inspector import firewall
from ironic_inspector import introspect
@ -459,7 +460,7 @@ class TestPlugins(unittest.TestCase):
@mock.patch.object(utils, 'spawn_n')
@mock.patch.object(firewall, 'init')
@mock.patch.object(utils, 'add_auth_middleware')
@mock.patch.object(utils, 'get_client')
@mock.patch.object(ir_utils, 'get_client')
@mock.patch.object(db, 'init')
class TestInit(test_base.BaseTest):
def test_ok(self, mock_node_cache, mock_get_client, mock_auth,

View File

@ -20,6 +20,7 @@ import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import db
from ironic_inspector import node_cache
from ironic_inspector.test import base as test_base
@ -421,7 +422,7 @@ class TestNodeInfoOptions(test_base.NodeTest):
self.assertEqual(data, new.options['name'])
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestNodeCacheIronicObjects(unittest.TestCase):
def setUp(self):
super(TestNodeCacheIronicObjects, self).setUp()
@ -553,7 +554,7 @@ class TestUpdate(test_base.NodeTest):
self.ironic.node.update.assert_called_once_with(self.uuid, mock.ANY)
patch = self.ironic.node.update.call_args[0][1]
new_caps = utils.capabilities_to_dict(patch[0]['value'])
new_caps = ir_utils.capabilities_to_dict(patch[0]['value'])
self.assertEqual({'foo': 'bar', 'x': '1', 'y': '2'}, new_caps)
def test_replace_field(self):
@ -678,7 +679,7 @@ class TestLock(test_base.NodeTest):
@mock.patch.object(node_cache, 'add_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestNodeCreate(test_base.NodeTest):
def setUp(self):
super(TestNodeCreate, self).setUp()

View File

@ -13,6 +13,7 @@
import copy
import mock
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import node_cache
from ironic_inspector.plugins import discovery
from ironic_inspector.test import base as test_base
@ -37,7 +38,7 @@ class TestEnrollNodeNotFoundHook(test_base.NodeTest):
self.ironic = mock.MagicMock()
@mock.patch.object(node_cache, 'create_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
@mock.patch.object(discovery, '_check_existing_nodes', autospec=True)
def test_enroll_default(self, mock_check_existing, mock_client,
mock_create_node):
@ -52,7 +53,7 @@ class TestEnrollNodeNotFoundHook(test_base.NodeTest):
introspection_data, {}, self.ironic)
@mock.patch.object(node_cache, 'create_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
@mock.patch.object(discovery, '_check_existing_nodes', autospec=True)
def test_enroll_with_ipmi_address(self, mock_check_existing, mock_client,
mock_create_node):
@ -72,7 +73,7 @@ class TestEnrollNodeNotFoundHook(test_base.NodeTest):
introspection_data)
@mock.patch.object(node_cache, 'create_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
@mock.patch.object(discovery, '_check_existing_nodes', autospec=True)
def test_enroll_with_non_default_driver(self, mock_check_existing,
mock_client, mock_create_node):

View File

@ -16,6 +16,7 @@
import mock
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import node_cache
from ironic_inspector.plugins import rules as rules_plugins
from ironic_inspector.test import base as test_base
@ -186,7 +187,7 @@ class TestSetCapabilityAction(test_base.NodeTest):
self.act.apply(self.node_info, self.params)
patch = mock_patch.call_args[0][0]
new_caps = utils.capabilities_to_dict(patch[0]['value'])
new_caps = ir_utils.capabilities_to_dict(patch[0]['value'])
self.assertEqual({'cap1': 'val', 'x': 'y', 'answer': '42'}, new_caps)

View File

@ -21,6 +21,7 @@ import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import firewall
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
@ -57,7 +58,7 @@ class BaseTest(test_base.NodeTest):
@mock.patch.object(process, '_process_node', autospec=True)
@mock.patch.object(node_cache, 'find_node', autospec=True)
@mock.patch.object(utils, 'get_client', autospec=True)
@mock.patch.object(ir_utils, 'get_client', autospec=True)
class TestProcess(BaseTest):
def setUp(self):
super(TestProcess, self).setUp()
@ -278,7 +279,7 @@ class TestProcessNode(BaseTest):
self.cli.node.update.return_value = self.node
self.cli.node.list_ports.return_value = []
@mock.patch.object(utils, 'get_client')
@mock.patch.object(ir_utils, 'get_client')
def call(self, mock_cli):
mock_cli.return_value = self.cli
return process._process_node(self.node, self.data, self.node_info)

View File

@ -11,11 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import socket
import unittest
from ironicclient import client
from keystoneclient import client as keystone_client
from keystonemiddleware import auth_token
from oslo_config import cfg
@ -35,36 +32,6 @@ class TestCheckAuth(base.BaseTest):
super(TestCheckAuth, self).setUp()
CONF.set_override('auth_strategy', 'keystone')
@mock.patch.object(client, 'get_client')
@mock.patch.object(keystone_client, 'Client')
def test_get_client_with_auth_token(self, mock_keystone_client,
mock_client):
fake_token = 'token'
fake_ironic_url = 'http://127.0.0.1:6385'
mock_keystone_client().service_catalog.url_for.return_value = (
fake_ironic_url)
utils.get_client(fake_token)
args = {'os_auth_token': fake_token,
'ironic_url': fake_ironic_url,
'os_ironic_api_version': '1.11',
'max_retries': CONF.ironic.max_retries,
'retry_interval': CONF.ironic.retry_interval}
mock_client.assert_called_once_with(1, **args)
@mock.patch.object(client, 'get_client')
def test_get_client_without_auth_token(self, mock_client):
utils.get_client(None)
args = {'os_password': CONF.ironic.os_password,
'os_username': CONF.ironic.os_username,
'os_auth_url': CONF.ironic.os_auth_url,
'os_tenant_name': CONF.ironic.os_tenant_name,
'os_endpoint_type': CONF.ironic.os_endpoint_type,
'os_service_type': CONF.ironic.os_service_type,
'os_ironic_api_version': '1.11',
'max_retries': CONF.ironic.max_retries,
'retry_interval': CONF.ironic.retry_interval}
mock_client.assert_called_once_with(1, **args)
@mock.patch.object(auth_token, 'AuthProtocol')
def test_middleware(self, mock_auth):
CONF.set_override('admin_user', 'admin', 'keystone_authtoken')
@ -139,61 +106,6 @@ class TestCheckAuth(base.BaseTest):
utils.check_auth(request)
class TestGetIpmiAddress(base.BaseTest):
def test_ipv4_in_resolves(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': '192.168.1.1'})
ip = utils.get_ipmi_address(node)
self.assertEqual(ip, '192.168.1.1')
@mock.patch('socket.gethostbyname')
def test_good_hostname_resolves(self, mock_socket):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'www.example.com'})
mock_socket.return_value = '192.168.1.1'
ip = utils.get_ipmi_address(node)
mock_socket.assert_called_once_with('www.example.com')
self.assertEqual(ip, '192.168.1.1')
@mock.patch('socket.gethostbyname')
def test_bad_hostname_errors(self, mock_socket):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'meow'},
uuid='uuid1')
mock_socket.side_effect = socket.gaierror('Boom')
self.assertRaises(utils.Error, utils.get_ipmi_address, node)
def test_additional_fields(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'foo': '192.168.1.1'})
self.assertIsNone(utils.get_ipmi_address(node))
CONF.set_override('ipmi_address_fields', ['foo', 'bar', 'baz'])
ip = utils.get_ipmi_address(node)
self.assertEqual(ip, '192.168.1.1')
def test_ipmi_bridging_enabled(self):
node = mock.Mock(spec=['driver_info', 'uuid'],
driver_info={'ipmi_address': 'www.example.com',
'ipmi_bridging': 'single'})
self.assertIsNone(utils.get_ipmi_address(node))
class TestCapabilities(unittest.TestCase):
def test_capabilities_to_dict(self):
capabilities = 'cat:meow,dog:wuff'
expected_output = {'cat': 'meow', 'dog': 'wuff'}
output = utils.capabilities_to_dict(capabilities)
self.assertEqual(expected_output, output)
def test_dict_to_capabilities(self):
capabilities_dict = {'cat': 'meow', 'dog': 'wuff'}
output = utils.dict_to_capabilities(capabilities_dict)
self.assertIn('cat:meow', output)
self.assertIn('dog:wuff', output)
class TestSpawnN(unittest.TestCase):
def setUp(self):

View File

@ -13,11 +13,8 @@
import logging as pylog
import re
import socket
import eventlet
from ironicclient import client
from keystoneclient import client as keystone_client
from keystonemiddleware import auth_token
from oslo_config import cfg
from oslo_log import log
@ -28,32 +25,8 @@ from ironic_inspector import conf # noqa
CONF = cfg.CONF
# See http://specs.openstack.org/openstack/ironic-specs/specs/kilo/new-ironic-state-machine.html # noqa
VALID_STATES = {'enroll', 'manageable', 'inspecting', 'inspectfail'}
SET_CREDENTIALS_VALID_STATES = {'enroll'}
GREEN_POOL = None
# 1.11 is API version, which support 'enroll' state
DEFAULT_IRONIC_API_VERSION = '1.11'
def get_ipmi_address(node):
ipmi_fields = ['ipmi_address'] + CONF.ipmi_address_fields
# NOTE(sambetts): IPMI Address is useless to us if bridging is enabled so
# just ignore it and return None
if node.driver_info.get("ipmi_bridging", "no") != "no":
return
for name in ipmi_fields:
value = node.driver_info.get(name)
if value:
try:
ip = socket.gethostbyname(value)
return ip
except socket.gaierror:
msg = ('Failed to resolve the hostname (%s) for node %s')
raise Error(msg % (value, node.uuid), node_info=node)
def get_ipmi_address_from_data(introspection_data):
try:
@ -150,40 +123,6 @@ def spawn_n(*args, **kwargs):
return GREEN_POOL.spawn_n(*args, **kwargs)
def get_client(token=None,
api_version=DEFAULT_IRONIC_API_VERSION): # pragma: no cover
"""Get Ironic client instance."""
# NOTE: To support standalone ironic without keystone
if CONF.ironic.auth_strategy == 'noauth':
args = {'os_auth_token': 'noauth',
'ironic_url': CONF.ironic.ironic_url}
elif token is None:
args = {'os_password': CONF.ironic.os_password,
'os_username': CONF.ironic.os_username,
'os_auth_url': CONF.ironic.os_auth_url,
'os_tenant_name': CONF.ironic.os_tenant_name,
'os_service_type': CONF.ironic.os_service_type,
'os_endpoint_type': CONF.ironic.os_endpoint_type}
else:
keystone_creds = {'password': CONF.ironic.os_password,
'username': CONF.ironic.os_username,
'auth_url': CONF.ironic.os_auth_url,
'tenant_name': CONF.ironic.os_tenant_name}
keystone = keystone_client.Client(**keystone_creds)
# FIXME(sambetts): Work around for Bug 1539839 as client.authenticate
# is not called.
keystone.authenticate()
ironic_url = keystone.service_catalog.url_for(
service_type=CONF.ironic.os_service_type,
endpoint_type=CONF.ironic.os_endpoint_type)
args = {'os_auth_token': token,
'ironic_url': ironic_url}
args['os_ironic_api_version'] = api_version
args['max_retries'] = CONF.ironic.max_retries
args['retry_interval'] = CONF.ironic.retry_interval
return client.get_client(1, **args)
def add_auth_middleware(app):
"""Add authentication middleware to Flask application.
@ -244,32 +183,3 @@ def get_auth_strategy():
if CONF.authenticate is not None:
return 'keystone' if CONF.authenticate else 'noauth'
return CONF.auth_strategy
def check_provision_state(node, with_credentials=False):
state = node.provision_state.lower()
if with_credentials and state not in SET_CREDENTIALS_VALID_STATES:
msg = _('Invalid provision state for setting IPMI credentials: '
'"%(state)s", valid states are %(valid)s')
raise Error(msg % {'state': state,
'valid': list(SET_CREDENTIALS_VALID_STATES)},
node_info=node)
elif not with_credentials and state not in VALID_STATES:
msg = _('Invalid provision state for introspection: '
'"%(state)s", valid states are "%(valid)s"')
raise Error(msg % {'state': state, 'valid': list(VALID_STATES)},
node_info=node)
def capabilities_to_dict(caps):
"""Convert the Node's capabilities into a dictionary."""
if not caps:
return {}
return dict([key.split(':', 1) for key in caps.split(',')])
def dict_to_capabilities(caps_dict):
"""Convert a dictionary into a string with the capabilities syntax."""
return ','.join(["%s:%s" % (key, value)
for key, value in caps_dict.items()
if value is not None])