Skip to content

Commit

Permalink
Adds Cinder client
Browse files Browse the repository at this point in the history
This change adds Cinder client in services/volume

* Keeps existing tests for Compute Extensions in tempest/tests/compute
* Copies existing volumes test to tests/volume and refactors them to test the
Cinder API, and adds a BaseVolumesTest class
* Renames the Nova Extensions' volumes_client to volumes_extensions_client
* Adds build_interval and build_timeout parameters in config for Cinder
specific tests
* Renames build_interval and build_timeout environment variables in the
Devstack template file to COMPUTE_BUILD_INTERVAL and COMPUTE_BUILD_TIMEOUT
* Adds volume specific environment variables VOLUME_BUILD_INTERVAL AND
VOLUME_BUILD_TIMEOUT to the Devstack template file.

Fixes LP Bug #1026190

Change-Id: I14d980ada1ddb29e8147f990aaf239fdcaae5eb6
  • Loading branch information
Rohit Karajgi committed Sep 3, 2012
1 parent fe2cdfc commit dd47d7e
Show file tree
Hide file tree
Showing 18 changed files with 706 additions and 39 deletions.
24 changes: 21 additions & 3 deletions etc/tempest.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ flavor_ref = 1
flavor_ref_alt = 2

# Number of seconds to wait while looping to check the status of an
# instance or volume that is building.
# instance that is building.
build_interval = 10

# Number of seconds to time out on waiting for an instance or volume
# Number of seconds to time out on waiting for an instance
# to build or reach an expected status
build_timeout = 600

Expand Down Expand Up @@ -125,7 +125,7 @@ bin_dir = /usr/local/bin
path_to_private_key = /home/user/.ssh/id_rsa

# Connection string to the database of Compute service
db_uri = mysql:///user:pass@localhost/nova
db_uri = mysql://user:pass@localhost/nova

[image]
# This section contains configuration options used when executing tests
Expand Down Expand Up @@ -167,7 +167,10 @@ tenant_name = admin
[network]
# This section contains configuration options used when executing tests
# against the OpenStack Network API.

# Version of the Quantum API
api_version = v1.1
# Catalog type of the Quantum Service
catalog_type = network

[identity-admin]
Expand All @@ -181,3 +184,18 @@ username = admin
password = pass
# The above administrative user's tenant name
tenant_name = admin

[volume]
# This section contains the configuration options used when executng tests
# against the OpenStack Block Storage API service

# The type of endpoint for a Cinder or Block Storage API service.
# Unless you have a custom Keystone service catalog implementation, you
# probably want to leave this value as "volume"
catalog_type = volume
# Number of seconds to wait while looping to check the status of a
# volume that is being made available
build_interval = 10
# Number of seconds to time out on waiting for a volume
# to be available or reach an expected status
build_timeout = 300
23 changes: 19 additions & 4 deletions etc/tempest.conf.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ flavor_ref = %FLAVOR_REF%
flavor_ref_alt = %FLAVOR_REF_ALT%

# Number of seconds to wait while looping to check the status of an
# instance or volume that is building.
build_interval = %BUILD_INTERVAL%
# instance that is building.
build_interval = %COMPUTE_BUILD_INTERVAL%

# Number of seconds to time out on waiting for an instance or volume
# Number of seconds to time out on waiting for an instance
# to build or reach an expected status
build_timeout = %BUILD_TIMEOUT%
build_timeout = %COMPUTE_BUILD_TIMEOUT%

# The type of endpoint for a Compute API service. Unless you have a
# custom Keystone service catalog implementation, you probably want to leave
Expand Down Expand Up @@ -154,3 +154,18 @@ username = %IDENTITY_ADMIN_USERNAME%
password = %IDENTITY_ADMIN_PASSWORD%
# The above administrative user's tenant name
tenant_name = %IDENTITY_ADMIN_TENANT_NAME%

[volume]
# This section contains the configuration options used when executing tests
# against the OpenStack Block Storage API service

# The type of endpoint for a Cinder or Block Storage API service.
# Unless you have a custom Keystone service catalog implementation, you
# probably want to leave this value as "volume"
catalog_type = %VOLUME_CATALOG_TYPE%
# Number of seconds to wait while looping to check the status of a
# volume that is being made available
build_interval = %VOLUME_BUILD_INTERVAL%
# Number of seconds to time out on waiting for a volume
# to be available or reach an expected status
build_timeout = %VOLUME_BUILD_TIMEOUT%
27 changes: 26 additions & 1 deletion tempest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def build_interval(self):

@property
def build_timeout(self):
"""Timeout in seconds to wait for an entity to build."""
"""Timeout in seconds to wait for an instance to build."""
return float(self.get("build_timeout", 300))

@property
Expand Down Expand Up @@ -355,6 +355,30 @@ def api_version(self):
return self.get("api_version", "v1.1")


