-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
3 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
90
cloudbaseinit/tests/metadata/services/test_nocloudservice.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters