2014-04-04 17:31:23 +04:00
|
|
|
# Copyright 2011 Justin Santa Barbara
|
|
|
|
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2022-06-17 09:34:07 +02:00
|
|
|
import base64
|
2014-12-29 14:17:41 +02:00
|
|
|
import errno
|
2015-03-09 12:23:12 +00:00
|
|
|
import glob
|
2016-05-30 17:39:13 +01:00
|
|
|
import io
|
2014-04-04 17:31:23 +04:00
|
|
|
import os
|
2015-05-04 10:11:57 +00:00
|
|
|
import shutil
|
2016-05-30 17:39:13 +01:00
|
|
|
import subprocess
|
|
|
|
import tarfile
|
2014-04-04 17:31:23 +04:00
|
|
|
import tempfile
|
2020-04-06 12:28:30 +02:00
|
|
|
from unittest import mock
|
2014-04-04 17:31:23 +04:00
|
|
|
|
2016-07-27 16:28:43 -04:00
|
|
|
from ironic_lib import utils as ironic_utils
|
2014-12-01 18:17:35 +02:00
|
|
|
from oslo_concurrency import processutils
|
2021-02-12 17:00:40 +01:00
|
|
|
import requests
|
2017-02-16 09:46:08 -08:00
|
|
|
import testtools
|
2014-12-01 18:17:35 +02:00
|
|
|
|
2015-03-09 12:23:12 +00:00
|
|
|
from ironic_python_agent import errors
|
2019-02-06 19:12:57 +01:00
|
|
|
from ironic_python_agent import hardware
|
2017-04-03 15:32:44 +10:00
|
|
|
from ironic_python_agent.tests.unit import base as ironic_agent_base
|
2014-04-04 17:31:23 +04:00
|
|
|
from ironic_python_agent import utils
|
|
|
|
|
|
|
|
|
2017-05-17 11:00:38 -07:00
|
|
|
class ExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
|
|
|
# This test case does call utils.execute(), so don't block access to the
|
|
|
|
# execute calls.
|
|
|
|
block_execute = False
|
2014-04-04 17:31:23 +04:00
|
|
|
|
2017-05-17 11:00:38 -07:00
|
|
|
# We do mock out the call to ironic_utils.execute() so we don't actually
|
|
|
|
# 'execute' anything, as utils.execute() calls ironic_utils.execute()
|
2016-07-27 16:28:43 -04:00
|
|
|
@mock.patch.object(ironic_utils, 'execute', autospec=True)
|
|
|
|
def test_execute(self, mock_execute):
|
2014-04-04 17:31:23 +04:00
|
|
|
utils.execute('/usr/bin/env', 'false', check_exit_code=False)
|
2016-07-27 16:28:43 -04:00
|
|
|
mock_execute.assert_called_once_with('/usr/bin/env', 'false',
|
|
|
|
check_exit_code=False)
|
2015-03-09 12:23:12 +00:00
|
|
|
|
|
|
|
|
2017-04-03 15:32:44 +10:00
|
|
|
class GetAgentParamsTestCase(ironic_agent_base.IronicAgentTest):
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch('oslo_log.log.getLogger', autospec=True)
|
2019-11-28 17:10:40 +01:00
|
|
|
@mock.patch('builtins.open', autospec=True)
|
2015-03-09 12:23:12 +00:00
|
|
|
def test__read_params_from_file_fail(self, logger_mock, open_mock):
|
|
|
|
open_mock.side_effect = Exception
|
|
|
|
params = utils._read_params_from_file('file-path')
|
2016-01-12 09:03:19 +00:00
|
|
|
self.assertEqual({}, params)
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2019-11-28 17:10:40 +01:00
|
|
|
@mock.patch('builtins.open', autospec=True)
|
2015-03-09 12:23:12 +00:00
|
|
|
def test__read_params_from_file(self, open_mock):
|
|
|
|
kernel_line = 'api-url=http://localhost:9999 baz foo=bar\n'
|
|
|
|
open_mock.return_value.__enter__ = lambda s: s
|
|
|
|
open_mock.return_value.__exit__ = mock.Mock()
|
|
|
|
read_mock = open_mock.return_value.read
|
|
|
|
read_mock.return_value = kernel_line
|
|
|
|
params = utils._read_params_from_file('file-path')
|
|
|
|
open_mock.assert_called_once_with('file-path')
|
|
|
|
read_mock.assert_called_once_with()
|
2016-01-12 09:03:19 +00:00
|
|
|
self.assertEqual('http://localhost:9999', params['api-url'])
|
|
|
|
self.assertEqual('bar', params['foo'])
|
2016-06-16 06:40:03 -04:00
|
|
|
self.assertNotIn('baz', params)
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_set_cached_params', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_read_params_from_file', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_get_cached_params', autospec=True)
|
2015-03-16 01:20:02 -07:00
|
|
|
def test_get_agent_params_kernel_cmdline(self, get_cache_mock,
|
|
|
|
read_params_mock,
|
|
|
|
set_cache_mock):
|
|
|
|
get_cache_mock.return_value = {}
|
2015-03-09 12:23:12 +00:00
|
|
|
expected_params = {'a': 'b'}
|
|
|
|
read_params_mock.return_value = expected_params
|
|
|
|
returned_params = utils.get_agent_params()
|
|
|
|
read_params_mock.assert_called_once_with('/proc/cmdline')
|
|
|
|
self.assertEqual(expected_params, returned_params)
|
2015-03-16 01:20:02 -07:00
|
|
|
set_cache_mock.assert_called_once_with(expected_params)
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_set_cached_params', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_get_vmedia_params', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_read_params_from_file', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_get_cached_params', autospec=True)
|
2015-03-16 01:20:02 -07:00
|
|
|
def test_get_agent_params_vmedia(self, get_cache_mock,
|
|
|
|
read_params_mock,
|
|
|
|
get_vmedia_params_mock,
|
|
|
|
set_cache_mock):
|
|
|
|
get_cache_mock.return_value = {}
|
2015-03-09 12:23:12 +00:00
|
|
|
kernel_params = {'boot_method': 'vmedia'}
|
|
|
|
vmedia_params = {'a': 'b'}
|
2018-05-03 08:04:20 -07:00
|
|
|
expected_params = dict(
|
|
|
|
list(kernel_params.items()) + list(vmedia_params.items()))
|
2015-03-09 12:23:12 +00:00
|
|
|
read_params_mock.return_value = kernel_params
|
|
|
|
get_vmedia_params_mock.return_value = vmedia_params
|
|
|
|
|
|
|
|
returned_params = utils.get_agent_params()
|
|
|
|
read_params_mock.assert_called_once_with('/proc/cmdline')
|
|
|
|
self.assertEqual(expected_params, returned_params)
|
2015-03-16 01:20:02 -07:00
|
|
|
# Make sure information is cached
|
|
|
|
set_cache_mock.assert_called_once_with(expected_params)
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_set_cached_params', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_get_cached_params', autospec=True)
|
2015-03-16 10:59:40 -07:00
|
|
|
def test_get_agent_params_from_cache(self, get_cache_mock,
|
|
|
|
set_cache_mock):
|
2015-03-16 01:20:02 -07:00
|
|
|
get_cache_mock.return_value = {'a': 'b'}
|
|
|
|
returned_params = utils.get_agent_params()
|
|
|
|
expected_params = {'a': 'b'}
|
|
|
|
self.assertEqual(expected_params, returned_params)
|
2015-03-16 10:59:40 -07:00
|
|
|
self.assertEqual(0, set_cache_mock.call_count)
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2019-11-28 17:10:40 +01:00
|
|
|
@mock.patch('builtins.open', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(glob, 'glob', autospec=True)
|
2015-03-09 12:23:12 +00:00
|
|
|
def test__get_vmedia_device(self, glob_mock, open_mock):
|
|
|
|
|
|
|
|
glob_mock.return_value = ['/sys/class/block/sda/device/model',
|
|
|
|
'/sys/class/block/sdb/device/model',
|
|
|
|
'/sys/class/block/sdc/device/model']
|
|
|
|
fobj_mock = mock.MagicMock()
|
2015-07-15 16:08:10 +01:00
|
|
|
mock_file_handle = mock.MagicMock()
|
2015-03-09 12:23:12 +00:00
|
|
|
mock_file_handle.__enter__.return_value = fobj_mock
|
|
|
|
open_mock.return_value = mock_file_handle
|
|
|
|
|
|
|
|
fobj_mock.read.side_effect = ['scsi disk', Exception, 'Virtual Media']
|
|
|
|
vmedia_device_returned = utils._get_vmedia_device()
|
|
|
|
self.assertEqual('sdc', vmedia_device_returned)
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test__find_vmedia_device_by_labels_handles_exec_error(self,
|
|
|
|
execute_mock):
|
|
|
|
execute_mock.side_effect = processutils.ProcessExecutionError
|
|
|
|
self.assertIsNone(utils._find_vmedia_device_by_labels(['l1', 'l2']))
|
2021-03-30 12:21:00 +02:00
|
|
|
execute_mock.assert_called_once_with('lsblk', '-p', '-P',
|
|
|
|
'-oKNAME,LABEL')
|
2015-03-12 04:14:11 +00:00
|
|
|
|
2020-11-18 16:48:27 +01:00
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test__find_vmedia_device_by_labels(self, execute_mock):
|
|
|
|
# NOTE(TheJulia): Case is intentionally mixed here to ensure
|
|
|
|
# proper matching occurs
|
2021-03-30 12:21:00 +02:00
|
|
|
disk_list = ('KNAME="/dev/sda" LABEL=""\n'
|
|
|
|
'KNAME="/dev/sda2" LABEL="Meow"\n'
|
|
|
|
'KNAME="/dev/sda3" LABEL="Recovery HD"\n'
|
|
|
|
'KNAME="/dev/sda1" LABEL="EFI"\n'
|
|
|
|
'KNAME="/dev/sdb" LABEL=""\n'
|
|
|
|
'KNAME="/dev/sdb1" LABEL=""\n'
|
|
|
|
'KNAME="/dev/sdb2" LABEL=""\n'
|
|
|
|
'KNAME="/dev/sdc" LABEL="meow"\n')
|
2021-03-25 13:02:04 -07:00
|
|
|
invalid_disk = ('KNAME="sda1" SIZE="1610612736" TYPE="part" TRAN=""\n'
|
|
|
|
'KNAME="sda" SIZE="1610612736" TYPE="disk" '
|
|
|
|
'TRAN="sata"\n')
|
|
|
|
valid_disk = ('KNAME="sdc" SIZE="1610612736" TYPE="disk" TRAN="usb"\n')
|
2020-11-18 16:48:27 +01:00
|
|
|
execute_mock.side_effect = [
|
2021-03-25 13:02:04 -07:00
|
|
|
(disk_list, ''),
|
|
|
|
(invalid_disk, ''),
|
|
|
|
(valid_disk, ''),
|
2020-11-18 16:48:27 +01:00
|
|
|
]
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertEqual('/dev/sdc',
|
|
|
|
utils._find_vmedia_device_by_labels(['cat', 'meOw']))
|
2020-11-18 16:48:27 +01:00
|
|
|
execute_mock.assert_has_calls([
|
2021-03-30 12:21:00 +02:00
|
|
|
mock.call('lsblk', '-p', '-P', '-oKNAME,LABEL'),
|
2021-03-25 13:02:04 -07:00
|
|
|
mock.call('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE', '/dev/sda2'),
|
|
|
|
mock.call('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE', '/dev/sdc'),
|
2020-11-18 16:48:27 +01:00
|
|
|
])
|
2015-03-12 04:14:11 +00:00
|
|
|
|
2020-11-18 16:48:27 +01:00
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test__find_vmedia_device_by_labels_not_found(self, execute_mock):
|
2021-03-30 12:21:00 +02:00
|
|
|
disk_list = ('KNAME="/dev/sdb" LABEL="evil"\n'
|
|
|
|
'KNAME="/dev/sdb1" LABEL="banana"\n'
|
|
|
|
'KNAME="/dev/sdb2" LABEL=""\n')
|
2021-03-25 13:02:04 -07:00
|
|
|
execute_mock.return_value = (disk_list, '')
|
|
|
|
self.assertIsNone(utils._find_vmedia_device_by_labels(['l1', 'l2']))
|
2021-03-30 12:21:00 +02:00
|
|
|
execute_mock.assert_called_once_with('lsblk', '-p', '-P',
|
|
|
|
'-oKNAME,LABEL')
|
2021-03-25 13:02:04 -07:00
|
|
|
|
|
|
|
@mock.patch.object(utils, '_check_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_find_vmedia_device_by_labels', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_read_params_from_file', autospec=True)
|
2021-03-22 14:43:56 +01:00
|
|
|
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test__get_vmedia_params(self, mount_mock, read_params_mock, find_mock,
|
|
|
|
check_vmedia_mock):
|
|
|
|
check_vmedia_mock.return_value = True
|
2020-11-18 16:48:27 +01:00
|
|
|
find_mock.return_value = '/dev/fake'
|
2021-03-22 14:43:56 +01:00
|
|
|
mount_mock.return_value.__enter__.return_value = '/tempdir'
|
2016-02-03 11:23:49 +09:00
|
|
|
expected_params = {'a': 'b'}
|
|
|
|
read_params_mock.return_value = expected_params
|
|
|
|
|
|
|
|
returned_params = utils._get_vmedia_params()
|
|
|
|
|
2021-03-22 14:43:56 +01:00
|
|
|
mount_mock.assert_called_once_with('/dev/fake')
|
2016-02-03 11:23:49 +09:00
|
|
|
read_params_mock.assert_called_once_with("/tempdir/parameters.txt")
|
|
|
|
self.assertEqual(expected_params, returned_params)
|
|
|
|
|
2021-03-25 13:02:04 -07:00
|
|
|
@mock.patch.object(utils, '_check_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_find_vmedia_device_by_labels', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_get_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_read_params_from_file', autospec=True)
|
2021-03-22 14:43:56 +01:00
|
|
|
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
|
|
|
def test__get_vmedia_params_by_device(self, mount_mock, read_params_mock,
|
2021-03-25 13:02:04 -07:00
|
|
|
get_device_mock, find_mock,
|
|
|
|
check_vmedia_mock):
|
|
|
|
check_vmedia_mock.return_value = True
|
2020-11-18 16:48:27 +01:00
|
|
|
find_mock.return_value = None
|
2021-03-22 14:43:56 +01:00
|
|
|
mount_mock.return_value.__enter__.return_value = '/tempdir'
|
2015-03-09 12:23:12 +00:00
|
|
|
expected_params = {'a': 'b'}
|
|
|
|
read_params_mock.return_value = expected_params
|
|
|
|
get_device_mock.return_value = "sda"
|
|
|
|
|
|
|
|
returned_params = utils._get_vmedia_params()
|
|
|
|
|
2021-03-22 14:43:56 +01:00
|
|
|
mount_mock.assert_called_once_with('/dev/sda')
|
2015-05-04 10:11:57 +00:00
|
|
|
read_params_mock.assert_called_once_with("/tempdir/parameters.txt")
|
2015-03-09 12:23:12 +00:00
|
|
|
self.assertEqual(expected_params, returned_params)
|
2021-03-25 13:02:04 -07:00
|
|
|
check_vmedia_mock.assert_called_with('/dev/sda')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, '_check_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_find_vmedia_device_by_labels', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_get_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_read_params_from_file', autospec=True)
|
|
|
|
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
|
|
|
def test__get_vmedia_params_by_device_device_invalid(
|
|
|
|
self, mount_mock, read_params_mock,
|
|
|
|
get_device_mock, find_mock,
|
|
|
|
check_vmedia_mock):
|
|
|
|
check_vmedia_mock.return_value = False
|
|
|
|
find_mock.return_value = None
|
|
|
|
expected_params = {}
|
|
|
|
read_params_mock.return_value = expected_params
|
|
|
|
get_device_mock.return_value = "sda"
|
|
|
|
|
|
|
|
returned_params = utils._get_vmedia_params()
|
|
|
|
|
|
|
|
mount_mock.assert_not_called()
|
|
|
|
read_params_mock.assert_not_called
|
|
|
|
self.assertEqual(expected_params, returned_params)
|
|
|
|
check_vmedia_mock.assert_called_with('/dev/sda')
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2021-03-25 13:02:04 -07:00
|
|
|
@mock.patch.object(utils, '_find_vmedia_device_by_labels', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, '_get_vmedia_device', autospec=True)
|
2020-11-18 16:48:27 +01:00
|
|
|
def test__get_vmedia_params_cannot_find_dev(self, get_device_mock,
|
|
|
|
find_mock):
|
|
|
|
find_mock.return_value = None
|
2015-03-09 12:23:12 +00:00
|
|
|
get_device_mock.return_value = None
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertEqual({}, utils._get_vmedia_params())
|
2015-03-09 12:23:12 +00:00
|
|
|
|
2015-07-24 17:23:33 +02:00
|
|
|
|
|
|
|
class TestFailures(testtools.TestCase):
|
|
|
|
def test_get_error(self):
|
|
|
|
f = utils.AccumulatedFailures()
|
|
|
|
self.assertFalse(f)
|
|
|
|
self.assertIsNone(f.get_error())
|
|
|
|
|
|
|
|
f.add('foo')
|
|
|
|
f.add('%s', 'bar')
|
|
|
|
f.add(RuntimeError('baz'))
|
|
|
|
self.assertTrue(f)
|
|
|
|
|
|
|
|
exp = ('The following errors were encountered:\n* foo\n* bar\n* baz')
|
|
|
|
self.assertEqual(exp, f.get_error())
|
|
|
|
|
|
|
|
def test_raise(self):
|
|
|
|
class FakeException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
f = utils.AccumulatedFailures(exc_class=FakeException)
|
|
|
|
self.assertIsNone(f.raise_if_needed())
|
|
|
|
f.add('foo')
|
2016-06-21 19:56:11 +03:00
|
|
|
self.assertRaisesRegex(FakeException, 'foo', f.raise_if_needed)
|
2016-05-30 17:39:13 +01:00
|
|
|
|
|
|
|
|
2021-10-28 17:58:02 +02:00
|
|
|
class TestUtils(ironic_agent_base.IronicAgentTest):
|
2016-05-30 17:39:13 +01:00
|
|
|
|
|
|
|
def _get_journalctl_output(self, mock_execute, lines=None, units=None):
|
|
|
|
contents = b'Krusty Krab'
|
|
|
|
mock_execute.return_value = (contents, '')
|
|
|
|
data = utils.get_journalctl_output(lines=lines, units=units)
|
|
|
|
|
|
|
|
cmd = ['journalctl', '--full', '--no-pager', '-b']
|
|
|
|
if lines is not None:
|
|
|
|
cmd.extend(['-n', str(lines)])
|
|
|
|
if units is not None:
|
|
|
|
[cmd.extend(['-u', u]) for u in units]
|
|
|
|
|
|
|
|
mock_execute.assert_called_once_with(*cmd, binary=True,
|
|
|
|
log_stdout=False)
|
|
|
|
self.assertEqual(contents, data.read())
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_get_journalctl_output(self, mock_execute):
|
|
|
|
self._get_journalctl_output(mock_execute)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_get_journalctl_output_with_lines(self, mock_execute):
|
|
|
|
self._get_journalctl_output(mock_execute, lines=123)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_get_journalctl_output_with_units(self, mock_execute):
|
|
|
|
self._get_journalctl_output(mock_execute, units=['fake-unit1',
|
|
|
|
'fake-unit2'])
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_get_journalctl_output_fail(self, mock_execute):
|
|
|
|
mock_execute.side_effect = processutils.ProcessExecutionError()
|
|
|
|
self.assertRaises(errors.CommandExecutionError,
|
|
|
|
self._get_journalctl_output, mock_execute)
|
|
|
|
|
|
|
|
def test_gzip_and_b64encode(self):
|
|
|
|
contents = b'Squidward Tentacles'
|
|
|
|
io_dict = {'fake-name': io.BytesIO(bytes(contents))}
|
|
|
|
data = utils.gzip_and_b64encode(io_dict=io_dict)
|
2019-11-28 17:10:40 +01:00
|
|
|
self.assertIsInstance(data, str)
|
2016-05-30 17:39:13 +01:00
|
|
|
|
2022-06-17 09:34:07 +02:00
|
|
|
res = io.BytesIO(base64.b64decode(data))
|
2016-05-30 17:39:13 +01:00
|
|
|
with tarfile.open(fileobj=res) as tar:
|
|
|
|
members = [(m.name, m.size) for m in tar]
|
|
|
|
self.assertEqual([('fake-name', len(contents))], members)
|
|
|
|
|
|
|
|
member = tar.extractfile('fake-name')
|
|
|
|
self.assertEqual(contents, member.read())
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_get_command_output(self, mock_execute):
|
|
|
|
contents = b'Sandra Sandy Cheeks'
|
|
|
|
mock_execute.return_value = (contents, '')
|
|
|
|
data = utils.get_command_output(['foo'])
|
|
|
|
|
|
|
|
mock_execute.assert_called_once_with(
|
|
|
|
'foo', binary=True, log_stdout=False)
|
|
|
|
self.assertEqual(contents, data.read())
|
|
|
|
|
2019-02-06 19:12:57 +01:00
|
|
|
@mock.patch.object(subprocess, 'check_call', autospec=True)
|
|
|
|
def test_guess_root_disk_primary_sort(self, mock_call):
|
|
|
|
block_devices = [
|
|
|
|
hardware.BlockDevice(name='/dev/sdc',
|
|
|
|
model='too small',
|
|
|
|
size=4294967295,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sda',
|
|
|
|
model='bigger than sdb',
|
|
|
|
size=21474836480,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sdb',
|
|
|
|
model='',
|
|
|
|
size=10737418240,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sdd',
|
|
|
|
model='bigger than sdb',
|
|
|
|
size=21474836480,
|
|
|
|
rotational=True),
|
|
|
|
]
|
|
|
|
device = utils.guess_root_disk(block_devices)
|
|
|
|
self.assertEqual(device.name, '/dev/sdb')
|
|
|
|
|
|
|
|
@mock.patch.object(subprocess, 'check_call', autospec=True)
|
|
|
|
def test_guess_root_disk_secondary_sort(self, mock_call):
|
|
|
|
block_devices = [
|
|
|
|
hardware.BlockDevice(name='/dev/sdc',
|
|
|
|
model='_',
|
|
|
|
size=10737418240,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sdb',
|
|
|
|
model='_',
|
|
|
|
size=10737418240,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sda',
|
|
|
|
model='_',
|
|
|
|
size=10737418240,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sdd',
|
|
|
|
model='_',
|
|
|
|
size=10737418240,
|
|
|
|
rotational=True),
|
|
|
|
]
|
|
|
|
device = utils.guess_root_disk(block_devices)
|
|
|
|
self.assertEqual(device.name, '/dev/sda')
|
|
|
|
|
|
|
|
@mock.patch.object(subprocess, 'check_call', autospec=True)
|
|
|
|
def test_guess_root_disk_disks_too_small(self, mock_call):
|
|
|
|
block_devices = [
|
|
|
|
hardware.BlockDevice(name='/dev/sda',
|
|
|
|
model='too small',
|
|
|
|
size=4294967295,
|
|
|
|
rotational=True),
|
|
|
|
hardware.BlockDevice(name='/dev/sdb',
|
|
|
|
model='way too small',
|
|
|
|
size=1,
|
|
|
|
rotational=True),
|
|
|
|
]
|
|
|
|
self.assertRaises(errors.DeviceNotFound,
|
|
|
|
utils.guess_root_disk, block_devices)
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(subprocess, 'check_call', autospec=True)
|
2016-05-30 17:39:13 +01:00
|
|
|
def test_is_journalctl_present(self, mock_call):
|
|
|
|
self.assertTrue(utils.is_journalctl_present())
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(subprocess, 'check_call', autospec=True)
|
2016-05-30 17:39:13 +01:00
|
|
|
def test_is_journalctl_present_false(self, mock_call):
|
|
|
|
os_error = OSError()
|
|
|
|
os_error.errno = errno.ENOENT
|
|
|
|
mock_call.side_effect = os_error
|
|
|
|
self.assertFalse(utils.is_journalctl_present())
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
|
2023-01-25 13:33:29 +01:00
|
|
|
@mock.patch.object(hardware, 'dispatch_to_all_managers', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
|
|
|
|
@mock.patch.object(utils, 'get_journalctl_output', autospec=True)
|
2016-05-30 17:39:13 +01:00
|
|
|
def test_collect_system_logs_journald(
|
2023-01-25 13:33:29 +01:00
|
|
|
self, mock_logs, mock_journalctl, mock_dispatch, mock_gzip_b64):
|
2016-05-30 17:39:13 +01:00
|
|
|
mock_journalctl.return_value = True
|
|
|
|
ret = 'Patrick Star'
|
|
|
|
mock_gzip_b64.return_value = ret
|
|
|
|
|
|
|
|
logs_string = utils.collect_system_logs()
|
|
|
|
self.assertEqual(ret, logs_string)
|
|
|
|
mock_logs.assert_called_once_with(lines=None)
|
|
|
|
mock_gzip_b64.assert_called_once_with(
|
2023-01-25 13:33:29 +01:00
|
|
|
io_dict=mock.ANY, file_list=[])
|
|
|
|
mock_dispatch.assert_called_once_with('collect_system_logs',
|
|
|
|
mock.ANY, [])
|
|
|
|
|
2021-10-28 17:58:02 +02:00
|
|
|
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
|
2023-01-25 13:33:29 +01:00
|
|
|
@mock.patch.object(hardware, 'dispatch_to_all_managers', autospec=True)
|
2021-10-28 17:58:02 +02:00
|
|
|
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
|
|
|
|
@mock.patch.object(utils, 'get_journalctl_output', autospec=True)
|
|
|
|
def test_collect_system_logs_journald_with_logfile(
|
2023-01-25 13:33:29 +01:00
|
|
|
self, mock_logs, mock_journalctl, mock_dispatch, mock_gzip_b64):
|
2021-10-28 17:58:02 +02:00
|
|
|
tmp = tempfile.NamedTemporaryFile()
|
|
|
|
self.addCleanup(lambda: tmp.close())
|
|
|
|
|
|
|
|
self.config(log_file=tmp.name)
|
|
|
|
mock_journalctl.return_value = True
|
|
|
|
ret = 'Patrick Star'
|
|
|
|
mock_gzip_b64.return_value = ret
|
|
|
|
|
|
|
|
logs_string = utils.collect_system_logs()
|
|
|
|
self.assertEqual(ret, logs_string)
|
|
|
|
mock_logs.assert_called_once_with(lines=None)
|
|
|
|
mock_gzip_b64.assert_called_once_with(
|
2023-01-25 13:33:29 +01:00
|
|
|
io_dict=mock.ANY, file_list=[tmp.name])
|
|
|
|
mock_dispatch.assert_called_once_with('collect_system_logs',
|
|
|
|
mock.ANY, [tmp.name])
|
|
|
|
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
|
2023-01-25 13:33:29 +01:00
|
|
|
@mock.patch.object(hardware, 'dispatch_to_all_managers', autospec=True)
|
2017-03-19 08:30:25 -07:00
|
|
|
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
|
2016-05-30 17:39:13 +01:00
|
|
|
def test_collect_system_logs_non_journald(
|
2023-01-25 13:33:29 +01:00
|
|
|
self, mock_journalctl, mock_dispatch, mock_gzip_b64):
|
2016-05-30 17:39:13 +01:00
|
|
|
mock_journalctl.return_value = False
|
|
|
|
ret = 'SpongeBob SquarePants'
|
|
|
|
mock_gzip_b64.return_value = ret
|
|
|
|
|
|
|
|
logs_string = utils.collect_system_logs()
|
|
|
|
self.assertEqual(ret, logs_string)
|
|
|
|
mock_gzip_b64.assert_called_once_with(
|
2023-01-25 13:33:29 +01:00
|
|
|
io_dict=mock.ANY, file_list=['/var/log'])
|
|
|
|
mock_dispatch.assert_called_once_with('collect_system_logs',
|
|
|
|
mock.ANY, ['/var/log'])
|
|
|
|
|
2021-10-28 17:58:02 +02:00
|
|
|
@mock.patch.object(utils, 'gzip_and_b64encode', autospec=True)
|
2023-01-25 13:33:29 +01:00
|
|
|
@mock.patch.object(hardware, 'dispatch_to_all_managers', autospec=True)
|
2021-10-28 17:58:02 +02:00
|
|
|
@mock.patch.object(utils, 'is_journalctl_present', autospec=True)
|
|
|
|
def test_collect_system_logs_non_journald_with_logfile(
|
2023-01-25 13:33:29 +01:00
|
|
|
self, mock_journalctl, mock_dispatch, mock_gzip_b64):
|
2021-10-28 17:58:02 +02:00
|
|
|
tmp = tempfile.NamedTemporaryFile()
|
|
|
|
self.addCleanup(lambda: tmp.close())
|
|
|
|
|
|
|
|
self.config(log_file=tmp.name)
|
|
|
|
mock_journalctl.return_value = False
|
|
|
|
ret = 'SpongeBob SquarePants'
|
|
|
|
mock_gzip_b64.return_value = ret
|
|
|
|
|
|
|
|
logs_string = utils.collect_system_logs()
|
|
|
|
self.assertEqual(ret, logs_string)
|
|
|
|
mock_gzip_b64.assert_called_once_with(
|
2023-01-25 13:33:29 +01:00
|
|
|
io_dict=mock.ANY, file_list=['/var/log', tmp.name])
|
|
|
|
mock_dispatch.assert_called_once_with('collect_system_logs',
|
|
|
|
mock.ANY, ['/var/log', tmp.name])
|
2021-10-28 17:58:02 +02:00
|
|
|
|
2016-11-17 13:26:28 +02:00
|
|
|
def test_get_ssl_client_options(self):
|
|
|
|
# defaults
|
|
|
|
conf = mock.Mock(insecure=False, cafile=None,
|
|
|
|
keyfile=None, certfile=None)
|
|
|
|
self.assertEqual((True, None), utils.get_ssl_client_options(conf))
|
|
|
|
|
|
|
|
# insecure=True overrides cafile
|
|
|
|
conf = mock.Mock(insecure=True, cafile='spam',
|
|
|
|
keyfile=None, certfile=None)
|
|
|
|
self.assertEqual((False, None), utils.get_ssl_client_options(conf))
|
|
|
|
|
|
|
|
# cafile returned as verify when not insecure
|
|
|
|
conf = mock.Mock(insecure=False, cafile='spam',
|
|
|
|
keyfile=None, certfile=None)
|
|
|
|
self.assertEqual(('spam', None), utils.get_ssl_client_options(conf))
|
|
|
|
|
|
|
|
# only both certfile and keyfile produce non-None result
|
|
|
|
conf = mock.Mock(insecure=False, cafile=None,
|
|
|
|
keyfile=None, certfile='ham')
|
|
|
|
self.assertEqual((True, None), utils.get_ssl_client_options(conf))
|
|
|
|
|
|
|
|
conf = mock.Mock(insecure=False, cafile=None,
|
|
|
|
keyfile='ham', certfile=None)
|
|
|
|
self.assertEqual((True, None), utils.get_ssl_client_options(conf))
|
|
|
|
|
|
|
|
conf = mock.Mock(insecure=False, cafile=None,
|
|
|
|
keyfile='spam', certfile='ham')
|
|
|
|
self.assertEqual((True, ('ham', 'spam')),
|
|
|
|
utils.get_ssl_client_options(conf))
|
2019-08-09 14:59:53 +02:00
|
|
|
|
|
|
|
def test_device_extractor(self):
|
|
|
|
self.assertEqual(
|
|
|
|
'md0',
|
|
|
|
utils.extract_device('md0p1')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'/dev/md0',
|
|
|
|
utils.extract_device('/dev/md0p1')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'sda',
|
|
|
|
utils.extract_device('sda12')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'/dev/sda',
|
|
|
|
utils.extract_device('/dev/sda12')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'nvme0n1',
|
|
|
|
utils.extract_device('nvme0n1p12')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'/dev/nvme0n1',
|
|
|
|
utils.extract_device('/dev/nvme0n1p12')
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
'/dev/hello',
|
|
|
|
utils.extract_device('/dev/hello42')
|
|
|
|
)
|
|
|
|
self.assertIsNone(
|
|
|
|
utils.extract_device('/dev/sda')
|
|
|
|
)
|
|
|
|
self.assertIsNone(
|
|
|
|
utils.extract_device('whatevernotmatchin12a')
|
|
|
|
)
|
2019-12-04 19:55:27 +01:00
|
|
|
|
2019-08-06 13:52:13 +02:00
|
|
|
def test_extract_capability_from_dict(self):
|
|
|
|
expected_dict = {"hello": "world"}
|
|
|
|
root = {"capabilities": expected_dict}
|
|
|
|
|
|
|
|
self.assertDictEqual(
|
|
|
|
expected_dict,
|
|
|
|
utils.parse_capabilities(root))
|
|
|
|
|
|
|
|
def test_extract_capability_from_json_string(self):
|
|
|
|
root = {'capabilities': '{"test": "world"}'}
|
|
|
|
self.assertDictEqual(
|
|
|
|
{"test": "world"},
|
|
|
|
utils.parse_capabilities(root))
|
|
|
|
|
|
|
|
def test_extract_capability_from_old_format_caps(self):
|
|
|
|
root = {'capabilities': 'test:world:2,hello:test1,badformat'}
|
|
|
|
self.assertDictEqual(
|
|
|
|
{'hello': 'test1'},
|
|
|
|
utils.parse_capabilities(root))
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=True, autospec=True)
|
|
|
|
def test_boot_mode_fallback_uefi(self, mock_os):
|
|
|
|
node = {}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_fallback_bios(self, mock_os):
|
|
|
|
node = {}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('bios', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_from_driver_internal_info(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'uefi'
|
|
|
|
},
|
|
|
|
}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_from_properties_str(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': 'boot_mode:uefi'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_from_properties_dict(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': {
|
|
|
|
'boot_mode': 'uefi'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_from_properties_json_str(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': '{"boot_mode": "uefi"}'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_override_with_instance_info(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': {
|
|
|
|
'boot_mode': 'bios'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'instance_info': {
|
|
|
|
'deploy_boot_mode': 'uefi'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_boot_mode_implicit_with_secure_boot(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': {
|
|
|
|
'boot_mode': 'bios',
|
|
|
|
'secure_boot': 'TrUe'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'instance_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_has_calls([])
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
|
|
|
def test_secure_boot_overriden_with_instance_info_caps(self, mock_os):
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': {
|
|
|
|
'boot_mode': 'bios',
|
|
|
|
'secure_boot': 'false'
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'instance_info': {
|
|
|
|
'deploy_boot_mode': 'bios',
|
|
|
|
'capabilities': {
|
|
|
|
'secure_boot': 'true'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_has_calls([])
|
|
|
|
|
|
|
|
@mock.patch.object(os.path, 'isdir', return_value=True, autospec=True)
|
|
|
|
def test_boot_mode_invalid_cap(self, mock_os):
|
|
|
|
# In case of invalid boot mode specified we fallback to ramdisk boot
|
|
|
|
# mode
|
|
|
|
node = {
|
|
|
|
'driver_internal_info': {
|
|
|
|
'deploy_boot_mode': 'bios'
|
|
|
|
},
|
|
|
|
'properties': {
|
|
|
|
'capabilities': {
|
|
|
|
'boot_mode': 'sfkshfks'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
boot_mode = utils.get_node_boot_mode(node)
|
|
|
|
self.assertEqual('uefi', boot_mode)
|
|
|
|
mock_os.assert_called_once_with('/sys/firmware/efi')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
|
|
|
autospec=True)
|
|
|
|
def test_specified_partition_table_type(self, mock_boot_mode):
|
|
|
|
node = {}
|
|
|
|
label = utils.get_partition_table_type_from_specs(node)
|
|
|
|
self.assertEqual('msdos', label)
|
|
|
|
mock_boot_mode.assert_called_once_with(node)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'get_node_boot_mode', return_value='uefi',
|
|
|
|
autospec=True)
|
|
|
|
def test_specified_partition_table_type_gpt(self, mock_boot_mode):
|
|
|
|
node = {}
|
|
|
|
label = utils.get_partition_table_type_from_specs(node)
|
|
|
|
self.assertEqual('gpt', label)
|
|
|
|
mock_boot_mode.assert_called_once_with(node)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
|
|
|
autospec=True)
|
|
|
|
def test_specified_partition_table_type_with_disk_label(self,
|
|
|
|
mock_boot_mode):
|
|
|
|
node = {
|
|
|
|
'properties': {
|
|
|
|
'capabilities': 'disk_label:gpt'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
label = utils.get_partition_table_type_from_specs(node)
|
|
|
|
self.assertEqual('gpt', label)
|
|
|
|
mock_boot_mode.assert_has_calls([])
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
|
|
|
autospec=True)
|
|
|
|
def test_specified_partition_table_type_with_instance_disk_label(
|
|
|
|
self, mock_boot_mode):
|
|
|
|
# In case of invalid boot mode specified we fallback to ramdisk boot
|
|
|
|
# mode
|
|
|
|
node = {
|
|
|
|
'instance_info': {
|
|
|
|
'capabilities': 'disk_label:gpt'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
label = utils.get_partition_table_type_from_specs(node)
|
|
|
|
self.assertEqual('gpt', label)
|
|
|
|
mock_boot_mode.assert_has_calls([])
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'get_node_boot_mode', return_value='uefi',
|
|
|
|
autospec=True)
|
|
|
|
def test_specified_partition_table_type_disk_label_ignored_with_uefi(
|
|
|
|
self, mock_boot_mode):
|
|
|
|
# In case of invalid boot mode specified we fallback to ramdisk boot
|
|
|
|
# mode
|
|
|
|
node = {
|
|
|
|
'instance_info': {
|
|
|
|
'capabilities': 'disk_label:msdos'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
label = utils.get_partition_table_type_from_specs(node)
|
|
|
|
self.assertEqual('gpt', label)
|
|
|
|
mock_boot_mode.assert_has_calls([])
|
|
|
|
|
2020-01-11 18:27:13 +01:00
|
|
|
|
|
|
|
class TestRemoveKeys(testtools.TestCase):
|
|
|
|
def test_remove_keys(self):
|
|
|
|
value = {'system_logs': 'abcd',
|
|
|
|
'key': 'value',
|
|
|
|
'other': [{'configdrive': 'foo'}, 'string', 0]}
|
|
|
|
expected = {'system_logs': '<...>',
|
|
|
|
'key': 'value',
|
|
|
|
'other': [{'configdrive': '<...>'}, 'string', 0]}
|
|
|
|
self.assertEqual(expected, utils.remove_large_keys(value))
|
2020-02-14 13:45:12 -08:00
|
|
|
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
class TestClockSyncUtils(ironic_agent_base.IronicAgentTest):
|
|
|
|
|
|
|
|
def test_determine_time_method_none(self, mock_execute):
|
|
|
|
mock_execute.side_effect = OSError
|
|
|
|
self.assertIsNone(utils.determine_time_method())
|
|
|
|
|
|
|
|
def test_determine_time_method_ntpdate(self, mock_execute):
|
|
|
|
mock_execute.side_effect = [
|
|
|
|
OSError, # No chronyd found
|
|
|
|
('', ''), # Returns nothing on ntpdate call
|
|
|
|
]
|
|
|
|
calls = [mock.call('chronyd', '-h'),
|
|
|
|
mock.call('ntpdate', '-v', check_exit_code=[0, 1])]
|
|
|
|
return_value = utils.determine_time_method()
|
|
|
|
self.assertEqual('ntpdate', return_value)
|
|
|
|
mock_execute.assert_has_calls(calls)
|
|
|
|
|
|
|
|
def test_determine_time_method_chronyd(self, mock_execute):
|
|
|
|
mock_execute.side_effect = [
|
|
|
|
('', ''), # Returns nothing on ntpdate call
|
|
|
|
]
|
|
|
|
calls = [mock.call('chronyd', '-h')]
|
|
|
|
return_value = utils.determine_time_method()
|
|
|
|
self.assertEqual('chronyd', return_value)
|
|
|
|
mock_execute.assert_has_calls(calls)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_ntp(self, mock_time_method, mock_execute):
|
|
|
|
self.config(ntp_server='192.168.1.1')
|
|
|
|
mock_time_method.return_value = 'ntpdate'
|
|
|
|
utils.sync_clock()
|
|
|
|
mock_execute.assert_has_calls([mock.call('ntpdate', '192.168.1.1')])
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_ntp_raises_exception(self, mock_time_method,
|
|
|
|
mock_execute):
|
|
|
|
self.config(ntp_server='192.168.1.1')
|
|
|
|
self.config(fail_if_clock_not_set=True)
|
|
|
|
mock_time_method.return_value = 'ntpdate'
|
|
|
|
mock_execute.side_effect = processutils.ProcessExecutionError()
|
|
|
|
self.assertRaises(errors.CommandExecutionError, utils.sync_clock)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_chrony(self, mock_time_method, mock_execute):
|
|
|
|
self.config(ntp_server='192.168.1.1')
|
|
|
|
mock_time_method.return_value = 'chronyd'
|
|
|
|
utils.sync_clock()
|
|
|
|
mock_execute.assert_has_calls([
|
2021-07-15 18:09:30 +02:00
|
|
|
mock.call('chronyc', 'shutdown', check_exit_code=[0, 1]),
|
|
|
|
mock.call("chronyd -q 'server 192.168.1.1 iburst'", shell=True),
|
2020-02-14 13:45:12 -08:00
|
|
|
])
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_chrony_failure(self, mock_time_method, mock_execute):
|
|
|
|
self.config(ntp_server='192.168.1.1')
|
|
|
|
self.config(fail_if_clock_not_set=True)
|
|
|
|
mock_time_method.return_value = 'chronyd'
|
|
|
|
mock_execute.side_effect = [
|
|
|
|
('', ''),
|
|
|
|
processutils.ProcessExecutionError(stderr='time verboten'),
|
|
|
|
]
|
|
|
|
self.assertRaisesRegex(errors.CommandExecutionError,
|
2021-07-15 18:09:30 +02:00
|
|
|
'Failed to sync time using chrony to ntp '
|
|
|
|
'server:', utils.sync_clock)
|
2020-02-14 13:45:12 -08:00
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_none(self, mock_time_method, mock_execute):
|
|
|
|
self.config(ntp_server='192.168.1.1')
|
|
|
|
mock_time_method.return_value = None
|
|
|
|
utils.sync_clock(ignore_errors=True)
|
|
|
|
self.assertEqual(0, mock_execute.call_count)
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'determine_time_method', autospec=True)
|
|
|
|
def test_sync_clock_ntp_server_is_none(self, mock_time_method,
|
|
|
|
mock_execute):
|
|
|
|
self.config(ntp_server=None)
|
|
|
|
mock_time_method.return_value = None
|
|
|
|
utils.sync_clock()
|
|
|
|
self.assertEqual(0, mock_execute.call_count)
|
2020-04-15 12:44:37 +02:00
|
|
|
|
|
|
|
|
2021-03-25 13:02:04 -07:00
|
|
|
@mock.patch.object(utils, '_booted_from_vmedia', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_check_vmedia_device', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_find_vmedia_device_by_labels', autospec=True)
|
2020-11-18 16:48:27 +01:00
|
|
|
@mock.patch.object(shutil, 'copy', autospec=True)
|
2021-03-22 14:43:56 +01:00
|
|
|
@mock.patch.object(ironic_utils, 'mounted', autospec=True)
|
2020-11-18 16:48:27 +01:00
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
class TestConfigFromVmedia(testtools.TestCase):
|
2020-11-18 16:48:27 +01:00
|
|
|
|
2021-03-25 13:02:04 -07:00
|
|
|
def test_vmedia_found_not_booted_from_vmedia(
|
|
|
|
self, mock_execute, mock_mount, mock_copy,
|
|
|
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
|
|
|
mock_booted_from_vmedia.return_value = False
|
|
|
|
mock_find_device.return_value = '/dev/fake'
|
|
|
|
utils.copy_config_from_vmedia()
|
|
|
|
mock_mount.assert_not_called()
|
|
|
|
mock_execute.assert_not_called()
|
|
|
|
mock_copy.assert_not_called()
|
|
|
|
mock_check_vmedia.assert_not_called()
|
|
|
|
self.assertTrue(mock_booted_from_vmedia.called)
|
|
|
|
|
|
|
|
def test_no_vmedia(
|
|
|
|
self, mock_execute, mock_mount, mock_copy,
|
|
|
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
|
|
|
mock_booted_from_vmedia.return_value = True
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_find_device.return_value = None
|
|
|
|
utils.copy_config_from_vmedia()
|
2021-03-22 14:43:56 +01:00
|
|
|
mock_mount.assert_not_called()
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_execute.assert_not_called()
|
|
|
|
mock_copy.assert_not_called()
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_check_vmedia.assert_not_called()
|
|
|
|
self.assertFalse(mock_booted_from_vmedia.called)
|
2020-11-18 16:48:27 +01:00
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
@mock.patch.object(os.path, 'ismount', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test_no_files(
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
self, mock_ismount, mock_execute, mock_mount, mock_copy,
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_ismount.return_value = False
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_booted_from_vmedia.return_value = True
|
2021-03-22 14:43:56 +01:00
|
|
|
temp_path = tempfile.mkdtemp()
|
|
|
|
self.addCleanup(lambda: shutil.rmtree(temp_path))
|
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.side_effect = [('/mnt/config', ()),
|
|
|
|
((), ())]
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_find_device.return_value = '/dev/something'
|
2021-03-22 14:43:56 +01:00
|
|
|
mock_mount.return_value.__enter__.return_value = temp_path
|
2020-11-18 16:48:27 +01:00
|
|
|
utils.copy_config_from_vmedia()
|
2021-03-22 14:43:56 +01:00
|
|
|
mock_mount.assert_called_once_with('/dev/something')
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.assert_has_calls([
|
|
|
|
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
|
|
|
mock.call('umount', '/mnt/config', run_as_root=True)])
|
|
|
|
self.assertEqual(2, mock_execute.call_count)
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_copy.assert_not_called()
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertTrue(mock_booted_from_vmedia.called)
|
2020-11-18 16:48:27 +01:00
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
@mock.patch.object(os.path, 'ismount', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test_mounted_no_files(
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
self, mock_ismount, mock_execute, mock_mount, mock_copy,
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_find_device, mock_check_vmedia, mock_booted_from_vmedia):
|
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_ismount.side_effect = [True, False]
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_booted_from_vmedia.return_value = True
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.side_effect = [('/some/path', ''),
|
|
|
|
((), ()),
|
|
|
|
((), ())]
|
|
|
|
mock_find_device.side_effect = ('/dev/something', '')
|
2020-12-16 18:17:24 +01:00
|
|
|
mock_find_device.return_value = '/dev/something'
|
|
|
|
utils.copy_config_from_vmedia()
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.assert_has_calls([
|
|
|
|
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
|
|
|
mock.call('umount', '/some/path', run_as_root=True),
|
|
|
|
mock.call('umount', '/mnt/config', run_as_root=True)])
|
|
|
|
self.assertEqual(3, mock_execute.call_count)
|
2020-12-16 18:17:24 +01:00
|
|
|
mock_copy.assert_not_called()
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_mount.assert_called_once_with('/dev/something')
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertTrue(mock_booted_from_vmedia.called)
|
2020-12-16 18:17:24 +01:00
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
@mock.patch.object(os.path, 'ismount', autospec=True)
|
2020-11-18 16:48:27 +01:00
|
|
|
@mock.patch.object(os, 'makedirs', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test_copy(
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
self, mock_makedirs, mock_ismount, mock_execute, mock_mount,
|
|
|
|
mock_copy, mock_find_device, mock_check_vmedia,
|
|
|
|
mock_booted_from_vmedia):
|
2021-03-25 13:02:04 -07:00
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_ismount.return_value = False
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_booted_from_vmedia.return_value = True
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_find_device.return_value = '/dev/something'
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.side_effect = processutils.ProcessExecutionError('meow')
|
2021-03-22 14:43:56 +01:00
|
|
|
path = tempfile.mkdtemp()
|
|
|
|
self.addCleanup(lambda: shutil.rmtree(path))
|
|
|
|
|
|
|
|
def _fake_mount(dev):
|
|
|
|
self.assertEqual('/dev/something', dev)
|
|
|
|
# NOTE(dtantsur): makedirs is mocked
|
|
|
|
os.mkdir(os.path.join(path, 'etc'))
|
|
|
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent'))
|
|
|
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent.d'))
|
|
|
|
with open(os.path.join(path, 'not copied'), 'wt') as fp:
|
|
|
|
fp.write('not copied')
|
|
|
|
with open(os.path.join(path, 'etc', 'ironic-python-agent',
|
|
|
|
'ironic.crt'), 'wt') as fp:
|
|
|
|
fp.write('I am a cert')
|
|
|
|
with open(os.path.join(path, 'etc', 'ironic-python-agent.d',
|
|
|
|
'ironic.conf'), 'wt') as fp:
|
|
|
|
fp.write('I am a config')
|
|
|
|
return mock.MagicMock(**{'__enter__.return_value': path})
|
2020-11-18 16:48:27 +01:00
|
|
|
|
|
|
|
mock_find_device.return_value = '/dev/something'
|
2021-03-22 14:43:56 +01:00
|
|
|
mock_mount.side_effect = _fake_mount
|
2020-11-18 16:48:27 +01:00
|
|
|
|
|
|
|
utils.copy_config_from_vmedia()
|
|
|
|
|
|
|
|
mock_makedirs.assert_has_calls([
|
|
|
|
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
|
|
|
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
|
|
|
], any_order=True)
|
2021-03-22 14:43:56 +01:00
|
|
|
mock_mount.assert_called_once_with('/dev/something')
|
2020-11-18 16:48:27 +01:00
|
|
|
mock_copy.assert_has_calls([
|
|
|
|
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
|
|
|
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
|
|
|
], any_order=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertTrue(mock_booted_from_vmedia.called)
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.assert_called_once_with(
|
|
|
|
'findmnt', '-n', '-oTARGET', '/dev/something')
|
2020-12-16 18:17:24 +01:00
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
@mock.patch.object(os.path, 'ismount', autospec=True)
|
2020-12-16 18:17:24 +01:00
|
|
|
@mock.patch.object(os, 'makedirs', autospec=True)
|
2021-03-25 13:02:04 -07:00
|
|
|
def test_copy_mounted(
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
self, mock_makedirs, mock_ismount, mock_execute, mock_mount,
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_copy, mock_find_device, mock_check_vmedia,
|
|
|
|
mock_booted_from_vmedia):
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_ismount.side_effect = [True, True, False]
|
2021-03-25 13:02:04 -07:00
|
|
|
mock_booted_from_vmedia.return_value = True
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_find_device.side_effect = ('/dev/something', '')
|
2020-12-16 18:17:24 +01:00
|
|
|
path = tempfile.mkdtemp()
|
|
|
|
self.addCleanup(lambda: shutil.rmtree(path))
|
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
def _fake_mount(dev):
|
|
|
|
self.assertEqual('/dev/something', dev)
|
|
|
|
# NOTE(dtantsur): makedirs is mocked
|
|
|
|
os.mkdir(os.path.join(path, 'etc'))
|
|
|
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent'))
|
|
|
|
os.mkdir(os.path.join(path, 'etc', 'ironic-python-agent.d'))
|
|
|
|
with open(os.path.join(path, 'not copied'), 'wt') as fp:
|
|
|
|
fp.write('not copied')
|
|
|
|
with open(os.path.join(path, 'etc', 'ironic-python-agent',
|
|
|
|
'ironic.crt'), 'wt') as fp:
|
|
|
|
fp.write('I am a cert')
|
|
|
|
with open(os.path.join(path, 'etc', 'ironic-python-agent.d',
|
|
|
|
'ironic.conf'), 'wt') as fp:
|
|
|
|
fp.write('I am a config')
|
|
|
|
return mock.MagicMock(**{'__enter__.return_value': path})
|
|
|
|
|
2020-12-16 18:17:24 +01:00
|
|
|
mock_find_device.return_value = '/dev/something'
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_mount.side_effect = _fake_mount
|
|
|
|
mock_execute.side_effect = [(path, ''),
|
|
|
|
((), ()),
|
|
|
|
((), ()),
|
|
|
|
((), ())]
|
2020-12-16 18:17:24 +01:00
|
|
|
|
|
|
|
utils.copy_config_from_vmedia()
|
|
|
|
|
|
|
|
mock_makedirs.assert_has_calls([
|
|
|
|
mock.call('/etc/ironic-python-agent', exist_ok=True),
|
|
|
|
mock.call('/etc/ironic-python-agent.d', exist_ok=True),
|
|
|
|
], any_order=True)
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_execute.assert_has_calls([
|
|
|
|
mock.call('findmnt', '-n', '-oTARGET', '/dev/something'),
|
|
|
|
mock.call('umount', mock.ANY, run_as_root=True),
|
|
|
|
mock.call('umount', '/mnt/config', run_as_root=True),
|
|
|
|
mock.call('umount', '/mnt/config', run_as_root=True)])
|
2020-12-16 18:17:24 +01:00
|
|
|
mock_copy.assert_has_calls([
|
|
|
|
mock.call(mock.ANY, '/etc/ironic-python-agent/ironic.crt'),
|
|
|
|
mock.call(mock.ANY, '/etc/ironic-python-agent.d/ironic.conf'),
|
|
|
|
], any_order=True)
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
mock_mount.assert_called_once_with('/dev/something')
|
2021-03-25 13:02:04 -07:00
|
|
|
self.assertTrue(mock_booted_from_vmedia.called)
|
2021-02-12 17:00:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
@mock.patch.object(requests, 'get', autospec=True)
|
|
|
|
class TestStreamingClient(ironic_agent_base.IronicAgentTest):
|
|
|
|
|
|
|
|
def test_ok(self, mock_get):
|
|
|
|
client = utils.StreamingClient()
|
|
|
|
self.assertTrue(client.verify)
|
|
|
|
self.assertIsNone(client.cert)
|
|
|
|
|
|
|
|
with client("http://url") as result:
|
|
|
|
response = mock_get.return_value.__enter__.return_value
|
|
|
|
self.assertIs(result, response.iter_content.return_value)
|
|
|
|
|
|
|
|
mock_get.assert_called_once_with("http://url", verify=True, cert=None,
|
|
|
|
stream=True, timeout=60)
|
|
|
|
response.iter_content.assert_called_once_with(1024 * 1024)
|
|
|
|
|
|
|
|
def test_retries(self, mock_get):
|
|
|
|
self.config(image_download_connection_retries=1,
|
|
|
|
image_download_connection_retry_interval=1)
|
|
|
|
mock_get.side_effect = requests.ConnectionError
|
|
|
|
|
|
|
|
client = utils.StreamingClient()
|
|
|
|
self.assertRaises(errors.CommandExecutionError,
|
|
|
|
client("http://url").__enter__)
|
|
|
|
|
|
|
|
mock_get.assert_called_with("http://url", verify=True, cert=None,
|
|
|
|
stream=True, timeout=60)
|
|
|
|
self.assertEqual(2, mock_get.call_count)
|
2021-03-25 13:02:04 -07:00
|
|
|
|
|
|
|
|
|
|
|
class TestCheckVirtualMedia(ironic_agent_base.IronicAgentTest):
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sdh" SIZE="1610612736" TYPE="disk" TRAN="usb"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertTrue(utils._check_vmedia_device('/dev/sdh'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_rom(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sr0" SIZE="1610612736" TYPE="rom" TRAN="usb"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertTrue(utils._check_vmedia_device('/dev/sr0'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sr0')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_too_large(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sdh" SIZE="1610612736000" TYPE="disk" TRAN="usb"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertFalse(utils._check_vmedia_device('/dev/sdh'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_part(self, mock_execute):
|
|
|
|
lsblk = ('KNAME="sdh1" SIZE="1610612736" TYPE="part" TRAN=""\n'
|
|
|
|
'KNAME="sdh" SIZE="1610612736" TYPE="disk" TRAN="sata"\n')
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertFalse(utils._check_vmedia_device('/dev/sdh1'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh1')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_other(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sdh" SIZE="1610612736" TYPE="other" TRAN="usb"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertFalse(utils._check_vmedia_device('/dev/sdh'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_sata(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sdh" SIZE="1610612736" TYPE="disk" TRAN="sata"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertFalse(utils._check_vmedia_device('/dev/sdh'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh')
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
def test_check_vmedia_device_scsi(self, mock_execute):
|
|
|
|
lsblk = 'KNAME="sdh" SIZE="1610612736" TYPE="other" TRAN="scsi"\n'
|
|
|
|
mock_execute.return_value = (lsblk, '')
|
|
|
|
self.assertFalse(utils._check_vmedia_device('/dev/sdh'))
|
|
|
|
mock_execute.assert_called_with('lsblk', '-n', '-s', '-P', '-b',
|
|
|
|
'-oKNAME,TRAN,TYPE,SIZE',
|
|
|
|
'/dev/sdh')
|
2021-03-30 07:58:34 -07:00
|
|
|
|
|
|
|
|
Fix vmedia network config drive handling
When performing DHCP-less deployments, the agent can start and
discover more than one configuration drive present on a host.
For example, a host was previously deployed using Ironic, and
is now being re-deployed again.
If Glean was present in the ramdisk, the glean-early.sh would end
mounting the folder based upon label.
If cloud-init, somehow is still in the ramdisk, the other folder
could somehow get mounted.
This patch, which is intended to be backportable, causes the agent
to unmount any configuration drive folders, mount the most likely
candidate based upon device type, partition, and overall state of
the machine, and then utilize that configuration, if present,
to re-configure and reload networking.
Thus allowing dhcp-less re-deployments to be fixed without
forcing any breaking changes.
It should also be noted that this fix was generated in concert
with an additional tempest test case, because this overall failure
case needed to be reproduced to ensure we had a workable non-breaking
path forward.
Closes-Bug: 2032377
Change-Id: I9a3b3dbb9ca98771ce2decf893eba7a4c1890eee
2023-09-15 15:03:30 -07:00
|
|
|
@mock.patch.object(shutil, 'copy', autospec=True)
|
|
|
|
@mock.patch.object(os, 'makedirs', autospec=True)
|
|
|
|
@mock.patch.object(utils, '_determine_networking_path', autospec=True)
|
|
|
|
@mock.patch.object(utils, 'execute', autospec=True)
|
|
|
|
class TestNetworkConfigRefresh(ironic_agent_base.IronicAgentTest):
|
|
|
|
|
|
|
|
def test_tinycore(self, mock_exec, mock_path, mock_makedirs,
|
|
|
|
mock_copy):
|
|
|
|
mock_path.return_value = 'tinycore'
|
|
|
|
utils.trigger_glean_network_refresh()
|
|
|
|
self.assertEqual(1, mock_path.call_count)
|
|
|
|
mock_exec.assert_has_calls([
|
|
|
|
mock.call('glean', '--distro', 'tinycore', run_as_root=True),
|
|
|
|
mock.call('/bin/sh', '/opt/network.sh', run_as_root=True)])
|
|
|
|
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
|
|
exist_ok=True)
|
|
|
|
|
|
|
|
def test_execution_error_is_handled(self, mock_exec, mock_path,
|
|
|
|
mock_makedirs, mock_copy):
|
|
|
|
mock_path.return_value = 'tinycore'
|
|
|
|
mock_exec.side_effect = OSError()
|
|
|
|
utils.trigger_glean_network_refresh()
|
|
|
|
self.assertEqual(1, mock_path.call_count)
|
|
|
|
mock_exec.assert_called_once_with(
|
|
|
|
'glean', '--distro', 'tinycore', run_as_root=True)
|
|
|
|
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
|
|
exist_ok=True)
|
|
|
|
mock_copy.assert_called_once_with(
|
|
|
|
'/etc/ironic-python-agent.d/network_data.json',
|
|
|
|
'/mnt/config/openstack/latest/network_data.json')
|
|
|
|
|
|
|
|
def test_networkd(self, mock_exec, mock_path,
|
|
|
|
mock_makedirs, mock_copy):
|
|
|
|
mock_path.return_value = 'networkd'
|
|
|
|
utils.trigger_glean_network_refresh()
|
|
|
|
self.assertEqual(1, mock_path.call_count)
|
|
|
|
mock_exec.assert_has_calls([
|
|
|
|
mock.call('glean', '--distro', 'networkd', run_as_root=True),
|
|
|
|
mock.call('systemctl', 'restart', 'systemd-networkd.service',
|
|
|
|
run_as_root=True)])
|
|
|
|
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
|
|
exist_ok=True)
|
|
|
|
mock_copy.assert_called_once_with(
|
|
|
|
'/etc/ironic-python-agent.d/network_data.json',
|
|
|
|
'/mnt/config/openstack/latest/network_data.json')
|
|
|
|
|
|
|
|
def test_networkmanager(self, mock_exec, mock_path,
|
|
|
|
mock_makedirs, mock_copy):
|
|
|
|
mock_path.return_value = 'networkmanager'
|
|
|
|
mock_exec.side_effect = [('', ''),
|
|
|
|
OSError(),
|
|
|
|
('', '')]
|
|
|
|
utils.trigger_glean_network_refresh()
|
|
|
|
self.assertEqual(1, mock_path.call_count)
|
|
|
|
mock_exec.assert_has_calls([
|
|
|
|
mock.call('glean', '--use-nm', run_as_root=True),
|
|
|
|
mock.call('systemctl', 'restart', 'NetworkManager',
|
|
|
|
run_as_root=True),
|
|
|
|
mock.call('systemctl', 'restart', 'NetworkManager.service',
|
|
|
|
run_as_root=True)])
|
|
|
|
mock_makedirs.assert_called_once_with('/mnt/config/openstack/latest/',
|
|
|
|
exist_ok=True)
|
|
|
|
mock_copy.assert_called_once_with(
|
|
|
|
'/etc/ironic-python-agent.d/network_data.json',
|
|
|
|
'/mnt/config/openstack/latest/network_data.json')
|
|
|
|
|
|
|
|
|
2021-03-30 07:58:34 -07:00
|
|
|
class TestCheckEarlyLogging(ironic_agent_base.IronicAgentTest):
|
|
|
|
|
|
|
|
@mock.patch.object(utils, 'LOG', autospec=True)
|
|
|
|
def test_early_logging_goes_to_logger(self, mock_log):
|
|
|
|
info = mock.Mock()
|
|
|
|
mock_log.info.side_effect = info
|
|
|
|
# Reset the buffer to be empty.
|
|
|
|
utils._EARLY_LOG_BUFFER = []
|
|
|
|
# Store some data via _early_log
|
|
|
|
utils._early_log('line 1.')
|
|
|
|
utils._early_log('line 2 %s', 'message')
|
|
|
|
expected_messages = ['line 1.',
|
|
|
|
'line 2 message']
|
|
|
|
self.assertEqual(expected_messages, utils._EARLY_LOG_BUFFER)
|
|
|
|
# Test we've got data in the buffer.
|
|
|
|
info.assert_not_called()
|
|
|
|
# Test the other half of this.
|
|
|
|
utils.log_early_log_to_logger()
|
|
|
|
expected_calls = [mock.call('Early logging: %s', 'line 1.'),
|
|
|
|
mock.call('Early logging: %s', 'line 2 message')]
|
|
|
|
info.assert_has_calls(expected_calls)
|