Updates to novacli and cindercli

* Removes redundant cindercli behaviors
 * Adds os_auth_system as parameter to cindercli client init.
 * Renames client methods to match CLI names ('create_volume' in
   the cinder client is now just 'create', etc.)
 * Adds the cinder composite.
 * Adds os_region_name and os_auth_system as configurable options
   to cinder config
 * Adds os_region_name to nova config.

Change-Id: I999c39fc9d99b689bf4c8ff967fd8b05c6ee79b3
This commit is contained in:
Jose Idar 2014-09-12 15:47:00 -05:00
parent d6219722b9
commit 90c4f05b2a
7 changed files with 89 additions and 214 deletions

View File

@ -1,198 +0,0 @@
from time import time, sleep
from cafe.engine.behaviors import behavior
from cloudcafe.common.tools.datagen import random_string
from cloudcafe.openstackcli.common.behaviors import (
OpenstackCLI_BaseBehavior, OpenstackCLI_BehaviorError)
from cloudcafe.openstackcli.cindercli.client import CinderCLI
from cloudcafe.openstackcli.cindercli.config import CinderCLI_Config
from cloudcafe.blockstorage.volumes_api.v1.config import VolumesAPIConfig
from cloudcafe.blockstorage.volumes_api.v1.models import statuses
class CinderCLIBehaviorError(OpenstackCLI_BehaviorError):
pass
class CinderCLI_Behaviors(OpenstackCLI_BaseBehavior):
_default_error = CinderCLIBehaviorError
def __init__(
self, cinder_cli_client, cinder_cli_config=None,
volumes_api_client=None, volumes_api_config=None):
super(CinderCLI_Behaviors, self).__init__()
self.client = cinder_cli_client
self.config = cinder_cli_config or CinderCLI_Config()
self.api_client = volumes_api_client
self.api_config = volumes_api_config or VolumesAPIConfig()
@behavior(CinderCLI)
def create_available_volume(self, name=None, type_=None, size=None):
name = random_string(prefix="Volume_", size=10)
size = size or self.api_config.min_volume_size
type_ = type_ or self.api_config.default_volume_type
resp = self.client.create_volume(
size=size, volume_type=type_, display_name=name)
self.raise_on_error(resp, "Cinder CLI Volume Create call failed.")
volume = resp.entity
self.wait_for_volume_status(volume.id_, statuses.Volume.AVAILABLE)
return volume
@behavior(CinderCLI)
def get_volume_status(self, volume_id):
resp = self.client.show_volume(volume_id=volume_id)
self.raise_on_error(resp, "Cinder CLI Show Volume call failed.")
return resp.entity.status
@behavior(CinderCLI)
def wait_for_volume_status(
self, volume_id, expected_status, timeout=None, poll_rate=None):
""" Waits for a specific status and returns None when that status is
observed.
Note: Unreliable for transient statuses like 'deleting'.
"""
poll_rate = int(
poll_rate or self.api_config.volume_status_poll_frequency)
timeout = int(timeout or self.api_config.volume_create_timeout)
end_time = time() + int(timeout)
while time() < end_time:
status = self.get_volume_status(volume_id)
if status == expected_status:
self._log.info(
"Expected Volume status '{0}' observed as expected".format(
expected_status))
break
sleep(poll_rate)
else:
msg = (
"wait_for_volume_status() ran for {0} seconds and did not "
"observe the volume attain the {1} status.".format(
timeout, expected_status))
self._log.info(msg)
raise self._default_error(msg)
@behavior(CinderCLI)
def list_volumes(self):
resp = self.client.list_volumes
self.raise_on_error(resp, "Unable to list volumes via the cinder cli")
return resp.entity
@behavior(CinderCLI)
def delete_volume(self, volume_id):
resp = self.client.delete_volume(volume_id)
self.raise_if(
self.is_process_error(resp),
"Cinder CLI Volume Delete did not execute successfully")
@behavior(CinderCLI)
def delete_volume_confirmed(self, volume_id, timeout=60, poll_rate=5):
self.delete_volume(volume_id)
return self.wait_for_volume_delete(volume_id, timeout, poll_rate)
@behavior(CinderCLI)
def wait_for_volume_delete(
self, volume_id, timeout=60, poll_rate=5):
expected_err_msg = (
"ERROR: No volume with a name or ID of '{0}' exists.".format(
volume_id))
end = time() + timeout
while time() <= end:
resp = self.client.show_volume(volume_id)
if self.is_cli_error(resp) or self.is_process_error(resp):
if resp.standard_error[-1] == expected_err_msg:
return True
sleep(poll_rate)
else:
return False
# snapshots
@behavior(CinderCLI)
def list_snapshots(self):
resp = self.client.list_snapshots
self.raise_on_error(resp, "Unable to list snapshots via the cinder cli")
return resp.entity
@behavior(CinderCLI)
def delete_snapshot(self, snapshot_id):
resp = self.client.delete_snapshot()
self.raise_if(
self.is_process_error(resp),
"Cinder CLI Snapshot Delete did not execute successfully")
@behavior(CinderCLI)
def get_snapshot_status(self, snapshot_id):
resp = self.client.show_snapshot(snapshot_id=snapshot_id)
self.raise_on_error(resp, "Cinder CLI snapshot-show call failed.")
return resp.entity.status
@behavior(CinderCLI)
def list_volume_snapshot_ids(self, volume_id):
snapshots = self.list_snapshots()
return [snap.id_ for snap in snapshots if snap.volume_id == volume_id]
@behavior(CinderCLI)
def wait_for_snapshot_status(
self, snapshot_id, expected_status, timeout, poll_rate=None):
""" Waits for a specific status and returns None when that status is
observed.
Note: Unreliable for transient statuses like 'deleting'.
"""
poll_rate = int(
poll_rate or self.api_config.snapshot_status_poll_frequency)
end_time = time() + int(timeout)
while time() < end_time:
status = self.get_snapshot_status(snapshot_id)
if status == expected_status:
self._log.info(
"Expected snapshot status '{0}' observed as "
"expected".format(expected_status))
break
sleep(poll_rate)
else:
msg = (
"wait_for_snapshot_status() ran for {0} seconds and did not "
"observe the volume attain the {1} status.".format(
timeout, expected_status))
self._log.info(msg)
raise self._default_error(msg)
@behavior(CinderCLI)
def wait_for_snapshot_delete(self, snapshot_id, timeout=60, poll_rate=5):
expected_err_msg = (
"ERROR: No snapshot with a name or ID of '{0}' exists.".format(
snapshot_id))
end = time() + timeout
while time() <= end:
resp = self.client.show_snapshot(snapshot_id)
if self.is_cli_error(resp) or self.is_process_error(resp):
if resp.standard_error[-1] == expected_err_msg:
return True
sleep(poll_rate)
else:
return False
# volume types
@behavior(CinderCLI)
def list_volume_types(self):
resp = self.client.list_volume_types()
self.raise_on_error(resp, "Unable to list snapshots via the cinder cli")
return resp.entity

View File

@ -7,27 +7,30 @@ class CinderCLI(BaseOpenstackPythonCLI_Client):
_KWMAP = {
'volume_service_name': 'volume-service-name',
'os_volume_api_version': 'os-volume-api-version'}
'os_volume_api_version': 'os-volume-api-version',
'os_auth_system': 'os-auth-system'}
# Make sure to include all openstack common cli paramaters in addition to
# the cinder specific ones
_KWMAP.update(BaseOpenstackPythonCLI_Client._KWMAP)
#The client command the must precede any call to the cli
# The client command the must precede any call to the cli
_CMD = 'cinder'
def __init__(
self, volume_service_name=None, os_volume_api_version=None,
**kwargs):
os_auth_system=None, **kwargs):
super(CinderCLI, self).__init__(**kwargs)
self.volume_service_name = volume_service_name
self.os_volume_api_version = os_volume_api_version
self.os_auth_system = os_auth_system
# Volumes
def create_volume(
def create(
self, size, snapshot_id=None, source_volid=None, image_id=None,
display_name=None, display_description=None, volume_type=None,
availability_zone=None, metadata=None):
"""Create a new volume via the cinder command line client"""
metadata = self._dict_to_string(metadata)
@ -44,17 +47,19 @@ class CinderCLI(BaseOpenstackPythonCLI_Client):
'metadata': 'metadata'}
return self._process_command()
def show_volume(self, volume_id):
def show(self, volume_id):
"""Get details for a volume via the cinder command line client"""
_cmd = 'show'
_response_type = CinderResponses.VolumeResponse
return self._process_command()
def delete_volume(self, volume_name_or_id):
def delete(self, volume_name_or_id):
"""delete a volume via the cinder command line client"""
_cmd = 'delete'
return self._process_command()
def list_volumes(self, display_name=None, status=None, all_tenants=False):
def list(self, display_name=None, status=None, all_tenants=False):
"""List all volumes via the cinder command line client"""
all_tenants = 1 if all_tenants is True else 0
_response_type = CinderResponses.VolumeListResponse
_cmd = 'list'
@ -65,10 +70,10 @@ class CinderCLI(BaseOpenstackPythonCLI_Client):
return self._process_command()
# Snapshots
def create_snapshot(
def snapshot_create(
self, volume_id, force=True, display_name=None,
display_description=None):
"""Create a snapshot of a volume via the cinder command line client"""
force = 'True' if force else 'False'
_kwmap = {
'force': 'force',
@ -78,18 +83,26 @@ class CinderCLI(BaseOpenstackPythonCLI_Client):
_response_type = CinderResponses.SnapshotResponse
return self._process_command()
def list_snapshots(self):
def snapshot_list(self):
"""List all snapshots via the cinder command line client"""
_cmd = 'snapshot-list'
_response_type = CinderResponses.SnapshotListResponse
return self._process_command()
def show_snapshot(self, snapshot_id):
def snapshot_show(self, snapshot_id):
"""Get details for a snapshot via the cinder command line client"""
_cmd = 'snapshot-show'
_response_type = CinderResponses.SnapshotResponse
return self._process_command()
def snapshot_delete(self, snapshot_id):
"""Delete a snapshot via the cinder command line client"""
_cmd = 'snapshot-delete'
return self._process_command()
# Volume Types
def list_volume_types(self):
def type_list(self):
"""Get a list of all volume types via the cinder command line client"""
_cmd = 'type-list'
_response_type = CinderResponses.VolumeTypeListResponse
return self._process_command()

View File

@ -0,0 +1,42 @@
"""
Copyright 2014 Rackspace
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 cloudcafe.openstackcli.cindercli.client import CinderCLI
from cloudcafe.openstackcli.common.config import OpenstackCLI_CommonConfig
from cloudcafe.openstackcli.cindercli.config import CinderCLI_Config
class CinderCLI_Composite(object):
def __init__(self):
self.openstack_config = OpenstackCLI_CommonConfig()
self.config = CinderCLI_Config()
self._cli_kwargs = {
'volume_service_name': self.config.volume_service_name,
'os_volume_api_version': self.config.os_volume_api_version,
'os_username': self.openstack_config.os_username,
'os_password': self.openstack_config.os_password,
'os_tenant_name': self.openstack_config.os_tenant_name,
'os_auth_url': self.openstack_config.os_auth_url,
'os_region_name': self.config.os_region_name or
self.openstack_config.os_region_name,
'os_cacert': self.openstack_config.os_cacert,
'os_auth_system': self.config.os_auth_system,
'retries': self.openstack_config.retries,
'debug': self.openstack_config.debug}
self.client = CinderCLI(**self._cli_kwargs)
self.client.set_environment_variables(
self.config.environment_variable_dictionary)

View File

@ -42,6 +42,16 @@ class CinderCLI_Config(ConfigSectionInterface):
def os_volume_api_version(self):
return self.get('os_volume_api_version')
@property
def os_region_name(self):
"""cinder cli endpoint region"""
return self.get('os_region_name')
@property
def os_auth_system(self):
"""Selects which auth system to use"""
return self.get('os_auth_system')
@property
def environment_variable_dictionary(self):
"""Expects a python dictionary as a string. Returns an empty dict

View File

@ -185,6 +185,7 @@ class NovaCLI(BaseOpenstackPythonCLI_Client):
"""
_cmd = 'volume-attach'
device = device or 'auto'
_response_type = responses.VolumeAttachResponse
return self._process_command()

View File

@ -1,5 +1,5 @@
from cloudcafe.openstackcli.novacli.client import NovaCLI
from cloudcafe.openstackcli.common.config import OpenstackCLI_CommonConfig
from cloudcafe.openstackcli.novacli.client import NovaCLI
from cloudcafe.openstackcli.novacli.config import NovaCLI_Config
@ -13,10 +13,12 @@ class NovaCLI_Composite(object):
'os_password': self.openstack_config.os_password,
'os_tenant_name': self.openstack_config.os_tenant_name,
'os_auth_url': self.openstack_config.os_auth_url,
'os_region_name': self.openstack_config.os_region_name,
'debug': self.openstack_config.debug,
'os_auth_system': self.config.os_auth_system,
'insecure': self.config.insecure}
'insecure': self.config.insecure,
# Allows for individual product configs to override the region name
'os_region_name':
self.config.os_region_name or self.openstack_config.os_region_name}
self.client = NovaCLI(**self._cli_kwargs)
self.client.set_environment_variables(
self.config.environment_variable_dictionary)

View File

@ -32,6 +32,11 @@ class NovaCLI_Config(ConfigSectionInterface):
"""Selects which auth system to use"""
return self.get('os_auth_system')
@property
def os_region_name(self):
"""nova cli endpoint region"""
return self.get('os_region_name')
@property
def environment_variable_dictionary(self):
"""Expects a python dictionary as a string. Returns an empty dict