Jay Faulkner 36e5993a04 [codespell] Fix spelling issues in IPA
This fixes several spelling issues identified by codepsell. In some
cases, I may have manually modified a line to make the output more clear
or to correct grammatical issues which were obvious in the codespell
output.

Later changes in this chain will provide the codespell config used to
generate this, as well as adding this commit's SHA, once landed, to a
.git-blame-ignore-revs file to ensure it will not pollute git historys
for modern clients.

Related-Bug: 2047654
Change-Id: I240cf8484865c9b748ceb51f3c7b9fd973cb5ada
2023-12-28 10:54:46 -08:00

422 lines
17 KiB
Python

# 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 os
import shutil
import stat
import tempfile
from unittest import mock
from ironic_python_agent import errors
from ironic_python_agent import inject_files
from ironic_python_agent.tests.unit import base
@mock.patch('ironic_lib.utils.mounted', autospec=True)
@mock.patch('ironic_lib.disk_utils.list_partitions', autospec=True)
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
lambda _call: '/dev/fake')
class TestFindPartitionWithPath(base.IronicAgentTest):
def setUp(self):
super().setUp()
self.tempdir = tempfile.mkdtemp()
self.addCleanup(lambda: shutil.rmtree(self.tempdir))
def test_found(self, mock_list_parts, mock_mount):
mock_list_parts.return_value = [
{'number': 1, 'flags': 'lvm'},
{'number': 2, 'flags': 'boot'},
]
mock_mount.return_value.__enter__.return_value = self.tempdir
expected = os.path.join(self.tempdir, "some/path")
os.makedirs(expected)
with inject_files.find_partition_with_path("/some/path") as path:
self.assertEqual(expected, path)
mock_mount.assert_called_once_with('/dev/fake2')
def test_found_with_dev(self, mock_list_parts, mock_mount):
mock_list_parts.return_value = [
{'number': 1, 'flags': 'lvm'},
{'number': 2, 'flags': 'boot'},
]
mock_mount.return_value.__enter__.return_value = self.tempdir
expected = os.path.join(self.tempdir, "some/path")
os.makedirs(expected)
with inject_files.find_partition_with_path("/some/path",
"/dev/nvme0n1") as path:
self.assertEqual(expected, path)
mock_mount.assert_called_once_with('/dev/nvme0n1p2')
def test_not_found(self, mock_list_parts, mock_mount):
mock_list_parts.return_value = [
{'number': 1, 'flags': 'lvm'},
{'number': 2, 'flags': 'boot'},
{'number': 3, 'flags': ''},
]
mock_mount.return_value.__enter__.return_value = self.tempdir
self.assertRaises(
errors.DeviceNotFound,
inject_files.find_partition_with_path("/some/path").__enter__)
mock_mount.assert_has_calls([
mock.call('/dev/fake2'),
mock.call('/dev/fake3'),
], any_order=True)
class TestFindAndMountPath(base.IronicAgentTest):
@mock.patch.object(inject_files, 'find_partition_with_path', autospec=True)
def test_without_on(self, mock_find_part):
mock_find_part.return_value.__enter__.return_value = '/mount/path'
with inject_files._find_and_mount_path('/etc/sysctl.d/my.conf',
None, '/dev/fake') as result:
# "etc" is included in a real result of find_partition_with_path
self.assertEqual('/mount/path/sysctl.d/my.conf', result)
mock_find_part.assert_called_once_with('etc', '/dev/fake')
def test_without_on_wrong_path(self):
self.assertRaises(
errors.InvalidCommandParamsError,
inject_files._find_and_mount_path('/etc', None,
'/dev/fake').__enter__)
@mock.patch('ironic_lib.utils.mounted', autospec=True)
def test_with_on_as_path(self, mock_mount):
mock_mount.return_value.__enter__.return_value = '/mount/path'
with inject_files._find_and_mount_path('/etc/sysctl.d/my.conf',
'/dev/on',
'/dev/fake') as result:
self.assertEqual('/mount/path/etc/sysctl.d/my.conf', result)
mock_mount.assert_called_once_with('/dev/on')
@mock.patch('ironic_lib.utils.mounted', autospec=True)
def test_with_on_as_number(self, mock_mount):
mock_mount.return_value.__enter__.return_value = '/mount/path'
with inject_files._find_and_mount_path('/etc/sysctl.d/my.conf',
2, '/dev/fake') as result:
self.assertEqual('/mount/path/etc/sysctl.d/my.conf', result)
mock_mount.assert_called_once_with('/dev/fake2')
@mock.patch('ironic_lib.utils.mounted', autospec=True)
def test_with_on_as_number_nvme(self, mock_mount):
mock_mount.return_value.__enter__.return_value = '/mount/path'
with inject_files._find_and_mount_path('/etc/sysctl.d/my.conf',
2, '/dev/nvme0n1') as result:
self.assertEqual('/mount/path/etc/sysctl.d/my.conf', result)
mock_mount.assert_called_once_with('/dev/nvme0n1p2')
@mock.patch.object(inject_files, '_find_and_mount_path', autospec=True)
class TestInjectOne(base.IronicAgentTest):
def setUp(self):
super().setUp()
self.tempdir = tempfile.mkdtemp()
self.addCleanup(lambda: shutil.rmtree(self.tempdir))
self.dirpath = os.path.join(self.tempdir, 'dir1', 'dir2')
self.path = os.path.join(self.dirpath, 'file.name')
self.http_get = mock.MagicMock()
self.http_get.return_value.__enter__.return_value = iter(
[b'con', b'tent', b''])
self.node = {'uuid': '1234'}
self.ports = [{'address': 'aabb'}]
def test_delete(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'deleted': True}
os.makedirs(self.dirpath)
with open(self.path, 'wb') as fp:
fp.write(b'content')
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
self.assertFalse(os.path.exists(self.path))
self.assertTrue(os.path.isdir(self.dirpath))
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_not_called()
def test_delete_not_exists(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'deleted': True}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
self.assertFalse(os.path.exists(self.path))
self.assertFalse(os.path.isdir(self.dirpath))
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_not_called()
def test_plain_content(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA=='}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_not_called()
def test_plain_content_with_on(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA==',
'partition': '/dev/sda1'}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], '/dev/sda1',
'/dev/root')
self.http_get.assert_not_called()
def test_plain_content_with_modes(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA==',
'mode': 0o602, 'dirmode': 0o703}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
self.assertEqual(0o602, stat.S_IMODE(os.stat(self.path).st_mode))
self.assertEqual(0o703, stat.S_IMODE(os.stat(self.dirpath).st_mode))
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_not_called()
def test_plain_content_with_modes_exists(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA==',
'mode': 0o602, 'dirmode': 0o703}
os.makedirs(self.dirpath)
with open(self.path, 'wb') as fp:
fp.write(b"I'm not a cat, I'm a lawyer")
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
self.assertEqual(0o602, stat.S_IMODE(os.stat(self.path).st_mode))
# Existing directories do not change their permissions
self.assertNotEqual(0o703, stat.S_IMODE(os.stat(self.dirpath).st_mode))
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_not_called()
@mock.patch.object(os, 'chown', autospec=True)
def test_plain_content_with_owner(self, mock_chown, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA==',
'owner': 42}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
mock_chown.assert_called_once_with(self.path, 42, -1)
self.http_get.assert_not_called()
@mock.patch.object(os, 'chown', autospec=True)
def test_plain_content_with_owner_and_group(self, mock_chown,
mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name', 'content': 'Y29udGVudA==',
'owner': 0, 'group': 0}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
mock_chown.assert_called_once_with(self.path, 0, 0)
self.http_get.assert_not_called()
def test_url(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name',
'content': 'http://example.com/path'}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_called_once_with('http://example.com/path')
def test_url_formatting(self, mock_find_and_mount):
mock_find_and_mount.return_value.__enter__.return_value = self.path
fl = {'path': '/etc/dir1/dir2/file.name',
'content': 'http://example.com/{node[uuid]}/{ports[0][address]}'}
inject_files._inject_one(self.node, self.ports, fl,
'/dev/root', self.http_get)
with open(self.path, 'rb') as fp:
self.assertEqual(b'content', fp.read())
mock_find_and_mount.assert_called_once_with(fl['path'], None,
'/dev/root')
self.http_get.assert_called_once_with('http://example.com/1234/aabb')
@mock.patch('ironic_python_agent.hardware.dispatch_to_managers',
lambda _call: '/dev/root')
@mock.patch.object(inject_files, '_inject_one', autospec=True)
class TestInjectFiles(base.IronicAgentTest):
def test_empty(self, mock_inject):
node = {
'properties': {}
}
inject_files.inject_files(node, [mock.sentinel.port], [])
mock_inject.assert_not_called()
def test_ok(self, mock_inject):
node = {
'properties': {
'inject_files': [
{'path': '/etc/default/grub', 'content': 'abcdef'},
{'path': '/etc/default/bluetooth', 'deleted': True},
]
}
}
files = [
{'path': '/boot/special.conf',
'content': 'http://example.com/data',
'mode': 0o600, 'dirmode': 0o750, 'owner': 0, 'group': 0},
{'path': 'service.conf', 'partition': '/dev/disk/by-label/OPT'},
]
inject_files.inject_files(node, [mock.sentinel.port], files)
mock_inject.assert_has_calls([
mock.call(node, [mock.sentinel.port], fl, '/dev/root', mock.ANY)
for fl in node['properties']['inject_files'] + files
])
http_get = mock_inject.call_args_list[0][0][4]
self.assertTrue(http_get.verify)
self.assertIsNone(http_get.cert)
def test_verify_false(self, mock_inject):
node = {
'properties': {
'inject_files': [
{'path': '/etc/default/grub', 'content': 'abcdef'},
{'path': '/etc/default/bluetooth', 'deleted': True},
]
}
}
files = [
{'path': '/boot/special.conf',
'content': 'http://example.com/data',
'mode': 0o600, 'dirmode': 0o750, 'owner': 0, 'group': 0},
{'path': 'service.conf', 'partition': '/dev/disk/by-label/OPT'},
]
inject_files.inject_files(node, [mock.sentinel.port], files, False)
mock_inject.assert_has_calls([
mock.call(node, [mock.sentinel.port], fl, '/dev/root', mock.ANY)
for fl in node['properties']['inject_files'] + files
])
http_get = mock_inject.call_args_list[0][0][4]
self.assertFalse(http_get.verify)
self.assertIsNone(http_get.cert)
def test_invalid_type_on_node(self, mock_inject):
node = {
'properties': {
'inject_files': 42
}
}
self.assertRaises(errors.InvalidCommandParamsError,
inject_files.inject_files, node, [], [])
mock_inject.assert_not_called()
def test_invalid_type_in_param(self, mock_inject):
node = {
'properties': {}
}
self.assertRaises(errors.InvalidCommandParamsError,
inject_files.inject_files, node, [], 42)
mock_inject.assert_not_called()
class TestValidateFiles(base.IronicAgentTest):
def test_missing_path(self):
fl = {'deleted': True}
self.assertRaisesRegex(errors.InvalidCommandParamsError, 'path',
inject_files._validate_files, [fl], [])
def test_unknown_fields(self):
fl = {'path': '/etc/passwd', 'cat': 'meow'}
self.assertRaisesRegex(errors.InvalidCommandParamsError, 'cat',
inject_files._validate_files, [fl], [])
def test_root_without_on(self):
fl = {'path': '/something', 'content': 'abcd'}
self.assertRaisesRegex(errors.InvalidCommandParamsError, 'partition',
inject_files._validate_files, [fl], [])
def test_no_directories(self):
fl = {'path': '/something/else/', 'content': 'abcd'}
self.assertRaisesRegex(errors.InvalidCommandParamsError, 'directories',
inject_files._validate_files, [fl], [])
def test_content_and_deleted(self):
fl = {'path': '/etc/password', 'content': 'abcd', 'deleted': True}
self.assertRaisesRegex(errors.InvalidCommandParamsError,
'content .* with deleted',
inject_files._validate_files, [fl], [])
def test_numeric_fields(self):
for field in ('owner', 'group', 'mode', 'dirmode'):
fl = {'path': '/etc/password', 'content': 'abcd', field: 'name'}
self.assertRaisesRegex(errors.InvalidCommandParamsError,
'must be a number',
inject_files._validate_files, [fl], [])