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:
Anton Arefiev 2016-09-13 12:17:29 +03:00
parent fa3fcd0233
commit 31906bfec7
5 changed files with 222 additions and 11 deletions

View File

@ -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

View File

@ -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"),
]

View File

@ -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)
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

View File

@ -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}

View File

@ -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)