Source code for ironic.dhcp.dnsmasq

# Copyright 2022 Red Hat, Inc.
#    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
#    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

from oslo_log import log as logging
from oslo_utils import uuidutils

from ironic.conf import CONF
from ironic.dhcp import base

LOG = logging.getLogger(__name__)

[docs] class DnsmasqDHCPApi(base.BaseDHCP): """API for managing host specific Dnsmasq configuration."""
[docs] def update_port_dhcp_opts(self, port_id, dhcp_options, token=None, context=None): pass
[docs] def update_dhcp_opts(self, task, options, vifs=None): """Send or update the DHCP BOOT options for this node. :param task: A TaskManager instance. :param options: this will be a list of dicts, e.g. :: [{'opt_name': '67', 'opt_value': 'pxelinux.0', 'ip_version': 4}, {'opt_name': '66', 'opt_value': '', 'ip_version': 4}] :param vifs: Ignored argument """ node = task.node macs = set(self._pxe_enabled_macs(task.ports)) tag = node.driver_internal_info.get('dnsmasq_tag') if not tag: tag = uuidutils.generate_uuid() node.set_driver_internal_info('dnsmasq_tag', tag) option_entries = [] for option in options: try: option_entries.append( f'tag:{tag},{option["opt_name"]},{option["opt_value"]}') except KeyError as missing: LOG.warning('Ignoring option %(opt)s for node %(node)s: ' 'missing %(missing)s', {'opt': option, 'node': node.uuid, 'missing': missing}) opt_file = self._opt_file_path(node) LOG.debug('Writing DHCP options for node %(node)s to %(dest)s: ' '%(opts)s', {'node': node.uuid, 'dest': opt_file, 'opts': '; '.join(option_entries)}) with open(opt_file, 'w') as f: f.write('\n'.join(option_entries) + '\n') for mac in macs: # Tag each address with the unique uuid scoped to # this node and DHCP transaction host_file = self._host_file_path(mac) entry = f'{mac},set:{tag},set:ironic' LOG.debug('Writing DHCP host file for node %(node)s to %(dest)s: ' '%(entry)s', {'node': node.uuid, 'dest': host_file, 'entry': entry}) with open(host_file, 'w') as f: f.write(entry + '\n')
def _opt_file_path(self, node): return os.path.join(CONF.dnsmasq.dhcp_optsdir, 'ironic-{}.conf'.format(node.uuid)) def _host_file_path(self, mac): return os.path.join(CONF.dnsmasq.dhcp_hostsdir, 'ironic-{}.conf'.format(mac)) def _pxe_enabled_macs(self, ports): for port in ports: if port.pxe_enabled: yield port.address
[docs] def get_ip_addresses(self, task): """Get IP addresses for all ports/portgroups in `task`. :param task: a TaskManager instance. :returns: List of IP addresses associated with task's ports/portgroups. """ lease_path = CONF.dnsmasq.dhcp_leasefile macs = set(self._pxe_enabled_macs(task.ports)) addresses = [] with open(lease_path, 'r') as f: for line in f.readlines(): lease = line.split() if lease[1] in macs: addresses.append(lease[2]) LOG.debug('Found addresses for %s: %s', task.node.uuid, ', '.join(addresses)) return addresses
[docs] def clean_dhcp_opts(self, task): """Clean up the DHCP BOOT options for the host in `task`. :param task: A TaskManager instance. :raises: FailedToCleanDHCPOpts """ node = task.node # Discard this unique tag node.del_driver_internal_info('dnsmasq_tag') # Changing the host rule to ignore will be picked up by dnsmasq # without requiring a SIGHUP. When the mac address is active again # this file will be replaced with one that applies a new unique tag. macs = set(self._pxe_enabled_macs(task.ports)) for mac in macs: host_file = self._host_file_path(mac) entry = f'{mac},ignore' LOG.debug('Writing DHCP host file for node %(node)s to %(dest)s: ' '%(entry)s', {'node': node.uuid, 'dest': host_file, 'entry': entry}) with open(host_file, 'w') as f: f.write(entry + '\n') # Deleting the file containing dhcp-option won't remove the rules from # dnsmasq but no requests will be tagged with the dnsmasq_tag uuid so # these rules will not apply. opt_file = self._opt_file_path(node) if os.path.exists(opt_file): LOG.debug('Removing DHCP options file for node %(node)s at ' '%(dest)s', {'node': node.uuid, 'dest': opt_file}) os.remove(opt_file)
[docs] def supports_ipxe_tag(self): """Whether the provider will correctly apply the 'ipxe' tag. When iPXE makes a DHCP request, does this provider support adding the tag `ipxe` or `ipxe6` (for IPv6). When the provider returns True, options can be added which filter on these tags. The `dnsmasq` provider sets this to True on the assumption that the following is included in the dnsmasq.conf: dhcp-match=set:ipxe,175 :returns: True """ return True