Store and expose introspection data
This adds the ability to store all of the data collected during introspection. The configuration option "[processing] store_data" (defaults to 'none'), determines this behavior. Initially, only 'none' and 'swift' are supported. If 'swift' is used, the data is stored in Swift with the object name of "inspector_data-<UUID>". Adds an endpoint /v1/introspection/<UUID>/data which retrieves the data according to the method in "[processing] store_data". Returns 404 if this option is disabled. There is a further option to store the location of the data in the Ironic Node.extra column. For 'swift', this will be the name of the swift object. The option, "[processing] store_data_location" determines the key name in the Node.extra column. (defaults to not storing the location). Change-Id: Ibc38064f7ea56f85b9f5a77ef6f62a50f0381ff4 Implements: blueprint store-introspection-data
This commit is contained in:
parent
4597919c9f
commit
6eb9f58c87
|
@ -83,6 +83,7 @@ Example local.conf
|
||||||
enable_service ironic ir-api ir-cond
|
enable_service ironic ir-api ir-cond
|
||||||
disable_service n-net n-novnc
|
disable_service n-net n-novnc
|
||||||
enable_service neutron q-svc q-agt q-dhcp q-l3 q-meta
|
enable_service neutron q-svc q-agt q-dhcp q-l3 q-meta
|
||||||
|
enable_service s-proxy s-object s-container s-account
|
||||||
disable_service heat h-api h-api-cfn h-api-cw h-eng
|
disable_service heat h-api h-api-cfn h-api-cw h-eng
|
||||||
disable_service cinder c-sch c-api c-vol
|
disable_service cinder c-sch c-api c-vol
|
||||||
|
|
||||||
|
|
18
HTTP-API.rst
18
HTTP-API.rst
|
@ -50,6 +50,23 @@ Response body: JSON dictionary with keys:
|
||||||
* ``finished`` (boolean) whether introspection is finished
|
* ``finished`` (boolean) whether introspection is finished
|
||||||
* ``error`` error string or ``null``
|
* ``error`` error string or ``null``
|
||||||
|
|
||||||
|
Get Introspection Data
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``GET /v1/introspection/<UUID>/data`` get stored data from successful
|
||||||
|
introspection.
|
||||||
|
|
||||||
|
Requires X-Auth-Token header with Keystone token for authentication.
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
* 200 - OK
|
||||||
|
* 400 - bad request
|
||||||
|
* 401, 403 - missing or invalid authentication
|
||||||
|
* 404 - data cannot be found or data storage not configured
|
||||||
|
|
||||||
|
Response body: JSON dictionary with introspection data
|
||||||
|
|
||||||
Ramdisk Callback
|
Ramdisk Callback
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -151,3 +168,4 @@ Version History
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
**1.0** version of API at the moment of introducing versioning.
|
**1.0** version of API at the moment of introducing versioning.
|
||||||
|
**1.1** adds endpoint to retrieve stored introspection data.
|
||||||
|
|
|
@ -71,6 +71,21 @@ function curl_ir {
|
||||||
curl -H "X-Auth-Token: $token" -X $1 "$ironic_url/$2"
|
curl -H "X-Auth-Token: $token" -X $1 "$ironic_url/$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function curl_ins {
|
||||||
|
curl -H "X-Auth-Token: $token" -X $1 "http://127.0.0.1:5050/$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_swift {
|
||||||
|
# Basic sanity check of the data stored in Swift
|
||||||
|
stored_data_json=$(curl_ins GET v1/introspection/$uuid/data)
|
||||||
|
stored_cpu_arch=$(echo $stored_data_json | jq -r '.cpu_arch')
|
||||||
|
echo CPU arch for $uuid from stored data: $stored_cpu_arch
|
||||||
|
if [ "$stored_cpu_arch" != "$expected_cpu_arch" ]; then
|
||||||
|
echo "The data stored in Swift does not match the expected data."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
for uuid in $nodes; do
|
for uuid in $nodes; do
|
||||||
node_json=$(curl_ir GET v1/nodes/$uuid)
|
node_json=$(curl_ir GET v1/nodes/$uuid)
|
||||||
properties=$(echo $node_json | jq '.properties')
|
properties=$(echo $node_json | jq '.properties')
|
||||||
|
@ -93,6 +108,8 @@ for uuid in $nodes; do
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
openstack service list | grep swift && test_swift
|
||||||
|
|
||||||
for attempt in {1..12}; do
|
for attempt in {1..12}; do
|
||||||
node_json=$(curl_ir GET v1/nodes/$uuid)
|
node_json=$(curl_ir GET v1/nodes/$uuid)
|
||||||
provision_state=$(echo $node_json | jq -r '.provision_state')
|
provision_state=$(echo $node_json | jq -r '.provision_state')
|
||||||
|
|
|
@ -137,10 +137,21 @@ function configure_inspector {
|
||||||
inspector_iniset firewall dnsmasq_interface $IRONIC_INSPECTOR_INTERFACE
|
inspector_iniset firewall dnsmasq_interface $IRONIC_INSPECTOR_INTERFACE
|
||||||
inspector_iniset database connection sqlite:///$IRONIC_INSPECTOR_DATA_DIR/inspector.sqlite
|
inspector_iniset database connection sqlite:///$IRONIC_INSPECTOR_DATA_DIR/inspector.sqlite
|
||||||
|
|
||||||
|
is_service_enabled swift && configure_inspector_swift
|
||||||
|
|
||||||
iniset "$IRONIC_CONF_FILE" inspector enabled True
|
iniset "$IRONIC_CONF_FILE" inspector enabled True
|
||||||
iniset "$IRONIC_CONF_FILE" inspector service_url $IRONIC_INSPECTOR_URI
|
iniset "$IRONIC_CONF_FILE" inspector service_url $IRONIC_INSPECTOR_URI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configure_inspector_swift {
|
||||||
|
inspector_iniset swift os_auth_url "$KEYSTONE_SERVICE_URI/v2.0"
|
||||||
|
inspector_iniset swift username $IRONIC_INSPECTOR_ADMIN_USER
|
||||||
|
inspector_iniset swift password $SERVICE_PASSWORD
|
||||||
|
inspector_iniset swift tenant_name $SERVICE_TENANT_NAME
|
||||||
|
|
||||||
|
inspector_iniset processing store_data swift
|
||||||
|
}
|
||||||
|
|
||||||
function configure_inspector_dhcp {
|
function configure_inspector_dhcp {
|
||||||
mkdir_chown_stack "$IRONIC_INSPECTOR_CONF_DIR"
|
mkdir_chown_stack "$IRONIC_INSPECTOR_CONF_DIR"
|
||||||
|
|
||||||
|
|
|
@ -589,6 +589,15 @@
|
||||||
# ignored by default. (string value)
|
# ignored by default. (string value)
|
||||||
#node_not_found_hook = <None>
|
#node_not_found_hook = <None>
|
||||||
|
|
||||||
|
# Method for storing introspection data. If set to 'none',
|
||||||
|
# introspection data will not be stored (string value)
|
||||||
|
# Allowed values: none, swift
|
||||||
|
#store_data = none
|
||||||
|
|
||||||
|
# Name of the key to store the location of stored data in the extra
|
||||||
|
# column of the Ironic database. (string value)
|
||||||
|
#store_data_location = <None>
|
||||||
|
|
||||||
|
|
||||||
[swift]
|
[swift]
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
# Mostly copied from ironic/common/swift.py
|
# Mostly copied from ironic/common/swift.py
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from swiftclient import client as swift_client
|
from swiftclient import client as swift_client
|
||||||
|
@ -71,6 +73,8 @@ def list_opts():
|
||||||
|
|
||||||
CONF.register_opts(SWIFT_OPTS, group='swift')
|
CONF.register_opts(SWIFT_OPTS, group='swift')
|
||||||
|
|
||||||
|
OBJECT_NAME_PREFIX = 'inspector_data'
|
||||||
|
|
||||||
|
|
||||||
class SwiftAPI(object):
|
class SwiftAPI(object):
|
||||||
"""API for communicating with Swift."""
|
"""API for communicating with Swift."""
|
||||||
|
@ -137,3 +141,53 @@ class SwiftAPI(object):
|
||||||
raise utils.Error(err_msg)
|
raise utils.Error(err_msg)
|
||||||
|
|
||||||
return obj_uuid
|
return obj_uuid
|
||||||
|
|
||||||
|
def get_object(self, object, container=CONF.swift.container):
|
||||||
|
"""Downloads a given object from Swift.
|
||||||
|
|
||||||
|
:param object: The name of the object in Swift
|
||||||
|
:param container: The name of the container for the object.
|
||||||
|
:returns: Swift object
|
||||||
|
:raises: utils.Error, if the Swift operation fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
headers, obj = self.connection.get_object(container, object)
|
||||||
|
except swift_exceptions.ClientException as e:
|
||||||
|
err_msg = (_('Swift failed to get object %(object)s in '
|
||||||
|
'container %(container)s. Error was: %(error)s') %
|
||||||
|
{'object': object, 'container': container, 'error': e})
|
||||||
|
raise utils.Error(err_msg)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def store_introspection_data(data, uuid):
|
||||||
|
"""Uploads introspection data to Swift.
|
||||||
|
|
||||||
|
:param data: data to store in Swift
|
||||||
|
:param uuid: UUID of the Ironic node that the data came from
|
||||||
|
:returns: name of the Swift object that the data is stored in
|
||||||
|
"""
|
||||||
|
swift_api = SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
|
swift_object_name = '%s-%s' % (OBJECT_NAME_PREFIX, uuid)
|
||||||
|
swift_api.create_object(swift_object_name, json.dumps(data))
|
||||||
|
return swift_object_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_introspection_data(uuid):
|
||||||
|
"""Downloads introspection data from Swift.
|
||||||
|
|
||||||
|
:param uuid: UUID of the Ironic node that the data came from
|
||||||
|
:returns: Swift object with the introspection data
|
||||||
|
"""
|
||||||
|
swift_api = SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
|
swift_object_name = '%s-%s' % (OBJECT_NAME_PREFIX, uuid)
|
||||||
|
return swift_api.get_object(swift_object_name)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from oslo_config import cfg
|
||||||
|
|
||||||
VALID_ADD_PORTS_VALUES = ('all', 'active', 'pxe')
|
VALID_ADD_PORTS_VALUES = ('all', 'active', 'pxe')
|
||||||
VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added')
|
VALID_KEEP_PORTS_VALUES = ('all', 'present', 'added')
|
||||||
|
VALID_STORE_DATA_VALUES = ('none', 'swift')
|
||||||
|
|
||||||
|
|
||||||
IRONIC_OPTS = [
|
IRONIC_OPTS = [
|
||||||
|
@ -160,7 +161,16 @@ PROCESSING_OPTS = [
|
||||||
default=None,
|
default=None,
|
||||||
help='The name of the hook to run when inspector receives '
|
help='The name of the hook to run when inspector receives '
|
||||||
'inspection information from a node it isn\'t already '
|
'inspection information from a node it isn\'t already '
|
||||||
'aware of. This hook is ignored by default.')
|
'aware of. This hook is ignored by default.'),
|
||||||
|
cfg.StrOpt('store_data',
|
||||||
|
default='none',
|
||||||
|
choices=VALID_STORE_DATA_VALUES,
|
||||||
|
help='Method for storing introspection data. If set to \'none'
|
||||||
|
'\', introspection data will not be stored.'),
|
||||||
|
cfg.StrOpt('store_data_location',
|
||||||
|
default=None,
|
||||||
|
help='Name of the key to store the location of stored data in '
|
||||||
|
'the extra column of the Ironic database.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from oslo_utils import uuidutils
|
||||||
|
|
||||||
from ironic_inspector import db
|
from ironic_inspector import db
|
||||||
from ironic_inspector.common.i18n import _, _LC, _LE, _LI, _LW
|
from ironic_inspector.common.i18n import _, _LC, _LE, _LI, _LW
|
||||||
|
from ironic_inspector.common import swift
|
||||||
from ironic_inspector import conf # noqa
|
from ironic_inspector import conf # noqa
|
||||||
from ironic_inspector import firewall
|
from ironic_inspector import firewall
|
||||||
from ironic_inspector import introspect
|
from ironic_inspector import introspect
|
||||||
|
@ -41,7 +42,7 @@ app = flask.Flask(__name__)
|
||||||
LOG = log.getLogger('ironic_inspector.main')
|
LOG = log.getLogger('ironic_inspector.main')
|
||||||
|
|
||||||
MINIMUM_API_VERSION = (1, 0)
|
MINIMUM_API_VERSION = (1, 0)
|
||||||
CURRENT_API_VERSION = (1, 0)
|
CURRENT_API_VERSION = (1, 1)
|
||||||
_MIN_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Minimum-Version'
|
_MIN_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Minimum-Version'
|
||||||
_MAX_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Maximum-Version'
|
_MAX_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Maximum-Version'
|
||||||
_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Version'
|
_VERSION_HEADER = 'X-OpenStack-Ironic-Inspector-API-Version'
|
||||||
|
@ -152,6 +153,20 @@ def api_introspection(uuid):
|
||||||
error=node_info.error or None)
|
error=node_info.error or None)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/v1/introspection/<uuid>/data', methods=['GET'])
|
||||||
|
@convert_exceptions
|
||||||
|
def api_introspection_data(uuid):
|
||||||
|
utils.check_auth(flask.request)
|
||||||
|
if CONF.processing.store_data == 'swift':
|
||||||
|
res = swift.get_introspection_data(uuid)
|
||||||
|
return res, 200, {'Content-Type': 'applications/json'}
|
||||||
|
else:
|
||||||
|
return error_response(_('Inspector is not configured to store data. '
|
||||||
|
'Set the [processing] store_data '
|
||||||
|
'configuration option to change this.'),
|
||||||
|
code=404)
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def handle_404(error):
|
def handle_404(error):
|
||||||
return error_response(error, code=404)
|
return error_response(error, code=404)
|
||||||
|
|
|
@ -15,14 +15,17 @@
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from ironicclient import exceptions
|
from ironicclient import exceptions
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from ironic_inspector.common.i18n import _, _LE, _LI
|
from ironic_inspector.common.i18n import _, _LE, _LI
|
||||||
|
from ironic_inspector.common import swift
|
||||||
from ironic_inspector import firewall
|
from ironic_inspector import firewall
|
||||||
from ironic_inspector import node_cache
|
from ironic_inspector import node_cache
|
||||||
from ironic_inspector.plugins import base as plugins_base
|
from ironic_inspector.plugins import base as plugins_base
|
||||||
from ironic_inspector import utils
|
from ironic_inspector import utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
LOG = log.getLogger("ironic_inspector.process")
|
LOG = log.getLogger("ironic_inspector.process")
|
||||||
|
|
||||||
|
@ -142,6 +145,15 @@ def _process_node(ironic, node, introspection_data, node_info):
|
||||||
node_patches, port_patches = _run_post_hooks(node_info,
|
node_patches, port_patches = _run_post_hooks(node_info,
|
||||||
introspection_data)
|
introspection_data)
|
||||||
|
|
||||||
|
if CONF.processing.store_data == 'swift':
|
||||||
|
swift_object_name = swift.store_introspection_data(introspection_data,
|
||||||
|
node_info.uuid)
|
||||||
|
if CONF.processing.store_data_location:
|
||||||
|
node_patches.append({'op': 'add',
|
||||||
|
'path': '/extra/%s' %
|
||||||
|
CONF.processing.store_data_location,
|
||||||
|
'value': swift_object_name})
|
||||||
|
|
||||||
node = ironic.node.update(node.uuid, node_patches)
|
node = ironic.node.update(node.uuid, node_patches)
|
||||||
for mac, patches in port_patches.items():
|
for mac, patches in port_patches.items():
|
||||||
port = node_info.ports(ironic)[mac]
|
port = node_info.ports(ironic)[mac]
|
||||||
|
|
|
@ -150,6 +150,37 @@ class TestApiGetStatus(BaseAPITest):
|
||||||
json.loads(res.data.decode('utf-8')))
|
json.loads(res.data.decode('utf-8')))
|
||||||
|
|
||||||
|
|
||||||
|
class TestApiGetData(BaseAPITest):
|
||||||
|
@mock.patch.object(main.swift, 'SwiftAPI', autospec=True)
|
||||||
|
def test_get_introspection_data(self, swift_mock):
|
||||||
|
CONF.set_override('store_data', 'swift', 'processing')
|
||||||
|
data = {
|
||||||
|
'ipmi_address': '1.2.3.4',
|
||||||
|
'cpus': 2,
|
||||||
|
'cpu_arch': 'x86_64',
|
||||||
|
'memory_mb': 1024,
|
||||||
|
'local_gb': 20,
|
||||||
|
'interfaces': {
|
||||||
|
'em1': {'mac': '11:22:33:44:55:66', 'ip': '1.2.0.1'},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swift_conn = swift_mock.return_value
|
||||||
|
swift_conn.get_object.return_value = json.dumps(data)
|
||||||
|
res = self.app.get('/v1/introspection/%s/data' % self.uuid)
|
||||||
|
name = 'inspector_data-%s' % self.uuid
|
||||||
|
swift_conn.get_object.assert_called_once_with(name)
|
||||||
|
self.assertEqual(200, res.status_code)
|
||||||
|
self.assertEqual(data, json.loads(res.data.decode('utf-8')))
|
||||||
|
|
||||||
|
@mock.patch.object(main.swift, 'SwiftAPI', autospec=True)
|
||||||
|
def test_introspection_data_not_stored(self, swift_mock):
|
||||||
|
CONF.set_override('store_data', 'none', 'processing')
|
||||||
|
swift_conn = swift_mock.return_value
|
||||||
|
res = self.app.get('/v1/introspection/%s/data' % self.uuid)
|
||||||
|
self.assertFalse(swift_conn.get_object.called)
|
||||||
|
self.assertEqual(404, res.status_code)
|
||||||
|
|
||||||
|
|
||||||
class TestApiMisc(BaseAPITest):
|
class TestApiMisc(BaseAPITest):
|
||||||
@mock.patch.object(node_cache, 'get_node', autospec=True)
|
@mock.patch.object(node_cache, 'get_node', autospec=True)
|
||||||
def test_404_expected(self, get_mock):
|
def test_404_expected(self, get_mock):
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
|
@ -578,6 +579,40 @@ class TestProcessNode(BaseTest):
|
||||||
self.cli.port.delete.assert_any_call(port.uuid)
|
self.cli.port.delete.assert_any_call(port.uuid)
|
||||||
self.assertEqual(2, self.cli.port.delete.call_count)
|
self.assertEqual(2, self.cli.port.delete.call_count)
|
||||||
|
|
||||||
|
@mock.patch.object(process.swift, 'SwiftAPI', autospec=True)
|
||||||
|
def test_store_data(self, swift_mock, filters_mock, post_hook_mock):
|
||||||
|
CONF.set_override('store_data', 'swift', 'processing')
|
||||||
|
swift_conn = swift_mock.return_value
|
||||||
|
name = 'inspector_data-%s' % self.uuid
|
||||||
|
expected = json.dumps(self.data)
|
||||||
|
|
||||||
|
self.call()
|
||||||
|
|
||||||
|
swift_conn.create_object.assert_called_once_with(name, expected)
|
||||||
|
self.cli.node.update.assert_called_once_with(self.uuid,
|
||||||
|
self.patch_props)
|
||||||
|
|
||||||
|
@mock.patch.object(process.swift, 'SwiftAPI', autospec=True)
|
||||||
|
def test_store_data_location(self, swift_mock, filters_mock,
|
||||||
|
post_hook_mock):
|
||||||
|
CONF.set_override('store_data', 'swift', 'processing')
|
||||||
|
CONF.set_override('store_data_location', 'inspector_data_object',
|
||||||
|
'processing')
|
||||||
|
swift_conn = swift_mock.return_value
|
||||||
|
name = 'inspector_data-%s' % self.uuid
|
||||||
|
self.patch_props.append(
|
||||||
|
{'path': '/extra/inspector_data_object',
|
||||||
|
'value': name,
|
||||||
|
'op': 'add'}
|
||||||
|
)
|
||||||
|
expected = json.dumps(self.data)
|
||||||
|
|
||||||
|
self.call()
|
||||||
|
|
||||||
|
swift_conn.create_object.assert_called_once_with(name, expected)
|
||||||
|
self.cli.node.update.assert_called_once_with(self.uuid,
|
||||||
|
self.patch_props)
|
||||||
|
|
||||||
|
|
||||||
class TestValidateInterfacesHook(test_base.BaseTest):
|
class TestValidateInterfacesHook(test_base.BaseTest):
|
||||||
def test_wrong_add_ports(self):
|
def test_wrong_add_ports(self):
|
||||||
|
|
|
@ -26,14 +26,34 @@ from swiftclient import client as swift_client
|
||||||
from swiftclient import exceptions as swift_exception
|
from swiftclient import exceptions as swift_exception
|
||||||
|
|
||||||
from ironic_inspector.common import swift
|
from ironic_inspector.common import swift
|
||||||
from ironic_inspector.test import base
|
from ironic_inspector.test import base as test_base
|
||||||
from ironic_inspector import utils
|
from ironic_inspector import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTest(test_base.NodeTest):
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseTest, self).setUp()
|
||||||
|
self.all_macs = self.macs + ['DE:AD:BE:EF:DE:AD']
|
||||||
|
self.pxe_mac = self.macs[1]
|
||||||
|
self.data = {
|
||||||
|
'ipmi_address': self.bmc_address,
|
||||||
|
'cpus': 2,
|
||||||
|
'cpu_arch': 'x86_64',
|
||||||
|
'memory_mb': 1024,
|
||||||
|
'local_gb': 20,
|
||||||
|
'interfaces': {
|
||||||
|
'em1': {'mac': self.macs[0], 'ip': '1.2.0.1'},
|
||||||
|
'em2': {'mac': self.macs[1], 'ip': '1.2.0.2'},
|
||||||
|
'em3': {'mac': self.all_macs[2]},
|
||||||
|
},
|
||||||
|
'boot_interface': '01-' + self.pxe_mac.replace(':', '-'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(swift_client, 'Connection', autospec=True)
|
@mock.patch.object(swift_client, 'Connection', autospec=True)
|
||||||
class SwiftTestCase(base.BaseTest):
|
class SwiftTestCase(BaseTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SwiftTestCase, self).setUp()
|
super(SwiftTestCase, self).setUp()
|
||||||
|
@ -54,7 +74,11 @@ class SwiftTestCase(base.BaseTest):
|
||||||
reload_module(sys.modules['ironic_inspector.common.swift'])
|
reload_module(sys.modules['ironic_inspector.common.swift'])
|
||||||
|
|
||||||
def test___init__(self, connection_mock):
|
def test___init__(self, connection_mock):
|
||||||
swift.SwiftAPI()
|
swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
params = {'retries': 2,
|
params = {'retries': 2,
|
||||||
'user': 'swift',
|
'user': 'swift',
|
||||||
'tenant_name': 'tenant',
|
'tenant_name': 'tenant',
|
||||||
|
@ -66,7 +90,11 @@ class SwiftTestCase(base.BaseTest):
|
||||||
connection_mock.assert_called_once_with(**params)
|
connection_mock.assert_called_once_with(**params)
|
||||||
|
|
||||||
def test_create_object(self, connection_mock):
|
def test_create_object(self, connection_mock):
|
||||||
swiftapi = swift.SwiftAPI()
|
swiftapi = swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
connection_obj_mock = connection_mock.return_value
|
connection_obj_mock = connection_mock.return_value
|
||||||
|
|
||||||
connection_obj_mock.put_object.return_value = 'object-uuid'
|
connection_obj_mock.put_object.return_value = 'object-uuid'
|
||||||
|
@ -80,7 +108,11 @@ class SwiftTestCase(base.BaseTest):
|
||||||
self.assertEqual('object-uuid', object_uuid)
|
self.assertEqual('object-uuid', object_uuid)
|
||||||
|
|
||||||
def test_create_object_create_container_fails(self, connection_mock):
|
def test_create_object_create_container_fails(self, connection_mock):
|
||||||
swiftapi = swift.SwiftAPI()
|
swiftapi = swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
connection_obj_mock = connection_mock.return_value
|
connection_obj_mock = connection_mock.return_value
|
||||||
connection_obj_mock.put_container.side_effect = self.swift_exception
|
connection_obj_mock.put_container.side_effect = self.swift_exception
|
||||||
self.assertRaises(utils.Error, swiftapi.create_object, 'object',
|
self.assertRaises(utils.Error, swiftapi.create_object, 'object',
|
||||||
|
@ -90,7 +122,11 @@ class SwiftTestCase(base.BaseTest):
|
||||||
self.assertFalse(connection_obj_mock.put_object.called)
|
self.assertFalse(connection_obj_mock.put_object.called)
|
||||||
|
|
||||||
def test_create_object_put_object_fails(self, connection_mock):
|
def test_create_object_put_object_fails(self, connection_mock):
|
||||||
swiftapi = swift.SwiftAPI()
|
swiftapi = swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
connection_obj_mock = connection_mock.return_value
|
connection_obj_mock = connection_mock.return_value
|
||||||
connection_obj_mock.put_object.side_effect = self.swift_exception
|
connection_obj_mock.put_object.side_effect = self.swift_exception
|
||||||
self.assertRaises(utils.Error, swiftapi.create_object, 'object',
|
self.assertRaises(utils.Error, swiftapi.create_object, 'object',
|
||||||
|
@ -99,3 +135,33 @@ class SwiftTestCase(base.BaseTest):
|
||||||
'inspector')
|
'inspector')
|
||||||
connection_obj_mock.put_object.assert_called_once_with(
|
connection_obj_mock.put_object.assert_called_once_with(
|
||||||
'ironic-inspector', 'object', 'some-string-data', headers=None)
|
'ironic-inspector', 'object', 'some-string-data', headers=None)
|
||||||
|
|
||||||
|
def test_get_object(self, connection_mock):
|
||||||
|
swiftapi = swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
|
connection_obj_mock = connection_mock.return_value
|
||||||
|
|
||||||
|
expected_obj = self.data
|
||||||
|
connection_obj_mock.get_object.return_value = ('headers', expected_obj)
|
||||||
|
|
||||||
|
swift_obj = swiftapi.get_object('object')
|
||||||
|
|
||||||
|
connection_obj_mock.get_object.assert_called_once_with(
|
||||||
|
'ironic-inspector', 'object')
|
||||||
|
self.assertEqual(expected_obj, swift_obj)
|
||||||
|
|
||||||
|
def test_get_object_fails(self, connection_mock):
|
||||||
|
swiftapi = swift.SwiftAPI(user=CONF.swift.username,
|
||||||
|
tenant_name=CONF.swift.tenant_name,
|
||||||
|
key=CONF.swift.password,
|
||||||
|
auth_url=CONF.swift.os_auth_url,
|
||||||
|
auth_version=CONF.swift.os_auth_version)
|
||||||
|
connection_obj_mock = connection_mock.return_value
|
||||||
|
connection_obj_mock.get_object.side_effect = self.swift_exception
|
||||||
|
self.assertRaises(utils.Error, swiftapi.get_object,
|
||||||
|
'object')
|
||||||
|
connection_obj_mock.get_object.assert_called_once_with(
|
||||||
|
'ironic-inspector', 'object')
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
# required for extra_hardware plugin
|
|
||||||
python-swiftclient>=2.2.0
|
|
|
@ -8,6 +8,7 @@ keystonemiddleware>=2.0.0
|
||||||
pbr<2.0,>=1.6
|
pbr<2.0,>=1.6
|
||||||
python-ironicclient>=0.6.0
|
python-ironicclient>=0.6.0
|
||||||
python-keystoneclient>=1.6.0
|
python-keystoneclient>=1.6.0
|
||||||
|
python-swiftclient>=2.2.0
|
||||||
oslo.config>=2.3.0 # Apache-2.0
|
oslo.config>=2.3.0 # Apache-2.0
|
||||||
oslo.db>=2.4.1 # Apache-2.0
|
oslo.db>=2.4.1 # Apache-2.0
|
||||||
oslo.i18n>=1.5.0 # Apache-2.0
|
oslo.i18n>=1.5.0 # Apache-2.0
|
||||||
|
|
Loading…
Reference in New Issue