1 | #============================================================================ |
---|
2 | # This library is free software; you can redistribute it and/or |
---|
3 | # modify it under the terms of version 2.1 of the GNU Lesser General Public |
---|
4 | # License as published by the Free Software Foundation. |
---|
5 | # |
---|
6 | # This library is distributed in the hope that it will be useful, |
---|
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
9 | # Lesser General Public License for more details. |
---|
10 | # |
---|
11 | # You should have received a copy of the GNU Lesser General Public |
---|
12 | # License along with this library; if not, write to the Free Software |
---|
13 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
---|
14 | #============================================================================ |
---|
15 | # Copyright (c) 2006 Xensource Inc. |
---|
16 | #============================================================================ |
---|
17 | |
---|
18 | import commands |
---|
19 | import logging |
---|
20 | import os |
---|
21 | import re |
---|
22 | from xen.xend import uuid as genuuid |
---|
23 | from xen.xend import XendAPIStore |
---|
24 | from xen.xend.XendBase import XendBase |
---|
25 | from xen.xend.XendPIFMetrics import XendPIFMetrics |
---|
26 | from xen.xend.XendError import * |
---|
27 | |
---|
28 | log = logging.getLogger("xend.XendPIF") |
---|
29 | log.setLevel(logging.TRACE) |
---|
30 | |
---|
31 | MAC_RE = re.compile(':'.join(['[0-9a-f]{2}'] * 6)) |
---|
32 | IP_IFACE_RE = re.compile(r'^\d+: (\w+):.*mtu (\d+) .* link/\w+ ([0-9a-f:]+)') |
---|
33 | |
---|
34 | def linux_phy_to_virt(pif_name): |
---|
35 | return 'eth' + re.sub(r'^[a-z]+', '', pif_name) |
---|
36 | |
---|
37 | def linux_get_phy_ifaces(): |
---|
38 | """Returns a list of physical interfaces. |
---|
39 | |
---|
40 | Identifies PIFs as those that have a interface name starting with 'p' |
---|
41 | and have the fake 'fe:ff:ff:ff:ff:ff' MAC address. |
---|
42 | |
---|
43 | See /etc/xen/scripts/network-bridge for how the devices are renamed. |
---|
44 | |
---|
45 | @rtype: array of 3-element tuple (name, mtu, mac) |
---|
46 | """ |
---|
47 | |
---|
48 | ip_cmd = 'ip -o link show' |
---|
49 | rc, output = commands.getstatusoutput(ip_cmd) |
---|
50 | ifaces = {} |
---|
51 | phy_ifaces = [] |
---|
52 | if rc == 0: |
---|
53 | # parse all interfaces into (name, mtu, mac) |
---|
54 | for line in output.split('\n'): |
---|
55 | has_if = re.search(IP_IFACE_RE, line) |
---|
56 | if has_if: |
---|
57 | ifaces[has_if.group(1)] = has_if.groups() |
---|
58 | |
---|
59 | # resolve pifs' mac addresses |
---|
60 | for name, mtu, mac in ifaces.values(): |
---|
61 | if name[0] == 'p' and mac == 'fe:ff:ff:ff:ff:ff': |
---|
62 | bridged_ifname = linux_phy_to_virt(name) |
---|
63 | bridged_if = ifaces.get(bridged_ifname) |
---|
64 | if bridged_if: |
---|
65 | bridged_mac = bridged_if[2] |
---|
66 | phy_ifaces.append((name, int(mtu), bridged_mac)) |
---|
67 | |
---|
68 | return phy_ifaces |
---|
69 | |
---|
70 | def linux_set_mac(iface, mac): |
---|
71 | if not re.search(MAC_RE, mac): |
---|
72 | return False |
---|
73 | |
---|
74 | ip_mac_cmd = 'ip link set %s addr %s' % \ |
---|
75 | (linux_phy_to_virt(iface), mac) |
---|
76 | rc, output = commands.getstatusoutput(ip_mac_cmd) |
---|
77 | if rc == 0: |
---|
78 | return True |
---|
79 | |
---|
80 | return False |
---|
81 | |
---|
82 | def linux_set_mtu(iface, mtu): |
---|
83 | try: |
---|
84 | ip_mtu_cmd = 'ip link set %s mtu %d' % \ |
---|
85 | (linux_phy_to_virt(iface), int(mtu)) |
---|
86 | rc, output = commands.getstatusoutput(ip_mtu_cmd) |
---|
87 | if rc == 0: |
---|
88 | return True |
---|
89 | return False |
---|
90 | except ValueError: |
---|
91 | return False |
---|
92 | |
---|
93 | def _create_VLAN(dev, vlan): |
---|
94 | rc, _ = commands.getstatusoutput('vconfig add %s %d' % |
---|
95 | (dev, vlan)) |
---|
96 | if rc != 0: |
---|
97 | return False |
---|
98 | |
---|
99 | rc, _ = commands.getstatusoutput('ifconfig %s.%d up' % |
---|
100 | (dev, vlan)) |
---|
101 | return rc == 0 |
---|
102 | |
---|
103 | def _destroy_VLAN(dev, vlan): |
---|
104 | rc, _ = commands.getstatusoutput('ifconfig %s.%d down' % |
---|
105 | (dev, vlan)) |
---|
106 | if rc != 0: |
---|
107 | return False |
---|
108 | |
---|
109 | rc, _ = commands.getstatusoutput('vconfig rem %s.%d' % |
---|
110 | (dev, vlan)) |
---|
111 | return rc == 0 |
---|
112 | |
---|
113 | class XendPIF(XendBase): |
---|
114 | """Representation of a Physical Network Interface.""" |
---|
115 | |
---|
116 | def getClass(self): |
---|
117 | return "PIF" |
---|
118 | |
---|
119 | def getAttrRO(self): |
---|
120 | attrRO = ['network', |
---|
121 | 'host', |
---|
122 | 'metrics', |
---|
123 | 'device', |
---|
124 | 'VLAN'] |
---|
125 | return XendBase.getAttrRO() + attrRO |
---|
126 | |
---|
127 | def getAttrRW(self): |
---|
128 | attrRW = ['MAC', |
---|
129 | 'MTU'] |
---|
130 | return XendBase.getAttrRW() + attrRW |
---|
131 | |
---|
132 | def getAttrInst(self): |
---|
133 | attrInst = ['network', |
---|
134 | 'device', |
---|
135 | 'MAC', |
---|
136 | 'MTU', |
---|
137 | 'VLAN'] |
---|
138 | return attrInst |
---|
139 | |
---|
140 | def getMethods(self): |
---|
141 | methods = ['plug', |
---|
142 | 'unplug', |
---|
143 | 'destroy'] |
---|
144 | return XendBase.getMethods() + methods |
---|
145 | |
---|
146 | def getFuncs(self): |
---|
147 | funcs = ['create_VLAN'] |
---|
148 | return XendBase.getFuncs() + funcs |
---|
149 | |
---|
150 | getClass = classmethod(getClass) |
---|
151 | getAttrRO = classmethod(getAttrRO) |
---|
152 | getAttrRW = classmethod(getAttrRW) |
---|
153 | getAttrInst = classmethod(getAttrInst) |
---|
154 | getMethods = classmethod(getMethods) |
---|
155 | getFuncs = classmethod(getFuncs) |
---|
156 | |
---|
157 | def create_phy(self, network_uuid, device, |
---|
158 | MAC, MTU): |
---|
159 | """ |
---|
160 | Called when a new physical PIF is found |
---|
161 | Could be a VLAN... |
---|
162 | """ |
---|
163 | # Create new uuids |
---|
164 | pif_uuid = genuuid.createString() |
---|
165 | metrics_uuid = genuuid.createString() |
---|
166 | |
---|
167 | # Create instances |
---|
168 | metrics = XendPIFMetrics(metrics_uuid, pif_uuid) |
---|
169 | |
---|
170 | # Is this a VLAN? |
---|
171 | VLANdot = device.split(".") |
---|
172 | VLANcolon = device.split(":") |
---|
173 | |
---|
174 | if len(VLANdot) > 1: |
---|
175 | VLAN = VLANdot[1] |
---|
176 | device = VLANdot[0] |
---|
177 | elif len(VLANcolon) > 1: |
---|
178 | VLAN = VLANcolon[1] |
---|
179 | device = VLANcolon[0] |
---|
180 | else: |
---|
181 | VLAN = -1 |
---|
182 | |
---|
183 | record = { |
---|
184 | 'network': network_uuid, |
---|
185 | 'device': device, |
---|
186 | 'MAC': MAC, |
---|
187 | 'MTU': MTU, |
---|
188 | 'VLAN': VLAN |
---|
189 | } |
---|
190 | pif = XendPIF(record, pif_uuid, metrics_uuid) |
---|
191 | |
---|
192 | return pif_uuid |
---|
193 | |
---|
194 | def recreate(self, record, uuid): |
---|
195 | """Called on xend start / restart""" |
---|
196 | pif_uuid = uuid |
---|
197 | metrics_uuid = record['metrics'] |
---|
198 | |
---|
199 | # Create instances |
---|
200 | metrics = XendPIFMetrics(metrics_uuid, pif_uuid) |
---|
201 | pif = XendPIF(record, pif_uuid, metrics_uuid) |
---|
202 | |
---|
203 | # If physical PIF, check exists |
---|
204 | # If VLAN, create if not exist |
---|
205 | ifs = [dev for dev, _1, _2 in linux_get_phy_ifaces()] |
---|
206 | if pif.get_VLAN() == -1: |
---|
207 | if pif.get_device() not in ifs: |
---|
208 | XendBase.destroy(pif) |
---|
209 | metrics.destroy() |
---|
210 | return None |
---|
211 | else: |
---|
212 | if pif.get_interface_name() not in ifs: |
---|
213 | _create_VLAN(pif.get_device(), pif.get_VLAN()) |
---|
214 | pif.plug() |
---|
215 | |
---|
216 | return pif_uuid |
---|
217 | |
---|
218 | def create_VLAN(self, device, network_uuid, host_ref, vlan): |
---|
219 | """Exposed via API - create a new VLAN from existing VIF""" |
---|
220 | |
---|
221 | ifs = [name for name, _, _ in linux_get_phy_ifaces()] |
---|
222 | |
---|
223 | vlan = int(vlan) |
---|
224 | |
---|
225 | # Check VLAN tag is valid |
---|
226 | if vlan < 0 or vlan >= 4096: |
---|
227 | raise VLANTagInvalid(vlan) |
---|
228 | |
---|
229 | # Check device exists |
---|
230 | if device not in ifs: |
---|
231 | raise InvalidDeviceError(device) |
---|
232 | |
---|
233 | # Check VLAN doesn't already exist |
---|
234 | if "%s.%d" % (device, vlan) in ifs: |
---|
235 | raise DeviceExistsError("%s.%d" % (device, vlan)) |
---|
236 | |
---|
237 | # Check network ref is valid |
---|
238 | from XendNetwork import XendNetwork |
---|
239 | if network_uuid not in XendNetwork.get_all(): |
---|
240 | raise InvalidHandleError("Network", network_uuid) |
---|
241 | |
---|
242 | # Check host_ref is this host |
---|
243 | import XendNode |
---|
244 | if host_ref != XendNode.instance().get_uuid(): |
---|
245 | raise InvalidHandleError("Host", host_ref) |
---|
246 | |
---|
247 | # Create the VLAN |
---|
248 | _create_VLAN(device, vlan) |
---|
249 | |
---|
250 | # Create new uuids |
---|
251 | pif_uuid = genuuid.createString() |
---|
252 | metrics_uuid = genuuid.createString() |
---|
253 | |
---|
254 | # Create the record |
---|
255 | record = { |
---|
256 | "device": device, |
---|
257 | "MAC": '', |
---|
258 | "MTU": '', |
---|
259 | "network": network_uuid, |
---|
260 | "VLAN": vlan |
---|
261 | } |
---|
262 | |
---|
263 | # Create instances |
---|
264 | metrics = XendPIFMetrics(metrics_uuid, pif_uuid) |
---|
265 | pif = XendPIF(record, pif_uuid, metrics_uuid) |
---|
266 | |
---|
267 | # Not sure if they should be created plugged or not... |
---|
268 | pif.plug() |
---|
269 | |
---|
270 | XendNode.instance().save_PIFs() |
---|
271 | return pif_uuid |
---|
272 | |
---|
273 | create_phy = classmethod(create_phy) |
---|
274 | recreate = classmethod(recreate) |
---|
275 | create_VLAN = classmethod(create_VLAN) |
---|
276 | |
---|
277 | def __init__(self, record, uuid, metrics_uuid): |
---|
278 | XendBase.__init__(self, uuid, record) |
---|
279 | self.metrics = metrics_uuid |
---|
280 | |
---|
281 | def plug(self): |
---|
282 | """Plug the PIF into the network""" |
---|
283 | network = XendAPIStore.get(self.network, |
---|
284 | "network") |
---|
285 | bridge_name = network.get_name_label() |
---|
286 | |
---|
287 | from xen.util import Brctl |
---|
288 | Brctl.vif_bridge_add({ |
---|
289 | "bridge": bridge_name, |
---|
290 | "vif": self.get_interface_name() |
---|
291 | }) |
---|
292 | |
---|
293 | def unplug(self): |
---|
294 | """Unplug the PIF from the network""" |
---|
295 | network = XendAPIStore.get(self.network, |
---|
296 | "network") |
---|
297 | bridge_name = network.get_name_label() |
---|
298 | |
---|
299 | from xen.util import Brctl |
---|
300 | Brctl.vif_bridge_rem({ |
---|
301 | "bridge": bridge_name, |
---|
302 | "vif": self.get_interface_name() |
---|
303 | }) |
---|
304 | |
---|
305 | def destroy(self): |
---|
306 | # Figure out if this is a physical device |
---|
307 | if self.get_interface_name() == \ |
---|
308 | self.get_device(): |
---|
309 | raise PIFIsPhysical() |
---|
310 | |
---|
311 | self.unplug() |
---|
312 | |
---|
313 | if _destroy_VLAN(self.get_device(), self.get_VLAN()): |
---|
314 | XendBase.destroy(self) |
---|
315 | import XendNode |
---|
316 | XendNode.instance().save_PIFs() |
---|
317 | else: |
---|
318 | raise NetworkError("Unable to delete VLAN", self.get_uuid()) |
---|
319 | |
---|
320 | def get_interface_name(self): |
---|
321 | if self.get_VLAN() == -1: |
---|
322 | return self.get_device() |
---|
323 | else: |
---|
324 | return "%s.%d" % (self.get_device(), self.get_VLAN()) |
---|
325 | |
---|
326 | def get_device(self): |
---|
327 | """ |
---|
328 | This is the base interface. |
---|
329 | For phy if (VLAN == -1) this is same as |
---|
330 | if name. |
---|
331 | For VLANs, this it the bit before the period |
---|
332 | """ |
---|
333 | return self.device |
---|
334 | |
---|
335 | def get_network(self): |
---|
336 | return self.network |
---|
337 | |
---|
338 | def get_host(self): |
---|
339 | from xen.xend import XendNode |
---|
340 | return XendNode.instance().get_uuid() |
---|
341 | |
---|
342 | def get_metrics(self): |
---|
343 | return self.metrics |
---|
344 | |
---|
345 | def get_MAC(self): |
---|
346 | return self.MAC |
---|
347 | |
---|
348 | def set_MAC(self, new_mac): |
---|
349 | success = linux_set_mac(self.device, new_mac) |
---|
350 | if success: |
---|
351 | self.MAC = new_mac |
---|
352 | import XendNode |
---|
353 | XendNode.instance().save_PIFs() |
---|
354 | return success |
---|
355 | |
---|
356 | def get_MTU(self): |
---|
357 | return self.MTU |
---|
358 | |
---|
359 | def set_MTU(self, new_mtu): |
---|
360 | success = linux_set_mtu(self.device, new_mtu) |
---|
361 | if success: |
---|
362 | self.MTU = new_mtu |
---|
363 | import XendNode |
---|
364 | XendNode.instance().save_PIFs() |
---|
365 | return success |
---|
366 | |
---|
367 | def get_VLAN(self): |
---|
368 | return self.VLAN |
---|