Merge "Adds Ironic test_baremetal_basic_ops scenario test"

This commit is contained in:
Jenkins 2014-04-09 00:08:22 +00:00 committed by Gerrit Code Review
commit dd80c02bcd
7 changed files with 313 additions and 6 deletions

View File

@ -101,14 +101,32 @@
# Options defined in tempest.config
#
# Catalog type of the baremetal provisioning service. (string
# Catalog type of the baremetal provisioning service (string
# value)
#catalog_type=baremetal
# Whether the Ironic nova-compute driver is enabled (boolean
# value)
#driver_enabled=false
# The endpoint type to use for the baremetal provisioning
# service. (string value)
# service (string value)
#endpoint_type=publicURL
# Timeout for Ironic node to completely provision (integer
# value)
#active_timeout=300
# Timeout for association of Nova instance and Ironic node
# (integer value)
#association_timeout=10
# Timeout for Ironic power transitions. (integer value)
#power_timeout=20
# Timeout for unprovisioning an Ironic node. (integer value)
#unprovision_timeout=20
[boto]

View File

@ -13,6 +13,7 @@ python-novaclient>=2.17.0
python-neutronclient>=2.3.4,<3
python-cinderclient>=1.0.6
python-heatclient>=0.2.3
python-ironicclient
python-saharaclient>=0.6.0
python-swiftclient>=1.6
testresources>=0.2.4

View File

@ -17,6 +17,7 @@
import cinderclient.client
import glanceclient
import heatclient.client
import ironicclient.client
import keystoneclient.exceptions
import keystoneclient.v2_0.client
import neutronclient.v2_0.client
@ -463,6 +464,7 @@ class OfficialClientManager(manager.Manager):
NOVACLIENT_VERSION = '2'
CINDERCLIENT_VERSION = '1'
HEATCLIENT_VERSION = '1'
IRONICCLIENT_VERSION = '1'
def __init__(self, username, password, tenant_name):
# FIXME(andreaf) Auth provider for client_type 'official' is
@ -472,6 +474,7 @@ class OfficialClientManager(manager.Manager):
# super cares for credentials validation
super(OfficialClientManager, self).__init__(
username=username, password=password, tenant_name=tenant_name)
self.baremetal_client = self._get_baremetal_client()
self.compute_client = self._get_compute_client(username,
password,
tenant_name)
@ -492,6 +495,22 @@ class OfficialClientManager(manager.Manager):
password,
tenant_name)
def _get_roles(self):
keystone_admin = self._get_identity_client(
CONF.identity.admin_username,
CONF.identity.admin_password,
CONF.identity.admin_tenant_name)
username = self.credentials['username']
tenant_name = self.credentials['tenant_name']
user_id = keystone_admin.users.find(name=username).id
tenant_id = keystone_admin.tenants.find(name=tenant_name).id
roles = keystone_admin.roles.roles_for_user(
user=user_id, tenant=tenant_id)
return [r.name for r in roles]
def _get_compute_client(self, username, password, tenant_name):
# Novaclient will not execute operations for anyone but the
# identified user, so a new client needs to be created for
@ -613,6 +632,34 @@ class OfficialClientManager(manager.Manager):
auth_url=auth_url,
insecure=dscv)
def _get_baremetal_client(self):
# ironic client is currently intended to by used by admin users
roles = self._get_roles()
if CONF.identity.admin_role not in roles:
return None
auth_url = CONF.identity.uri
api_version = self.IRONICCLIENT_VERSION
insecure = CONF.identity.disable_ssl_certificate_validation
service_type = CONF.baremetal.catalog_type
endpoint_type = CONF.baremetal.endpoint_type
creds = {
'os_username': self.credentials['username'],
'os_password': self.credentials['password'],
'os_tenant_name': self.credentials['tenant_name']
}
try:
return ironicclient.client.get_client(
api_version=api_version,
os_auth_url=auth_url,
insecure=insecure,
os_service_type=service_type,
os_endpoint_type=endpoint_type,
**creds)
except keystoneclient.exceptions.EndpointNotFound:
return None
def _get_network_client(self):
# The intended configuration is for the network client to have
# admin privileges and indicate for whom resources are being

View File

@ -844,13 +844,29 @@ baremetal_group = cfg.OptGroup(name='baremetal',
BaremetalGroup = [
cfg.StrOpt('catalog_type',
default='baremetal',
help="Catalog type of the baremetal provisioning service."),
help="Catalog type of the baremetal provisioning service"),
cfg.BoolOpt('driver_enabled',
default=False,
help="Whether the Ironic nova-compute driver is enabled"),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the baremetal provisioning "
"service."),
"service"),
cfg.IntOpt('active_timeout',
default=300,
help="Timeout for Ironic node to completely provision"),
cfg.IntOpt('association_timeout',
default=10,
help="Timeout for association of Nova instance and Ironic "
"node"),
cfg.IntOpt('power_timeout',
default=20,
help="Timeout for Ironic power transitions."),
cfg.IntOpt('unprovision_timeout',
default=20,
help="Timeout for unprovisioning an Ironic node.")
]
cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options")

View File

@ -19,6 +19,7 @@ import os
import six
import subprocess
from ironicclient import exc as ironic_exceptions
import netaddr
from neutronclient.common import exceptions as exc
from novaclient import exceptions as nova_exceptions
@ -71,6 +72,7 @@ class OfficialClientTest(tempest.test.BaseTestCase):
username, password, tenant_name)
cls.compute_client = cls.manager.compute_client
cls.image_client = cls.manager.image_client
cls.baremetal_client = cls.manager.baremetal_client
cls.identity_client = cls.manager.identity_client
cls.network_client = cls.manager.network_client
cls.volume_client = cls.manager.volume_client
@ -283,7 +285,7 @@ class OfficialClientTest(tempest.test.BaseTestCase):
return rules
def create_server(self, client=None, name=None, image=None, flavor=None,
create_kwargs={}):
wait=True, create_kwargs={}):
if client is None:
client = self.compute_client
if name is None:
@ -318,6 +320,7 @@ class OfficialClientTest(tempest.test.BaseTestCase):
server = client.servers.create(name, image, flavor, **create_kwargs)
self.assertEqual(server.name, name)
self.set_resource(name, server)
if wait:
self.status_timeout(client.servers, server.id, 'ACTIVE')
# The instance retrieved on creation is missing network
# details, necessitating retrieval after it becomes active to
@ -439,6 +442,80 @@ class OfficialClientTest(tempest.test.BaseTestCase):
LOG.debug("image:%s" % self.image)
class BaremetalScenarioTest(OfficialClientTest):
@classmethod
def setUpClass(cls):
super(BaremetalScenarioTest, cls).setUpClass()
if (not CONF.service_available.ironic or
not CONF.baremetal.driver_enabled):
msg = 'Ironic not available or Ironic compute driver not enabled'
raise cls.skipException(msg)
# use an admin client manager for baremetal client
username, password, tenant = cls.admin_credentials()
manager = clients.OfficialClientManager(username, password, tenant)
cls.baremetal_client = manager.baremetal_client
# allow any issues obtaining the node list to raise early
cls.baremetal_client.node.list()
def _node_state_timeout(self, node_id, state_attr,
target_states, timeout=10, interval=1):
if not isinstance(target_states, list):
target_states = [target_states]
def check_state():
node = self.get_node(node_id=node_id)
if getattr(node, state_attr) in target_states:
return True
return False
if not tempest.test.call_until_true(
check_state, timeout, interval):
msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
(node_id, state_attr, target_states))
raise exceptions.TimeoutException(msg)
def wait_provisioning_state(self, node_id, state, timeout):
self._node_state_timeout(
node_id=node_id, state_attr='provision_state',
target_states=state, timeout=timeout)
def wait_power_state(self, node_id, state):
self._node_state_timeout(
node_id=node_id, state_attr='power_state',
target_states=state, timeout=CONF.baremetal.power_timeout)
def wait_node(self, instance_id):
"""Waits for a node to be associated with instance_id."""
def _get_node():
node = None
try:
node = self.get_node(instance_id=instance_id)
except ironic_exceptions.HTTPNotFound:
pass
return node is not None
if not tempest.test.call_until_true(
_get_node, CONF.baremetal.association_timeout, 1):
msg = ('Timed out waiting to get Ironic node by instance id %s'
% instance_id)
raise exceptions.TimeoutException(msg)
def get_node(self, node_id=None, instance_id=None):
if node_id:
return self.baremetal_client.node.get(node_id)
elif instance_id:
return self.baremetal_client.node.get_by_instance_uuid(instance_id)
def get_ports(self, node_id):
ports = []
for port in self.baremetal_client.node.list_ports(node_id):
ports.append(self.baremetal_client.port.get(port.uuid))
return ports
class NetworkScenarioTest(OfficialClientTest):
"""
Base class for network scenario tests

View File

@ -0,0 +1,147 @@
#
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# 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 import config
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest import test
CONF = config.CONF
LOG = logging.getLogger(__name__)
# power/provision states as of icehouse
class PowerStates(object):
"""Possible power states of an Ironic node."""
POWER_ON = 'power on'
POWER_OFF = 'power off'
REBOOT = 'rebooting'
SUSPEND = 'suspended'
class ProvisionStates(object):
"""Possible provision states of an Ironic node."""
NOSTATE = None
INIT = 'initializing'
ACTIVE = 'active'
BUILDING = 'building'
DEPLOYWAIT = 'wait call-back'
DEPLOYING = 'deploying'
DEPLOYFAIL = 'deploy failed'
DEPLOYDONE = 'deploy complete'
DELETING = 'deleting'
DELETED = 'deleted'
ERROR = 'error'
class BaremetalBasicOptsPXESSH(manager.BaremetalScenarioTest):
"""
This smoke test tests the pxe_ssh Ironic driver. It follows this basic
set of operations:
* Creates a keypair
* Boots an instance using the keypair
* Monitors the associated Ironic node for power and
expected state transitions
* Validates Ironic node's driver_info has been properly
updated
* Validates Ironic node's port data has been properly updated
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
* Verifies SSH connectivity using created keypair via floating IP
* Deletes instance
* Monitors the associated Ironic node for power and
expected state transitions
"""
def add_keypair(self):
self.keypair = self.create_keypair()
def add_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
self.instance.add_floating_ip(floating_ip)
return floating_ip.ip
def verify_connectivity(self, ip=None):
if ip:
dest = self.get_remote_client(ip)
else:
dest = self.get_remote_client(self.instance)
dest.validate_authentication()
def validate_driver_info(self):
f_id = self.instance.flavor['id']
flavor_extra = self.compute_client.flavors.get(f_id).get_keys()
driver_info = self.node.driver_info
self.assertEqual(driver_info['pxe_deploy_kernel'],
flavor_extra['baremetal:deploy_kernel_id'])
self.assertEqual(driver_info['pxe_deploy_ramdisk'],
flavor_extra['baremetal:deploy_ramdisk_id'])
self.assertEqual(driver_info['pxe_image_source'],
self.instance.image['id'])
def validate_ports(self):
for port in self.get_ports(self.node.uuid):
n_port_id = port.extra['vif_port_id']
n_port = self.network_client.show_port(n_port_id)['port']
self.assertEqual(n_port['device_id'], self.instance.id)
self.assertEqual(n_port['mac_address'], port.address)
def boot_instance(self):
create_kwargs = {
'key_name': self.keypair.id
}
self.instance = self.create_server(
wait=False, create_kwargs=create_kwargs)
self.set_resource('instance', self.instance)
self.wait_node(self.instance.id)
self.node = self.get_node(instance_id=self.instance.id)
self.wait_power_state(self.node.uuid, PowerStates.POWER_ON)
self.wait_provisioning_state(
self.node.uuid,
[ProvisionStates.DEPLOYWAIT, ProvisionStates.ACTIVE],
timeout=15)
self.wait_provisioning_state(self.node.uuid, ProvisionStates.ACTIVE,
timeout=CONF.baremetal.active_timeout)
self.status_timeout(
self.compute_client.servers, self.instance.id, 'ACTIVE')
self.node = self.get_node(instance_id=self.instance.id)
self.instance = self.compute_client.servers.get(self.instance.id)
def terminate_instance(self):
self.instance.delete()
self.remove_resource('instance')
self.wait_power_state(self.node.uuid, PowerStates.POWER_OFF)
self.wait_provisioning_state(
self.node.uuid,
ProvisionStates.NOSTATE,
timeout=CONF.baremetal.unprovision_timeout)
@test.services('baremetal', 'compute', 'image', 'network')
def test_baremetal_server_ops(self):
self.add_keypair()
self.boot_instance()
self.validate_driver_info()
self.validate_ports()
self.verify_connectivity()
floating_ip = self.add_floating_ip()
self.verify_connectivity(ip=floating_ip)
self.terminate_instance()

View File

@ -94,6 +94,7 @@ def services(*args, **kwargs):
service_list = {
'compute': CONF.service_available.nova,
'image': CONF.service_available.glance,
'baremetal': CONF.service_available.ironic,
'volume': CONF.service_available.cinder,
'orchestration': CONF.service_available.heat,
# NOTE(mtreinish) nova-network will provide networking functionality