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:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -15,6 +15,7 @@ import testtools
|
||||
|
||||
from openstack.orchestration.v1 import stack
|
||||
|
||||
|
||||
FAKE_ID = 'ce8ae86c-9810-4cb1-8888-7fb53bc523bf'
|
||||
FAKE_NAME = 'test_stack'
|
||||
FAKE = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user