Tempest: add auto-discovery test
Add test, which delete pre-created baremetal vms, and discovers it via 'enroll' not_found_hook with default configuration. Note, test contains workaround for working on infra, as infra 'tempest' user doesn't have access to virsh, for running node and whitelisting firewall rules on existing node, inspector's inspect api is used. Change-Id: Ib0ec63295a496229b27552cd1bcf7e763c0c3e03
This commit is contained in:
parent
fa3fcd0233
commit
31906bfec7
@ -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