Merge "Tempest: add auto-discovery test"

This commit is contained in:
Jenkins 2016-09-21 12:04:41 +00:00 committed by Gerrit Code Review
commit f33658a2b4
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)
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

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)