Skip to content

Commit

Permalink
Add NoCloudConfigDriveService metadata provider
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ader1990 committed Apr 3, 2020
1 parent fcb68a4 commit 4b0d94c
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
70 changes: 70 additions & 0 deletions cloudbaseinit/metadata/services/nocloudservice.py
Original file line number Diff line number Diff line change
@@ -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 changes: 90 additions & 0 deletions cloudbaseinit/tests/metadata/services/test_nocloudservice.py
Original file line number Diff line number Diff line change
@@ -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)
55 changes: 55 additions & 0 deletions doc/source/services.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------

Expand Down

0 comments on commit 4b0d94c

Please sign in to comment.