Merge "Adds Ironic test_baremetal_basic_ops scenario test"
This commit is contained in:
commit
dd80c02bcd
@ -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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
147
tempest/scenario/test_baremetal_basic_ops.py
Normal file
147
tempest/scenario/test_baremetal_basic_ops.py
Normal 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()
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user