Added behaviors and behavior tests

* Added verify_volume_status_progression_during_detachment
   behavior.
 * Modified verify_volume_status_progression_during_attachment
   behavior for better code reuse.
 * Added metatests for both behaviors.

Change-Id: I0f3290e01b1380ecfcbc34e02e2aae2abf384a8c
This commit is contained in:
Jose Idar 2015-03-12 16:18:53 -05:00
parent ac40f3e2a7
commit 523f22d015
3 changed files with 171 additions and 23 deletions

View File

@ -1,5 +1,6 @@
from time import sleep, time
from cloudcafe.common.behaviors import StatusProgressionVerifier
from cloudcafe.common.behaviors import (
StatusProgressionVerifier, StatusProgressionVerifierError)
from cloudcafe.compute.common.behaviors import BaseComputeBehavior
from cloudcafe.compute.volume_attachments_api.config import \
VolumeAttachmentsAPIConfig
@ -14,6 +15,7 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior):
def __init__(
self, volume_attachments_client=None,
volume_attachments_config=None, volumes_client=None):
super(VolumeAttachmentsAPI_Behaviors, self).__init__()
self.client = volume_attachments_client
self.config = volume_attachments_config or VolumeAttachmentsAPIConfig()
@ -34,53 +36,95 @@ class VolumeAttachmentsAPI_Behaviors(BaseComputeBehavior):
else:
return False
def _get_volume_status(self, volume_id):
resp = self.volumes_client.get_volume_info(volume_id=volume_id)
if not resp.ok:
msg = (
"get_volume_status() failure: get_volume_info() call"
" failed with a {0} status code".format(resp.status_code))
self._log.error(msg)
raise Exception(msg)
if resp.entity is None:
msg = (
"get_volume_status() failure: unable to deserialize"
" response from get_volume_info() call")
self._log.error(msg)
raise Exception(msg)
return resp.entity.status
def verify_volume_status_progression_during_attachment(
self, volume_id, state_list=None):
def _get_volume_status(self, volume_id):
resp = self.volumes_client.get_volume_info(volume_id=volume_id)
if not resp.ok:
msg = (
"get_volume_status() failure: get_volume_info() call"
" failed with a {0} status code".format(resp.status_code))
self._log.error(msg)
raise Exception(msg)
if resp.entity is None:
msg = (
"get_volume_status() failure: unable to deserialize"
" response from get_volume_info() call")
self._log.error(msg)
raise Exception(msg)
return resp.entity.status
verifier = StatusProgressionVerifier(
'volume', volume_id, _get_volume_status, *[self, volume_id])
'volume', volume_id, self._get_volume_status, volume_id)
verifier.set_global_state_properties(
timeout=self.config.attachment_timeout)
verifier.add_state(
expected_statuses=['available'],
acceptable_statuses=['attaching', 'in-use'],
error_statuses=['error', 'creating'],
timeout=10, poll_rate=1, poll_failure_retry_limit=3)
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
verifier.add_state(
expected_statuses=['attaching'],
acceptable_statuses=['in-use'],
error_statuses=['error', 'creating'],
timeout=self.config.attachment_timeout,
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
verifier.add_state(
expected_statuses=['in-use'],
error_statuses=['available', 'error', 'creating'],
timeout=self.config.attachment_timeout,
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
verifier.start()
def verify_volume_status_progression_during_detachment(
self, volume_id, raise_on_error=True):
"""
Track the status progression of volume volume_id being detached.
Optionally fails silently if rais_on_error is set to False.
:param volume_id: the uuid of the volume being tracked
:returns: None
"""
verifier = StatusProgressionVerifier(
'volume', volume_id, self._get_volume_status, volume_id)
verifier.set_global_state_properties(
timeout=self.config.attachment_timeout)
verifier.add_state(
expected_statuses=['in-use'],
acceptable_statuses=['detaching', 'available'],
error_statuses=['error', 'attaching', 'creating', 'deleting'],
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
verifier.add_state(
expected_statuses=['detaching'],
acceptable_statuses=['available'],
error_statuses=['error', 'attaching', 'creating', 'deleting'],
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
verifier.add_state(
expected_statuses=['available'],
error_statuses=['error', 'attaching', 'creating', 'deleting'],
poll_rate=self.config.api_poll_rate,
poll_failure_retry_limit=3)
try:
verifier.start()
except Exception as exception:
if raise_on_error:
raise exception
def delete_volume_attachment(
self, attachment_id, server_id, timeout=None, poll_rate=None):
"""Waits timeout seconds for volume attachment to 404 after issuing

View File

@ -0,0 +1,15 @@
"""
Copyright 2015 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.
"""

View File

@ -0,0 +1,89 @@
"""
Copyright 2015 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 collections import OrderedDict
from mock import MagicMock, Mock
from requests import Response
import sys
import unittest
from cafe.common.reporting import cclogging
from cafe.configurator.managers import _lazy_property
cclogging.init_root_log_handler()
from cloudcafe.common.behaviors import StatusProgressionVerifierError
from cloudcafe.compute.volume_attachments_api.behaviors import \
VolumeAttachmentsAPI_Behaviors
from cloudcafe.compute.volume_attachments_api.client import \
VolumeAttachmentsAPIClient
from cloudcafe.compute.volume_attachments_api.config import \
VolumeAttachmentsAPIConfig
from cloudcafe.blockstorage.volumes_api.v2.client import \
VolumesClient
from cloudcafe.blockstorage.volumes_api.v2.models.responses import \
VolumeResponse
class MockBuilder(object):
def _mock(self, name, **defaults):
defaults.update(**self._overrides.get(name, {}))
setattr(self, name, Mock(**defaults))
class TestMocks(MockBuilder):
def __init__(self, **kwargs):
self._overrides = kwargs
self._mock(
'volume_model', spec=VolumeResponse, status='in-use', id_='111111')
self._mock(
'response', spec=Response, ok=True, entity=self.volume_model)
self._mock(
'volumes_client', spec=VolumesClient,
get_volume_info=MagicMock(return_value=self.response))
self._mock(
'volume_attachments_client', spec=VolumeAttachmentsAPIClient)
self._mock(
'volume_attachments_config', spec=VolumeAttachmentsAPIConfig,
attachment_timeout=1, api_poll_rate=1)
class BaseTestCase(object):
mocks = TestMocks()
def behavior_class_under_test(self):
return VolumeAttachmentsAPI_Behaviors(
self.mocks.volume_attachments_client,
self.mocks.volume_attachments_config,
self.mocks.volumes_client)
def setUp(self):
self.behaviors = self.behavior_class_under_test()
class MethodTests_verify_volume_status_progression_during_attachment(
BaseTestCase, unittest.TestCase):
def test_volume_is_attached(self):
r = self.behaviors.verify_volume_status_progression_during_attachment(
self.mocks.volume_model.id_)
class MethodTests_verify_volume_status_progression_during_detachment(
BaseTestCase, unittest.TestCase):
mocks = TestMocks(volume_model=dict(status='available'))
def test_volume_is_detached(self):
r = self.behaviors.verify_volume_status_progression_during_detachment(
self.mocks.volume_model.id_)