Merge remote-tracking branch 'inspector/master'

This commit is contained in:
Dmitry Tantsur 2017-12-12 08:06:44 +01:00
commit b609cea4be
9 changed files with 765 additions and 7 deletions

View File

@ -2,8 +2,8 @@
Ironic tempest plugin
=====================
This directory contains Tempest tests to cover the Ironic project,
as well as a plugin to automatically load these tests into tempest.
This directory contains Tempest tests to cover the ironic and ironic-inspector
projects, as well as a plugin to automatically load these tests into tempest.
See the tempest plugin documentation for information about creating
a plugin, stable API interface, TempestPlugin class interface, plugin

View File

@ -18,11 +18,15 @@ from oslo_config import cfg
from tempest import config # noqa
service_option = cfg.BoolOpt('ironic',
default=False,
help='Whether or not Ironic is expected to be '
'available')
ironic_service_option = cfg.BoolOpt('ironic',
default=False,
help='Whether or not ironic is expected '
'to be available')
inspector_service_option = cfg.BoolOpt("ironic-inspector",
default=True,
help="Whether or not ironic-inspector "
"is expected to be available")
baremetal_group = cfg.OptGroup(name='baremetal',
title='Baremetal provisioning service options',
@ -34,6 +38,12 @@ baremetal_group = cfg.OptGroup(name='baremetal',
'live_migration, pause, rescue, resize, '
'shelve, snapshot, and suspend')
baremetal_introspection_group = cfg.OptGroup(
name="baremetal_introspection",
title="Baremetal introspection service options",
help="When enabling baremetal introspection tests,"
"Ironic must be configured.")
baremetal_features_group = cfg.OptGroup(
name='baremetal_feature_enabled',
title="Enabled Baremetal Service Features")
@ -114,3 +124,45 @@ BaremetalFeaturesGroup = [
default=True,
help="Defines if IPXE is enabled"),
]
BaremetalIntrospectionGroup = [
cfg.StrOpt('catalog_type',
default='baremetal-introspection',
help="Catalog type of the baremetal provisioning service"),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the baremetal introspection"
" service"),
cfg.IntOpt('introspection_sleep',
default=30,
help="Introspection sleep before check status"),
cfg.IntOpt('introspection_timeout',
default=600,
help="Introspection time out"),
cfg.IntOpt('hypervisor_update_sleep',
default=60,
help="Time to wait until nova becomes aware of "
"bare metal instances"),
cfg.IntOpt('hypervisor_update_timeout',
default=300,
help="Time out for wait until nova becomes aware of "
"bare metal instances"),
# NOTE(aarefiev): status_check_period default is 60s, but checking
# node state takes some time(API call), so races appear here,
# 80s would be enough to make one more check.
cfg.IntOpt('ironic_sync_timeout',
default=80,
help="Time it might take for Ironic--Inspector "
"sync to happen"),
cfg.IntOpt('discovery_timeout',
default=300,
help="Time to wait until new node would enrolled in "
"ironic"),
cfg.BoolOpt('auto_discovery_feature',
default=False,
help="Is the auto-discovery feature enabled. Enroll hook "
"should be specified in node_not_found_hook - processing "
"section of inspector.conf"),
]

View File

@ -0,0 +1,25 @@
# 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.
from tempest.lib import exceptions
class IntrospectionFailed(exceptions.TempestException):
message = "Introspection failed"
class IntrospectionTimeout(exceptions.TempestException):
message = "Introspection time out"
class HypervisorUpdateTimeout(exceptions.TempestException):
message = "Hypervisor stats update time out"

View File

