Add wait for delete method

Add wait for delete method to resource and add two proxy
methods to wait for delete for servers and stacks.

Change-Id: Iaff65457e94ffd5f64f5951f0c2ba211f4f06bdd
This commit is contained in:
TerryHowe
2015-06-20 06:36:22 -06:00
parent 17c9360f4f
commit bea7336732
10 changed files with 140 additions and 52 deletions

View File

@@ -14,7 +14,6 @@ from openstack import exceptions
from openstack.orchestration.v1 import resource as stack_resource
from openstack.orchestration.v1 import stack
from openstack import proxy
from openstack import resource
class Proxy(proxy.BaseProxy):
@@ -84,11 +83,6 @@ class Proxy(proxy.BaseProxy):
"""
self._delete(stack.Stack, value, ignore_missing=ignore_missing)
def wait_for_stack(self, value, status='CREATE_COMPLETE',
failures=['CREATE_FAILED'], interval=2, wait=120):
return resource.wait_for_status(self.session, value, status,
failures, interval, wait)
def resources(self, value, **query):
"""Return a generator of resources

View File

@@ -204,3 +204,41 @@ class BaseProxy(object):
res = self._get_resource(resource_type, value, path_args)
return res.head(self.session)
def wait_for_status(self, value, status, failures=[], interval=2,
wait=120):
"""Wait for a resource to be in a particular status.
:param value: The resource to wait on to reach the status. The
resource must have a status attribute.
:type value: :class:`~openstack.resource.Resource`
:param status: Desired status of the resource.
:param list failures: Statuses that would indicate the transition
failed such as 'ERROR'.
:param interval: Number of seconds to wait between checks.
:param wait: Maximum number of seconds to wait for the change.
:return: Method returns resource on success.
:raises: :class:`~openstack.exceptions.ResourceTimeout` transition
to status failed to occur in wait seconds.
:raises: :class:`~openstack.exceptions.ResourceFailure` resource
transitioned to one of the failure states.
:raises: :class:`~AttributeError` if the resource does not have a
status attribute
"""
return resource.wait_for_status(self.session, value, status,
failures, interval, wait)
def wait_for_delete(self, value, interval=2, wait=120):
"""Wait for the resource to be deleted.
:param value: The resource to wait on to be deleted.
:type value: :class:`~openstack.resource.Resource`
:param interval: Number of seconds to wait between checks.
:param wait: Maximum number of seconds to wait for the delete.
:return: Method returns resource on success.
:raises: :class:`~openstack.exceptions.ResourceTimeout` transition
to delete failed to occur in wait seconds.
"""
return resource.wait_for_delete(self.session, value, interval, wait)

View File

