16 |
21 |
17 import abc |
22 import abc |
18 import os |
23 import os |
19 import re |
24 import re |
20 import shutil |
25 import shutil |
|
26 import socket |
21 import StringIO |
27 import StringIO |
|
28 import sys |
|
29 import uuid |
22 |
30 |
23 import netaddr |
31 import netaddr |
24 |
|
25 from oslo.config import cfg |
32 from oslo.config import cfg |
26 from quantum.agent.linux import utils |
33 |
27 from quantum.openstack.common import log as logging |
34 from neutron.agent.linux import utils |
28 from quantum.openstack.common import uuidutils |
35 from neutron.agent.solaris import net_lib |
|
36 from neutron.common import exceptions |
|
37 from neutron.openstack.common import importutils |
|
38 from neutron.openstack.common import jsonutils |
|
39 from neutron.openstack.common import log as logging |
|
40 from neutron.openstack.common import uuidutils |
29 |
41 |
30 LOG = logging.getLogger(__name__) |
42 LOG = logging.getLogger(__name__) |
31 |
43 |
32 OPTS = [ |
44 OPTS = [ |
33 cfg.StrOpt('dhcp_confs', |
45 cfg.StrOpt('dhcp_confs', |
34 default='$state_path/dhcp', |
46 default='$state_path/dhcp', |
35 help=_('Location to store DHCP server config files')), |
47 help=_('Location to store DHCP server config files')), |
36 cfg.IntOpt('dhcp_lease_time', |
|
37 default=120, |
|
38 help=_('Lifetime of a DHCP lease in seconds')), |
|
39 cfg.StrOpt('dhcp_domain', |
48 cfg.StrOpt('dhcp_domain', |
40 default='openstacklocal', |
49 default='openstacklocal', |
41 help=_('Domain to use for building the hostnames')), |
50 help=_('Domain to use for building the hostnames')), |
42 cfg.StrOpt('dnsmasq_config_file', |
51 cfg.StrOpt('dnsmasq_config_file', |
43 default='', |
52 default='', |
44 help=_('Override the default dnsmasq settings with this file')), |
53 help=_('Override the default dnsmasq settings with this file')), |
45 cfg.StrOpt('dnsmasq_dns_server', |
54 cfg.StrOpt('dnsmasq_dns_server', |
46 help=_('Use another DNS server before any in ' |
55 help=_('Use another DNS server before any in ' |
47 '/etc/resolv.conf.')), |
56 '/etc/resolv.conf.')), |
|
57 cfg.IntOpt( |
|
58 'dnsmasq_lease_max', |
|
59 default=(2 ** 24), |
|
60 help=_('Limit number of leases to prevent a denial-of-service.')), |
|
61 cfg.StrOpt('interface_driver', |
|
62 help=_("The driver used to manage the virtual interface.")), |
48 ] |
63 ] |
49 |
64 |
50 IPV4 = 4 |
65 IPV4 = 4 |
51 IPV6 = 6 |
66 IPV6 = 6 |
52 UDP = 'udp' |
67 UDP = 'udp' |
53 TCP = 'tcp' |
68 TCP = 'tcp' |
54 DNS_PORT = 53 |
69 DNS_PORT = 53 |
55 DHCPV4_PORT = 67 |
70 DHCPV4_PORT = 67 |
56 DHCPV6_PORT = 467 |
71 DHCPV6_PORT = 547 |
|
72 METADATA_DEFAULT_PREFIX = 16 |
|
73 METADATA_DEFAULT_IP = '169.254.169.254' |
|
74 METADATA_DEFAULT_CIDR = '%s/%d' % (METADATA_DEFAULT_IP, |
|
75 METADATA_DEFAULT_PREFIX) |
|
76 METADATA_PORT = 80 |
|
77 WIN2k3_STATIC_DNS = 249 |
|
78 |
|
79 |
|
80 class DictModel(object): |
|
81 """Convert dict into an object that provides attribute access to values.""" |
|
82 def __init__(self, d): |
|
83 for key, value in d.iteritems(): |
|
84 if isinstance(value, list): |
|
85 value = [DictModel(item) if isinstance(item, dict) else item |
|
86 for item in value] |
|
87 elif isinstance(value, dict): |
|
88 value = DictModel(value) |
|
89 |
|
90 setattr(self, key, value) |
|
91 |
|
92 |
|
93 class NetModel(DictModel): |
|
94 |
|
95 def __init__(self, use_namespaces, d): |
|
96 super(NetModel, self).__init__(d) |
|
97 |
|
98 self._ns_name = None |
|
99 |
|
100 @property |
|
101 def namespace(self): |
|
102 return self._ns_name |
57 |
103 |
58 |
104 |
59 class DhcpBase(object): |
105 class DhcpBase(object): |
60 __metaclass__ = abc.ABCMeta |
106 __metaclass__ = abc.ABCMeta |
61 |
107 |
62 def __init__(self, conf, network, root_helper='sudo', |
108 def __init__(self, conf, network, root_helper='sudo', |
63 device_delegate=None, namespace=None, version=None): |
109 version=None, plugin=None): |
64 self.conf = conf |
110 self.conf = conf |
65 self.network = network |
111 self.network = network |
66 self.root_helper = root_helper |
112 self.root_helper = None |
67 self.device_delegate = device_delegate |
113 self.device_manager = DeviceManager(self.conf, |
68 self.namespace = namespace |
114 self.root_helper, plugin) |
69 self.version = version |
115 self.version = version |
70 |
116 |
71 @abc.abstractmethod |
117 @abc.abstractmethod |
72 def enable(self): |
118 def enable(self): |
73 """Enables DHCP for this network.""" |
119 """Enables DHCP for this network.""" |
202 pass |
255 pass |
203 |
256 |
204 |
257 |
205 class Dnsmasq(DhcpLocalProcess): |
258 class Dnsmasq(DhcpLocalProcess): |
206 # The ports that need to be opened when security policies are active |
259 # The ports that need to be opened when security policies are active |
207 # on the Quantum port used for DHCP. These are provided as a convenience |
260 # on the Neutron port used for DHCP. These are provided as a convenience |
208 # for users of this class. |
261 # for users of this class. |
209 PORTS = {IPV4: [(UDP, DNS_PORT), (TCP, DNS_PORT), (UDP, DHCPV4_PORT)], |
262 PORTS = {IPV4: [(UDP, DNS_PORT), (TCP, DNS_PORT), (UDP, DHCPV4_PORT)], |
210 IPV6: [(UDP, DNS_PORT), (TCP, DNS_PORT), (UDP, DHCPV6_PORT)]} |
263 IPV6: [(UDP, DNS_PORT), (TCP, DNS_PORT), (UDP, DHCPV6_PORT)], |
|
264 } |
211 |
265 |
212 _TAG_PREFIX = 'tag%d' |
266 _TAG_PREFIX = 'tag%d' |
213 |
267 |
214 QUANTUM_NETWORK_ID_KEY = 'QUANTUM_NETWORK_ID' |
268 NEUTRON_NETWORK_ID_KEY = 'NEUTRON_NETWORK_ID' |
215 QUANTUM_RELAY_SOCKET_PATH_KEY = 'QUANTUM_RELAY_SOCKET_PATH' |
269 NEUTRON_RELAY_SOCKET_PATH_KEY = 'NEUTRON_RELAY_SOCKET_PATH' |
|
270 MINIMUM_VERSION = 2.59 |
216 |
271 |
217 @classmethod |
272 @classmethod |
218 def check_version(cls): |
273 def check_version(cls): |
219 # For Solaris, we rely on the packaging system to ensure a |
274 ver = 0 |
220 # matching/supported version of dnsmasq |
275 try: |
221 pass |
276 cmd = ['/usr/lib/inet/dnsmasq', '--version'] |
|
277 out = utils.execute(cmd) |
|
278 ver = re.findall("\d+.\d+", out)[0] |
|
279 is_valid_version = float(ver) >= cls.MINIMUM_VERSION |
|
280 # For Solaris, we rely on the packaging system to ensure a |
|
281 # matching/supported version of dnsmasq. |
|
282 if not is_valid_version: |
|
283 LOG.warning(_('FAILED VERSION REQUIREMENT FOR DNSMASQ. ' |
|
284 'DHCP AGENT MAY NOT RUN CORRECTLY! ' |
|
285 'Please ensure that its version is %s ' |
|
286 'or above!'), cls.MINIMUM_VERSION) |
|
287 except (OSError, RuntimeError, IndexError, ValueError): |
|
288 LOG.warning(_('Unable to determine dnsmasq version. ' |
|
289 'Please ensure that its version is %s ' |
|
290 'or above!'), cls.MINIMUM_VERSION) |
|
291 return float(ver) |
222 |
292 |
223 @classmethod |
293 @classmethod |
224 def existing_dhcp_networks(cls, conf, root_helper): |
294 def existing_dhcp_networks(cls, conf, root_helper): |
225 """Return a list of existing networks ids (ones we have configs for)""" |
295 """Return a list of existing networks ids that we have configs for.""" |
226 |
296 |
227 confs_dir = os.path.abspath(os.path.normpath(conf.dhcp_confs)) |
297 confs_dir = os.path.abspath(os.path.normpath(conf.dhcp_confs)) |
228 |
|
229 class FakeNetwork: |
|
230 def __init__(self, net_id): |
|
231 self.id = net_id |
|
232 |
298 |
233 return [ |
299 return [ |
234 c for c in os.listdir(confs_dir) |
300 c for c in os.listdir(confs_dir) |
235 if (uuidutils.is_uuid_like(c) and |
301 if uuidutils.is_uuid_like(c) |
236 cls(conf, FakeNetwork(c), root_helper).active) |
|
237 ] |
302 ] |
238 |
303 |
239 def spawn_process(self): |
304 def spawn_process(self): |
240 """Spawns a Dnsmasq process for the network.""" |
305 """Spawns a Dnsmasq process for the network.""" |
241 env = { |
306 env = { |
242 self.QUANTUM_NETWORK_ID_KEY: self.network.id |
307 self.NEUTRON_NETWORK_ID_KEY: self.network.id, |
243 } |
308 } |
244 |
309 |
245 cmd = [ |
310 cmd = [ |
246 '/usr/lib/inet/dnsmasq', |
311 '/usr/lib/inet/dnsmasq', |
247 '--no-hosts', |
312 '--no-hosts', |
250 '--bind-interfaces', |
315 '--bind-interfaces', |
251 '--interface=%s' % self.interface_name, |
316 '--interface=%s' % self.interface_name, |
252 '--except-interface=lo0', |
317 '--except-interface=lo0', |
253 '--pid-file=%s' % self.get_conf_file_name( |
318 '--pid-file=%s' % self.get_conf_file_name( |
254 'pid', ensure_conf_dir=True), |
319 'pid', ensure_conf_dir=True), |
255 # TODO(gmoodalb): calculate value from cidr (defaults to 150) |
|
256 # '--dhcp-lease-max=%s' % ?, |
|
257 '--dhcp-hostsfile=%s' % self._output_hosts_file(), |
320 '--dhcp-hostsfile=%s' % self._output_hosts_file(), |
258 '--dhcp-optsfile=%s' % self._output_opts_file(), |
321 '--dhcp-optsfile=%s' % self._output_opts_file(), |
259 # '--dhcp-script=%s' % self._lease_relay_script_path(), |
|
260 '--leasefile-ro', |
322 '--leasefile-ro', |
261 ] |
323 ] |
262 |
324 |
|
325 possible_leases = 0 |
263 for i, subnet in enumerate(self.network.subnets): |
326 for i, subnet in enumerate(self.network.subnets): |
264 # if a subnet is specified to have dhcp disabled |
327 # if a subnet is specified to have dhcp disabled |
265 if not subnet.enable_dhcp: |
328 if not subnet.enable_dhcp: |
266 continue |
329 continue |
267 if subnet.ip_version == 4: |
330 if subnet.ip_version == 4: |
268 mode = 'static' |
331 mode = 'static' |
269 else: |
332 else: |
270 # TODO(gmoodalb): how do we indicate other options |
333 # TODO(gmoodalb): how do we indicate other options |
271 # ra-only, slaac, ra-nameservers, and ra-stateless. |
334 # ra-only, slaac, ra-nameservers, and ra-stateless. |
272 # We need to also set the DUID for DHCPv6 server to use |
335 # We need to also set the DUID for the DHCPv6 server to use |
273 macaddr_cmd = ['/usr/sbin/dladm', 'show-linkprop', |
336 macaddr_cmd = ['/usr/sbin/dladm', 'show-linkprop', |
274 '-co', 'value', '-p', 'mac-address', |
337 '-co', 'value', '-p', 'mac-address', |
275 self.interface_name] |
338 self.interface_name] |
276 stdout = utils.execute(macaddr_cmd) |
339 stdout = utils.execute(macaddr_cmd) |
277 uid = stdout.splitlines()[0].strip() |
340 uid = stdout.splitlines()[0].strip() |
278 # IANA assigned ID for Oracle |
341 # IANA assigned ID for Oracle |
279 enterprise_id = '111' |
342 enterprise_id = '111' |
280 cmd.append('--dhcp-duid=%s,%s' % (enterprise_id, uid)) |
343 cmd.append('--dhcp-duid=%s,%s' % (enterprise_id, uid)) |
281 mode = 'static' |
344 mode = 'static' |
282 cmd.append('--dhcp-range=set:%s,%s,%s,%ss' % |
345 if self.version >= self.MINIMUM_VERSION: |
283 (self._TAG_PREFIX % i, |
346 set_tag = 'set:' |
284 netaddr.IPNetwork(subnet.cidr).network, |
347 else: |
285 mode, self.conf.dhcp_lease_time)) |
348 set_tag = '' |
|
349 |
|
350 cidr = netaddr.IPNetwork(subnet.cidr) |
|
351 |
|
352 cmd.append('--dhcp-range=%s%s,%s,%s,%ss' % |
|
353 (set_tag, self._TAG_PREFIX % i, |
|
354 cidr.network, |
|
355 mode, |
|
356 self.conf.dhcp_lease_duration)) |
|
357 possible_leases += cidr.size |
|
358 |
|
359 # Cap the limit because creating lots of subnets can inflate |
|
360 # this possible lease cap. |
|
361 cmd.append('--dhcp-lease-max=%d' % |
|
362 min(possible_leases, self.conf.dnsmasq_lease_max)) |
286 |
363 |
287 cmd.append('--conf-file=%s' % self.conf.dnsmasq_config_file) |
364 cmd.append('--conf-file=%s' % self.conf.dnsmasq_config_file) |
288 if self.conf.dnsmasq_dns_server: |
365 if self.conf.dnsmasq_dns_server: |
289 cmd.append('--server=%s' % self.conf.dnsmasq_dns_server) |
366 cmd.append('--server=%s' % self.conf.dnsmasq_dns_server) |
290 |
367 |
291 if self.conf.dhcp_domain: |
368 if self.conf.dhcp_domain: |
292 cmd.append('--domain=%s' % self.conf.dhcp_domain) |
369 cmd.append('--domain=%s' % self.conf.dhcp_domain) |
293 utils.execute(cmd) |
370 |
|
371 # TODO(gmoodalb): prepend the env vars before command |
|
372 utils.execute(cmd, self.root_helper) |
|
373 |
|
374 def release_lease(self, mac_address, removed_ips): |
|
375 # TODO(gmoodalb): we need to support dnsmasq's dhcp_release |
|
376 pass |
294 |
377 |
295 def reload_allocations(self): |
378 def reload_allocations(self): |
296 """Rebuild the dnsmasq config and signal the dnsmasq to reload.""" |
379 """Rebuild the dnsmasq config and signal the dnsmasq to reload.""" |
297 |
380 |
298 # If all subnets turn off dhcp, kill the process. |
381 # If all subnets turn off dhcp, kill the process. |
302 'turned off DHCP: %s'), self.network.id) |
385 'turned off DHCP: %s'), self.network.id) |
303 return |
386 return |
304 |
387 |
305 self._output_hosts_file() |
388 self._output_hosts_file() |
306 self._output_opts_file() |
389 self._output_opts_file() |
307 |
|
308 if self.active: |
390 if self.active: |
309 cmd = ['kill', '-HUP', self.pid] |
391 cmd = ['kill', '-HUP', self.pid] |
310 utils.execute(cmd) |
392 utils.execute(cmd, self.root_helper) |
311 else: |
393 else: |
312 LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), self.pid) |
394 LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), self.pid) |
313 LOG.debug(_('Reloading allocations for network: %s'), self.network.id) |
395 LOG.debug(_('Reloading allocations for network: %s'), self.network.id) |
|
396 self.device_manager.update(self.network) |
314 |
397 |
315 def _output_hosts_file(self): |
398 def _output_hosts_file(self): |
316 """Writes a dnsmasq compatible hosts file.""" |
399 """Writes a dnsmasq compatible hosts file.""" |
317 r = re.compile('[:.]') |
400 r = re.compile('[:.]') |
318 buf = StringIO.StringIO() |
401 buf = StringIO.StringIO() |
319 |
402 |
320 for port in self.network.ports: |
403 for port in self.network.ports: |
321 for alloc in port.fixed_ips: |
404 for alloc in port.fixed_ips: |
322 name = 'host-%s.%s' % (r.sub('-', alloc.ip_address), |
405 name = 'host-%s.%s' % (r.sub('-', alloc.ip_address), |
323 self.conf.dhcp_domain) |
406 self.conf.dhcp_domain) |
324 buf.write('%s,%s,%s\n' % |
407 set_tag = '' |
325 (port.mac_address, name, alloc.ip_address)) |
408 if getattr(port, 'extra_dhcp_opts', False): |
|
409 if self.version >= self.MINIMUM_VERSION: |
|
410 set_tag = 'set:' |
|
411 |
|
412 buf.write('%s,%s,%s,%s%s\n' % |
|
413 (port.mac_address, name, alloc.ip_address, |
|
414 set_tag, port.id)) |
|
415 else: |
|
416 buf.write('%s,%s,%s\n' % |
|
417 (port.mac_address, name, alloc.ip_address)) |
326 |
418 |
327 name = self.get_conf_file_name('host') |
419 name = self.get_conf_file_name('host') |
328 utils.replace_file(name, buf.getvalue()) |
420 utils.replace_file(name, buf.getvalue()) |
329 return name |
421 return name |
330 |
422 |
331 def _output_opts_file(self): |
423 def _output_opts_file(self): |
332 """Write a dnsmasq compatible options file.""" |
424 """Write a dnsmasq compatible options file.""" |
|
425 |
|
426 if self.conf.enable_isolated_metadata: |
|
427 subnet_to_interface_ip = self._make_subnet_interface_ip_map() |
333 |
428 |
334 options = [] |
429 options = [] |
335 for i, subnet in enumerate(self.network.subnets): |
430 for i, subnet in enumerate(self.network.subnets): |
336 if not subnet.enable_dhcp: |
431 if not subnet.enable_dhcp: |
337 continue |
432 continue |
338 if subnet.dns_nameservers: |
433 if subnet.dns_nameservers: |
339 options.append( |
434 options.append( |
340 self._format_option(i, 'dns-server', |
435 self._format_option(i, 'dns-server', |
341 ','.join(subnet.dns_nameservers))) |
436 ','.join(subnet.dns_nameservers))) |
342 |
437 |
343 host_routes = ["%s,%s" % (hr.destination, hr.nexthop) |
438 gateway = subnet.gateway_ip |
344 for hr in subnet.host_routes] |
439 host_routes = [] |
|
440 for hr in subnet.host_routes: |
|
441 if hr.destination == "0.0.0.0/0": |
|
442 gateway = hr.nexthop |
|
443 else: |
|
444 host_routes.append("%s,%s" % (hr.destination, hr.nexthop)) |
|
445 |
|
446 # Add host routes for isolated network segments |
|
447 enable_metadata = ( |
|
448 self.conf.enable_isolated_metadata |
|
449 and not subnet.gateway_ip |
|
450 and subnet.ip_version == 4) |
|
451 |
|
452 if enable_metadata: |
|
453 subnet_dhcp_ip = subnet_to_interface_ip[subnet.id] |
|
454 host_routes.append( |
|
455 '%s/32,%s' % (METADATA_DEFAULT_IP, subnet_dhcp_ip) |
|
456 ) |
345 |
457 |
346 if host_routes: |
458 if host_routes: |
347 options.append( |
459 options.append( |
348 self._format_option(i, 'classless-static-route', |
460 self._format_option(i, 'classless-static-route', |
349 ','.join(host_routes))) |
461 ','.join(host_routes))) |
|
462 options.append( |
|
463 self._format_option(i, WIN2k3_STATIC_DNS, |
|
464 ','.join(host_routes))) |
350 |
465 |
351 if subnet.ip_version == 4: |
466 if subnet.ip_version == 4: |
352 if subnet.gateway_ip: |
467 if gateway: |
353 options.append(self._format_option(i, 'router', |
468 options.append(self._format_option(i, 'router', gateway)) |
354 subnet.gateway_ip)) |
|
355 else: |
469 else: |
356 options.append(self._format_option(i, 'router')) |
470 options.append(self._format_option(i, 'router')) |
|
471 |
|
472 for port in self.network.ports: |
|
473 if getattr(port, 'extra_dhcp_opts', False): |
|
474 options.extend( |
|
475 self._format_option(port.id, opt.opt_name, opt.opt_value) |
|
476 for opt in port.extra_dhcp_opts) |
357 |
477 |
358 name = self.get_conf_file_name('opts') |
478 name = self.get_conf_file_name('opts') |
359 utils.replace_file(name, '\n'.join(options)) |
479 utils.replace_file(name, '\n'.join(options)) |
360 return name |
480 return name |
361 |
481 |
362 def _format_option(self, index, option_name, *args): |
482 def _make_subnet_interface_ip_map(self): |
363 return ','.join(('tag:' + self._TAG_PREFIX % index, |
483 # TODO(gmoodalb): need to complete this when we support metadata |
364 'option:%s' % option_name) + args) |
484 pass |
|
485 |
|
486 def _format_option(self, tag, option, *args): |
|
487 """Format DHCP option by option name or code.""" |
|
488 if self.version >= self.MINIMUM_VERSION: |
|
489 set_tag = 'tag:' |
|
490 else: |
|
491 set_tag = '' |
|
492 |
|
493 option = str(option) |
|
494 |
|
495 if isinstance(tag, int): |
|
496 tag = self._TAG_PREFIX % tag |
|
497 |
|
498 if not option.isdigit(): |
|
499 option = 'option:%s' % option |
|
500 |
|
501 return ','.join((set_tag + tag, '%s' % option) + args) |
365 |
502 |
366 @classmethod |
503 @classmethod |
367 def lease_update(cls): |
504 def lease_update(cls): |
|
505 network_id = os.environ.get(cls.NEUTRON_NETWORK_ID_KEY) |
|
506 dhcp_relay_socket = os.environ.get(cls.NEUTRON_RELAY_SOCKET_PATH_KEY) |
|
507 |
|
508 action = sys.argv[1] |
|
509 if action not in ('add', 'del', 'old'): |
|
510 sys.exit() |
|
511 |
|
512 mac_address = sys.argv[2] |
|
513 ip_address = sys.argv[3] |
|
514 |
|
515 if action == 'del': |
|
516 lease_remaining = 0 |
|
517 else: |
|
518 lease_remaining = int(os.environ.get('DNSMASQ_TIME_REMAINING', 0)) |
|
519 |
|
520 data = dict(network_id=network_id, mac_address=mac_address, |
|
521 ip_address=ip_address, lease_remaining=lease_remaining) |
|
522 |
|
523 if os.path.exists(dhcp_relay_socket): |
|
524 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|
525 sock.connect(dhcp_relay_socket) |
|
526 sock.send(jsonutils.dumps(data)) |
|
527 sock.close() |
|
528 |
|
529 |
|
530 class DeviceManager(object): |
|
531 |
|
532 def __init__(self, conf, root_helper, plugin): |
|
533 self.conf = conf |
|
534 self.root_helper = root_helper |
|
535 self.plugin = plugin |
|
536 if not conf.interface_driver: |
|
537 raise SystemExit(_('You must specify an interface driver')) |
|
538 try: |
|
539 self.driver = importutils.import_object( |
|
540 conf.interface_driver, conf) |
|
541 except Exception as e: |
|
542 msg = (_("Error importing interface driver '%(driver)s': " |
|
543 "%(inner)s") % {'driver': conf.interface_driver, |
|
544 'inner': e}) |
|
545 raise SystemExit(msg) |
|
546 |
|
547 def get_interface_name(self, network, port): |
|
548 """Return interface(device) name for use by the DHCP process.""" |
|
549 return self.driver.get_device_name(port) |
|
550 |
|
551 def get_device_id(self, network): |
|
552 """Return a unique DHCP device ID for this host on the network.""" |
|
553 # There could be more than one dhcp server per network, so create |
|
554 # a device id that combines host and network ids |
|
555 |
|
556 host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname()) |
|
557 return 'dhcp%s-%s' % (host_uuid, network.id) |
|
558 |
|
559 def setup_dhcp_port(self, network): |
|
560 """Create/update DHCP port for the host if needed and return port.""" |
|
561 |
|
562 device_id = self.get_device_id(network) |
|
563 subnets = {} |
|
564 dhcp_enabled_subnet_ids = [] |
|
565 for subnet in network.subnets: |
|
566 if subnet.enable_dhcp: |
|
567 dhcp_enabled_subnet_ids.append(subnet.id) |
|
568 subnets[subnet.id] = subnet |
|
569 |
|
570 dhcp_port = None |
|
571 for port in network.ports: |
|
572 port_device_id = getattr(port, 'device_id', None) |
|
573 if port_device_id == device_id: |
|
574 port_fixed_ips = [] |
|
575 for fixed_ip in port.fixed_ips: |
|
576 port_fixed_ips.append({'subnet_id': fixed_ip.subnet_id, |
|
577 'ip_address': fixed_ip.ip_address}) |
|
578 if fixed_ip.subnet_id in dhcp_enabled_subnet_ids: |
|
579 dhcp_enabled_subnet_ids.remove(fixed_ip.subnet_id) |
|
580 |
|
581 # If there are dhcp_enabled_subnet_ids here that means that |
|
582 # we need to add those to the port and call update. |
|
583 if dhcp_enabled_subnet_ids: |
|
584 port_fixed_ips.extend( |
|
585 [dict(subnet_id=s) for s in dhcp_enabled_subnet_ids]) |
|
586 dhcp_port = self.plugin.update_dhcp_port( |
|
587 port.id, {'port': {'fixed_ips': port_fixed_ips}}) |
|
588 else: |
|
589 dhcp_port = port |
|
590 # break since we found port that matches device_id |
|
591 break |
|
592 |
|
593 # DHCP port has not yet been created. |
|
594 if dhcp_port is None: |
|
595 LOG.debug(_('DHCP port %(device_id)s on network %(network_id)s' |
|
596 ' does not yet exist.'), {'device_id': device_id, |
|
597 'network_id': network.id}) |
|
598 port_dict = dict( |
|
599 name='', |
|
600 admin_state_up=True, |
|
601 device_id=device_id, |
|
602 network_id=network.id, |
|
603 tenant_id=network.tenant_id, |
|
604 fixed_ips=[dict(subnet_id=s) for s in dhcp_enabled_subnet_ids]) |
|
605 dhcp_port = self.plugin.create_dhcp_port({'port': port_dict}) |
|
606 |
|
607 # Convert subnet_id to subnet dict |
|
608 fixed_ips = [dict(subnet_id=fixed_ip.subnet_id, |
|
609 ip_address=fixed_ip.ip_address, |
|
610 subnet=subnets[fixed_ip.subnet_id]) |
|
611 for fixed_ip in dhcp_port.fixed_ips] |
|
612 |
|
613 ips = [DictModel(item) if isinstance(item, dict) else item |
|
614 for item in fixed_ips] |
|
615 dhcp_port.fixed_ips = ips |
|
616 |
|
617 return dhcp_port |
|
618 |
|
619 def setup(self, network, reuse_existing=False): |
|
620 """Create and initialize a device for network's DHCP on this host.""" |
|
621 port = self.setup_dhcp_port(network) |
|
622 interface_name = self.get_interface_name(network, port) |
|
623 |
|
624 if net_lib.Datalink.datalink_exists(interface_name): |
|
625 if not reuse_existing: |
|
626 raise exceptions.PreexistingDeviceFailure( |
|
627 dev_name=interface_name) |
|
628 |
|
629 LOG.debug(_('Reusing existing device: %s.'), interface_name) |
|
630 else: |
|
631 self.driver.plug(network.tenant_id, network.id, |
|
632 port.id, |
|
633 interface_name) |
|
634 ip_cidrs = [] |
|
635 for fixed_ip in port.fixed_ips: |
|
636 subnet = fixed_ip.subnet |
|
637 net = netaddr.IPNetwork(subnet.cidr) |
|
638 ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen) |
|
639 ip_cidrs.append(ip_cidr) |
|
640 |
|
641 if (self.conf.enable_isolated_metadata and self.conf.use_namespaces): |
|
642 ip_cidrs.append(METADATA_DEFAULT_CIDR) |
|
643 |
|
644 self.driver.init_l3(interface_name, ip_cidrs) |
|
645 |
|
646 return interface_name |
|
647 |
|
648 def update(self, network): |
|
649 """Update device settings for the network's DHCP on this host.""" |
368 pass |
650 pass |
|
651 |
|
652 def destroy(self, network, device_name): |
|
653 """Destroy the device used for the network's DHCP on this host.""" |
|
654 |
|
655 self.driver.fini_l3(device_name) |
|
656 self.driver.unplug(device_name) |
|
657 self.plugin.release_dhcp_port(network.id, |
|
658 self.get_device_id(network)) |