diff --git a/cloudcafe/openstackcli/cindercli/behaviors.py b/cloudcafe/openstackcli/cindercli/behaviors.py deleted file mode 100644 index 7b9d1240..00000000 --- a/cloudcafe/openstackcli/cindercli/behaviors.py +++ /dev/null @@ -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 diff --git a/cloudcafe/openstackcli/cindercli/client.py b/cloudcafe/openstackcli/cindercli/client.py index 2fba0517..1b9e76de 100644 --- a/cloudcafe/openstackcli/cindercli/client.py +++ b/cloudcafe/openstackcli/cindercli/client.py @@ -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() diff --git a/cloudcafe/openstackcli/cindercli/composites.py b/cloudcafe/openstackcli/cindercli/composites.py new file mode 100644 index 00000000..23d6cf3f --- /dev/null +++ b/cloudcafe/openstackcli/cindercli/composites.py @@ -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) diff --git a/cloudcafe/openstackcli/cindercli/config.py b/cloudcafe/openstackcli/cindercli/config.py index 3711c158..bf544538 100644 --- a/cloudcafe/openstackcli/cindercli/config.py +++ b/cloudcafe/openstackcli/cindercli/config.py @@ -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 diff --git a/cloudcafe/openstackcli/novacli/client.py b/cloudcafe/openstackcli/novacli/client.py index 9b5b8961..1b6910ae 100644 --- a/cloudcafe/openstackcli/novacli/client.py +++ b/cloudcafe/openstackcli/novacli/client.py @@ -185,6 +185,7 @@ class NovaCLI(BaseOpenstackPythonCLI_Client): """ _cmd = 'volume-attach' + device = device or 'auto' _response_type = responses.VolumeAttachResponse return self._process_command() diff --git a/cloudcafe/openstackcli/novacli/composites.py b/cloudcafe/openstackcli/novacli/composites.py index a36b0529..e6e9c452 100644 --- a/cloudcafe/openstackcli/novacli/composites.py +++ b/cloudcafe/openstackcli/novacli/composites.py @@ -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) diff --git a/cloudcafe/openstackcli/novacli/config.py b/cloudcafe/openstackcli/novacli/config.py index 9f015a58..83d18e1a 100644 --- a/cloudcafe/openstackcli/novacli/config.py +++ b/cloudcafe/openstackcli/novacli/config.py @@ -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