Move wait_for_status to resource module

wait_for_status is a function that is useful to many resources such as
volumes, stacks, databases, servers, and images. It would useful to have a
generic utility function to wait on the status of any such resource.

In the resource proxies the wait_for functions are named specifically to
that resource (e.g. compute.v2._proxy.wait_for_server) to stay consistent
with the verb_resource naming convention.

Change-Id: I6290235a9f7b0c204fc3933bf35497d0d8c4d0fe
This commit is contained in:
Everett Toews
2015-04-17 12:52:17 -05:00
parent d13aa6c3b9
commit 43a1dbe84f
12 changed files with 143 additions and 109 deletions

View File

@@ -54,7 +54,7 @@ def create_jenkins(conn, name, opts):
server = conn.get(server)
print(str(server))
print('Waiting for the server to come up....')
conn.compute.wait_for_status(server)
conn.compute.wait_for_server(server)
print('Server is up.')
if len(server.get_floating_ips()) <= 0:

View File

@@ -19,6 +19,7 @@ from openstack.compute.v2 import server
from openstack.compute.v2 import server_interface
from openstack.compute.v2 import server_ip
from openstack import proxy
from openstack import resource
class Proxy(proxy.BaseProxy):
@@ -174,10 +175,10 @@ class Proxy(proxy.BaseProxy):
def update_server(self, **data):
return server.Server(data).update(self.session)
def wait_for_status(self, server, status='ACTIVE', failures=['ERROR'],
def wait_for_server(self, value, status='ACTIVE', failures=['ERROR'],
interval=2, wait=120):
return server.wait_for_status(self.session, status, failures, interval,
wait)
return resource.wait_for_status(self.session, value, status,
failures, interval, wait)
def create_server_interface(self, **data):
return server_interface.ServerInterface(data).create(self.session)

View File

@@ -10,13 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
from openstack.compute import compute_service
from openstack.compute.v2 import flavor
from openstack.compute.v2 import image
from openstack.compute.v2 import server_ip
from openstack import exceptions
from openstack import resource
from openstack import utils
@@ -146,45 +143,6 @@ class Server(resource.Resource):
body = {'createImage': action}
return self.action(session, body)
def wait_for_status(self, session, status='ACTIVE', failures=None,
interval=5, wait=120):
"""Wait for the server to be in some status.
:param session: The session to use for making this request.
:type session: :class:`~openstack.session.Session`
:param status: Desired status of the server.
: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 transition.
:return: Method returns self 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.
"""
try:
if self.status == status:
return self
except AttributeError:
pass
total_sleep = 0
if failures is None:
failures = []
while total_sleep < wait:
self.get(session)
if self.status == status:
return self
if self.status in failures:
msg = ("Resource %s transitioned to failure state %s" %
(self.id, self.status))
raise exceptions.ResourceFailure(msg)
time.sleep(interval)
total_sleep += interval
msg = "Timeout waiting for %s to transition to %s" % (self.id, status)
raise exceptions.ResourceTimeout(msg)
def get_floating_ips(self):
"""Get the floating ips associated with this server."""
addresses = self.addresses[self.name]

View File

@@ -12,6 +12,7 @@
from openstack.orchestration.v1 import stack
from openstack import proxy
from openstack import resource
class Proxy(proxy.BaseProxy):
@@ -43,3 +44,8 @@ class Proxy(proxy.BaseProxy):
:returns: ``None``
"""
self._delete(stack.Stack, value, 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)

View File

@@ -32,6 +32,7 @@ There are plenty of examples of use of this class in the SDK code.
import abc
import collections
import itertools
import time
import six
from six.moves.urllib import parse as url_parse
@@ -942,3 +943,47 @@ class Resource(collections.MutableMapping):
raise exceptions.DuplicateResource(msg)
return None
def wait_for_status(session, resource, status=None, failures=None,
interval=5, wait=120):
"""Wait for the resource to be in a particular status.
: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'.
:param interval: Number of seconds to wait between checks.
:param wait: Maximum number of seconds to wait for transition.
:return: Method returns self 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
"""
if resource.status == status:
return resource
total_sleep = 0
if failures is None:
failures = []
while total_sleep < wait:
resource.get(session)
if resource.status == status:
return resource
if resource.status in failures:
msg = ("Resource %s transitioned to failure state %s" %
(resource.id, resource.status))
raise exceptions.ResourceFailure(msg)
time.sleep(interval)
total_sleep += interval
msg = "Timeout waiting for %s to transition to %s" % (resource.id, status)
raise exceptions.ResourceTimeout(msg)

View File

@@ -45,5 +45,7 @@ class TestStack(unittest.TestCase):
'heat-templates/plain/hot/F20/WordPress_Native.yaml'
)
self.conn.orchestration.wait_for_stack(stack)
self.assertIsNotNone(stack.id)
self.assertEqual('test_stack', stack.name)

View File

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

View File

@@ -16,7 +16,6 @@ import testtools
from openstack.compute.v2 import flavor
from openstack.compute.v2 import image
from openstack.compute.v2 import server
from openstack import exceptions
IDENTIFIER = 'IDENTIFIER'
EXAMPLE = {
@@ -221,67 +220,6 @@ class TestServer(testtools.TestCase):
body = {"createImage": {'name': name}}
self.sess.put.assert_called_with(url, service=sot.service, json=body)
def test_wait_for_status_nothing(self):
self.sess.get = mock.MagicMock()
sot = server.Server(attrs={'id': IDENTIFIER, 'status': 'ACTIVE'})
self.assertEqual(sot, sot.wait_for_status(self.sess, 'ACTIVE', [],
1, 2))
expected = []
self.assertEqual(expected, self.sess.get.call_args_list)
def test_wait_for_status(self):
resp1 = mock.Mock()
resp1.body = {'server': {'status': 'BUILDING'}}
resp2 = mock.Mock()
resp2.body = {'server': {'status': 'ACTIVE'}}
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [resp1, resp2]
sot = server.Server(attrs={'id': IDENTIFIER})
self.assertEqual(sot, sot.wait_for_status(self.sess, 'ACTIVE', [],
1, 2))
url = 'servers/IDENTIFIER'
thecall = mock.call(url, service=sot.service)
expected = [thecall, thecall]
self.assertEqual(expected, self.sess.get.call_args_list)
def test_wait_for_status_timeout(self):
resp1 = mock.Mock()
resp1.body = {'server': {'status': 'BUILDING'}}
resp2 = mock.Mock()
resp2.body = {'server': {'status': 'BUILDING'}}
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [resp1, resp2]
sot = server.Server(attrs={'id': IDENTIFIER})
self.assertRaises(exceptions.ResourceTimeout, sot.wait_for_status,
self.sess, 'ACTIVE', ['ERROR'], 1, 2)
url = 'servers/IDENTIFIER'
thecall = mock.call(url, service=sot.service)
expected = [thecall, thecall]
self.assertEqual(expected, self.sess.get.call_args_list)
def test_wait_for_status_failures(self):
resp1 = mock.Mock()
resp1.body = {'server': {'status': 'BUILDING'}}
resp2 = mock.Mock()
resp2.body = {'server': {'status': 'ERROR'}}
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [resp1, resp2]
sot = server.Server(attrs={'id': IDENTIFIER})
self.assertRaises(exceptions.ResourceFailure, sot.wait_for_status,
self.sess, 'ACTIVE', ['ERROR'], 1, 2)
url = 'servers/IDENTIFIER'
thecall = mock.call(url, service=sot.service)
expected = [thecall, thecall]
self.assertEqual(expected, self.sess.get.call_args_list)
def test_get_ips(self):
name = "jenkins"
fixed = {

View File

@@ -41,3 +41,12 @@ class TestOrchestrationProxy(test_proxy_base.TestProxyBase):
def test_stack_delete_ignore(self):
self.verify_delete2(stack.Stack, self.proxy.delete_stack, True)
def test_stack_wait_for(self):
value = stack.Stack(attrs={'id': '1234'})
self.verify_wait_for_status(
'openstack.resource.wait_for_status',
self.proxy.wait_for_stack,
method_args=[value],
expected_args=[value, 'CREATE_COMPLETE', ['CREATE_FAILED'],
2, 120])

View File

@@ -15,6 +15,7 @@ import testtools
from openstack.orchestration.v1 import stack
FAKE_ID = 'ce8ae86c-9810-4cb1-8888-7fb53bc523bf'
FAKE_NAME = 'test_stack'
FAKE = {

View File

@@ -99,3 +99,6 @@ class TestProxyBase(base.TestCase):
def verify_update(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, expected_result="result",
**kwargs)
def verify_wait_for_status(self, mock_method, test_method, **kwargs):
self._verify(mock_method, test_method, **kwargs)

View File

@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import json
import os
@@ -41,7 +42,8 @@ fake_data = {'id': fake_id,
'enabled': True,
'name': fake_name,
'attr1': fake_attr1,
'attr2': fake_attr2}
'attr2': fake_attr2,
'status': None}
fake_body = {fake_resource: fake_data}
@@ -59,6 +61,7 @@ class FakeResource(resource.Resource):
first = resource.prop('attr1')
second = resource.prop('attr2')
third = resource.prop('attr3', alias='attr_three')
status = resource.prop('status')
class FakeResourceNoKeys(FakeResource):
@@ -1224,3 +1227,63 @@ class TestFind(base.TestCase):
FakeResource.name_attribute = None
self.assertEqual(None, FakeResource.find(self.mock_session, self.NAME))
class TestWaitForStatus(base.TestCase):
def __init__(self, *args, **kwargs):
super(TestWaitForStatus, self).__init__(*args, **kwargs)
self.build = FakeResponse(self.body_with_status(fake_body, 'BUILD'))
self.active = FakeResponse(self.body_with_status(fake_body, 'ACTIVE'))
self.error = FakeResponse(self.body_with_status(fake_body, 'ERROR'))
def setUp(self):
super(TestWaitForStatus, self).setUp()
self.sess = mock.MagicMock()
def body_with_status(self, body, status):
body_copy = copy.deepcopy(body)
body_copy[fake_resource]['status'] = status
return body_copy
def test_wait_for_status_nothing(self):
self.sess.get = mock.MagicMock()
sot = FakeResource.new(**fake_data)
sot.status = 'ACTIVE'
self.assertEqual(sot, resource.wait_for_status(
self.sess, sot, 'ACTIVE', [], 1, 2))
self.assertEqual([], self.sess.get.call_args_list)
def test_wait_for_status(self):
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [self.build, self.active]
sot = FakeResource.new(**fake_data)
self.assertEqual(sot, resource.wait_for_status(
self.sess, sot, 'ACTIVE', [], 1, 2))
def test_wait_for_status_timeout(self):
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [self.build, self.build]
sot = FakeResource.new(**fake_data)
self.assertRaises(exceptions.ResourceTimeout, resource.wait_for_status,
self.sess, sot, 'ACTIVE', ['ERROR'], 1, 2)
def test_wait_for_status_failures(self):
self.sess.get = mock.MagicMock()
self.sess.get.side_effect = [self.build, self.error]
sot = FakeResource.new(**fake_data)
self.assertRaises(exceptions.ResourceFailure, resource.wait_for_status,
self.sess, sot, 'ACTIVE', ['ERROR'], 1, 2)
def test_wait_for_status_no_status(self):
class FakeResourceNoStatus(resource.Resource):
allow_retrieve = True
sot = FakeResourceNoStatus.new(id=123)
self.assertRaises(AttributeError, resource.wait_for_status,
self.sess, sot, 'ACTIVE', ['ERROR'], 1, 2)