Gets a dictionary of device aliases to logicalnames for the site_design.

Change-Id: I9c5ec6d4dd3ac0114fc6d866a7dd8033b7807b15
This commit is contained in:
Aaron Sheffield 2018-01-15 14:42:15 -06:00 committed by Scott Hussey
parent e7f630651f
commit 8939447669
18 changed files with 309 additions and 58 deletions

View File

@ -1001,9 +1001,12 @@ class ApplyNodeNetworking(BaseMaasAction):
ctx=n.name,
ctx_type='node')
hw_iface_list = i.get_hw_slaves()
hw_iface_logicalname_list = []
for hw_iface in hw_iface_list:
hw_iface_logicalname_list.append(n.get_logicalname(hw_iface))
iface = machine.interfaces.create_bond(
device_name=i.device_name,
parent_names=hw_iface_list,
parent_names=hw_iface_logicalname_list,
mtu=nl.mtu,
fabric=fabric.resource_id,
mode=nl.bonding_mode,
@ -1012,7 +1015,6 @@ class ApplyNodeNetworking(BaseMaasAction):
updelay=nl.bonding_up_delay,
lacp_rate=nl.bonding_peer_rate,
hash_policy=nl.bonding_xmit_hash)
self.task.success(focus=n.name)
else:
msg = "Network link %s indicates bonding, " \
"interface %s has less than 2 slaves." % \
@ -1059,8 +1061,7 @@ class ApplyNodeNetworking(BaseMaasAction):
hw_iface = i.get_hw_slaves()[0]
# TODO(sh8121att): HardwareProfile device alias integration
iface = machine.get_network_interface(
hw_iface)
self.task.success(focus=n.name)
n.get_logicalname(hw_iface))
if iface is None:
msg = "Interface %s not found on node %s, skipping configuration" % (
@ -1448,7 +1449,7 @@ class ApplyNodeStorage(BaseMaasAction):
storage_layout = dict()
if isinstance(root_block, hostprofile.HostPartition):
storage_layout['layout_type'] = 'flat'
storage_layout['root_device'] = root_dev.name
storage_layout['root_device'] = n.get_logicalname(root_dev.name)
storage_layout['root_size'] = root_block.size
elif isinstance(root_block, hostprofile.HostVolume):
storage_layout['layout_type'] = 'lvm'
@ -1460,8 +1461,7 @@ class ApplyNodeStorage(BaseMaasAction):
msg=msg, error=True, ctx=n.name, ctx_type='node')
self.task.failure(focus=n.get_id())
continue
storage_layout['root_device'] = root_dev.physical_devices[
0]
storage_layout['root_device'] = n.get_logicalname(root_dev.physical_devices[0])
storage_layout['root_lv_size'] = root_block.size
storage_layout['root_lv_name'] = root_block.name
storage_layout['root_vg_name'] = root_dev.name
@ -1479,24 +1479,24 @@ class ApplyNodeStorage(BaseMaasAction):
for d in n.storage_devices:
maas_dev = machine.block_devices.singleton({
'name': d.name
'name': n.get_logicalname(d.name)
})
if maas_dev is None:
self.logger.warning("Dev %s not found on node %s" %
(d.name, n.name))
self.logger.warning("Dev %s (%s) not found on node %s" %
(d.name, n.get_logicalname(d.name), n.name))
continue
if d.volume_group is not None:
self.logger.debug("Adding dev %s to volume group %s" %
(d.name, d.volume_group))
self.logger.debug("Adding dev %s (%s) to volume group %s" %
(d.name, n.get_logicalname(d.name), d.volume_group))
if d.volume_group not in vg_devs:
vg_devs[d.volume_group] = {'b': [], 'p': []}
vg_devs[d.volume_group]['b'].append(
maas_dev.resource_id)
continue
self.logger.debug("Partitioning dev %s on node %s" %
(d.name, n.name))
self.logger.debug("Partitioning dev %s (%s) on node %s" %
(d.name, n.get_logicalname(d.name), n.name))
for p in d.partitions:
if p.is_sys():
self.logger.debug(
@ -1510,8 +1510,9 @@ class ApplyNodeStorage(BaseMaasAction):
self.maas_client, size=size, bootable=p.bootable)
if p.part_uuid is not None:
part.uuid = p.part_uuid
msg = "Creating partition %s on dev %s" % (p.name,
d.name)
msg = "Creating partition %s on dev %s (%s)" % (p.name,
d.name,
n.get_logicalname(d.name))
self.logger.debug(msg)
part = maas_dev.create_partition(part)
self.task.add_status_msg(

View File

@ -16,8 +16,12 @@
#
"""Drydock model of a baremetal node."""
from defusedxml.ElementTree import fromstring
import logging
from oslo_versionedobjects import fields as ovo_fields
import drydock_provisioner.config as config
import drydock_provisioner.error as errors
import drydock_provisioner.objects as objects
import drydock_provisioner.objects.hostprofile
import drydock_provisioner.objects.base as base
@ -39,20 +43,24 @@ class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):
# the same set of CIs
def __init__(self, **kwargs):
super(BaremetalNode, self).__init__(**kwargs)
self.logicalnames = {}
self.logger = logging.getLogger(
config.config_mgr.conf.logging.global_logger_name)
# Compile the applied version of this model sourcing referenced
# data from the passed site design
def compile_applied_model(self, site_design):
def compile_applied_model(self, site_design, state_manager):
self.apply_host_profile(site_design)
self.apply_hardware_profile(site_design)
self.source = hd_fields.ModelSource.Compiled
self.apply_logicalnames(site_design, state_manager)
return
def apply_host_profile(self, site_design):
self.apply_inheritance(site_design)
return
# Translate device alises to physical selectors and copy
# Translate device aliases to physical selectors and copy
# other hardware attributes into this object
def apply_hardware_profile(self, site_design):
if self.hardware_profile is None:
@ -112,6 +120,64 @@ class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):
return (sd, p)
return (None, None)
def _apply_logicalname(self, xml_root, alias_name, bus_type, address):
"""Given xml_data, checks for a matching businfo and returns the logicalname
:param xml_root: Parsed ElementTree, it is searched for the logicalname.
:param alias_name: String value of the current device alias, it is returned
if a logicalname is not found.
:param bus_type: String value that is used to find the logicalname.
:param address: String value that is used to find the logicalname.
:return: String value of the logicalname or the alias_name if logicalname is not found.
"""
nodes = xml_root.findall(".//node[businfo='" + bus_type + "@" + address + "'].logicalname")
if len(nodes) >= 1 and nodes[0].text:
# TODO (as1452): log.info() when more than one result after the logger refactor.
for logicalname in reversed(nodes[0].text.split("/")):
self.logger.debug("Logicalname build dict: alias_name = %s, bus_type = %s, address = %s, "
"to logicalname = %s" % (alias_name, bus_type, address, logicalname))
return logicalname
self.logger.debug("Logicalname build dict: alias_name = %s, bus_type = %s, address = %s, not found" %
(alias_name, bus_type, address))
return alias_name
def apply_logicalnames(self, site_design, state_manager):
"""Gets the logicalnames for devices from lshw.
:param site_design: SiteDesign object.
:param state_manager: DrydockState object.
:return: Returns sets a dictionary of aliases that map to logicalnames in self.logicalnames.
"""
logicalnames = {}
results = state_manager.get_build_data(node_name=self.get_name(), latest=True)
xml_data = None
for result in results:
if result.generator == "lshw":
xml_data = result.data_element
break
if xml_data:
xml_root = fromstring(xml_data)
for hardware_profile in site_design.hardware_profiles:
for device in hardware_profile.devices:
logicalname = self._apply_logicalname(xml_root, device.alias, device.bus_type,
device.address)
logicalnames[device.alias] = logicalname
else:
raise errors.BuildDataError("No Build Data found for node_name %s" % (self.get_name()))
self.logicalnames = logicalnames
def get_logicalname(self, alias):
"""Gets the logicalname from self.logicalnames for an alias or returns the alias if not in the dictionary.
"""
if (self.logicalnames and self.logicalnames.get(alias)):
self.logger.debug("Logicalname input = %s with output %s." % (alias, self.logicalnames[alias]))
return self.logicalnames[alias]
else:
self.logger.debug("Logicalname input = %s not in logicalnames dictionary." % alias)
return alias
@base.DrydockObjectRegistry.register
class BaremetalNodeList(base.DrydockObjectListBase, base.DrydockObject):

View File

@ -244,17 +244,19 @@ class Orchestrator(object):
def compute_model_inheritance(self, site_design):
"""Compute inheritance of the design model.
Given a fully populated Site model, compute the effecitve
Given a fully populated Site model, compute the effective
design by applying inheritance and references
"""
try:
nodes = site_design.baremetal_nodes
for n in nodes or []:
n.compile_applied_model(site_design)
n.compile_applied_model(site_design, state_manager=self.state_manager)
except AttributeError:
self.logger.debug(
"Model inheritance skipped, no node definitions in site design."
)
except errors.BuildDataError:
self.logger.info("No Build Data found.")
return

View File

@ -21,3 +21,4 @@ psycopg2==2.7.3.1
jsonschema==2.6.0
jinja2==2.9.6
ulid2==0.1.1
defusedxml===0.5.0

View File

@ -1,18 +1,19 @@
alembic==0.8.2
amqp==2.2.2
Babel==2.5.1
Babel==2.5.3
bson==0.4.7
cachetools==2.0.1
certifi==2017.11.5
chardet==3.0.4
click==6.7
contextlib2==0.5.5
debtcollector==1.18.0
debtcollector==1.19.0
defusedxml==0.5.0
enum-compat==0.0.2
eventlet==0.20.0
falcon==1.3.0
falcon==1.4.1
fasteners==0.14.1
futurist==1.4.0
futurist==1.6.0
greenlet==0.4.12
idna==2.6
iso8601==0.1.11
@ -24,26 +25,26 @@ kombu==4.1.0
Mako==1.0.7
MarkupSafe==1.0
monotonic==1.4
msgpack-python==0.4.8
msgpack-python==0.5.1
netaddr==0.7.19
netifaces==0.10.6
oauthlib==2.0.6
oslo.concurrency==3.23.0
oslo.concurrency==3.25.0
oslo.config==3.16.0
oslo.context==2.19.3
oslo.i18n==3.18.0
oslo.log==3.33.0
oslo.messaging==5.33.1
oslo.middleware==3.32.1
oslo.context==2.20.0
oslo.i18n==3.19.0
oslo.log==3.36.0
oslo.messaging==5.35.0
oslo.middleware==3.34.0
oslo.policy==1.22.1
oslo.serialization==2.21.2
oslo.service==1.27.0
oslo.utils==3.31.0
oslo.serialization==2.23.0
oslo.service==1.29.0
oslo.utils==3.35.0
oslo.versionedobjects==1.23.0
Paste==2.0.3
PasteDeploy==1.5.2
pbr==3.1.1
pika==0.11.0
pika==0.11.2
pika-pool==0.1.3
pip==9.0.1
positional==1.2.1
@ -53,11 +54,10 @@ PTable==0.9.2
pycadf==2.6.0
pycrypto==2.6.1
pyghmi==1.0.18
pyinotify==0.9.6
pyparsing==2.2.0
python-dateutil==2.6.1
python-editor==1.0.3
python-keystoneclient==3.13.0
python-keystoneclient==3.14.0
python-mimeparse==1.6.0
pytz==2017.3
PyYAML==3.12
@ -65,16 +65,16 @@ repoze.lru==0.7
requests==2.18.4
rfc3986==1.1.0
Routes==2.4.1
setuptools==36.7.2
setuptools==38.4.0
six==1.11.0
SQLAlchemy==1.1.14
statsd==3.2.1
stevedore==1.27.1
tenacity==4.7.0
statsd==3.2.2
stevedore==1.28.0
tenacity==4.8.0
ulid2==0.1.1
urllib3==1.22
uWSGI==2.0.15
vine==1.1.4
WebOb==1.7.3
WebOb==1.7.4
wheel==0.30.0
wrapt==1.10.11

View File

@ -15,6 +15,7 @@
import logging
import os
import shutil
from unittest.mock import Mock
import drydock_provisioner.config as config
import drydock_provisioner.objects as objects
@ -128,3 +129,20 @@ def setup_logging():
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
@pytest.fixture(scope='module')
def mock_get_build_data(drydock_state):
def side_effect(**kwargs):
build_data = objects.BuildData(
node_name="test",
task_id="tid",
generator="lshw",
data_format="text/plain",
data_element="<mocktest></mocktest>")
return [build_data]
drydock_state.real_get_build_data = drydock_state.get_build_data
drydock_state.get_build_data = Mock(side_effect=side_effect)
yield
drydock_state.get_build_data = Mock(wraps=None, side_effect=None)
drydock_state.get_build_data = drydock_state.real_get_build_data

View File

@ -20,7 +20,7 @@ from drydock_provisioner.orchestrator.actions.orchestrator import PrepareNodes
class TestActionPrepareNodes(object):
def test_preparenodes(self, mocker, input_files, deckhand_ingester, setup,
drydock_state):
drydock_state, mock_get_build_data):
mock_images = mocker.patch("drydock_provisioner.drivers.node.driver.NodeDriver"
".get_available_images")
mock_images.return_value = ['xenial']

View File

@ -66,7 +66,8 @@ class TestClass(object):
assert result.status == falcon.HTTP_403
@pytest.fixture()
def seed_bootaction(self, blank_state, yaml_orchestrator, input_files):
def seed_bootaction(self, blank_state, yaml_orchestrator, input_files,
mock_get_build_data):
"""Add a task and boot action to the database for testing."""
input_file = input_files.join("fullsite.yaml")
design_ref = "file://%s" % input_file
@ -83,7 +84,8 @@ class TestClass(object):
return ba_ctx
@pytest.fixture()
def falcontest(self, drydock_state, yaml_ingester, yaml_orchestrator):
def falcontest(self, drydock_state, yaml_ingester, yaml_orchestrator,
mock_get_build_data):
"""Create a test harness for the the Falcon API framework."""
return testing.TestClient(
start_api(

View File

@ -18,7 +18,7 @@ from drydock_provisioner.objects import fields as hd_fields
class TestBootActionSignal(object):
def test_bootaction_signal_disable(self, deckhand_orchestrator,
drydock_state, input_files):
drydock_state, input_files, mock_get_build_data):
"""Test that disabled signaling omits a status entry in the DB."""
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)

