Split ironic-related functions from utils to separate common.ironic module
Change-Id: I56c1a5eececb555c14847aecfc153ed40e680863
This commit is contained in:
parent
e03cbe8c8b
commit
0f8b5de248
110
ironic_inspector/common/ironic.py
Normal file
110
ironic_inspector/common/ironic.py
Normal 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])
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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']})
|
||||
|
@ -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()
|
||||
|
118
ironic_inspector/test/test_common_ironic.py
Normal file
118
ironic_inspector/test/test_common_ironic.py
Normal 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)
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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])
|
||||
|
Loading…
Reference in New Issue
Block a user