Rename/update common/tftp.py to common/pxe_utils.py

The functions in the common/tftp.py are not related to TFTP, the
functions are PXE related and most of them are used to generate the
PXE configuration file. Moving it from tftp.py to pxe_utils.py will make
it less confusing and is also needed by the iPXE work since iPXE doesn't
generate any configuration file under the TFTP folder.

The dhcp_options_for_instance() method was updated to not receive the PXE
boot file image from the parameters and just get it from the configuration
because we were always using the value from the configuration anyway.

Implements: blueprint ipxe-boot
Change-Id: Icfbaa4d6cdb9cbe0c392bf3dbd50ad980c70560c
This commit is contained in:
Lucas Alvares Gomes 2014-06-30 17:00:56 +01:00
parent 036c79e38f
commit a9d7a6567f
8 changed files with 261 additions and 225 deletions

View File

@ -972,6 +972,13 @@
# Directory where images are stored on disk. (string value)
#images_path=/var/lib/ironic/images/
# IP address of Ironic compute node's tftp server. (string
# value)
#tftp_server=$my_ip
# Ironic compute node's tftp root path. (string value)
#tftp_root=/tftpboot
# Directory where master tftp images are stored on disk.
# (string value)
#tftp_master_path=/tftpboot/master_images
@ -1016,17 +1023,3 @@
#libvirt_uri=qemu:///system
[tftp]
#
# Options defined in ironic.common.tftp
#
# IP address of Ironic compute node's tftp server. (string
# value)
#tftp_server=$my_ip
# Ironic compute node's tftp root path. (string value)
#tftp_root=/tftpboot

View File