class VolumeConfig(BaseConfig):
"""Provides configuration information for connecting to an OpenStack Block
Storage Service.
"""

SECTION_NAME = "volume"

@property
def build_interval(self):
"""Time in seconds between volume availability checks."""
return float(self.get("build_interval", 10))

@property
def build_timeout(self):
"""Timeout in seconds to wait for a volume to become available."""
return float(self.get("build_timeout", 300))

@property
def catalog_type(self):
"""Catalog type of the Volume Service"""
return self.get("catalog_type", 'volume')


# TODO(jaypipes): Move this to a common utils (not data_utils...)
def singleton(cls):
"""Simple wrapper for classes that should only have a single instance"""
instances = {}
Expand Down Expand Up @@ -402,6 +426,7 @@ def __init__(self):
self.identity_admin = IdentityAdminConfig(self._conf)
self.images = ImagesConfig(self._conf)
self.network = NetworkConfig(self._conf)
self.volume = VolumeConfig(self._conf)

def load_config(self, path):
"""Read configuration from given path and return a config object."""
Expand Down
5 changes: 4 additions & 1 deletion tempest/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from tempest import exceptions
# Tempest REST Fuzz testing client libs
from tempest.services.network.json import network_client
from tempest.services.volume.json import volumes_client
from tempest.services.nova.json import images_client
from tempest.services.nova.json import flavors_client
from tempest.services.nova.json import servers_client
Expand All @@ -33,7 +34,7 @@
from tempest.services.nova.json import security_groups_client
from tempest.services.nova.json import floating_ips_client
from tempest.services.nova.json import keypairs_client
from tempest.services.nova.json import volumes_client
from tempest.services.nova.json import volumes_extensions_client
from tempest.services.nova.json import console_output_client

NetworkClient = network_client.NetworkClient
Expand All @@ -45,6 +46,7 @@
SecurityGroupsClient = security_groups_client.SecurityGroupsClient
FloatingIPsClient = floating_ips_client.FloatingIPsClient
KeyPairsClient = keypairs_client.KeyPairsClientJSON
VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClient
VolumesClient = volumes_client.VolumesClient
ConsoleOutputsClient = console_output_client.ConsoleOutputsClient