@ -25,6 +25,8 @@ _opts = [
(project_config.baremetal_group, project_config.BaremetalGroup),
(project_config.baremetal_features_group,
project_config.BaremetalFeaturesGroup)
(project_config.baremetal_introspection_group,
project_config.BaremetalIntrospectionGroup),
]
@ -37,7 +39,9 @@ class IronicTempestPlugin(plugins.TempestPlugin):
return full_test_dir, base_path
def register_opts(self, conf):
conf.register_opt(project_config.service_option,
conf.register_opt(project_config.ironic_service_option,
group='service_available')
conf.register_opt(project_config.inspector_service_option,
group='service_available')
for group, option in _opts:
config.register_opt_group(conf, group, option)

View File

@ -0,0 +1,25 @@
[
{
"description": "Successful Rule",
"conditions": [
{"op": "ge", "field": "memory_mb", "value": 256},
{"op": "ge", "field": "local_gb", "value": 1}
],
"actions": [
{"action": "set-attribute", "path": "/extra/rule_success",
"value": "yes"}
]
},
{
"description": "Failing Rule",
"conditions": [
{"op": "lt", "field": "memory_mb", "value": 42},
{"op": "eq", "field": "local_gb", "value": 0}
],
"actions": [
{"action": "set-attribute", "path": "/extra/rule_success",
"value": "no"},
{"action": "fail", "message": "This rule should not have run"}
]
}
]

View File

@ -0,0 +1,83 @@
# 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.
from ironic_tempest_plugin.services.baremetal import base
from tempest import clients
from tempest.common import credentials_factory as common_creds
from tempest import config
CONF = config.CONF
ADMIN_CREDS = common_creds.get_configured_admin_credentials()
class Manager(clients.Manager):
def __init__(self,
credentials=ADMIN_CREDS,
api_microversions=None):
super(Manager, self).__init__(credentials)
self.introspection_client = BaremetalIntrospectionClient(
self.auth_provider,
CONF.baremetal_introspection.catalog_type,
CONF.identity.region,
endpoint_type=CONF.baremetal_introspection.endpoint_type)
class BaremetalIntrospectionClient(base.BaremetalClient):
"""Base Tempest REST client for Ironic Inspector API v1."""
version = '1'
uri_prefix = 'v1'
@base.handle_errors
def purge_rules(self):
"""Purge all existing rules."""
return self._delete_request('rules', uuid=None)
@base.handle_errors
def create_rules(self, rules):
"""Create introspection rules."""
if not isinstance(rules, list):
rules = [rules]
for rule in rules:
self._create_request('rules', rule)
@base.handle_errors
def get_status(self, uuid):
"""Get introspection status for a node."""
return self._show_request('introspection', uuid=uuid)
@base.handle_errors
def get_data(self, uuid):
"""Get introspection data for a node."""
return self._show_request('introspection', uuid=uuid,
uri='/%s/introspection/%s/data' %
(self.uri_prefix, uuid))
@base.handle_errors
def start_introspection(self, uuid):
"""Start introspection for a node."""
resp, _body = self.post(url=('/%s/introspection/%s' %
(self.uri_prefix, uuid)),
body=None)
self.expected_success(202, resp.status)
return resp
@base.handle_errors
def abort_introspection(self, uuid):
"""Abort introspection for a node."""
resp, _body = self.post(url=('/%s/introspection/%s/abort' %
(self.uri_prefix, uuid)),
body=None)
self.expected_success(202, resp.status)
return resp

View File

@ -0,0 +1,243 @@
# 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 json
import os
import time
from ironic_tempest_plugin.tests.api.admin.api_microversion_fixture import \
APIMicroversionFixture as IronicMicroversionFixture
from ironic_tempest_plugin.tests.scenario.baremetal_manager import \
BaremetalProvisionStates
from ironic_tempest_plugin.tests.scenario.baremetal_manager import \
BaremetalScenarioTest
import six
import tempest
from tempest import config
from tempest.lib.common.api_version_utils import LATEST_MICROVERSION
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from ironic_inspector.test.inspector_tempest_plugin import exceptions
from ironic_inspector.test.inspector_tempest_plugin.services import \
introspection_client
CONF = config.CONF
class InspectorScenarioTest(BaremetalScenarioTest):
"""Provide harness to do Inspector scenario tests."""
wait_provisioning_state_interval = 15
credentials = ['primary', 'admin']
ironic_api_version = LATEST_MICROVERSION
@classmethod
def setup_clients(cls):
super(InspectorScenarioTest, cls).setup_clients()
inspector_manager = introspection_client.Manager()
cls.introspection_client = inspector_manager.introspection_client
def setUp(self):
super(InspectorScenarioTest, self).setUp()
# we rely on the 'available' provision_state; using latest
# microversion
self.useFixture(IronicMicroversionFixture(self.ironic_api_version))
self.flavor = self.baremetal_flavor()
self.node_ids = {node['uuid'] for node in
self.node_filter(filter=lambda node:
node['provision_state'] ==
BaremetalProvisionStates.AVAILABLE)}
self.rule_purge()
def item_filter(self, list_method, show_method,
filter=lambda item: True, items=None):
if items is None:
items = [show_method(item['uuid']) for item in
list_method()]
return [item for item in items if filter(item)]
def node_list(self):
return self.baremetal_client.list_nodes()[1]['nodes']
def node_port_list(self, node_uuid):
return self.baremetal_client.list_node_ports(node_uuid)[1]['ports']
def node_update(self, uuid, patch):
return self.baremetal_client.update_node(uuid, **patch)
def node_show(self, uuid):
return self.baremetal_client.show_node(uuid)[1]
def node_delete(self, uuid):
return self.baremetal_client.delete_node(uuid)
def node_filter(self, filter=lambda node: True, nodes=None):
return self.item_filter(self.node_list, self.node_show,
filter=filter, items=nodes)
def node_set_power_state(self, uuid, state):
self.baremetal_client.set_node_power_state(uuid, state)
def node_set_provision_state(self, uuid, state):
self.baremetal_client.set_node_provision_state(self, uuid, state)
def hypervisor_stats(self):
return (self.os_admin.hypervisor_client.
show_hypervisor_statistics())
def server_show(self, uuid):
self.servers_client.show_server(uuid)
def rule_purge(self):
self.introspection_client.purge_rules()
def rule_import(self, rule_path):
with open(rule_path, 'r') as fp:
rules = json.load(fp)
self.introspection_client.create_rules(rules)
def rule_import_from_dict(self, rules):
self.introspection_client.create_rules(rules)
def introspection_status(self, uuid):
return self.introspection_client.get_status(uuid)[1]
def introspection_data(self, uuid):
return self.introspection_client.get_data(uuid)[1]
def introspection_start(self, uuid):
return self.introspection_client.start_introspection(uuid)
def introspection_abort(self, uuid):
return self.introspection_client.abort_introspection(uuid)
def baremetal_flavor(self):
flavor_id = CONF.compute.flavor_ref
flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
flavor['properties'] = self.flavors_client.list_flavor_extra_specs(
flavor_id)['extra_specs']
return flavor
def get_rule_path(self, rule_file):
base_path = os.path.split(
os.path.dirname(os.path.abspath(__file__)))[0]
base_path = os.path.split(base_path)[0]
return os.path.join(base_path, "inspector_tempest_plugin",
"rules", rule_file)
def boot_instance(self):
return super(InspectorScenarioTest, self).boot_instance()
def terminate_instance(self, instance):
return super(InspectorScenarioTest, self).terminate_instance(instance)
def wait_for_node(self, node_name):
def check_node():
try:
self.node_show(node_name)
except lib_exc.NotFound:
return False
return True
if not test_utils.call_until_true(
check_node,
duration=CONF.baremetal_introspection.discovery_timeout,
sleep_for=20):
msg = ("Timed out waiting for node %s " % node_name)
raise lib_exc.TimeoutException(msg)
inspected_node = self.node_show(self.node_info['name'])
self.wait_for_introspection_finished(inspected_node['uuid'])
# TODO(aarefiev): switch to call_until_true
def wait_for_introspection_finished(self, node_ids):
"""Waits for introspection of baremetal nodes to finish.
"""
if isinstance(node_ids, six.text_type):
node_ids = [node_ids]
start = int(time.time())
not_introspected = {node_id for node_id in node_ids}
while not_introspected:
time.sleep(CONF.baremetal_introspection.introspection_sleep)
for node_id in node_ids:
status = self.introspection_status(node_id)
if status['finished']:
if status['error']:
message = ('Node %(node_id)s introspection failed '
'with %(error)s.' %
{'node_id': node_id,
'error': status['error']})
raise exceptions.IntrospectionFailed(message)
not_introspected = not_introspected - {node_id}
if (int(time.time()) - start >=
CONF.baremetal_introspection.introspection_timeout):
message = ('Introspection timed out for nodes: %s' %
not_introspected)
raise exceptions.IntrospectionTimeout(message)
def wait_for_nova_aware_of_bvms(self):
start = int(time.time())
while True:
time.sleep(CONF.baremetal_introspection.hypervisor_update_sleep)
stats = self.hypervisor_stats()
expected_cpus = self.baremetal_flavor()['vcpus']
if int(stats['hypervisor_statistics']['vcpus']) >= expected_cpus:
break
timeout = CONF.baremetal_introspection.hypervisor_update_timeout
if (int(time.time()) - start >= timeout):
message = (
'Timeout while waiting for nova hypervisor-stats: '
'%(stats)s required time (%(timeout)s s).' %
{'stats': stats,
'timeout': timeout})
raise exceptions.HypervisorUpdateTimeout(message)
def node_cleanup(self, node_id):
if (self.node_show(node_id)['provision_state'] ==
BaremetalProvisionStates.AVAILABLE):
return
# in case when introspection failed we need set provision state
# to 'manage' to make it possible transit into 'provide' state
if self.node_show(node_id)['provision_state'] == 'inspect failed':
self.baremetal_client.set_node_provision_state(node_id, 'manage')
try:
self.baremetal_client.set_node_provision_state(node_id, 'provide')
except tempest.lib.exceptions.RestClientException:
# maybe node already cleaning or available
pass
self.wait_provisioning_state(
node_id, [BaremetalProvisionStates.AVAILABLE,
BaremetalProvisionStates.NOSTATE],
timeout=CONF.baremetal.unprovision_timeout,
interval=self.wait_provisioning_state_interval)
def introspect_node(self, node_id, remove_props=True):
if remove_props:
# in case there are properties remove those
patch = {('properties/%s' % key): None for key in
self.node_show(node_id)['properties']}
# reset any previous rule result
patch['extra/rule_success'] = None
self.node_update(node_id, patch)
self.baremetal_client.set_node_provision_state(node_id, 'manage')
self.baremetal_client.set_node_provision_state(node_id, 'inspect')
self.addCleanup(self.node_cleanup, node_id)

View File

@ -0,0 +1,176 @@
# 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.
from ironic_tempest_plugin.tests.scenario import baremetal_manager
from tempest.common import utils
from tempest.config import CONF
from tempest.lib import decorators
from ironic_inspector.test.inspector_tempest_plugin.tests import manager
class InspectorBasicTest(manager.InspectorScenarioTest):
def verify_node_introspection_data(self, node):
data = self.introspection_data(node['uuid'])
self.assertEqual(data['cpu_arch'],
self.flavor['properties']['cpu_arch'])
self.assertEqual(int(data['memory_mb']),
int(self.flavor['ram']))
self.assertEqual(int(data['cpus']), int(self.flavor['vcpus']))
def verify_node_flavor(self, node):
expected_cpus = self.flavor['vcpus']
expected_memory_mb = self.flavor['ram']
expected_cpu_arch = self.flavor['properties']['cpu_arch']
disk_size = self.flavor['disk']
ephemeral_size = self.flavor['OS-FLV-EXT-DATA:ephemeral']
expected_local_gb = disk_size + ephemeral_size
self.assertEqual(expected_cpus,
int(node['properties']['cpus']))
self.assertEqual(expected_memory_mb,
int(node['properties']['memory_mb']))
self.assertEqual(expected_local_gb,
int(node['properties']['local_gb']))
self.assertEqual(expected_cpu_arch,
node['properties']['cpu_arch'])
def verify_introspection_aborted(self, uuid):
status = self.introspection_status(uuid)
self.assertEqual('Canceled by operator', status['error'])
self.assertTrue(status['finished'])
self.wait_provisioning_state(
uuid, 'inspect failed',
timeout=CONF.baremetal.active_timeout,
interval=self.wait_provisioning_state_interval)
@decorators.idempotent_id('03bf7990-bee0-4dd7-bf74-b97ad7b52a4b')
@utils.services('compute', 'image', 'network')
def test_baremetal_introspection(self):
"""This smoke test case follows this set of operations:
* Fetches expected properties from baremetal flavor
* Removes all properties from nodes
* Sets nodes to manageable state
* Imports introspection rule basic_ops_rule.json
* Inspects nodes
* Verifies all properties are inspected
* Verifies introspection data
* Sets node to available state
* Creates a keypair
* Boots an instance using the keypair
* Deletes the instance
"""
# prepare introspection rule
rule_path = self.get_rule_path("basic_ops_rule.json")
self.rule_import(rule_path)
self.addCleanup(self.rule_purge)
for node_id in self.node_ids:
self.introspect_node(node_id)
# settle down introspection
self.wait_for_introspection_finished(self.node_ids)
for node_id in self.node_ids:
self.wait_provisioning_state(
node_id, 'manageable',
timeout=CONF.baremetal_introspection.ironic_sync_timeout,
interval=self.wait_provisioning_state_interval)
for node_id in self.node_ids:
node = self.node_show(node_id)
self.assertEqual('yes', node['extra']['rule_success'])
if CONF.service_available.swift:
self.verify_node_introspection_data(node)
self.verify_node_flavor(node)
for node_id in self.node_ids:
self.baremetal_client.set_node_provision_state(node_id, 'provide')
for node_id in self.node_ids:
self.wait_provisioning_state(
node_id, baremetal_manager.BaremetalProvisionStates.AVAILABLE,
timeout=CONF.baremetal.active_timeout,
interval=self.wait_provisioning_state_interval)
self.wait_for_nova_aware_of_bvms()
self.add_keypair()
ins, _node = self.boot_instance()
self.terminate_instance(ins)
@decorators.idempotent_id('70ca3070-184b-4b7d-8892-e977d2bc2870')
def test_introspection_abort(self):
"""This smoke test case follows this very basic set of operations:
* Start nodes introspection
* Wait until nodes power on
* Abort introspection
* Verifies nodes status and power state
"""
# start nodes introspection
for node_id in self.node_ids:
self.introspect_node(node_id, remove_props=False)
# wait for nodes power on
for node_id in self.node_ids:
self.wait_power_state(
node_id,
baremetal_manager.BaremetalPowerStates.POWER_ON)
# abort introspection
for node_id in self.node_ids:
self.introspection_abort(node_id)
# wait for nodes power off
for node_id in self.node_ids:
self.wait_power_state(
node_id,
baremetal_manager.BaremetalPowerStates.POWER_OFF)
# verify nodes status and provision state
for node_id in self.node_ids:
self.verify_introspection_aborted(node_id)
class InspectorSmokeTest(manager.InspectorScenarioTest):
@decorators.idempotent_id('a702d1f1-88e4-42ce-88ef-cba2d9e3312e')
@decorators.attr(type='smoke')
@utils.services('object_storage')
def test_baremetal_introspection(self):
"""This smoke test case follows this very basic set of operations:
* Fetches expected properties from baremetal flavor
* Removes all properties from one node
* Sets the node to manageable state
* Inspects the node
* Sets the node to available state
"""
# NOTE(dtantsur): we can't silently skip this test because it runs in
# grenade with several other tests, and we won't have any indication
# that it was not run.
assert self.node_ids, "No available nodes"
node_id = next(iter(self.node_ids))
self.introspect_node(node_id)
# settle down introspection
self.wait_for_introspection_finished([node_id])
self.wait_provisioning_state(
node_id, 'manageable',
timeout=CONF.baremetal_introspection.ironic_sync_timeout,
interval=self.wait_provisioning_state_interval)

View File

@ -0,0 +1,150 @@
# 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 six
from ironic_tempest_plugin.tests.scenario import baremetal_manager
from tempest import config
from tempest.lib import decorators
from tempest import test # noqa
from ironic_inspector.test.inspector_tempest_plugin.tests import manager
CONF = config.CONF
ProvisionStates = baremetal_manager.BaremetalProvisionStates
class InspectorDiscoveryTest(manager.InspectorScenarioTest):
@classmethod
def skip_checks(cls):
super(InspectorDiscoveryTest, cls).skip_checks()
if not CONF.baremetal_introspection.auto_discovery_feature:
msg = ("Please, provide a value for node_not_found_hook in "
"processing section of inspector.conf for enable "
"auto-discovery feature.")
raise cls.skipException(msg)
def setUp(self):
super(InspectorDiscoveryTest, self).setUp()
discovered_node = self._get_discovery_node()
self.node_info = self._get_node_info(discovered_node)
rule = self._generate_discovery_rule(self.node_info)
self.rule_import_from_dict(rule)
self.addCleanup(self.rule_purge)
def _get_node_info(self, node_uuid):
node = self.node_show(node_uuid)
ports = self.node_port_list(node_uuid)
node['port_macs'] = [port['address'] for port in ports]
return node
def _get_discovery_node(self):
nodes = self.node_list()
discovered_node = None
for node in nodes:
if (node['provision_state'] == ProvisionStates.AVAILABLE or
node['provision_state'] == ProvisionStates.ENROLL or
node['provision_state'] is ProvisionStates.NOSTATE):
discovered_node = node['uuid']
break
self.assertIsNotNone(discovered_node)
return discovered_node
def _generate_discovery_rule(self, node):
rule = dict()
rule["description"] = "Node %s discovery rule" % node['name']
rule["actions"] = [
{"action": "set-attribute", "path": "/name",
"value": "%s" % node['name']},
{"action": "set-attribute", "path": "/driver",
"value": "%s" % node['driver']},
]
for key, value in node['driver_info'].items():
rule["actions"].append(
{"action": "set-attribute", "path": "/driver_info/%s" % key,
"value": "%s" % value})
rule["conditions"] = [
{"op": "eq", "field": "data://auto_discovered", "value": True}
]
return rule
def verify_node_introspection_data(self, node):
data = self.introspection_data(node['uuid'])
self.assertEqual(data['cpu_arch'],
self.flavor['properties']['cpu_arch'])
self.assertEqual(int(data['memory_mb']),
int(self.flavor['ram']))
self.assertEqual(int(data['cpus']), int(self.flavor['vcpus']))
def verify_node_flavor(self, node):
expected_cpus = self.flavor['vcpus']
expected_memory_mb = self.flavor['ram']
expected_cpu_arch = self.flavor['properties']['cpu_arch']
disk_size = self.flavor['disk']
ephemeral_size = self.flavor['OS-FLV-EXT-DATA:ephemeral']
expected_local_gb = disk_size + ephemeral_size
self.assertEqual(expected_cpus,
int(node['properties']['cpus']))
self.assertEqual(expected_memory_mb,
int(node['properties']['memory_mb']))
self.assertEqual(expected_local_gb,
int(node['properties']['local_gb']))
self.assertEqual(expected_cpu_arch,
node['properties']['cpu_arch'])
def verify_node_driver_info(self, node_info, inspected_node):
for key in node_info['driver_info']:
self.assertEqual(six.text_type(node_info['driver_info'][key]),
inspected_node['driver_info'].get(key))
@decorators.idempotent_id('dd3abe5e-0d23-488d-bb4e-344cdeff7dcb')
def test_bearmetal_auto_discovery(self):
"""This test case follows this set of operations:
* Choose appropriate node, based on provision state;
* Get node info;
* Generate discovery rule;
* Delete discovered node from ironic;
* Start baremetal vm via virsh;
* Wating for node introspection;
* Verify introspected node.
"""
# NOTE(aarefiev): workaround for infra, 'tempest' user doesn't
# have virsh privileges, so lets power on the node via ironic
# and then delete it. Because of node is blacklisted in inspector
# we can't just power on it, therefor start introspection is used
# to whitelist discovered node first.
self.baremetal_client.set_node_provision_state(
self.node_info['uuid'], 'manage')
self.introspection_start(self.node_info['uuid'])
self.wait_power_state(
self.node_info['uuid'],
baremetal_manager.BaremetalPowerStates.POWER_ON)
self.node_delete(self.node_info['uuid'])
self.wait_for_node(self.node_info['name'])
inspected_node = self.node_show(self.node_info['name'])
self.verify_node_flavor(inspected_node)
if CONF.service_available.swift:
self.verify_node_introspection_data(inspected_node)
self.verify_node_driver_info(self.node_info, inspected_node)
self.assertEqual(ProvisionStates.ENROLL,
inspected_node['provision_state'])