@ -23,7 +23,6 @@ from oslo.config import cfg
from ironic.api import acl
from ironic.common import exception
from ironic.common import keystone
from ironic.common import tftp
from ironic.drivers.modules import ssh
from ironic.openstack.common import log as logging
@ -144,9 +143,8 @@ def get_node_vif_ids(task):
return port_vifs
def update_neutron(task, pxe_bootfile_name):
def update_neutron(task, options):
"""Send or update the DHCP BOOT options to Neutron for this node."""
options = tftp.dhcp_options_for_instance(pxe_bootfile_name)
vifs = get_node_vif_ids(task)
if not vifs:
LOG.warning(_("No VIFs found for node %(node)s when attempting to "

167
ironic/common/pxe_utils.py Normal file
View File

@ -0,0 +1,167 @@
#
# Copyright 2014 Rackspace, Inc
# All 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 os
import jinja2
from oslo.config import cfg
from ironic.common import utils
from ironic.drivers import utils as driver_utils
from ironic.openstack.common import fileutils
from ironic.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
PXE_CFG_DIR_NAME = 'pxelinux.cfg'
def _ensure_config_dirs_exist(node_uuid):
"""Ensure that the node's and PXE configuration directories exist.
:param node_uuid: the UUID of the node.
"""
tftp_root = CONF.pxe.tftp_root
fileutils.ensure_tree(os.path.join(tftp_root, node_uuid))
fileutils.ensure_tree(os.path.join(tftp_root, PXE_CFG_DIR_NAME))
def _build_pxe_config(pxe_options, template):
"""Build the PXE boot configuration file.
This method builds the PXE boot configuration file by rendering the
template with the given parameters.
:param pxe_options: A dict of values to set on the configuration file.
:param template: The PXE configuration template.
:returns: A formatted string with the file content.
"""
tmpl_path, tmpl_file = os.path.split(template)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
template = env.get_template(tmpl_file)
return template.render({'pxe_options': pxe_options,
'ROOT': '{{ ROOT }}'})
def _link_mac_pxe_configs(task):
"""Link each MAC address with the PXE configuration file.
:param task: A TaskManager instance.
"""
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
for mac in driver_utils.get_node_mac_addresses(task):
mac_path = _get_pxe_mac_path(mac)
utils.unlink_without_raise(mac_path)
utils.create_link_without_raise(pxe_config_file_path, mac_path)
def _get_pxe_mac_path(mac):
"""Convert a MAC address into a PXE config file name.
:param mac: A MAC address string in the format xx:xx:xx:xx:xx:xx.
:returns: the path to the config file.
"""
return os.path.join(
CONF.pxe.tftp_root,
PXE_CFG_DIR_NAME,
"01-" + mac.replace(":", "-").lower()
)
def get_deploy_kr_info(node_uuid, driver_info):
"""Get uuid and tftp path for deploy kernel and ramdisk.
Note: driver_info should be validated outside of this method.
"""
image_info = {}
for label in ('deploy_kernel', 'deploy_ramdisk'):
# the values for these keys will look like "glance://image-uuid"
image_info[label] = (
str(driver_info[label]).split('/')[-1],
os.path.join(CONF.pxe.tftp_root, node_uuid, label)
)
return image_info
def get_pxe_config_file_path(node_uuid):
"""Generate the path for the node's PXE configuration file.
:param node_uuid: the UUID of the node.
:returns: The path to the node's PXE configuration file.
"""
return os.path.join(CONF.pxe.tftp_root, node_uuid, 'config')
def create_pxe_config(task, pxe_options, template=None):
"""Generate PXE configuration file and MAC address links for it.
This method will generate the PXE configuration file for the task's
node under a directory named with the UUID of that node. For each
MAC address (port) of that node, a symlink for the configuration file
will be created under the PXE configuration directory, so regardless
of which port boots first they'll get the same PXE configuration.
:param task: A TaskManager instance.
:param pxe_options: A dictionary with the PXE configuration
parameters.
:param template: The PXE configuration template. If no template is
given the CONF.pxe.pxe_config_template will be used.
"""
LOG.debug("Building PXE config for node %s", task.node.uuid)
if template is None:
template = CONF.pxe.pxe_config_template
_ensure_config_dirs_exist(task.node.uuid)
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
pxe_config = _build_pxe_config(pxe_options, template)
utils.write_to_file(pxe_config_file_path, pxe_config)
_link_mac_pxe_configs(task)
def clean_up_pxe_config(task):
"""Clean up the TFTP environment for the task's node.
:param task: A TaskManager instance.
"""
LOG.debug("Cleaning up PXE config for node %s", task.node.uuid)
for mac in driver_utils.get_node_mac_addresses(task):
utils.unlink_without_raise(_get_pxe_mac_path(mac))
utils.rmtree_without_raise(os.path.join(CONF.pxe.tftp_root,
task.node.uuid))
def dhcp_options_for_instance():
"""Retrieves the DHCP PXE boot options."""
return [{'opt_name': 'bootfile-name',
'opt_value': CONF.pxe.pxe_bootfile_name},
{'opt_name': 'server-ip-address',
'opt_value': CONF.pxe.tftp_server},
{'opt_name': 'tftp-server',
'opt_value': CONF.pxe.tftp_server}
]

View File

@ -1,140 +0,0 @@
#
# Copyright 2014 Rackspace, Inc
# All 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 os
import jinja2
from oslo.config import cfg
from ironic.common import utils
from ironic.drivers import utils as driver_utils
from ironic.openstack.common import fileutils
from ironic.openstack.common import log as logging
tftp_opts = [
cfg.StrOpt('tftp_server',
default='$my_ip',
help='IP address of Ironic compute node\'s tftp server.',
deprecated_group='pxe'),
cfg.StrOpt('tftp_root',
default='/tftpboot',
help='Ironic compute node\'s tftp root path.',
deprecated_group='pxe')
]
CONF = cfg.CONF
CONF.register_opts(tftp_opts, group='tftp')
LOG = logging.getLogger(__name__)
def get_deploy_kr_info(node_uuid, driver_info):
"""Get uuid and tftp path for deploy kernel and ramdisk.
Note: driver_info should be validated outside of this method.
"""
image_info = {}
for label in ('deploy_kernel', 'deploy_ramdisk'):
# the values for these keys will look like "glance://image-uuid"
image_info[label] = (
str(driver_info[label]).split('/')[-1],
os.path.join(CONF.tftp.tftp_root, node_uuid, label)
)
return image_info
def create_pxe_config(task, pxe_options, pxe_config_template):
"""Generate PXE configuration file and MAC symlinks for it."""
node = task.node
fileutils.ensure_tree(os.path.join(CONF.tftp.tftp_root,
node.uuid))
fileutils.ensure_tree(os.path.join(CONF.tftp.tftp_root,
'pxelinux.cfg'))
pxe_config_file_path = get_pxe_config_file_path(node.uuid)
pxe_config = build_pxe_config(node, pxe_options, pxe_config_template)
utils.write_to_file(pxe_config_file_path, pxe_config)
_write_mac_pxe_configs(task)
def clean_up_pxe_config(task):
"""Clean up the TFTP environment for the task's node."""
node = task.node
utils.unlink_without_raise(get_pxe_config_file_path(node.uuid))
for port in driver_utils.get_node_mac_addresses(task):
utils.unlink_without_raise(get_pxe_mac_path(port))
utils.rmtree_without_raise(os.path.join(CONF.tftp.tftp_root, node.uuid))
def _write_mac_pxe_configs(task):
"""Create a file in the PXE config directory for each MAC so regardless
of which port boots first, they'll get the same PXE config.
"""
pxe_config_file_path = get_pxe_config_file_path(task.node.uuid)
for port in driver_utils.get_node_mac_addresses(task):
mac_path = get_pxe_mac_path(port)
utils.unlink_without_raise(mac_path)
utils.create_link_without_raise(pxe_config_file_path, mac_path)
def build_pxe_config(node, pxe_options, pxe_config_template):
"""Build the PXE config file for a node
This method builds the PXE boot configuration file for a node,
given all the required parameters.
:param pxe_options: A dict of values to set on the configuration file
:returns: A formatted string with the file content.
"""
LOG.debug("Building PXE config for deployment %s."), node['id']
tmpl_path, tmpl_file = os.path.split(pxe_config_template)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
template = env.get_template(tmpl_file)
return template.render({'pxe_options': pxe_options,
'ROOT': '{{ ROOT }}'})
def get_pxe_mac_path(mac):
"""Convert a MAC address into a PXE config file name.
:param mac: A mac address string in the format xx:xx:xx:xx:xx:xx.
:returns: the path to the config file.
"""
return os.path.join(
CONF.tftp.tftp_root,
'pxelinux.cfg',
"01-" + mac.replace(":", "-").lower()
)
def get_pxe_config_file_path(node_uuid):
"""Generate the path for an instances PXE config file."""
return os.path.join(CONF.tftp.tftp_root, node_uuid, 'config')
def dhcp_options_for_instance(pxe_bootfile_name):
"""Retrives the DHCP PXE boot options."""
return [{'opt_name': 'bootfile-name',
'opt_value': pxe_bootfile_name},
{'opt_name': 'server-ip-address',
'opt_value': CONF.tftp.tftp_server},
{'opt_name': 'tftp-server',
'opt_value': CONF.tftp.tftp_server}
]

View File

@ -27,8 +27,8 @@ from ironic.common import images
from ironic.common import keystone
from ironic.common import neutron
from ironic.common import paths
from ironic.common import pxe_utils
from ironic.common import states
from ironic.common import tftp
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
@ -56,6 +56,12 @@ pxe_opts = [
cfg.StrOpt('images_path',
default='/var/lib/ironic/images/',
help='Directory where images are stored on disk.'),
cfg.StrOpt('tftp_server',
default='$my_ip',
help='IP address of Ironic compute node\'s tftp server.'),
cfg.StrOpt('tftp_root',
default='/tftpboot',
help='Ironic compute node\'s tftp root path.'),
cfg.StrOpt('tftp_master_path',
default='/tftpboot/master_images',
help='Directory where master tftp images are stored on disk.'),
@ -187,8 +193,8 @@ def _build_pxe_config_options(node, pxe_info, ctx):
This method builds the PXE boot options for a node,
given all the required parameters.
The options should then be passed to tftp.create_pxe_config to create
the actual config files.
The options should then be passed to pxe_utils.create_pxe_config to
create the actual config files.
:param node: a single Node.
:param pxe_info: a dict of values to set on the configuration file
@ -233,7 +239,7 @@ def _get_image_file_path(node_uuid):
def _get_token_file_path(node_uuid):
"""Generate the path for PKI token file."""
return os.path.join(CONF.tftp.tftp_root, 'token-' + node_uuid)
return os.path.join(CONF.pxe.tftp_root, 'token-' + node_uuid)
class PXEImageCache(image_cache.ImageCache):
@ -315,7 +321,7 @@ def _fetch_images(ctx, cache, images_info):
def _cache_tftp_images(ctx, node, pxe_info):
"""Fetch the necessary kernels and ramdisks for the instance."""
fileutils.ensure_tree(
os.path.join(CONF.tftp.tftp_root, node.uuid))
os.path.join(CONF.pxe.tftp_root, node.uuid))
LOG.debug("Fetching kernel and ramdisk for node %s",
node.uuid)
_fetch_images(ctx, TFTPImageCache(), pxe_info.values())
@ -329,7 +335,7 @@ def _cache_instance_image(ctx, node):
to the appropriate places on local disk.
Both sets of kernel and ramdisk are needed for PXE booting, so these
are stored under CONF.tftp.tftp_root.
are stored under CONF.pxe.tftp_root.
At present, the AMI is cached and certain files are injected.
Debian/ubuntu-specific assumptions are made regarding the injected
@ -363,7 +369,7 @@ def _get_tftp_image_info(node, ctx):
d_info = _parse_deploy_info(node)
image_info = {}
image_info.update(tftp.get_deploy_kr_info(node.uuid, d_info))
image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
i_info = node.instance_info
labels = ('kernel', 'ramdisk')
@ -378,7 +384,7 @@ def _get_tftp_image_info(node, ctx):
for label in labels:
image_info[label] = (
i_info[label],
os.path.join(CONF.tftp.tftp_root, node.uuid, label)
os.path.join(CONF.pxe.tftp_root, node.uuid, label)
)
return image_info
@ -503,7 +509,8 @@ class PXEDeploy(base.DeployInterface):
# TODO(yuriyz): more secure way needed for pass auth token
# to deploy ramdisk
_create_token_file(task)
neutron.update_neutron(task, CONF.pxe.pxe_bootfile_name)
dhcp_opts = pxe_utils.dhcp_options_for_instance()
neutron.update_neutron(task, dhcp_opts)
manager_utils.node_set_boot_device(task, 'pxe', persistent=True)
manager_utils.node_power_action(task, states.REBOOT)
@ -535,7 +542,8 @@ class PXEDeploy(base.DeployInterface):
pxe_info = _get_tftp_image_info(task.node, task.context)
pxe_options = _build_pxe_config_options(task.node, pxe_info,
task.context)
tftp.create_pxe_config(task, pxe_options, CONF.pxe.pxe_config_template)
pxe_utils.create_pxe_config(task, pxe_options,
CONF.pxe.pxe_config_template)
_cache_tftp_images(task.context, task.node, pxe_info)
def clean_up(self, task):
@ -555,13 +563,14 @@ class PXEDeploy(base.DeployInterface):
utils.unlink_without_raise(path)
TFTPImageCache().clean_up()
tftp.clean_up_pxe_config(task)
pxe_utils.clean_up_pxe_config(task)
_destroy_images(d_info, node.uuid)
_destroy_token_file(node)
def take_over(self, task):
neutron.update_neutron(task, CONF.pxe.pxe_bootfile_name)
dhcp_opts = pxe_utils.dhcp_options_for_instance()
neutron.update_neutron(task, dhcp_opts)
class VendorPassthru(base.VendorInterface):
@ -581,7 +590,7 @@ class VendorPassthru(base.VendorInterface):
'lun': kwargs.get('lun', '1'),
'image_path': _get_image_file_path(node.uuid),
'pxe_config_path':
tftp.get_pxe_config_file_path(node.uuid),
pxe_utils.get_pxe_config_file_path(node.uuid),
'root_mb': 1024 * int(d_info['root_gb']),
'swap_mb': int(d_info['swap_mb']),
'ephemeral_mb': 1024 * int(d_info['ephemeral_gb']),

View File

@ -29,8 +29,8 @@ from ironic.common.glance_service import base_image_service
from ironic.common import image_service
from ironic.common import keystone
from ironic.common import neutron
from ironic.common import pxe_utils
from ironic.common import states
from ironic.common import tftp
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
@ -223,22 +223,22 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
expected_info = {'ramdisk':
('instance_ramdisk_uuid',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'ramdisk')),
'kernel':
('instance_kernel_uuid',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'kernel')),
'deploy_ramdisk':
('deploy_ramdisk_uuid',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_ramdisk')),
'deploy_kernel':
('deploy_kernel_uuid',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_kernel'))}
show_mock.return_value = properties
@ -258,7 +258,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self.node.instance_info.get('ramdisk'))
@mock.patch.object(utils, 'random_alnum')
@mock.patch.object(tftp, 'build_pxe_config')
@mock.patch.object(pxe_utils, '_build_pxe_config')
def test_build_pxe_config_options(self, build_pxe_mock, random_alnum_mock):
self.config(pxe_append_params='test_param', group='pxe')
# NOTE: right '/' should be removed from url string
@ -286,19 +286,19 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
u'c02d7f33c123/deploy_kernel'
}
image_info = {'deploy_kernel': ('deploy_kernel',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_kernel')),
'deploy_ramdisk': ('deploy_ramdisk',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_ramdisk')),
'kernel': ('kernel_id',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'kernel')),
'ramdisk': ('ramdisk_id',
os.path.join(CONF.tftp.tftp_root,
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'ramdisk'))
}
@ -332,7 +332,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(pxe, '_fetch_images')
def test__cache_tftp_images_master_path(self, mock_fetch_image):
temp_dir = tempfile.mkdtemp()
self.config(tftp_root=temp_dir, group='tftp')
self.config(tftp_root=temp_dir, group='pxe')
self.config(tftp_master_path=os.path.join(temp_dir,
'tftp_master_path'),
group='pxe')
@ -499,7 +499,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
self.context = context.get_admin_context()
self.context.auth_token = '4562138218392831'
self.temp_dir = tempfile.mkdtemp()
self.config(tftp_root=self.temp_dir, group='tftp')
self.config(tftp_root=self.temp_dir, group='pxe')
self.temp_dir = tempfile.mkdtemp()
self.config(images_path=self.temp_dir, group='pxe')
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
@ -646,7 +646,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(pxe, '_get_tftp_image_info')
@mock.patch.object(pxe, '_cache_tftp_images')
@mock.patch.object(pxe, '_build_pxe_config_options')
@mock.patch.object(tftp, 'create_pxe_config')
@mock.patch.object(pxe_utils, 'create_pxe_config')
def test_prepare(self, mock_pxe_config,
mock_build_pxe, mock_cache_tftp_images,
mock_tftp_img_info):
@ -675,6 +675,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
fake_img_path = '/test/path/test.img'
mock_get_image_file_path.return_value = fake_img_path
mock_get_image_mb.return_value = 1
dhcp_opts = pxe_utils.dhcp_options_for_instance()
with task_manager.acquire(self.context,
self.node.uuid, shared=False) as task:
@ -685,7 +686,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_get_image_file_path.assert_called_once_with(task.node.uuid)
mock_get_image_mb.assert_called_once_with(fake_img_path)
mock_update_neutron.assert_called_once_with(
task, CONF.pxe.pxe_bootfile_name)
task, dhcp_opts)
mock_node_set_boot.assert_called_once_with(task, 'pxe',
persistent=True)
mock_node_power_action.assert_called_once_with(task, states.REBOOT)
@ -724,11 +725,12 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(neutron, 'update_neutron')
def test_take_over(self, update_neutron_mock):
dhcp_opts = pxe_utils.dhcp_options_for_instance()
with task_manager.acquire(
self.context, self.node.uuid, shared=True) as task:
task.driver.deploy.take_over(task)
update_neutron_mock.assert_called_once_with(
task, CONF.pxe.pxe_bootfile_name)
task, dhcp_opts)
@mock.patch.object(pxe, 'InstanceImageCache')
def test_continue_deploy_good(self, mock_image_cache):
@ -837,8 +839,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(pxe, '_get_tftp_image_info')
def clean_up_config(self, get_tftp_image_info_mock, master=None):
temp_dir = tempfile.mkdtemp()
self.config(tftp_root=temp_dir, group='tftp')
tftp_master_dir = os.path.join(CONF.tftp.tftp_root,
self.config(tftp_root=temp_dir, group='pxe')
tftp_master_dir = os.path.join(CONF.pxe.tftp_root,
'tftp_master')
self.config(tftp_master_path=tftp_master_dir, group='pxe')
os.makedirs(tftp_master_dir)
@ -858,16 +860,16 @@ class PXEDriverTestCase(db_base.DbTestCase):
uuid='bb43dc0b-03f2-4d2e-ae87-c02d7f33cc53',
node_id='123')))
d_kernel_path = os.path.join(CONF.tftp.tftp_root,
d_kernel_path = os.path.join(CONF.pxe.tftp_root,
self.node.uuid, 'deploy_kernel')
image_info = {'deploy_kernel': ('deploy_kernel_uuid', d_kernel_path)}
get_tftp_image_info_mock.return_value = image_info
pxecfg_dir = os.path.join(CONF.tftp.tftp_root, 'pxelinux.cfg')
pxecfg_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')
os.makedirs(pxecfg_dir)
instance_dir = os.path.join(CONF.tftp.tftp_root,
instance_dir = os.path.join(CONF.pxe.tftp_root,
self.node.uuid)
image_dir = os.path.join(CONF.pxe.images_path, self.node.uuid)
os.makedirs(instance_dir)
@ -889,7 +891,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
os.link(master_deploy_kernel_path, deploy_kernel_path)
os.link(master_instance_path, image_path)
if master == 'in_use':
deploy_kernel_link = os.path.join(CONF.tftp.tftp_root,
deploy_kernel_link = os.path.join(CONF.pxe.tftp_root,
'deploy_kernel_link')
image_link = os.path.join(CONF.pxe.images_path,
'image_link')

View File

@ -21,7 +21,7 @@ from oslo.config import cfg
from ironic.common import exception
from ironic.common import neutron
from ironic.common import tftp
from ironic.common import pxe_utils
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.db import api as dbapi
@ -224,7 +224,7 @@ class TestNeutron(base.TestCase):
@mock.patch('ironic.common.neutron.NeutronAPI.update_port_dhcp_opts')
@mock.patch('ironic.common.neutron.get_node_vif_ids')
def test_update_neutron(self, mock_gnvi, mock_updo, mock_wait_neutron):
opts = tftp.dhcp_options_for_instance(CONF.pxe.pxe_bootfile_name)
opts = pxe_utils.dhcp_options_for_instance()
mock_gnvi.return_value = {'port-uuid': 'vif-uuid'}
with task_manager.acquire(self.context,
self.node.uuid) as task:

View File

@ -19,20 +19,22 @@ import os
import mock
from oslo.config import cfg
from ironic.common import tftp
from ironic.common import pxe_utils
from ironic.conductor import task_manager
from ironic.db import api as dbapi
from ironic.openstack.common import context
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import base as db_base
from ironic.tests.db import utils as dbutils
from ironic.tests.objects import utils as object_utils
CONF = cfg.CONF
class TestNetworkUtils(db_base.DbTestCase):
class TestPXEUtils(db_base.DbTestCase):
def setUp(self):
super(TestNetworkUtils, self).setUp()
super(TestPXEUtils, self).setUp()
mgr_utils.mock_the_extension_manager(driver="fake")
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
@ -54,10 +56,10 @@ class TestNetworkUtils(db_base.DbTestCase):
}
self.node = object_utils.create_test_node(self.context)
def test_build_pxe_config(self):
def test__build_pxe_config(self):
rendered_template = tftp.build_pxe_config(
self.node, self.pxe_options, CONF.pxe.pxe_config_template)
rendered_template = pxe_utils._build_pxe_config(
self.pxe_options, CONF.pxe.pxe_config_template)
expected_template = open(
'ironic/tests/drivers/pxe_config.template').read()
@ -85,67 +87,72 @@ class TestNetworkUtils(db_base.DbTestCase):
mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-67')
]
with task_manager.acquire(self.context, self.node.uuid) as task:
tftp._write_mac_pxe_configs(task)
pxe_utils._link_mac_pxe_configs(task)
unlink_mock.assert_has_calls(unlink_calls)
create_link_mock.assert_has_calls(create_link_calls)
@mock.patch('ironic.common.utils.write_to_file')
@mock.patch('ironic.common.tftp.build_pxe_config')
@mock.patch.object(pxe_utils, '_build_pxe_config')
@mock.patch('ironic.openstack.common.fileutils.ensure_tree')
def test_create_pxe_config(self, ensure_tree_mock, build_mock,
write_mock):
build_mock.return_value = self.pxe_options
with task_manager.acquire(self.context, self.node.uuid) as task:
tftp.create_pxe_config(task, self.pxe_options,
CONF.pxe.pxe_config_template)
build_mock.assert_called_with(task.node, self.pxe_options,
CONF.pxe.pxe_config_template)
pxe_utils.create_pxe_config(task, self.pxe_options,
CONF.pxe.pxe_config_template)
build_mock.assert_called_with(self.pxe_options,
CONF.pxe.pxe_config_template)
ensure_calls = [
mock.call(os.path.join(CONF.tftp.tftp_root, self.node.uuid)),
mock.call(os.path.join(CONF.tftp.tftp_root, 'pxelinux.cfg'))
mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)),
mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg'))
]
ensure_tree_mock.has_calls(ensure_calls)
pxe_config_file_path = tftp.get_pxe_config_file_path(self.node.uuid)
write_mock.assert_called_with(pxe_config_file_path, self.pxe_options)
pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options)
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
@mock.patch('ironic.common.utils.unlink_without_raise', autospec=True)
def test_clean_up_pxe_config(self, unlink_mock, rmtree_mock):
address = "aa:aa:aa:aa:aa:aa"
pdict = dbutils.get_test_port(node_uuid=self.node.uuid,
address=address)
self.dbapi.create_port(pdict)
with task_manager.acquire(self.context, self.node.uuid) as task:
tftp.clean_up_pxe_config(task)
pxe_utils.clean_up_pxe_config(task)
unlink_mock.assert_called_once_with(
tftp.get_pxe_config_file_path(self.node.uuid))
unlink_mock.assert_called_once_with("/tftpboot/pxelinux.cfg/01-%s"
% address.replace(':', '-'))
rmtree_mock.assert_called_once_with(
os.path.join(CONF.tftp.tftp_root, self.node.uuid))
os.path.join(CONF.pxe.tftp_root, self.node.uuid))
def test_get_pxe_mac_path(self):
def test__get_pxe_mac_path(self):
mac = '00:11:22:33:44:55:66'
self.assertEqual('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-66',
tftp.get_pxe_mac_path(mac))
pxe_utils._get_pxe_mac_path(mac))
def test_get_pxe_config_file_path(self):
self.assertEqual(os.path.join(CONF.tftp.tftp_root,
self.assertEqual(os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'config'),
tftp.get_pxe_config_file_path(self.node.uuid))
pxe_utils.get_pxe_config_file_path(self.node.uuid))
def test_dhcp_options_for_instance(self):
self.config(tftp_server='192.0.2.1', group='tftp')
self.config(tftp_server='192.0.2.1', group='pxe')
self.config(pxe_bootfile_name='fake-bootfile', group='pxe')
expected_info = [{'opt_name': 'bootfile-name',
'opt_value': CONF.pxe.pxe_bootfile_name},
'opt_value': 'fake-bootfile'},
{'opt_name': 'server-ip-address',
'opt_value': '192.0.2.1'},
{'opt_name': 'tftp-server',
'opt_value': '192.0.2.1'}
]
self.assertEqual(expected_info, tftp.dhcp_options_for_instance(
CONF.pxe.pxe_bootfile_name))
self.assertEqual(expected_info, pxe_utils.dhcp_options_for_instance())
def test_get_deploy_kr_info(self):
self.config(tftp_root='/tftp', group='tftp')
self.config(tftp_root='/tftp', group='pxe')
node_uuid = 'fake-node'
driver_info = {
'deploy_kernel': 'glance://deploy-kernel',
@ -159,14 +166,14 @@ class TestNetworkUtils(db_base.DbTestCase):
'/tftp/fake-node/deploy_ramdisk'),
}
kr_info = tftp.get_deploy_kr_info(node_uuid, driver_info)
kr_info = pxe_utils.get_deploy_kr_info(node_uuid, driver_info)
self.assertEqual(expected, kr_info)
def test_get_deploy_kr_info_bad_driver_info(self):
self.config(tftp_root='/tftp', group='tftp')
self.config(tftp_root='/tftp', group='pxe')
node_uuid = 'fake-node'
driver_info = {}
self.assertRaises(KeyError,
tftp.get_deploy_kr_info,
pxe_utils.get_deploy_kr_info,
node_uuid,
driver_info)