View File

@ -21,7 +21,7 @@ import drydock_provisioner.objects.fields as hd_fields
class TestClass(object):
def test_task_complete(self, yaml_ingester, input_files, setup,
blank_state):
blank_state, mock_get_build_data):
input_file = input_files.join("fullsite.yaml")
design_ref = "file://%s" % str(input_file)

View File

@ -24,7 +24,8 @@ import falcon
class TestValidationApi(object):
def test_post_validation_resp(self, input_files, falcontest):
def test_post_validation_resp(self, input_files, falcontest, drydock_state,
mock_get_build_data):
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
@ -81,7 +82,7 @@ class TestValidationApi(object):
assert result.status == falcon.HTTP_400
def test_invalid_post_resp(self, input_files, falcontest):
def test_invalid_post_resp(self, input_files, falcontest, drydock_state, mock_get_build_data):
input_file = input_files.join("invalid_validation.yaml")
design_ref = "file://%s" % str(input_file)
@ -105,7 +106,7 @@ class TestValidationApi(object):
@pytest.fixture()
def falcontest(self, drydock_state, deckhand_ingester,
deckhand_orchestrator):
deckhand_orchestrator, mock_get_build_data):
"""Create a test harness for the the Falcon API framework."""
policy.policy_engine = policy.DrydockPolicy()
policy.policy_engine.register_policy()

View File

@ -17,7 +17,8 @@ import drydock_provisioner.objects as objects
class TestClass(object):
def test_bootaction_scoping_blankfilter(self, input_files,
deckhand_orchestrator):
deckhand_orchestrator, drydock_state,
mock_get_build_data):
"""Test a boot action with no node filter scopes correctly."""
input_file = input_files.join("deckhand_fullsite.yaml")
@ -36,7 +37,8 @@ class TestClass(object):
assert 'controller01' in ba.target_nodes
def test_bootaction_scoping_unionfilter(self, input_files,
deckhand_orchestrator):
deckhand_orchestrator, drydock_state,
mock_get_build_data):
"""Test a boot action with a union node filter scopes correctly."""
input_file = input_files.join("deckhand_fullsite.yaml")

View File

@ -0,0 +1,158 @@
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# 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 pytest
from unittest.mock import Mock
import drydock_provisioner.objects as objects
import drydock_provisioner.error as errors
class TestClass(object):
def test_apply_logicalnames_exception(self, input_files, deckhand_orchestrator,
drydock_state, mock_get_build_data):
"""Test node apply_logicalnames to get an exception"""
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
design_status, design_data = deckhand_orchestrator.get_effective_site(
design_ref)
def side_effect(**kwargs):
return []
drydock_state.get_build_data = Mock(side_effect=side_effect)
nodes = design_data.baremetal_nodes
with pytest.raises(errors.BuildDataError):
for n in nodes or []:
n.apply_logicalnames(design_data, state_manager=drydock_state)
def test_apply_logicalnames_success(self, input_files, deckhand_orchestrator,
drydock_state, mock_get_build_data):
"""Test node apply_logicalnames to get the proper dictionary"""
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
xml_example = """
<?xml version="1.0" standalone="yes" ?>
<!-- generated by lshw-B.02.17 -->
<!-- GCC 5.4.0 20160609 -->
<!-- Linux 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 -->
<!-- GNU libc 2 (glibc 2.23) -->
<list>
<node id="cab23-r720-16" claimed="true" class="system" handle="DMI:0100">
<description>Rack Mount Chassis</description>
<product>PowerEdge R720xd (SKU=NotProvided;ModelName=PowerEdge R720xd)</product>
<vendor>Dell Inc.</vendor>
<serial>6H5LBY1</serial>
<width units="bits">64</width>
<configuration>
<setting id="boot" value="normal" />
<setting id="chassis" value="rackmount" />
<setting id="sku" value="SKU=NotProvided;ModelName=PowerEdge R720xd" />
<setting id="uuid" value="44454C4C-4800-1035-804C-B6C04F425931" />
</configuration>
<capabilities>
<capability id="smbios-2.7">SMBIOS version 2.7</capability>
<capability id="dmi-2.7">DMI version 2.7</capability>
<capability id="vsyscall32">32-bit processes</capability>
</capabilities>
<node id="core" claimed="true" class="bus" handle="DMI:0200">
<node id="pci:1" claimed="true" class="bridge" handle="PCIBUS:0000:03">
<description>PCI bridge</description>
<product>Xeon E5/Core i7 IIO PCI Express Root Port 2a</product>
<vendor>Intel Corporation</vendor>
<physid>2</physid>
<businfo>pci@0000:00:02.0</businfo>
<version>07</version>
<width units="bits">32</width>
<clock units="Hz">33000000</clock>
<configuration>
<setting id="driver" value="pcieport" />
</configuration>
<capabilities>
<capability id="pci" />
<capability id="msi">Message Signalled Interrupts</capability>
<capability id="pciexpress">PCI Express</capability>
<capability id="pm">Power Management</capability>
<capability id="normal_decode" />
<capability id="bus_master">bus mastering</capability>
<capability id="cap_list">PCI capabilities listing</capability>
</capabilities>
<resources>
<resource type="irq" value="26" />
</resources>
<node id="network:0" claimed="true" class="network" handle="PCI:0000:00:03.0">
<description>Ethernet interface</description>
<product>I350 Gigabit Network Connection</product>
<vendor>Intel Corporation</vendor>
<physid>0</physid>
<businfo>pci@0000:00:03.0</businfo>
<logicalname>eno1</logicalname>
<version>01</version>
<serial>b8:ca:3a:65:7d:d8</serial>
<size units="bit/s">1000000000</size>
<capacity>1000000000</capacity>
<width units="bits">32</width>
<clock units="Hz">33000000</clock>
</node>
</node>
</node>
<node id="disk:0" claimed="true" class="disk" handle="SCSI:00:02:00:00">
<description>SCSI Disk</description>
<product>PERC H710P</product>
<vendor>DELL</vendor>
<physid>2.0.0</physid>
<businfo>scsi@2:0.0.0</businfo>
<logicalname>/dev/sda</logicalname>
<dev>8:0</dev>
<version>3.13</version>
<serial>0044016c12771be71900034cfba0a38c</serial>
<size units="bytes">299439751168</size>
</node>
</node>
</list>
"""
xml_example = xml_example.replace('\n', '')
def side_effect(**kwargs):
build_data = objects.BuildData(
node_name="controller01",
task_id="tid",
generator="lshw",
data_format="text/plain",
data_element=xml_example)
return [build_data]
drydock_state.get_build_data = Mock(side_effect=side_effect)
design_status, design_data = deckhand_orchestrator.get_effective_site(
design_ref)
nodes = design_data.baremetal_nodes
nodes[0].apply_logicalnames(design_data, state_manager=drydock_state)
expected = {'primary_boot': 'sda', 'prim_nic02': 'prim_nic02', 'prim_nic01': 'eno1'}
# Tests the whole dictionary
assert nodes[0].logicalnames == expected
# Makes sure the path and / are both removed from primary_boot
assert nodes[0].logicalnames['primary_boot'] == 'sda'
assert nodes[0].get_logicalname('primary_boot') == 'sda'
# A simple logicalname
assert nodes[0].logicalnames['prim_nic01'] == 'eno1'
assert nodes[0].get_logicalname('prim_nic01') == 'eno1'
# Logicalname is not found, returns the alias
assert nodes[0].logicalnames['prim_nic02'] == 'prim_nic02'
assert nodes[0].get_logicalname('prim_nic02') == 'prim_nic02'

View File

@ -19,7 +19,7 @@ from drydock_provisioner.orchestrator.validations.validator import Validator
class TestDesignValidator(object):
def test_validate_design(self, deckhand_ingester, drydock_state,
input_files):
input_files, mock_get_build_data):
"""Test the basic validation engine."""
input_file = input_files.join("deckhand_fullsite.yaml")

View File

@ -21,7 +21,7 @@ from drydock_provisioner.orchestrator.validations.validator import Validator
class TestRationalBootStorage(object):
def test_boot_storage_rational(self, deckhand_ingester, drydock_state,
input_files):
input_files, mock_get_build_data):
input_file = input_files.join("validation.yaml")
design_ref = "file://%s" % str(input_file)
@ -39,7 +39,7 @@ class TestRationalBootStorage(object):
assert len(message_list) == 1
def test_invalid_boot_storage_small(self, deckhand_ingester, drydock_state,
input_files):
input_files, mock_get_build_data):
input_file = input_files.join("invalid_boot_storage_small.yaml")
design_ref = "file://%s" % str(input_file)

View File

@ -56,7 +56,7 @@ class TestRationalNetworkTrunking(object):
assert msg.get('error') is False
def test_invalid_storage_partitioning(self, deckhand_ingester,
drydock_state, input_files):
drydock_state, input_files, mock_get_build_data):
input_file = input_files.join("invalid_validation.yaml")
design_ref = "file://%s" % str(input_file)

View File

@ -39,7 +39,7 @@ class TestStorageSizing(object):
assert msg.get('error') is False
def test_invalid_storage_sizing(self, deckhand_ingester, drydock_state,
input_files):
input_files, mock_get_build_data):
input_file = input_files.join("invalid_validation.yaml")
design_ref = "file://%s" % str(input_file)

View File

@ -21,7 +21,7 @@ from drydock_provisioner.orchestrator.validations.validator import Validator
class TestValidPlatform(object):
def test_valid_platform(self, mocker, deckhand_ingester, drydock_state,
input_files):
input_files, mock_get_build_data):
mock_images = mocker.patch(
"drydock_provisioner.drivers.node.maasdriver.driver."
"MaasNodeDriver.get_available_images")