Expand Down Expand Up @@ -199,6 +201,7 @@ def __init__(self, username=None, password=None, tenant_name=None):
self.keypairs_client = KeyPairsClient(*client_args)
self.security_groups_client = SecurityGroupsClient(*client_args)
self.floating_ips_client = FloatingIPsClient(*client_args)
self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
self.volumes_client = VolumesClient(*client_args)
self.console_outputs_client = ConsoleOutputsClient(*client_args)
self.network_client = NetworkClient(*client_args)
Expand Down
24 changes: 14 additions & 10 deletions tempest/openstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from tempest.services.image import service as image_service
from tempest.services.network.json.network_client import NetworkClient
from tempest.services.nova.json.flavors_client import FlavorsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClient
from tempest.services.nova.json.images_client import ImagesClient
from tempest.services.nova.json.limits_client import LimitsClientJSON
from tempest.services.nova.json.servers_client import ServersClientJSON
Expand All @@ -30,7 +31,8 @@
import SecurityGroupsClient
from tempest.services.nova.json.floating_ips_client import FloatingIPsClient
from tempest.services.nova.json.keypairs_client import KeyPairsClientJSON
from tempest.services.nova.json.volumes_client import VolumesClient
from tempest.services.nova.json.volumes_extensions_client \
import VolumesExtensionsClient
from tempest.services.nova.json.console_output_client \
import ConsoleOutputsClient
from tempest.services.nova.xml.flavors_client import FlavorsClientXML
Expand Down Expand Up @@ -82,23 +84,24 @@ def __init__(self, username=None, password=None, tenant_name=None,

# If no creds are provided, we fall back on the defaults
# in the config file for the Compute API.
username = username or self.config.compute.username
password = password or self.config.compute.password
tenant_name = tenant_name or self.config.compute.tenant_name
self.username = username or self.config.compute.username
self.password = password or self.config.compute.password
self.tenant_name = tenant_name or self.config.compute.tenant_name

if None in (username, password, tenant_name):
if None in (self.username, self.password, self.tenant_name):
msg = ("Missing required credentials. "
"username: %(username)s, password: %(password)s, "
"tenant_name: %(tenant_name)s") % locals()
raise exceptions.InvalidConfiguration(msg)

auth_url = self.config.identity.auth_url
self.auth_url = self.config.identity.auth_url

if self.config.identity.strategy == 'keystone':
client_args = (self.config, username, password, auth_url,
tenant_name)
client_args = (self.config, self.username, self.password,
self.auth_url, self.tenant_name)
else:
client_args = (self.config, username, password, auth_url)
client_args = (self.config, self.username, self.password,
self.auth_url)

try:
self.servers_client = SERVERS_CLIENTS[interface](*client_args)
Expand All @@ -112,9 +115,10 @@ def __init__(self, username=None, password=None, tenant_name=None,
self.extensions_client = ExtensionsClient(*client_args)
self.security_groups_client = SecurityGroupsClient(*client_args)
self.floating_ips_client = FloatingIPsClient(*client_args)
self.volumes_client = VolumesClient(*client_args)
self.console_outputs_client = ConsoleOutputsClient(*client_args)
self.network_client = NetworkClient(*client_args)
self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
self.volumes_client = VolumesClient(*client_args)


class AltManager(Manager):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import time


class VolumesClient(RestClient):
class VolumesExtensionsClient(RestClient):

def __init__(self, config, username, password, auth_url, tenant_name=None):
super(VolumesClient, self).__init__(config, username, password,
auth_url, tenant_name)
super(VolumesExtensionsClient, self).__init__(config, username,
password, auth_url,
tenant_name)
self.service = self.config.compute.catalog_type
self.build_interval = self.config.compute.build_interval
self.build_timeout = self.config.compute.build_timeout
self.build_interval = self.config.volume.build_interval
self.build_timeout = self.config.volume.build_timeout

def list_volumes(self, params=None):
"""List all the volumes created"""
Expand Down
Empty file.
Empty file.
120 changes: 120 additions & 0 deletions tempest/services/volume/json/volumes_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 OpenStack, LLC
# All Rights Reserved.
#
# 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 json
import time

from tempest.common.rest_client import RestClient
from tempest import exceptions


class VolumesClient(RestClient):
"""
Client class to send CRUD Volume API requests to a Cinder endpoint
"""

def __init__(self, config, username, password, auth_url, tenant_name=None):
super(VolumesClient, self).__init__(config, username, password,
auth_url, tenant_name)

self.service = self.config.volume.catalog_type
self.build_interval = self.config.volume.build_interval
self.build_timeout = self.config.volume.build_timeout

def list_volumes(self, params=None):
"""List all the volumes created"""
url = 'volumes'
if params != None:
param_list = []
for param, value in params.iteritems():
param_list.append("%s=%s&" % (param, value))

url += '?' + ' '.join(param_list)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['volumes']

def list_volumes_with_detail(self, params=None):
"""List the details of all volumes"""
url = 'volumes/detail'
if params != None:
param_list = []
for param, value in params.iteritems():
param_list.append("%s=%s&" % (param, value))

url = '?' + ' '.join(param_list)

resp, body = self.get(url)
body = json.loads(body)
return resp, body['volumes']

def get_volume(self, volume_id):
"""Returns the details of a single volume"""
url = "volumes/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['volume']

def create_volume(self, size, **kwargs):
"""
Creates a new Volume.
size(Required): Size of volume in GB.
Following optional keyword arguments are accepted:
display_name: Optional Volume Name.
metadata: A dictionary of values to be used as metadata.
"""
post_body = {
'size': size,
'display_name': kwargs.get('display_name'),
'metadata': kwargs.get('metadata'),
}

post_body = json.dumps({'volume': post_body})
resp, body = self.post('volumes', post_body, self.headers)
body = json.loads(body)
return resp, body['volume']

def delete_volume(self, volume_id):
"""Deletes the Specified Volume"""
return self.delete("volumes/%s" % str(volume_id))

def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status"""
resp, body = self.get_volume(volume_id)
volume_name = body['display_name']
volume_status = body['status']
start = int(time.time())

while volume_status != status:
time.sleep(self.build_interval)
resp, body = self.get_volume(volume_id)
volume_status = body['status']
if volume_status == 'error':
raise exceptions.VolumeBuildErrorException(volume_id=volume_id)

if int(time.time()) - start >= self.build_timeout:
message = 'Volume %s failed to reach %s status within '\
'the required time (%s s).' % (volume_name, status,
self.build_timeout)
raise exceptions.TimeoutException(message)

def is_resource_deleted(self, id):
try:
self.get_volume(id)
except exceptions.NotFound:
return True
return False
2 changes: 2 additions & 0 deletions tempest/tests/compute/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import logging
import time
import nose

import unittest2 as unittest

Expand Down Expand Up @@ -60,6 +61,7 @@ def setUpClass(cls):
cls.security_groups_client = os.security_groups_client
cls.console_outputs_client = os.console_outputs_client
cls.limits_client = os.limits_client
cls.volumes_extensions_client = os.volumes_extensions_client
cls.volumes_client = os.volumes_client
cls.build_interval = cls.config.compute.build_interval
cls.build_timeout = cls.config.compute.build_timeout
Expand Down

0 comments on commit dd47d7e

Please sign in to comment.