Add NoCloudConfigDriveService metadata provider
Add support for NoCloud metadata provider, where the metadata is provided on a config-drive (vfat or iso9660) with the label cidata or CIDATA. The folder structure for NoCloud is: * /user-data * /meta-data The user-data and meta-data files respect the EC2 metadata service format. Supported features for the NoCloud metadata service: * instance id * hostname * plublic keys * static network configuration (Debian format) * user data More information: cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html Change-Id: Ib434cf2b2b21bf9faa58e05ba40eb0135385c9ea Implements: blueprint nocloud-metadata-support
This commit is contained in:
parent
fcb68a4dc7
commit
4b0d94cd0f
70
cloudbaseinit/metadata/services/nocloudservice.py
Normal file
70
cloudbaseinit/metadata/services/nocloudservice.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright 2020 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.
|
||||
|
||||
from oslo_log import log as oslo_logging
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit.metadata.services import base
|
||||
from cloudbaseinit.metadata.services import baseconfigdrive
|
||||
from cloudbaseinit.utils import debiface
|
||||
from cloudbaseinit.utils import serialization
|
||||
|
||||
|
||||
CONF = cloudbaseinit_conf.CONF
|
||||
LOG = oslo_logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
|
||||
|
||||
def __init__(self):
|
||||
super(NoCloudConfigDriveService, self).__init__(
|
||||
'cidata', 'meta-data')
|
||||
self._meta_data = {}
|
||||
|
||||
def get_user_data(self):
|
||||
return self._get_cache_data("user-data")
|
||||
|
||||
def _get_meta_data(self):
|
||||
if self._meta_data:
|
||||
return self._meta_data
|
||||
|
||||
raw_meta_data = self._get_cache_data("meta-data", decode=True)
|
||||
try:
|
||||
self._meta_data = (
|
||||
serialization.parse_json_yaml(raw_meta_data))
|
||||
except base.YamlParserConfigError as ex:
|
||||
LOG.error("Metadata could not be parsed")
|
||||
LOG.exception(ex)
|
||||
|
||||
return self._meta_data
|
||||
|
||||
def get_host_name(self):
|
||||
return self._get_meta_data().get('local-hostname')
|
||||
|
||||
def get_instance_id(self):
|
||||
return self._get_meta_data().get('instance-id')
|
||||
|
||||
def get_public_keys(self):
|
||||
raw_ssh_keys = self._get_meta_data().get('public-keys')
|
||||
if not raw_ssh_keys:
|
||||
return []
|
||||
|
||||
return [raw_ssh_keys[key].get('openssh-key') for key in raw_ssh_keys]
|
||||
|
||||
def get_network_details(self):
|
||||
debian_net_config = self._get_meta_data().get('network-interfaces')
|
||||
if not debian_net_config:
|
||||
return None
|
||||
|
||||
return debiface.parse(debian_net_config)
|
90
cloudbaseinit/tests/metadata/services/test_nocloudservice.py
Normal file
90
cloudbaseinit/tests/metadata/services/test_nocloudservice.py
Normal file
@ -0,0 +1,90 @@
|
||||
# Copyright 2020 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 os
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import unittest.mock as mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from cloudbaseinit.tests import testutils
|
||||
|
||||
MODULE_PATH = "cloudbaseinit.metadata.services.nocloudservice"
|
||||
|
||||
|
||||
class TestNoCloudConfigDriveService(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.configdrive_module = importlib.import_module(MODULE_PATH)
|
||||
self._config_drive = (
|
||||
self.configdrive_module.NoCloudConfigDriveService())
|
||||
self.snatcher = testutils.LogSnatcher(MODULE_PATH)
|
||||
|
||||
@mock.patch('os.path.normpath')
|
||||
@mock.patch('os.path.join')
|
||||
def test_get_data(self, mock_join, mock_normpath):
|
||||
fake_path = os.path.join('fake', 'path')
|
||||
with mock.patch('six.moves.builtins.open',
|
||||
mock.mock_open(read_data='fake data'), create=True):
|
||||
response = self._config_drive._get_data(fake_path)
|
||||
self.assertEqual('fake data', response)
|
||||
mock_join.assert_called_with(
|
||||
self._config_drive._metadata_path, fake_path)
|
||||
mock_normpath.assert_called_once_with(mock_join.return_value)
|
||||
|
||||
@mock.patch('shutil.rmtree')
|
||||
def test_cleanup(self, mock_rmtree):
|
||||
fake_path = os.path.join('fake', 'path')
|
||||
self._config_drive._metadata_path = fake_path
|
||||
mock_mgr = mock.Mock()
|
||||
self._config_drive._mgr = mock_mgr
|
||||
mock_mgr.target_path = fake_path
|
||||
self._config_drive.cleanup()
|
||||
mock_rmtree.assert_called_once_with(fake_path,
|
||||
ignore_errors=True)
|
||||
self.assertEqual(None, self._config_drive._metadata_path)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.NoCloudConfigDriveService._get_meta_data')
|
||||
def test_get_public_keys(self, mock_get_metadata):
|
||||
fake_key = 'fake key'
|
||||
expected_result = [fake_key]
|
||||
mock_get_metadata.return_value = {
|
||||
'public-keys': {
|
||||
'0': {
|
||||
'openssh-key': fake_key
|
||||
}
|
||||
}
|
||||
}
|
||||
result = self._config_drive.get_public_keys()
|
||||
self.assertEqual(result, expected_result)
|
@ -113,6 +113,61 @@ Config options for `config_drive` section:
|
||||
* locations (list: ["cdrom", "hdd", "partition"])
|
||||
|
||||
|
||||
.. _nocloudconfigdrive:
|
||||
|
||||
NoCloud configuration drive
|
||||
-------------------------------
|
||||
|
||||
.. class:: cloudbaseinit.metadata.services.nocloudservice.NoCloudConfigDriveService
|
||||
|
||||
NoCloudConfigDriveService is similar to OpenStack config drive metadata in terms of
|
||||
the medium on which the data is provided (as an attached ISO, partition or disk) and
|
||||
similar to the EC2 metadata in terms of how the metadata files are named and structured.
|
||||
|
||||
The metadata is provided on a config-drive (vfat or iso9660) with the label cidata or CIDATA.
|
||||
|
||||
The folder structure for NoCloud is:
|
||||
|
||||
* /user-data
|
||||
* /meta-data
|
||||
|
||||
The user-data and meta-data files respect the EC2 metadata service format.
|
||||
|
||||
Capabilities:
|
||||
|
||||
* instance id
|
||||
* hostname
|
||||
* public keys
|
||||
* static network configuration (Debian format)
|
||||
* user data
|
||||
|
||||
Config options for `config_drive` section:
|
||||
|
||||
* raw_hdd (bool: True)
|
||||
* cdrom (bool: True)
|
||||
* vfat (bool: True)
|
||||
* types (list: ["vfat", "iso"])
|
||||
* locations (list: ["cdrom", "hdd", "partition"])
|
||||
|
||||
Example metadata:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
instance-id: windows1
|
||||
network-interfaces: |
|
||||
iface Ethernet0 inet static
|
||||
address 10.0.0.2
|
||||
network 10.0.0.0
|
||||
netmask 255.255.255.0
|
||||
broadcast 10.0.0.255
|
||||
gateway 10.0.0.1
|
||||
hwaddress ether 00:11:22:33:44:55
|
||||
hostname: windowshost1
|
||||
|
||||
|
||||
More information on the NoCloud metadata service specifications can be found
|
||||
`here <https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html>`_.
|
||||
|
||||
Amazon EC2
|
||||
----------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user