Skip to content

Commit 4b0d94c

Browse files
committed
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
1 parent fcb68a4 commit 4b0d94c

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2020 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from oslo_log import log as oslo_logging
16+
17+
from cloudbaseinit import conf as cloudbaseinit_conf
18+
from cloudbaseinit.metadata.services import base
19+
from cloudbaseinit.metadata.services import baseconfigdrive
20+
from cloudbaseinit.utils import debiface
21+
from cloudbaseinit.utils import serialization
22+
23+
24+
CONF = cloudbaseinit_conf.CONF
25+
LOG = oslo_logging.getLogger(__name__)
26+
27+
28+
class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
29+
30+
def __init__(self):
31+
super(NoCloudConfigDriveService, self).__init__(
32+
'cidata', 'meta-data')
33+
self._meta_data = {}
34+
35+
def get_user_data(self):
36+
return self._get_cache_data("user-data")
37+
38+
def _get_meta_data(self):
39+
if self._meta_data:
40+
return self._meta_data
41+
42+
raw_meta_data = self._get_cache_data("meta-data", decode=True)
43+
try:
44+
self._meta_data = (
45+
serialization.parse_json_yaml(raw_meta_data))
46+
except base.YamlParserConfigError as ex:
47+
LOG.error("Metadata could not be parsed")
48+
LOG.exception(ex)
49+
50+
return self._meta_data
51+
52+
def get_host_name(self):
53+
return self._get_meta_data().get('local-hostname')
54+
55+
def get_instance_id(self):
56+
return self._get_meta_data().get('instance-id')
57+
58+
def get_public_keys(self):
59+
raw_ssh_keys = self._get_meta_data().get('public-keys')
60+
if not raw_ssh_keys:
61+
return []
62+
63+
return [raw_ssh_keys[key].get('openssh-key') for key in raw_ssh_keys]
64+
65+
def get_network_details(self):
66+
debian_net_config = self._get_meta_data().get('network-interfaces')
67+
if not debian_net_config:
68+
return None
69+
70+
return debiface.parse(debian_net_config)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright 2020 Cloudbase Solutions Srl
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
16+
import importlib
17+
import os
18+
import unittest
19+
20+
try:
21+
import unittest.mock as mock
22+
except ImportError:
23+
import mock
24+
25+
from cloudbaseinit.tests import testutils
26+
27+
MODULE_PATH = "cloudbaseinit.metadata.services.nocloudservice"
28+
29+
30+
class TestNoCloudConfigDriveService(unittest.TestCase):
31+
32+
def setUp(self):
33+
self._win32com_mock = mock.MagicMock()
34+
self._ctypes_mock = mock.MagicMock()
35+
self._ctypes_util_mock = mock.MagicMock()
36+
self._win32com_client_mock = mock.MagicMock()
37+
self._pywintypes_mock = mock.MagicMock()
38+
39+
self._module_patcher = mock.patch.dict(
40+
'sys.modules',
41+
{'win32com': self._win32com_mock,
42+
'ctypes': self._ctypes_mock,
43+
'ctypes.util': self._ctypes_util_mock,
44+
'win32com.client': self._win32com_client_mock,
45+
'pywintypes': self._pywintypes_mock})
46+
self._module_patcher.start()
47+
self.addCleanup(self._module_patcher.stop)
48+
49+
self.configdrive_module = importlib.import_module(MODULE_PATH)
50+
self._config_drive = (
51+
self.configdrive_module.NoCloudConfigDriveService())
52+
self.snatcher = testutils.LogSnatcher(MODULE_PATH)
53+
54+
@mock.patch('os.path.normpath')
55+
@mock.patch('os.path.join')
56+
def test_get_data(self, mock_join, mock_normpath):
57+
fake_path = os.path.join('fake', 'path')
58+
with mock.patch('six.moves.builtins.open',
59+
mock.mock_open(read_data='fake data'), create=True):
60+
response = self._config_drive._get_data(fake_path)
61+
self.assertEqual('fake data', response)
62+
mock_join.assert_called_with(
63+
self._config_drive._metadata_path, fake_path)
64+
mock_normpath.assert_called_once_with(mock_join.return_value)
65+
66+
@mock.patch('shutil.rmtree')
67+
def test_cleanup(self, mock_rmtree):
68+
fake_path = os.path.join('fake', 'path')
69+
self._config_drive._metadata_path = fake_path
70+
mock_mgr = mock.Mock()
71+
self._config_drive._mgr = mock_mgr
72+
mock_mgr.target_path = fake_path
73+
self._config_drive.cleanup()
74+
mock_rmtree.assert_called_once_with(fake_path,
75+
ignore_errors=True)
76+
self.assertEqual(None, self._config_drive._metadata_path)
77+
78+
@mock.patch(MODULE_PATH + '.NoCloudConfigDriveService._get_meta_data')
79+
def test_get_public_keys(self, mock_get_metadata):
80+
fake_key = 'fake key'
81+
expected_result = [fake_key]
82+
mock_get_metadata.return_value = {
83+
'public-keys': {
84+
'0': {
85+
'openssh-key': fake_key
86+
}
87+
}
88+
}
89+
result = self._config_drive.get_public_keys()
90+
self.assertEqual(result, expected_result)

doc/source/services.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,61 @@ Config options for `config_drive` section:
113113
* locations (list: ["cdrom", "hdd", "partition"])
114114

115115

116+
.. _nocloudconfigdrive:
117+
118+
NoCloud configuration drive
119+
-------------------------------
120+
121+
.. class:: cloudbaseinit.metadata.services.nocloudservice.NoCloudConfigDriveService
122+
123+
NoCloudConfigDriveService is similar to OpenStack config drive metadata in terms of
124+
the medium on which the data is provided (as an attached ISO, partition or disk) and
125+
similar to the EC2 metadata in terms of how the metadata files are named and structured.
126+
127+
The metadata is provided on a config-drive (vfat or iso9660) with the label cidata or CIDATA.
128+
129+
The folder structure for NoCloud is:
130+
131+
* /user-data
132+
* /meta-data
133+
134+
The user-data and meta-data files respect the EC2 metadata service format.
135+
136+
Capabilities:
137+
138+
* instance id
139+
* hostname
140+
* public keys
141+
* static network configuration (Debian format)
142+
* user data
143+
144+
Config options for `config_drive` section:
145+
146+
* raw_hdd (bool: True)
147+
* cdrom (bool: True)
148+
* vfat (bool: True)
149+
* types (list: ["vfat", "iso"])
150+
* locations (list: ["cdrom", "hdd", "partition"])
151+
152+
Example metadata:
153+
154+
.. code-block:: yaml
155+
156+
instance-id: windows1
157+
network-interfaces: |
158+
iface Ethernet0 inet static
159+
address 10.0.0.2
160+
network 10.0.0.0
161+
netmask 255.255.255.0
162+
broadcast 10.0.0.255
163+
gateway 10.0.0.1
164+
hwaddress ether 00:11:22:33:44:55
165+
hostname: windowshost1
166+
167+
168+
More information on the NoCloud metadata service specifications can be found
169+
`here <https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html>`_.
170+
116171
Amazon EC2
117172
----------
118173

0 commit comments

Comments
 (0)