1 # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
|
2 |
|
3 # Copyright 2012 OpenStack Foundation |
|
4 # All Rights Reserved. |
|
5 # |
|
6 # Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
7 # not use this file except in compliance with the License. You may obtain |
|
8 # a copy of the License at |
|
9 # |
|
10 # http://www.apache.org/licenses/LICENSE-2.0 |
|
11 # |
|
12 # Unless required by applicable law or agreed to in writing, software |
|
13 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
14 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
15 # License for the specific language governing permissions and limitations |
|
16 # under the License. |
|
17 |
|
18 import socket |
|
19 import uuid |
|
20 |
|
21 import netaddr |
|
22 from oslo.config import cfg |
|
23 |
|
24 from quantum.agent.common import config |
|
25 from quantum.agent.linux import interface |
|
26 from quantum.agent.linux import ip_lib |
|
27 from quantum.common import exceptions |
|
28 from quantum.openstack.common import importutils |
|
29 from quantum.openstack.common import log as logging |
|
30 |
|
31 LOG = logging.getLogger(__name__) |
|
32 NS_PREFIX = 'qdhcp-' |
|
33 METADATA_DEFAULT_PREFIX = 16 |
|
34 METADATA_DEFAULT_IP = '169.254.169.254/%d' % METADATA_DEFAULT_PREFIX |
|
35 |
|
36 |
|
37 class DeviceManager(object): |
|
38 OPTS = [ |
|
39 cfg.StrOpt('interface_driver', |
|
40 help=_("The driver used to manage the virtual interface.")) |
|
41 ] |
|
42 |
|
43 def __init__(self, conf, plugin): |
|
44 self.conf = conf |
|
45 self.root_helper = config.get_root_helper(conf) |
|
46 self.plugin = plugin |
|
47 cfg.CONF.register_opts(DeviceManager.OPTS) |
|
48 cfg.CONF.register_opts(interface.OPTS) |
|
49 if not conf.interface_driver: |
|
50 raise SystemExit(_('You must specify an interface driver')) |
|
51 try: |
|
52 self.driver = importutils.import_object(conf.interface_driver, |
|
53 conf) |
|
54 except: |
|
55 msg = _("Error importing interface driver " |
|
56 "'%s'") % conf.interface_driver |
|
57 raise SystemExit(msg) |
|
58 |
|
59 def get_interface_name(self, network, port=None): |
|
60 """Return interface(device) name for use by the DHCP process.""" |
|
61 if not port: |
|
62 device_id = self.get_device_id(network) |
|
63 port = self.plugin.get_dhcp_port(network.id, device_id) |
|
64 return self.driver.get_device_name(port) |
|
65 |
|
66 def get_device_id(self, network): |
|
67 """Return a unique DHCP device ID for this host on the network.""" |
|
68 # There could be more than one dhcp server per network, so create |
|
69 # a device id that combines host and network ids |
|
70 |
|
71 host_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, socket.gethostname()) |
|
72 return 'dhcp%s-%s' % (host_uuid, network.id) |
|
73 |
|
74 def _get_device(self, network): |
|
75 """Return DHCP ip_lib device for this host on the network.""" |
|
76 device_id = self.get_device_id(network) |
|
77 port = self.plugin.get_dhcp_port(network.id, device_id) |
|
78 interface_name = self.get_interface_name(network, port) |
|
79 namespace = NS_PREFIX + network.id |
|
80 return ip_lib.IPDevice(interface_name, |
|
81 self.root_helper, |
|
82 namespace) |
|
83 |
|
84 def _set_default_route(self, network): |
|
85 """Sets the default gateway for this dhcp namespace. |
|
86 |
|
87 This method is idempotent and will only adjust the route if adjusting |
|
88 it would change it from what it already is. This makes it safe to call |
|
89 and avoids unnecessary perturbation of the system. |
|
90 """ |
|
91 device = self._get_device(network) |
|
92 gateway = device.route.get_gateway() |
|
93 |
|
94 for subnet in network.subnets: |
|
95 skip_subnet = ( |
|
96 subnet.ip_version != 4 |
|
97 or not subnet.enable_dhcp |
|
98 or subnet.gateway_ip is None) |
|
99 |
|
100 if skip_subnet: |
|
101 continue |
|
102 |
|
103 if gateway != subnet.gateway_ip: |
|
104 m = _('Setting gateway for dhcp netns on net %(n)s to %(ip)s') |
|
105 LOG.debug(m, {'n': network.id, 'ip': subnet.gateway_ip}) |
|
106 |
|
107 device.route.add_gateway(subnet.gateway_ip) |
|
108 |
|
109 return |
|
110 |
|
111 # No subnets on the network have a valid gateway. Clean it up to avoid |
|
112 # confusion from seeing an invalid gateway here. |
|
113 if gateway is not None: |
|
114 msg = _('Removing gateway for dhcp netns on net %s') |
|
115 LOG.debug(msg, network.id) |
|
116 |
|
117 device.route.delete_gateway(gateway) |
|
118 |
|
119 def setup(self, network, reuse_existing=False): |
|
120 """Create and initialize a device for network's DHCP on this host.""" |
|
121 device_id = self.get_device_id(network) |
|
122 port = self.plugin.get_dhcp_port(network.id, device_id) |
|
123 |
|
124 interface_name = self.get_interface_name(network, port) |
|
125 |
|
126 if self.conf.use_namespaces: |
|
127 namespace = NS_PREFIX + network.id |
|
128 else: |
|
129 namespace = None |
|
130 |
|
131 if ip_lib.device_exists(interface_name, |
|
132 self.root_helper, |
|
133 namespace): |
|
134 if not reuse_existing: |
|
135 raise exceptions.PreexistingDeviceFailure( |
|
136 dev_name=interface_name) |
|
137 |
|
138 LOG.debug(_('Reusing existing device: %s.'), interface_name) |
|
139 else: |
|
140 self.driver.plug(network.id, |
|
141 port.id, |
|
142 interface_name, |
|
143 port.mac_address, |
|
144 namespace=namespace) |
|
145 ip_cidrs = [] |
|
146 for fixed_ip in port.fixed_ips: |
|
147 subnet = fixed_ip.subnet |
|
148 net = netaddr.IPNetwork(subnet.cidr) |
|
149 ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen) |
|
150 ip_cidrs.append(ip_cidr) |
|
151 |
|
152 if (self.conf.enable_isolated_metadata and |
|
153 self.conf.use_namespaces): |
|
154 ip_cidrs.append(METADATA_DEFAULT_IP) |
|
155 |
|
156 self.driver.init_l3(interface_name, ip_cidrs, |
|
157 namespace=namespace) |
|
158 |
|
159 # ensure that the dhcp interface is first in the list |
|
160 if namespace is None: |
|
161 device = ip_lib.IPDevice(interface_name, |
|
162 self.root_helper) |
|
163 device.route.pullup_route(interface_name) |
|
164 |
|
165 if self.conf.enable_metadata_network: |
|
166 meta_cidr = netaddr.IPNetwork(METADATA_DEFAULT_IP) |
|
167 metadata_subnets = [s for s in network.subnets if |
|
168 netaddr.IPNetwork(s.cidr) in meta_cidr] |
|
169 if metadata_subnets: |
|
170 # Add a gateway so that packets can be routed back to VMs |
|
171 device = ip_lib.IPDevice(interface_name, |
|
172 self.root_helper, |
|
173 namespace) |
|
174 # Only 1 subnet on metadata access network |
|
175 gateway_ip = metadata_subnets[0].gateway_ip |
|
176 device.route.add_gateway(gateway_ip) |
|
177 elif self.conf.use_namespaces: |
|
178 self._set_default_route(network) |
|
179 |
|
180 return interface_name |
|
181 |
|
182 def update(self, network): |
|
183 """Update device settings for the network's DHCP on this host.""" |
|
184 if self.conf.use_namespaces and not self.conf.enable_metadata_network: |
|
185 self._set_default_route(network) |
|
186 |
|
187 def destroy(self, network, device_name): |
|
188 """Destroy the device used for the network's DHCP on this host.""" |
|
189 if self.conf.use_namespaces: |
|
190 namespace = NS_PREFIX + network.id |
|
191 else: |
|
192 namespace = None |
|
193 |
|
194 self.driver.unplug(device_name, namespace=namespace) |
|
195 |
|
196 self.plugin.release_dhcp_port(network.id, |
|
197 self.get_device_id(network)) |
|