OpenStackProvider: Optionally wait for cloud-init

cloud-init writes a result.json when finished[1].  When
wait_for_cloud_init=true is provided, this change uses result.json to
detect when cloud-init has finished, and raises an exception if it
finished with errors.

[1] http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/view/head:/doc/status.txt

Change-Id: I7e0bcce733b008b488c8e1cf9bb74b5b6192cfbc
This commit is contained in:
Angus Lees
2015-07-22 11:38:16 +10:00
parent 30c0f53284
commit 3f79951a11
3 changed files with 62 additions and 3 deletions

View File

@@ -14,8 +14,8 @@
# under the License.
import itertools
import json
import os
import time
import novaclient.exceptions
@@ -46,6 +46,23 @@ def _get_address(s):
raise RuntimeError("No address found for %s" % s)
def _cloud_init_success(s):
status, stdout, stderr = s.ssh.execute(
"cat /run/cloud-init/result.json")
if status:
LOG.debug("Failed to read result.json on %s: %s" %
(s, stderr))
return False # Not finished (or no cloud-init)
res = json.loads(stdout)
if res["v1"]["errors"]:
raise RuntimeError("cloud-init exited with errors on %s: %s" %
(s, res["v1"]["errors"]))
LOG.debug("cloud-init finished with no errors")
return True # Success!
@provider.configure(name="OpenStackProvider")
class OpenStackProvider(provider.ProviderFactory):
"""Provide VMs using an existing OpenStack cloud.
@@ -84,6 +101,7 @@ class OpenStackProvider(provider.ProviderFactory):
"region": {"type": "string"},
"config_drive": {"type": "boolean"},
"flavor_id": {"type": "string"},
"wait_for_cloud_init": {"type": "boolean", "default": False},
"image": {
"type": "object",
"properties": {
@@ -224,8 +242,10 @@ class OpenStackProvider(provider.ProviderFactory):
for s in servers:
s.ssh.wait(timeout=120, interval=5)
# NOTE(eyerediskin): usually ssh is ready much earlier then cloud-init
time.sleep(8)
if self.config.get("wait_for_cloud_init", False):
for s in servers:
utils.wait_for(s, is_ready=_cloud_init_success)
return servers
def destroy_servers(self):

View File

@@ -11,6 +11,7 @@
"password": "admin",
"auth_url": "http://example.net:5000/v2.0",
"amount": 1,
"wait_for_cloud_init": true,
"image": {
"checksum": "5101b2013b31d9f2f96f64f728926054",
"name": "Ubuntu raring(added by rally)",

View File

@@ -15,6 +15,8 @@
"""Tests for OpenStack VM provider."""
import textwrap
import jsonschema
import mock
from oslotest import mockpatch
@@ -154,6 +156,42 @@ class OpenStackProviderTestCase(test.TestCase):
cfg["image"] = dict(checksum="checksum")
OSProvider(mock.MagicMock(), cfg)
def test_cloud_init_success_notready(self):
fake_server = mock.Mock()
fake_server.ssh.execute.return_value = (1, "", "")
# Not ready yet -> False
self.assertFalse(provider._cloud_init_success(fake_server))
def test_cloud_init_success_completed(self):
fake_server = mock.Mock()
result_json_text = textwrap.dedent("""
{
"v1": {
"errors": [],
"datasource": "DataSourceFoo"
}
}
""")
fake_server.ssh.execute.return_value = (0, result_json_text, "")
# Completed (with no errors) -> True
self.assertTrue(provider._cloud_init_success(fake_server))
def test_cloud_init_success_errors(self):
fake_server = mock.Mock()
result_json_text = textwrap.dedent("""
{
"v1": {
"errors": ["omg!"],
"datasource": "DataSourceFoo"
}
}
""")
fake_server.ssh.execute.return_value = (0, result_json_text, "")
# Completed with errors -> Exception
self.assertRaises(RuntimeError,
provider._cloud_init_success, fake_server)
@mock.patch("time.sleep")
@mock.patch(MOD_NAME + ".provider.Server")
@mock.patch(MOD_NAME + ".osclients")