@@ -936,15 +936,14 @@ class Resource(collections.MutableMapping):
"No %s found for %s" % (cls.__name__, name_or_id))
def wait_for_status(session, resource, status=None, failures=None,
interval=5, wait=120):
def wait_for_status(session, resource, status, failures, interval, wait):
"""Wait for the resource to be in a particular status.
:param session: The session to use for making this request.
:type session: :class:`~openstack.session.Session`
:param resource: The resource to wait on to reach the status. The resource
must have a status attribute.
:type resource: :class:`~openstack.resource.Resource`
:param session: The session to use for making this request.
:type session: :class:`~openstack.session.Session`
:param status: Desired status of the resource.
:param list failures: Statuses that would indicate the transition
failed such as 'ERROR'.
@@ -978,3 +977,29 @@ def wait_for_status(session, resource, status=None, failures=None,
total_sleep += interval
msg = "Timeout waiting for %s to transition to %s" % (resource.id, status)
raise exceptions.ResourceTimeout(msg)
def wait_for_delete(session, resource, interval, wait):
"""Wait for the resource to be deleted.
:param session: The session to use for making this request.
:type session: :class:`~openstack.session.Session`
:param resource: The resource to wait on to be deleted.
:type resource: :class:`~openstack.resource.Resource`
:param interval: Number of seconds to wait between checks.
:param wait: Maximum number of seconds to wait for the delete.
:return: Method returns self on success.
:raises: :class:`~openstack.exceptions.ResourceTimeout` transition
to status failed to occur in wait seconds.
"""
total_sleep = 0
while total_sleep < wait:
try:
resource.get(session)
except exceptions.NotFoundException:
return resource
time.sleep(interval)
total_sleep += interval
msg = "Timeout waiting for %s delete" % (resource.id)
raise exceptions.ResourceTimeout(msg)

View File

@@ -81,19 +81,6 @@ class BaseFunctionalTest(unittest.TestCase):
if expected != actual:
raise Exception(expected + ' != ' + actual)
# TODO(thowe): This should probably be logic moved info the proxy or
# or the resource, but for now I just want to get the functional tests
# working again.
@classmethod
def wait_for_delete(cls, proxy_find, name_or_id, interval=2, linger=15,
wait=60):
total_sleep = 0
while total_sleep < wait:
if proxy_find(name_or_id) is None:
# Wait a little longer for the vestiges of the deleted object
time.sleep(linger)
return True
print('waiting for delete ' + name_or_id)
time.sleep(interval)
total_sleep += interval
return False
def linger_for_delete(cls):
time.sleep(40)

View File

@@ -20,7 +20,7 @@ from openstack.tests.functional.network.v2 import test_network
class TestServer(base.BaseFunctionalTest):
NAME = uuid.uuid4().hex
ID = None
server = None
network = None
subnet = None
@@ -40,24 +40,25 @@ class TestServer(base.BaseFunctionalTest):
name=cls.NAME, flavor=flavor, image=image.id, **args)
assert isinstance(sot, server.Server)
cls.assertIs(cls.NAME, sot.name)
cls.ID = sot.id
cls.server = sot
@classmethod
def tearDownClass(cls):
sot = cls.conn.compute.delete_server(cls.ID)
sot = cls.conn.compute.delete_server(cls.server.id)
cls.assertIs(None, sot)
# Need to wait for the stack to go away before network delete
cls.wait_for_delete(cls.conn.compute.find_server, cls.ID)
cls.conn.compute.wait_for_delete(cls.server)
cls.linger_for_delete()
test_network.delete_network(cls.conn, cls.network, cls.subnet)
def test_find(self):
sot = self.conn.compute.find_server(self.NAME)
self.assertEqual(self.ID, sot.id)
self.assertEqual(self.server.id, sot.id)
def test_get(self):
sot = self.conn.compute.get_server(self.ID)
sot = self.conn.compute.get_server(self.server.id)
self.assertEqual(self.NAME, sot.name)
self.assertEqual(self.ID, sot.id)
self.assertEqual(self.server.id, sot.id)
def test_list(self):
names = [o.name for o in self.conn.compute.servers()]

View File

@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack import exceptions
from openstack.orchestration.v1 import stack
from openstack.tests.functional import base
from openstack.tests.functional.network.v2 import test_network
@@ -18,7 +19,7 @@ from openstack.tests.functional.network.v2 import test_network
class TestStack(base.BaseFunctionalTest):
NAME = 'test_stack'
ID = None
stack = None
network = None
subnet = None
@@ -45,17 +46,23 @@ class TestStack(base.BaseFunctionalTest):
)
assert isinstance(sot, stack.Stack)
cls.assertIs(True, (sot.id is not None))
cls.ID = sot.id
cls.stack = sot
cls.assertIs(cls.NAME, sot.name)
cls.conn.orchestration.wait_for_stack(sot)
cls.conn.orchestration.wait_for_status(
sot, status='CREATE_COMPLETE', failures=['CREATE_FAILED'])
@classmethod
def tearDownClass(cls):
super(TestStack, cls).tearDownClass()
cls.conn.orchestration.delete_stack(cls.ID, ignore_missing=False)
cls.conn.orchestration.delete_stack(cls.stack, ignore_missing=False)
cls.conn.compute.delete_keypair(cls.NAME)
# Need to wait for the stack to go away before network delete
cls.wait_for_delete(cls.conn.orchestration.find_stack, cls.ID)
try:
cls.conn.orchestration.wait_for_status(
cls.stack, 'DELETE_COMPLETE')
except exceptions.NotFoundException:
pass
cls.linger_for_delete()
test_network.delete_network(cls.conn, cls.network, cls.subnet)
def test_list(self):

View File

@@ -188,10 +188,3 @@ class TestComputeProxy(test_proxy_base.TestProxyBase):
def test_server_update(self):
self.verify_update(self.proxy.update_server, server.Server)
def test_server_wait_for(self):
value = server.Server(attrs={'id': '1234'})
self.verify_wait_for_status(
self.proxy.wait_for_server,
method_args=[value],
expected_args=[value, 'ACTIVE', ['ERROR'], 2, 120])

View File

@@ -44,14 +44,6 @@ class TestOrchestrationProxy(test_proxy_base.TestProxyBase):
def test_stack_delete_ignore(self):
self.verify_delete(self.proxy.delete_stack, stack.Stack, True)
def test_stack_wait_for(self):
value = stack.Stack(attrs={'id': '1234'})
self.verify_wait_for_status(
self.proxy.wait_for_stack,
method_args=[value],
expected_args=[value, 'CREATE_COMPLETE', ['CREATE_FAILED'],
2, 120])
@mock.patch.object(stack.Stack, 'from_id')
@mock.patch.object(stack.Stack, 'find')
def test_resources_with_stack_object(self, mock_find, mock_from):

View File

@@ -313,3 +313,35 @@ class TestProxyHead(testtools.TestCase):
MockHeadResource.assert_called_with()
instance.head.assert_called_with(self.session)
@mock.patch("openstack.resource.wait_for_status")
def test_wait_for(self, mock_wait):
mock_resource = mock.Mock()
mock_wait.return_value = mock_resource
self.sot.wait_for_status(mock_resource, 'ACTIVE')
mock_wait.assert_called_once_with(
self.session, mock_resource, 'ACTIVE', [], 2, 120)
@mock.patch("openstack.resource.wait_for_status")
def test_wait_for_params(self, mock_wait):
mock_resource = mock.Mock()
mock_wait.return_value = mock_resource
self.sot.wait_for_status(mock_resource, 'ACTIVE', ['ERROR'], 1, 2)
mock_wait.assert_called_once_with(
self.session, mock_resource, 'ACTIVE', ['ERROR'], 1, 2)
@mock.patch("openstack.resource.wait_for_delete")
def test_wait_for_delete(self, mock_wait):
mock_resource = mock.Mock()
mock_wait.return_value = mock_resource
self.sot.wait_for_delete(mock_resource)
mock_wait.assert_called_once_with(
self.session, mock_resource, 2, 120)
@mock.patch("openstack.resource.wait_for_delete")
def test_wait_for_delete_params(self, mock_wait):
mock_resource = mock.Mock()
mock_wait.return_value = mock_resource
self.sot.wait_for_delete(mock_resource, 1, 2)
mock_wait.assert_called_once_with(
self.session, mock_resource, 1, 2)

View File

@@ -1316,3 +1316,22 @@ class TestWaitForStatus(base.TestCase):
self.assertRaises(AttributeError, resource.wait_for_status,
self.sess, sot, 'ACTIVE', ['ERROR'], 1, 2)
class TestWaitForDelete(base.TestCase):
def test_wait_for_delete(self):
sess = mock.Mock()
sot = FakeResource.new(**fake_data)
sot.get = mock.MagicMock()
sot.get.side_effect = [sot, exceptions.NotFoundException(mock.Mock())]
self.assertEqual(sot, resource.wait_for_delete(sess, sot, 1, 2))
def test_wait_for_delete_fail(self):
sess = mock.Mock()
sot = FakeResource.new(**fake_data)
sot.get = mock.MagicMock(return_value=sot)
self.assertRaises(exceptions.ResourceTimeout, resource.wait_for_delete,
sess, sot, 1, 2)