1 # Copyright (c) 2012 OpenStack Foundation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from oslo_config import cfg
21 from oslo_db import exception as db_exc
22 from oslo_log import log as logging
24 from oslo_utils import excutils
26 from neutron._i18n import _, _LW
27 from neutron.api.v2 import attributes
28 from neutron.common import constants
29 from neutron.common import exceptions as n_exc
30 from neutron.common import utils
31 from neutron.db import api as db_api
32 from neutron.extensions import portbindings
33 from neutron import manager
34 from neutron.plugins.common import utils as p_utils
35 from neutron.quota import resource_registry
38 LOG = logging.getLogger(__name__)
41 class DhcpRpcCallback(object):
42 """DHCP agent RPC callback in plugin implementations.
44 This class implements the server side of an rpc interface. The client
45 side of this interface can be found in
46 neutron.agent.dhcp.agent.DhcpPluginApi. For more information about
47 changing rpc interfaces, see doc/source/devref/rpc_api.rst.
50 # API version history:
51 # 1.0 - Initial version.
52 # 1.1 - Added get_active_networks_info, create_dhcp_port,
53 # and update_dhcp_port methods.
54 # 1.2 - Removed get_dhcp_port. When removing a method (Making a
55 # backwards incompatible change) you would normally bump the
56 # major version. However, since the method was unused in the
57 # RPC client for many releases, it should be OK to bump the
58 # minor release instead and claim RPC compatibility with the
59 # last few client versions.
60 # 1.3 - Removed release_port_fixed_ip. It's not used by reference DHCP
61 # agent since Juno, so similar rationale for not bumping the
62 # major version as above applies here too.
63 target = oslo_messaging.Target(
64 namespace=constants.RPC_NAMESPACE_DHCP_PLUGIN,
67 def _get_active_networks(self, context, **kwargs):
68 """Retrieve and return a list of the active networks."""
69 host = kwargs.get('host')
70 plugin = manager.NeutronManager.get_plugin()
71 if utils.is_extension_supported(
72 plugin, constants.DHCP_AGENT_SCHEDULER_EXT_ALIAS):
73 if cfg.CONF.network_auto_schedule:
74 plugin.auto_schedule_networks(context, host)
75 nets = plugin.list_active_networks_on_active_dhcp_agent(
78 filters = dict(admin_state_up=[True])
79 nets = plugin.get_networks(context, filters=filters)
82 def _port_action(self, plugin, context, port, action):
83 """Perform port operations taking care of concurrency issues."""
85 if action == 'create_port':
86 return p_utils.create_port(plugin, context, port)
87 elif action == 'update_port':
88 return plugin.update_port(context, port['id'], port)
90 msg = _('Unrecognized action')
91 raise n_exc.Invalid(message=msg)
92 except (db_exc.DBError, n_exc.NetworkNotFound,
93 n_exc.SubnetNotFound, n_exc.IpAddressGenerationFailure) as e:
94 with excutils.save_and_reraise_exception(reraise=False) as ctxt:
95 if isinstance(e, n_exc.IpAddressGenerationFailure):
96 # Check if the subnet still exists and if it does not,
97 # this is the reason why the ip address generation failed.
98 # In any other unlikely event re-raise
100 subnet_id = port['port']['fixed_ips'][0]['subnet_id']
101 plugin.get_subnet(context, subnet_id)
102 except n_exc.SubnetNotFound:
106 net_id = port['port']['network_id']
107 LOG.warn(_LW("Action %(action)s for network %(net_id)s "
108 "could not complete successfully: %(reason)s"),
109 {"action": action, "net_id": net_id, 'reason': e})
111 def get_active_networks(self, context, **kwargs):
112 """Retrieve and return a list of the active network ids."""
113 # NOTE(arosen): This method is no longer used by the DHCP agent but is
114 # left so that neutron-dhcp-agents will still continue to work if
115 # neutron-server is upgraded and not the agent.
116 host = kwargs.get('host')
117 LOG.debug('get_active_networks requested from %s', host)
118 nets = self._get_active_networks(context, **kwargs)
119 return [net['id'] for net in nets]
121 def _group_by_network_id(self, res):
123 keyfunc = operator.itemgetter('network_id')
124 for net_id, values in itertools.groupby(sorted(res, key=keyfunc),
126 grouped[net_id] = list(values)
129 def get_active_networks_info(self, context, **kwargs):
130 """Returns all the networks/subnets/ports in system."""
131 host = kwargs.get('host')
132 LOG.debug('get_active_networks_info from %s', host)
133 networks = self._get_active_networks(context, **kwargs)
134 plugin = manager.NeutronManager.get_plugin()
135 filters = {'network_id': [network['id'] for network in networks]}
136 ports = plugin.get_ports(context, filters=filters)
137 filters['enable_dhcp'] = [True]
138 subnets = plugin.get_subnets(context, filters=filters)
140 grouped_subnets = self._group_by_network_id(subnets)
141 grouped_ports = self._group_by_network_id(ports)
142 for network in networks:
143 network['subnets'] = grouped_subnets.get(network['id'], [])
144 network['ports'] = grouped_ports.get(network['id'], [])
148 def get_network_info(self, context, **kwargs):
149 """Retrieve and return extended information about a network."""
150 network_id = kwargs.get('network_id')
151 host = kwargs.get('host')
152 LOG.debug('Network %(network_id)s requested from '
153 '%(host)s', {'network_id': network_id,
155 plugin = manager.NeutronManager.get_plugin()
157 network = plugin.get_network(context, network_id)
158 except n_exc.NetworkNotFound:
159 LOG.warn(_LW("Network %s could not be found, it might have "
160 "been deleted concurrently."), network_id)
162 filters = dict(network_id=[network_id])
163 network['subnets'] = plugin.get_subnets(context, filters=filters)
164 network['ports'] = plugin.get_ports(context, filters=filters)
167 @db_api.retry_db_errors
168 def release_dhcp_port(self, context, **kwargs):
169 """Release the port currently being used by a DHCP agent."""
170 host = kwargs.get('host')
171 network_id = kwargs.get('network_id')
172 device_id = kwargs.get('device_id')
174 LOG.debug('DHCP port deletion for %(network_id)s request from '
176 {'network_id': network_id, 'host': host})
177 plugin = manager.NeutronManager.get_plugin()
178 plugin.delete_ports_by_device_id(context, device_id, network_id)
180 def update_lease_expiration(self, context, **kwargs):
181 """Release the fixed_ip associated the subnet on a port."""
182 # NOTE(arosen): This method is no longer used by the DHCP agent but is
183 # left so that neutron-dhcp-agents will still continue to work if
184 # neutron-server is upgraded and not the agent.
185 host = kwargs.get('host')
187 LOG.warning(_LW('Updating lease expiration is now deprecated. Issued '
188 'from host %s.'), host)
190 @db_api.retry_db_errors
191 @resource_registry.mark_resources_dirty
192 def create_dhcp_port(self, context, **kwargs):
193 """Create and return dhcp port information.
195 If an expected failure occurs, a None port is returned.
198 host = kwargs.get('host')
199 # Note(pbondar): Create deep copy of port to prevent operating
200 # on changed dict if RetryRequest is raised
201 port = copy.deepcopy(kwargs.get('port'))
202 LOG.debug('Create dhcp port %(port)s '
207 port['port']['device_owner'] = constants.DEVICE_OWNER_DHCP
208 port['port'][portbindings.HOST_ID] = host
209 if 'mac_address' not in port['port']:
210 port['port']['mac_address'] = attributes.ATTR_NOT_SPECIFIED
211 plugin = manager.NeutronManager.get_plugin()
212 return self._port_action(plugin, context, port, 'create_port')
214 @db_api.retry_db_errors
215 def update_dhcp_port(self, context, **kwargs):
216 """Update the dhcp port."""
217 host = kwargs.get('host')
218 port = kwargs.get('port')
219 port['id'] = kwargs.get('port_id')
220 port['port'][portbindings.HOST_ID] = host
221 plugin = manager.NeutronManager.get_plugin()
222 old_port = plugin.get_port(context, port['id'])
223 if (old_port['device_id'] != constants.DEVICE_ID_RESERVED_DHCP_PORT
224 and old_port['device_id'] !=
225 utils.get_dhcp_agent_device_id(port['port']['network_id'], host)):
226 raise n_exc.DhcpPortInUse(port_id=port['id'])
227 LOG.debug('Update dhcp port %(port)s '
231 return self._port_action(plugin, context, port, 'update_port')