Make DHCP provider pluggable

Previously, all dhcp was done explicitly through Neutron.  This commit
adds an interface for dhcp providers, currently implemented by a
Neutron provider and a noop provider.

The provider can be set in the dhcp_provider config option, and
defaults to neutron.

Co-Authored-By: Jim Rollenhagen <jim@jimrollenhagen.com>
Co-Authored-By: Lucas Alvares Gomes <lucasagomes@gmail.com>
Implements: blueprint support-external-dhcp
Closes-Bug: #1282168
Change-Id: I7eea490c573914c6cd2a73056fe3a0ec91065dc0
This commit is contained in:
Ellen Hui 2014-08-05 23:50:56 +00:00 committed by Chris Krelle
parent ff4d810cbd
commit c0fc1197a4
2 changed files with 4 additions and 187 deletions

View File

@ -1,187 +0,0 @@
#
# Copyright 2014 OpenStack Foundation
# 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 time
from neutronclient.common import exceptions as neutron_client_exc
from neutronclient.v2_0 import client as clientv20
from oslo.config import cfg
from ironic.common import exception
from ironic.common import keystone
from ironic.drivers.modules import ssh
from ironic.openstack.common import log as logging
neutron_opts = [
cfg.StrOpt('url',
default='http://$my_ip:9696',
help='URL for connecting to neutron.'),
cfg.IntOpt('url_timeout',
default=30,
help='Timeout value for connecting to neutron in seconds.'),
cfg.StrOpt('auth_strategy',
default='keystone',
help='Default authentication strategy to use when connecting '
'to neutron. Can be either "keystone" or "noauth". '
'Running neutron in noauth mode (related to but not '
'affected by this setting) is insecure and should only be '
'used for testing.')
]
CONF = cfg.CONF
CONF.import_opt('my_ip', 'ironic.netconf')
CONF.register_opts(neutron_opts, group='neutron')
LOG = logging.getLogger(__name__)
class NeutronAPI(object):
"""API for communicating to neutron 2.x API."""
def __init__(self, context):
self.context = context
self.client = None
params = {
'timeout': CONF.neutron.url_timeout,
'insecure': CONF.keystone_authtoken.insecure,
'ca_cert': CONF.keystone_authtoken.certfile,
}
if CONF.neutron.auth_strategy not in ['noauth', 'keystone']:
raise exception.ConfigInvalid(_('Neutron auth_strategy should be '
'either "noauth" or "keystone".'))
if CONF.neutron.auth_strategy == 'noauth':
params['endpoint_url'] = CONF.neutron.url
params['auth_strategy'] = 'noauth'
elif (CONF.neutron.auth_strategy == 'keystone' and
context.auth_token is None):
params['endpoint_url'] = (CONF.neutron.url or
keystone.get_service_url('neutron'))
params['username'] = CONF.keystone_authtoken.admin_user
params['tenant_name'] = CONF.keystone_authtoken.admin_tenant_name
params['password'] = CONF.keystone_authtoken.admin_password
params['auth_url'] = (CONF.keystone_authtoken.auth_uri or '')
else:
params['token'] = context.auth_token
params['endpoint_url'] = CONF.neutron.url
params['auth_strategy'] = None
self.client = clientv20.Client(**params)
def update_port_dhcp_opts(self, port_id, dhcp_options):
"""Update a port's attributes.
Update one or more DHCP options on the specified port.
For the relevant API spec, see
http://docs.openstack.org/api/openstack-network/2.0/content/extra-dhc-opt-ext-update.html # noqa
:param port_id: designate which port these attributes
will be applied to.
:param dhcp_options: this will be a list of dicts, e.g.
[{'opt_name': 'bootfile-name',
'opt_value': 'pxelinux.0'},
{'opt_name': 'server-ip-address',
'opt_value': '123.123.123.456'},
{'opt_name': 'tftp-server',
'opt_value': '123.123.123.123'}]
:raises: FailedToUpdateDHCPOptOnPort
"""
port_req_body = {'port': {'extra_dhcp_opts': dhcp_options}}
try:
self.client.update_port(port_id, port_req_body)
except neutron_client_exc.NeutronClientException:
LOG.exception(_("Failed to update Neutron port %s."), port_id)
raise exception.FailedToUpdateDHCPOptOnPort(port_id=port_id)
def update_port_address(self, port_id, address):
"""Update a port's mac address.
:param port_id: Neutron port id.
:param address: new MAC address.
:raises: FailedToUpdateMacOnPort
"""
port_req_body = {'port': {'mac_address': address}}
try:
self.client.update_port(port_id, port_req_body)
except neutron_client_exc.NeutronClientException:
LOG.exception(_("Failed to update MAC address on Neutron port %s."
), port_id)
raise exception.FailedToUpdateMacOnPort(port_id=port_id)
def get_node_vif_ids(task):
"""Get all Neutron VIF ids for a node.
This function does not handle multi node operations.
:param task: a TaskManager instance.
:returns: A dict of the Node's port UUIDs and their associated VIFs
"""
port_vifs = {}
for port in task.ports:
vif = port.extra.get('vif_port_id')
if vif:
port_vifs[port.uuid] = vif
return port_vifs
def update_neutron(task, options):
"""Send or update the DHCP BOOT options to Neutron for this node."""
vifs = get_node_vif_ids(task)
if not vifs:
LOG.warning(_("No VIFs found for node %(node)s when attempting to "
"update Neutron DHCP BOOT options."),
{'node': task.node.uuid})
return
# TODO(deva): decouple instantiation of NeutronAPI from task.context.
# Try to use the user's task.context.auth_token, but if it
# is not present, fall back to a server-generated context.
# We don't need to recreate this in every method call.
api = NeutronAPI(task.context)
failures = []
for port_id, port_vif in vifs.iteritems():
try:
api.update_port_dhcp_opts(port_vif, options)
except exception.FailedToUpdateDHCPOptOnPort:
failures.append(port_id)
if failures:
if len(failures) == len(vifs):
raise exception.FailedToUpdateDHCPOptOnPort(_(
"Failed to set DHCP BOOT options for any port on node %s.") %
task.node.uuid)
else:
LOG.warning(_("Some errors were encountered when updating the "
"DHCP BOOT options for node %(node)s on the "
"following ports: %(ports)s."),
{'node': task.node.uuid, 'ports': failures})
_wait_for_neutron_update(task)
def _wait_for_neutron_update(task):
"""Wait for Neutron agents to process all requested changes if required."""
# TODO(adam_g): Hack to workaround bug 1334447 until we have a mechanism
# for synchronizing events with Neutron. We need to sleep only if we are
# booting VMs, which is implied by SSHPower, to ensure they do not boot
# before Neutron agents have setup sufficent DHCP config for netboot.
if isinstance(task.driver.power, ssh.SSHPower):
LOG.debug(_("Waiting 15 seconds for Neutron."))
time.sleep(15)

View File

@ -29,6 +29,10 @@ console_scripts =
ironic-conductor = ironic.cmd.conductor:main
ironic-rootwrap = oslo.rootwrap.cmd:main
ironic.dhcp =
neutron = ironic.dhcp.neutron:NeutronDHCPApi
none = ironic.dhcp.none:NoneDHCPApi
ironic.drivers =
agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver