Add support for packet json metadata service
Packet is a NYC-based infrastructure startup, focused on reinventing how SaaS/PaaS companies go global with premium bare metal and container hosting. This commit adds the following features: - ssh keys addition - userdata execution - hostname setting Partially implements: blueprint packet-metadata-suport Co-Authored-By: Alexandru Coman <acoman@cloudbasesolutions.com> Co-Authored-By: Stefan Caraiman <scaraiman@cloudbasesolutions.com> Co-Authored-By: Paula Madalina Crismaru <pcrismaru@cloudbasesolutions.com> Change-Id: I80b4f8b91fd8ef2b63735d5569a8009e34cee759
This commit is contained in:
parent
8a03b4700e
commit
526d939240
@ -23,6 +23,7 @@ _OPT_PATHS = (
|
||||
'cloudbaseinit.conf.openstack.OpenStackOptions',
|
||||
'cloudbaseinit.conf.azure.AzureOptions',
|
||||
'cloudbaseinit.conf.ovf.OvfOptions',
|
||||
'cloudbaseinit.conf.packet.PacketOptions',
|
||||
)
|
||||
|
||||
|
||||
|
50
cloudbaseinit/conf/packet.py
Normal file
50
cloudbaseinit/conf/packet.py
Normal file
@ -0,0 +1,50 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Config options available for the Packet metadata service."""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from cloudbaseinit.conf import base as conf_base
|
||||
|
||||
|
||||
class PacketOptions(conf_base.Options):
|
||||
|
||||
"""Config options available for the Packet metadata service."""
|
||||
|
||||
def __init__(self, config):
|
||||
super(PacketOptions, self).__init__(config, group="packet")
|
||||
self._options = [
|
||||
cfg.StrOpt(
|
||||
'metadata_base_url', default="https://metadata.packet.net/",
|
||||
help='The URL where the service looks for metadata'),
|
||||
cfg.BoolOpt(
|
||||
"https_allow_insecure", default=False,
|
||||
help="Whether to disable the validation of HTTPS "
|
||||
"certificates."),
|
||||
cfg.StrOpt(
|
||||
"https_ca_bundle", default=None,
|
||||
help="The path to a CA_BUNDLE file or directory with "
|
||||
"certificates of trusted CAs."),
|
||||
]
|
||||
|
||||
def register(self):
|
||||
"""Register the current options to the global ConfigOpts object."""
|
||||
group = cfg.OptGroup(self.group_name, title='Packet Options')
|
||||
self._config.register_group(group)
|
||||
self._config.register_opts(self._options, group=group)
|
||||
|
||||
def list(self):
|
||||
"""Return a list which contains all the available options."""
|
||||
return self._options
|
85
cloudbaseinit/metadata/services/packet.py
Normal file
85
cloudbaseinit/metadata/services/packet.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Metadata Service for Packet."""
|
||||
|
||||
import json
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit.metadata.services import base
|
||||
from oslo_log import log as oslo_logging
|
||||
|
||||
CONF = cloudbaseinit_conf.CONF
|
||||
LOG = oslo_logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PacketService(base.BaseHTTPMetadataService):
|
||||
|
||||
"""Metadata Service for Packet.
|
||||
|
||||
Packet is a NYC-based infrastructure startup, focused on reinventing
|
||||
how SaaS/PaaS companies go global with premium bare metal and container
|
||||
hosting.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PacketService, self).__init__(
|
||||
base_url=CONF.packet.metadata_base_url,
|
||||
https_allow_insecure=CONF.packet.https_allow_insecure,
|
||||
https_ca_bundle=CONF.packet.https_ca_bundle)
|
||||
|
||||
self._enable_retry = True
|
||||
|
||||
def _get_meta_data(self):
|
||||
data = self._get_cache_data("metadata", decode=True)
|
||||
if data:
|
||||
return json.loads(data)
|
||||
|
||||
def load(self):
|
||||
"""Load all the available information from the metadata service."""
|
||||
super(PacketService, self).load()
|
||||
try:
|
||||
self._get_meta_data()
|
||||
return True
|
||||
except Exception:
|
||||
LOG.debug('Metadata not found at URL \'%s\'' %
|
||||
CONF.packet.metadata_base_url)
|
||||
return False
|
||||
|
||||
def get_instance_id(self):
|
||||
"""Get the identifier for the current instance.
|
||||
|
||||
The instance identifier provides an unique way to address an
|
||||
instance into the current metadata provider.
|
||||
"""
|
||||
return self._get_meta_data().get("id")
|
||||
|
||||
def get_host_name(self):
|
||||
"""Get the hostname for the current instance.
|
||||
|
||||
The hostname is the label assigned to the current instance used to
|
||||
identify it in various forms of electronic communication.
|
||||
"""
|
||||
return self._get_meta_data().get("hostname")
|
||||
|
||||
def get_public_keys(self):
|
||||
"""Get a list of space-stripped strings as public keys."""
|
||||
meta_data = self._get_meta_data()
|
||||
ssh_keys = meta_data.get("ssh_keys")
|
||||
if not ssh_keys:
|
||||
return []
|
||||
return list(set((key.strip() for key in ssh_keys)))
|
||||
|
||||
def get_user_data(self):
|
||||
"""Get the available user data for the current instance."""
|
||||
return self._get_cache_data("userdata")
|
123
cloudbaseinit/tests/metadata/services/test_packet.py
Normal file
123
cloudbaseinit/tests/metadata/services/test_packet.py
Normal file
@ -0,0 +1,123 @@
|
||||
# Copyright 2017 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import importlib
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import unittest.mock as mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit.tests import testutils
|
||||
|
||||
|
||||
CONF = cloudbaseinit_conf.CONF
|
||||
BASE_MODULE_PATH = ("cloudbaseinit.metadata.services.base."
|
||||
"BaseHTTPMetadataService")
|
||||
MODULE_PATH = "cloudbaseinit.metadata.services.packet"
|
||||
|
||||
|
||||
class PacketServiceTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._win32com_mock = mock.MagicMock()
|
||||
self._ctypes_mock = mock.MagicMock()
|
||||
self._ctypes_util_mock = mock.MagicMock()
|
||||
self._win32com_client_mock = mock.MagicMock()
|
||||
self._pywintypes_mock = mock.MagicMock()
|
||||
self._module_patcher = mock.patch.dict(
|
||||
'sys.modules',
|
||||
{'win32com': self._win32com_mock,
|
||||
'ctypes': self._ctypes_mock,
|
||||
'ctypes.util': self._ctypes_util_mock,
|
||||
'win32com.client': self._win32com_client_mock,
|
||||
'pywintypes': self._pywintypes_mock})
|
||||
self._module_patcher.start()
|
||||
self.addCleanup(self._module_patcher.stop)
|
||||
|
||||
self._packet_module = importlib.import_module(MODULE_PATH)
|
||||
self._packet_service = self._packet_module.PacketService()
|
||||
self.snatcher = testutils.LogSnatcher(MODULE_PATH)
|
||||
|
||||
@mock.patch(MODULE_PATH + ".PacketService._get_cache_data")
|
||||
def test_get_meta_data(self, mock_get_cache_data):
|
||||
mock_get_cache_data.return_value = '{"fake": "data"}'
|
||||
response = self._packet_service._get_meta_data()
|
||||
mock_get_cache_data.assert_called_with("metadata", decode=True)
|
||||
self.assertEqual({"fake": "data"}, response)
|
||||
|
||||
@mock.patch(BASE_MODULE_PATH + ".load")
|
||||
@mock.patch(MODULE_PATH + ".PacketService._get_cache_data")
|
||||
def test_load(self, mock_get_cache_data, mock_load):
|
||||
mock_get_cache_data.return_value = '{"fake": "data"}'
|
||||
self.assertTrue(self._packet_service.load())
|
||||
|
||||
@mock.patch(BASE_MODULE_PATH + ".load")
|
||||
@mock.patch(MODULE_PATH + ".PacketService._get_cache_data")
|
||||
def test_load_fails(self, mock_get_cache_data, mock_load):
|
||||
with testutils.LogSnatcher(MODULE_PATH) as snatcher:
|
||||
self.assertFalse(self._packet_service.load())
|
||||
self.assertEqual(snatcher.output,
|
||||
['Metadata not found at URL \'%s\'' %
|
||||
CONF.packet.metadata_base_url])
|
||||
|
||||
@mock.patch(MODULE_PATH + ".PacketService._get_meta_data")
|
||||
def test_get_instance_id(self, mock_get_meta_data):
|
||||
response = self._packet_service.get_instance_id()
|
||||
mock_get_meta_data.assert_called_once_with()
|
||||
mock_get_meta_data().get.assert_called_once_with('id')
|
||||
self.assertEqual(mock_get_meta_data.return_value.get.return_value,
|
||||
response)
|
||||
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_meta_data")
|
||||
def test_get_host_name(self, mock_get_meta_data):
|
||||
response = self._packet_service.get_host_name()
|
||||
mock_get_meta_data.assert_called_once_with()
|
||||
mock_get_meta_data().get.assert_called_once_with('hostname')
|
||||
self.assertEqual(mock_get_meta_data.return_value.get.return_value,
|
||||
response)
|
||||
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_meta_data")
|
||||
def _test_get_public_keys(self, mock_get_meta_data,
|
||||
public_keys):
|
||||
mock_get_meta_data.return_value = {
|
||||
"ssh_keys": public_keys
|
||||
}
|
||||
response = self._packet_service.get_public_keys()
|
||||
mock_get_meta_data.assert_called_once_with()
|
||||
|
||||
if public_keys:
|
||||
public_keys = list(set((key.strip() for key in public_keys)))
|
||||
else:
|
||||
public_keys = []
|
||||
|
||||
self.assertEqual(sorted(public_keys),
|
||||
sorted(response))
|
||||
|
||||
def test_get_public_keys(self):
|
||||
self._test_get_public_keys(public_keys=["fake keys"] * 3)
|
||||
|
||||
def test_get_public_keys_empty(self):
|
||||
self._test_get_public_keys(public_keys=None)
|
||||
|
||||
@mock.patch(MODULE_PATH +
|
||||
".PacketService._get_cache_data")
|
||||
def test_get_user_data(self, mock_get_cache_data):
|
||||
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)
|
Loading…
x
Reference in New Issue
Block a user