Merge "Tempest: add auto-discovery test"
This commit is contained in:
commit
f33658a2b4
@ -327,6 +327,13 @@ elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
||||
start_inspector_dhcp
|
||||
fi
|
||||
start_inspector
|
||||
elif [[ "$1" == "stack" && "$2" == "test-config" ]]; then
|
||||
if is_service_enabled tempest; then
|
||||
echo_summary "Configuring Tempest for Ironic Inspector"
|
||||
if [ -n "$IRONIC_INSPECTOR_NODE_NOT_FOUND_HOOK" ]; then
|
||||
iniset $TEMPEST_CONFIG baremetal_introspection auto_discovery_feature True
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$1" == "unstack" ]]; then
|
||||
|
@ -61,4 +61,13 @@ BaremetalIntrospectionGroup = [
|
||||
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"),
|
||||
]
|
||||
|
@ -10,8 +10,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
from ironic_tempest_plugin.services.baremetal import base
|
||||
from tempest import clients
|
||||
from tempest.common import credentials_factory as common_creds
|
||||
@ -47,13 +45,10 @@ class BaremetalIntrospectionClient(base.BaremetalClient):
|
||||
return self._delete_request('rules', uuid=None)
|
||||
|
||||
@base.handle_errors
|
||||
def import_rule(self, rule_path):
|
||||
"""Import introspection rules from a json file."""
|
||||
with open(rule_path, 'r') as fp:
|
||||
rules = json.load(fp)
|
||||
if not isinstance(rules, list):
|
||||
rules = [rules]
|
||||
|
||||
def create_rules(self, rules):
|
||||
"""Create introspection rules."""
|
||||
if not isinstance(rules, list):
|
||||
rules = [rules]
|
||||
for rule in rules:
|
||||
self._create_request('rules', rule)
|
||||
|
||||
@ -68,3 +63,13 @@ class BaremetalIntrospectionClient(base.BaremetalClient):
|
||||
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
|
||||
|
@ -10,13 +10,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import six
|
||||
import time
|
||||
|
||||
import tempest
|
||||
from tempest import config
|
||||
from tempest.lib.common.api_version_utils import LATEST_MICROVERSION
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from tempest import test
|
||||
|
||||
from ironic_inspector.test.inspector_tempest_plugin import exceptions
|
||||
from ironic_inspector.test.inspector_tempest_plugin.services import \
|
||||
@ -69,16 +72,28 @@ class InspectorScenarioTest(BaremetalScenarioTest):
|
||||
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.admin_manager.hypervisor_client.
|
||||
show_hypervisor_statistics())
|
||||
@ -90,7 +105,12 @@ class InspectorScenarioTest(BaremetalScenarioTest):
|
||||
self.introspection_client.purge_rules()
|
||||
|
||||
def rule_import(self, rule_path):
|
||||
self.introspection_client.import_rule(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]
|
||||
@ -98,6 +118,9 @@ class InspectorScenarioTest(BaremetalScenarioTest):
|
||||
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 baremetal_flavor(self):
|
||||
flavor_id = CONF.compute.flavor_ref
|
||||
flavor = self.flavors_client.show_flavor(flavor_id)['flavor']
|
||||
@ -118,11 +141,31 @@ class InspectorScenarioTest(BaremetalScenarioTest):
|
||||
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.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}
|
||||
|
||||
|
@ -0,0 +1,147 @@
|
||||
# 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 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))
|
||||
|
||||
@test.idempotent_id('dd3abe5e-0d23-488d-bb4e-344cdeff7dcb')
|
||||
@test.services('baremetal', 'compute')
|
||||
def test_berametal_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)
|
||||
self.verify_node_introspection_data(inspected_node)
|
||||
self.verify_node_driver_info(self.node_info, inspected_node)
|
Loading…
Reference in New Issue
Block a user