Merge "packet: add phone_home and post_password support"
This commit is contained in:
commit
d53e765b26
@ -41,6 +41,13 @@ class MetadataNotFoundException(CloudbaseInitException):
|
||||
pass
|
||||
|
||||
|
||||
class MetadataEndpointException(CloudbaseInitException):
|
||||
|
||||
"""Exception thrown in case the metadata is unresponsive or errors out."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CertificateVerifyFailed(ServiceException):
|
||||
|
||||
"""The received certificate is not valid.
|
||||
|
@ -14,10 +14,13 @@
|
||||
"""Metadata Service for Packet."""
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit import exception
|
||||
from cloudbaseinit.metadata.services import base
|
||||
from oslo_log import log as oslo_logging
|
||||
from six.moves.urllib import error
|
||||
|
||||
CONF = cloudbaseinit_conf.CONF
|
||||
LOG = oslo_logging.getLogger(__name__)
|
||||
@ -83,3 +86,49 @@ class PacketService(base.BaseHTTPMetadataService):
|
||||
def get_user_data(self):
|
||||
"""Get the available user data for the current instance."""
|
||||
return self._get_cache_data("userdata")
|
||||
|
||||
def _get_phone_home_url(self):
|
||||
return self._get_meta_data().get("phone_home_url")
|
||||
|
||||
def get_user_pwd_encryption_key(self):
|
||||
phone_home_url = self._get_phone_home_url()
|
||||
key_url = requests.compat.urljoin('%s/' % phone_home_url, "key")
|
||||
return self._get_cache_data(key_url, decode=True)
|
||||
|
||||
@property
|
||||
def can_post_password(self):
|
||||
"""The Packet metadata service supports posting the password."""
|
||||
return True
|
||||
|
||||
def post_password(self, enc_password_b64):
|
||||
phone_home_url = self._get_phone_home_url()
|
||||
LOG.info("Posting password to: %s", phone_home_url)
|
||||
try:
|
||||
action = lambda: self._http_request(
|
||||
url=phone_home_url,
|
||||
data=json.dumps({'password': enc_password_b64.decode()}))
|
||||
return self._exec_with_retry(action)
|
||||
except error.HTTPError as exc:
|
||||
LOG.exception(exc)
|
||||
raise exception.MetadataEndpointException(
|
||||
"Failed to post password to the metadata service")
|
||||
|
||||
def provisioning_completed(self):
|
||||
"""Signal to Packet that the instance is ready.
|
||||
|
||||
To complete the provisioning, on the first boot after installation
|
||||
make a GET request to CONF.packet.metadata_url, which will return a
|
||||
JSON object which contains phone_home_url entry.
|
||||
Make a POST request to phone_home_url with no body (important!)
|
||||
and this will complete the installation process.
|
||||
"""
|
||||
phone_home_url = self._get_phone_home_url()
|
||||
LOG.info("Calling home to: %s", phone_home_url)
|
||||
try:
|
||||
action = lambda: self._http_request(url=phone_home_url,
|
||||
method="post")
|
||||
return self._exec_with_retry(action)
|
||||
except error.HTTPError as exc:
|
||||
LOG.exception(exc)
|
||||
raise exception.MetadataEndpointException(
|
||||
"Failed to call home to the metadata service")
|
||||
|
@ -20,7 +20,10 @@ try:
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from six.moves.urllib import error
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit import exception
|
||||
from cloudbaseinit.tests import testutils
|
||||
|
||||
|
||||
@ -121,3 +124,106 @@ class PacketServiceTest(unittest.TestCase):
|
||||
response = self._packet_service.get_user_data()
|
||||
mock_get_cache_data.assert_called_once_with("userdata")
|
||||
self.assertEqual(mock_get_cache_data.return_value, response)
|
||||
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_meta_data")
|
||||
def test_get_phone_home_url(self, mock_get_meta_data):
|
||||
fake_phone_url = 'fake_phone_url'
|
||||
mock_get_meta_data.return_value = {
|
||||
"phone_home_url": fake_phone_url
|
||||
}
|
||||
response = self._packet_service._get_phone_home_url()
|
||||
|
||||
self.assertEqual(response, fake_phone_url)
|
||||
|
||||
def test_can_post_password(self):
|
||||
self.assertEqual(self._packet_service.can_post_password,
|
||||
True)
|
||||
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_phone_home_url")
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_cache_data")
|
||||
def test_get_user_pwd_encryption_key(self, mock_get_cache_data,
|
||||
mock_get_phone_url):
|
||||
fake_phone_url = 'fake_phone_url'
|
||||
user_pwd_encryption_key = 'fake_key'
|
||||
|
||||
mock_get_cache_data.return_value = user_pwd_encryption_key
|
||||
mock_get_phone_url.return_value = fake_phone_url
|
||||
|
||||
response = self._packet_service.get_user_pwd_encryption_key()
|
||||
mock_get_phone_url.assert_called_once()
|
||||
mock_get_cache_data.assert_called_once_with(
|
||||
"%s/%s" % (fake_phone_url, 'key'), decode=True)
|
||||
|
||||
self.assertEqual(response, user_pwd_encryption_key)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_phone_home_url")
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._http_request")
|
||||
def _test_post_password(self, mock_http_request,
|
||||
mock_get_phone_url, mock_sleep, fail=False):
|
||||
fake_phone_url = 'fake_phone_url'
|
||||
fake_response = 'fake_response'
|
||||
fake_encoded_password = b'fake_password'
|
||||
|
||||
if fail:
|
||||
mock_http_request.side_effect = (
|
||||
error.HTTPError(401, "invalid", {}, 0, 0))
|
||||
with self.assertRaises(exception.MetadataEndpointException):
|
||||
self._packet_service.post_password(fake_encoded_password)
|
||||
else:
|
||||
mock_http_request.return_value = fake_response
|
||||
mock_get_phone_url.return_value = fake_phone_url
|
||||
|
||||
response = self._packet_service.post_password(
|
||||
fake_encoded_password)
|
||||
mock_get_phone_url.assert_called_once()
|
||||
mock_http_request.assert_called_once_with(
|
||||
data='{"password": "fake_password"}',
|
||||
url=fake_phone_url)
|
||||
|
||||
self.assertEqual(response, fake_response)
|
||||
|
||||
def test_post_password(self):
|
||||
self._test_post_password()
|
||||
|
||||
def test_post_password_with_failure(self):
|
||||
self._test_post_password(fail=True)
|
||||
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_phone_home_url")
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._http_request")
|
||||
def _test_provisioning_completed(self, mock_http_request,
|
||||
mock_get_phone_url, mock_sleep,
|
||||
fail=False):
|
||||
fake_phone_url = 'fake_phone_url'
|
||||
fake_response = 'fake_response'
|
||||
|
||||
if fail:
|
||||
mock_http_request.side_effect = (
|
||||
error.HTTPError(401, "invalid", {}, 0, 0))
|
||||
with self.assertRaises(exception.MetadataEndpointException):
|
||||
self._packet_service.provisioning_completed()
|
||||
else:
|
||||
mock_http_request.return_value = fake_response
|
||||
mock_get_phone_url.return_value = fake_phone_url
|
||||
|
||||
response = self._packet_service.provisioning_completed()
|
||||
mock_get_phone_url.assert_called_once()
|
||||
mock_http_request.assert_called_once_with(
|
||||
url=fake_phone_url,
|
||||
method="post")
|
||||
|
||||
self.assertEqual(response, fake_response)
|
||||
|
||||
def test_provisioning_completed(self):
|
||||
self._test_provisioning_completed()
|
||||
|
||||
def test_provisioning_completed_with_failure(self):
|
||||
self._test_provisioning_completed(fail=True)
|
||||
|
@ -338,7 +338,9 @@ Capabilities:
|
||||
* instance id
|
||||
* hostname
|
||||
* public keys
|
||||
* post admin user password (only once)
|
||||
* user data
|
||||
* call home on successful provision
|
||||
|
||||
Config options for `packet` section:
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user