Merge citrix branch into master.
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2008,2009 Citrix Systems, Inc.
4 # Copyright (c) 2009 Nicira Networks.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published
8 # by the Free Software Foundation; version 2.1 only. with the special
9 # exception on linking described in file LICENSE.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU Lesser General Public License for more details.
15 #
16 """Usage:
17
18     %(command-name)s <PIF> up
19     %(command-name)s <PIF> down
20     %(command-name)s [<PIF>] rewrite
21     %(command-name)s --force <BRIDGE> up
22     %(command-name)s --force <BRIDGE> down
23     %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> <CONFIG>
24     %(command-name)s --force all down
25
26     where <PIF> is one of:
27        --session <SESSION-REF> --pif <PIF-REF>
28        --pif-uuid <PIF-UUID>
29     and <CONFIG> is one of:
30        --mode=dhcp
31        --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
32
33   Options:
34     --session           A session reference to use to access the xapi DB
35     --pif               A PIF reference within the session.
36     --pif-uuid          The UUID of a PIF.
37     --force             An interface name.
38 """
39
40 #
41 # Undocumented parameters for test & dev:
42 #
43 #  --output-directory=<DIR>     Write configuration to <DIR>. Also disables actually
44 #                               raising/lowering the interfaces
45 #
46 #
47 #
48 # Notes:
49 # 1. Every pif belongs to exactly one network
50 # 2. Every network has zero or one pifs
51 # 3. A network may have an associated bridge, allowing vifs to be attached
52 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
53
54 import XenAPI
55 import os, sys, getopt, time, signal
56 import syslog
57 import traceback
58 import re
59 import random
60 from xml.dom.minidom import getDOMImplementation
61 from xml.dom.minidom import parse as parseXML
62
63 output_directory = None
64
65 db = None
66 management_pif = None
67
68 vswitch_state_dir = "/var/lib/openvswitch/"
69 dbcache_file = vswitch_state_dir + "dbcache"
70
71 #
72 # Debugging and Logging.
73 #
74
75 def debug_mode():
76     return output_directory is not None
77
78 def log(s):
79     if debug_mode():
80         print >>sys.stderr, s
81     else:
82         syslog.syslog(s)
83
84 def log_pif_action(action, pif):
85     pifrec = db.get_pif_record(pif)
86     rec = {}
87     rec['uuid'] = pifrec['uuid']
88     rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
89     rec['action'] = action
90     rec['pif_netdev_name'] = pif_netdev_name(pif)
91     rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
92     log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
93
94
95 def run_command(command):
96     log("Running command: " + ' '.join(command))
97     rc = os.spawnl(os.P_WAIT, command[0], *command)
98     if rc != 0:
99         log("Command failed %d: " % rc + ' '.join(command))
100         return False
101     return True
102
103 #
104 # Exceptions.
105 #
106
107 class Usage(Exception):
108     def __init__(self, msg):
109         Exception.__init__(self)
110         self.msg = msg
111
112 class Error(Exception):
113     def __init__(self, msg):
114         Exception.__init__(self)
115         self.msg = msg
116
117 #
118 # Configuration File Handling.
119 #
120
121 class ConfigurationFile(object):
122     """Write a file, tracking old and new versions.
123
124     Supports writing a new version of a file and applying and
125     reverting those changes.
126     """
127
128     __STATE = {"OPEN":"OPEN",
129                "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
130                "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
131
132     def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
133
134         self.__state = self.__STATE['OPEN']
135         self.__fname = fname
136         self.__children = []
137
138         if debug_mode():
139             dirname = output_directory
140         else:
141             dirname = path
142
143         self.__path    = os.path.join(dirname, fname)
144         self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
145         self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
146         self.__unlink = False
147
148         self.__f = open(self.__newpath, "w")
149
150     def attach_child(self, child):
151         self.__children.append(child)
152
153     def path(self):
154         return self.__path
155
156     def readlines(self):
157         try:
158             return open(self.path()).readlines()
159         except:
160             return ""
161
162     def write(self, args):
163         if self.__state != self.__STATE['OPEN']:
164             raise Error("Attempt to write to file in state %s" % self.__state)
165         self.__f.write(args)
166
167     def unlink(self):
168         if self.__state != self.__STATE['OPEN']:
169             raise Error("Attempt to unlink file in state %s" % self.__state)
170         self.__unlink = True
171         self.__f.close()
172         self.__state = self.__STATE['NOT-APPLIED']
173
174     def close(self):
175         if self.__state != self.__STATE['OPEN']:
176             raise Error("Attempt to close file in state %s" % self.__state)
177
178         self.__f.close()
179         self.__state = self.__STATE['NOT-APPLIED']
180
181     def changed(self):
182         if self.__state != self.__STATE['NOT-APPLIED']:
183             raise Error("Attempt to compare file in state %s" % self.__state)
184
185         return True
186
187     def apply(self):
188         if self.__state != self.__STATE['NOT-APPLIED']:
189             raise Error("Attempt to apply configuration from state %s" % self.__state)
190
191         for child in self.__children:
192             child.apply()
193
194         log("Applying changes to %s configuration" % self.__fname)
195
196         # Remove previous backup.
197         if os.access(self.__oldpath, os.F_OK):
198             os.unlink(self.__oldpath)
199
200         # Save current configuration.
201         if os.access(self.__path, os.F_OK):
202             os.link(self.__path, self.__oldpath)
203             os.unlink(self.__path)
204
205         # Apply new configuration.
206         assert(os.path.exists(self.__newpath))
207         if not self.__unlink:
208             os.link(self.__newpath, self.__path)
209         else:
210             pass # implicit unlink of original file
211
212         # Remove temporary file.
213         os.unlink(self.__newpath)
214
215         self.__state = self.__STATE['APPLIED']
216
217     def revert(self):
218         if self.__state != self.__STATE['APPLIED']:
219             raise Error("Attempt to revert configuration from state %s" % self.__state)
220
221         for child in self.__children:
222             child.revert()
223
224         log("Reverting changes to %s configuration" % self.__fname)
225
226         # Remove existing new configuration
227         if os.access(self.__newpath, os.F_OK):
228             os.unlink(self.__newpath)
229
230         # Revert new configuration.
231         if os.access(self.__path, os.F_OK):
232             os.link(self.__path, self.__newpath)
233             os.unlink(self.__path)
234
235         # Revert to old configuration.
236         if os.access(self.__oldpath, os.F_OK):
237             os.link(self.__oldpath, self.__path)
238             os.unlink(self.__oldpath)
239
240         # Leave .*.xapi-new as an aid to debugging.
241
242         self.__state = self.__STATE['REVERTED']
243
244     def commit(self):
245         if self.__state != self.__STATE['APPLIED']:
246             raise Error("Attempt to commit configuration from state %s" % self.__state)
247
248         for child in self.__children:
249             child.commit()
250
251         log("Committing changes to %s configuration" % self.__fname)
252
253         if os.access(self.__oldpath, os.F_OK):
254             os.unlink(self.__oldpath)
255         if os.access(self.__newpath, os.F_OK):
256             os.unlink(self.__newpath)
257
258         self.__state = self.__STATE['COMMITTED']
259
260 #
261 # Helper functions for encoding/decoding database attributes to/from XML.
262 #
263
264 def str_to_xml(xml, parent, tag, val):
265     e = xml.createElement(tag)
266     parent.appendChild(e)
267     v = xml.createTextNode(val)
268     e.appendChild(v)
269 def str_from_xml(n):
270     def getText(nodelist):
271         rc = ""
272         for node in nodelist:
273             if node.nodeType == node.TEXT_NODE:
274                 rc = rc + node.data
275         return rc
276     return getText(n.childNodes).strip()
277
278 def bool_to_xml(xml, parent, tag, val):
279     if val:
280         str_to_xml(xml, parent, tag, "True")
281     else:
282         str_to_xml(xml, parent, tag, "False")
283 def bool_from_xml(n):
284     s = str_from_xml(n)
285     if s == "True":
286         return True
287     elif s == "False":
288         return False
289     else:
290         raise Error("Unknown boolean value %s" % s)
291
292 def strlist_to_xml(xml, parent, ltag, itag, val):
293     e = xml.createElement(ltag)
294     parent.appendChild(e)
295     for v in val:
296         c = xml.createElement(itag)
297         e.appendChild(c)
298         cv = xml.createTextNode(v)
299         c.appendChild(cv)
300 def strlist_from_xml(n, ltag, itag):
301     ret = []
302     for n in n.childNodes:
303         if n.nodeName == itag:
304             ret.append(str_from_xml(n))
305     return ret
306
307 def otherconfig_to_xml(xml, parent, val, attrs):
308     otherconfig = xml.createElement("other_config")
309     parent.appendChild(otherconfig)
310     for n,v in val.items():
311         if not n in attrs:
312             raise Error("Unknown other-config attribute: %s" % n)
313         str_to_xml(xml, otherconfig, n, v)
314 def otherconfig_from_xml(n, attrs):
315     ret = {}
316     for n in n.childNodes:
317         if n.nodeName in attrs:
318             ret[n.nodeName] = str_from_xml(n)
319     return ret
320
321 #
322 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
323 #
324 # Each object is defined by a dictionary mapping an attribute name in
325 # the xapi database to a tuple containing two items:
326 #  - a function which takes this attribute and encodes it as XML.
327 #  - a function which takes XML and decocdes it into a value.
328 #
329 # other-config attributes are specified as a simple array of strings
330
331 PIF_XML_TAG = "pif"
332 VLAN_XML_TAG = "vlan"
333 BOND_XML_TAG = "bond"
334 NETWORK_XML_TAG = "network"
335
336 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
337
338 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
339                         [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
340                         ETHTOOL_OTHERCONFIG_ATTRS
341
342 PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
343               'management': (bool_to_xml,bool_from_xml),
344               'network': (str_to_xml,str_from_xml),
345               'device': (str_to_xml,str_from_xml),
346               'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
347                                  lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')),
348               'bond_slave_of': (str_to_xml,str_from_xml),
349               'VLAN': (str_to_xml,str_from_xml),
350               'VLAN_master_of': (str_to_xml,str_from_xml),
351               'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
352                                 lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
353               'ip_configuration_mode': (str_to_xml,str_from_xml),
354               'IP': (str_to_xml,str_from_xml),
355               'netmask': (str_to_xml,str_from_xml),
356               'gateway': (str_to_xml,str_from_xml),
357               'DNS': (str_to_xml,str_from_xml),
358               'MAC': (str_to_xml,str_from_xml),
359               'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS),
360                                lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)),
361
362               # Special case: We write the current value
363               # PIF.currently-attached to the cache but since it will
364               # not be valid when we come to use the cache later
365               # (i.e. after a reboot) we always read it as False.
366               'currently_attached': (bool_to_xml, lambda n: False),
367             }
368
369 VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
370                'tagged_PIF': (str_to_xml,str_from_xml),
371                'untagged_PIF': (str_to_xml,str_from_xml),
372              }
373
374 BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
375                'master': (str_to_xml,str_from_xml),
376                'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v),
377                           lambda n: strlist_from_xml(n, 'slaves', 'slave')),
378              }
379
380 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
381
382 NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml),
383                   'bridge': (str_to_xml,str_from_xml),
384                   'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v),
385                            lambda n: strlist_from_xml(n, 'PIFs', 'PIF')),
386                   'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS),
387                                    lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)),
388                 }
389
390 class DatabaseCache(object):
391     def __read_xensource_inventory(self):
392         filename = "/etc/xensource-inventory"
393         f = open(filename, "r")
394         lines = [x.strip("\n") for x in f.readlines()]
395         f.close()
396
397         defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
398         defs = [ (a, b.strip("'")) for (a,b) in defs ]
399
400         return dict(defs)
401     def __pif_on_host(self,pif):
402         return self.__pifs.has_key(pif)
403
404     def __get_pif_records_from_xapi(self, session, host):
405         self.__pifs = {}
406         for (p,rec) in session.xenapi.PIF.get_all_records().items():
407             if rec['host'] != host:
408                 continue
409             self.__pifs[p] = {}
410             for f in PIF_ATTRS:
411                 self.__pifs[p][f] = rec[f]
412             self.__pifs[p]['other_config'] = {}
413             for f in PIF_OTHERCONFIG_ATTRS:
414                 if not rec['other_config'].has_key(f): continue
415                 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
416
417     def __get_vlan_records_from_xapi(self, session):
418         self.__vlans = {}
419         for v in session.xenapi.VLAN.get_all():
420             rec = session.xenapi.VLAN.get_record(v)
421             if not self.__pif_on_host(rec['untagged_PIF']):
422                 continue
423             self.__vlans[v] = {}
424             for f in VLAN_ATTRS:
425                 self.__vlans[v][f] = rec[f]
426
427     def __get_bond_records_from_xapi(self, session):
428         self.__bonds = {}
429         for b in session.xenapi.Bond.get_all():
430             rec = session.xenapi.Bond.get_record(b)
431             if not self.__pif_on_host(rec['master']):
432                 continue
433             self.__bonds[b] = {}
434             for f in BOND_ATTRS:
435                 self.__bonds[b][f] = rec[f]
436
437     def __get_network_records_from_xapi(self, session):
438         self.__networks = {}
439         for n in session.xenapi.network.get_all():
440             rec = session.xenapi.network.get_record(n)
441             self.__networks[n] = {}
442             for f in NETWORK_ATTRS:
443                 if f == "PIFs":
444                     # drop PIFs on other hosts
445                     self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
446                 else:
447                     self.__networks[n][f] = rec[f]
448             self.__networks[n]['other_config'] = {}
449             for f in NETWORK_OTHERCONFIG_ATTRS:
450                 if not rec['other_config'].has_key(f): continue
451                 self.__networks[n]['other_config'][f] = rec['other_config'][f]
452
453     def __to_xml(self, xml, parent, key, ref, rec, attrs):
454         """Encode a database object as XML"""
455         e = xml.createElement(key)
456         parent.appendChild(e)
457         if ref:
458             e.setAttribute('ref', ref)
459
460         for n,v in rec.items():
461             if attrs.has_key(n):
462                 h,_ = attrs[n]
463                 h(xml, e, n, v)
464             else:
465                 raise Error("Unknown attribute %s" % n)
466     def __from_xml(self, e, attrs):
467         """Decode a database object from XML"""
468         ref = e.attributes['ref'].value
469         rec = {}
470         for n in e.childNodes:
471             if n.nodeName in attrs:
472                 _,h = attrs[n.nodeName]
473                 rec[n.nodeName] = h(n)
474         return (ref,rec)
475
476     def __init__(self, session_ref=None, cache_file=None):
477         if session_ref and cache_file:
478             raise Error("can't specify session reference and cache file")
479         if cache_file == None:
480             session = XenAPI.xapi_local()
481
482             if not session_ref:
483                 log("No session ref given on command line, logging in.")
484                 session.xenapi.login_with_password("root", "")
485             else:
486                 session._session = session_ref
487
488             try:
489
490                 inventory = self.__read_xensource_inventory()
491                 assert(inventory.has_key('INSTALLATION_UUID'))
492                 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
493
494                 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
495
496                 self.__get_pif_records_from_xapi(session, host)
497
498                 self.__get_vlan_records_from_xapi(session)
499                 self.__get_bond_records_from_xapi(session)
500                 self.__get_network_records_from_xapi(session)
501             finally:
502                 if not session_ref:
503                     session.xenapi.session.logout()
504         else:
505             log("Loading xapi database cache from %s" % cache_file)
506
507             xml = parseXML(cache_file)
508
509             self.__pifs = {}
510             self.__bonds = {}
511             self.__vlans = {}
512             self.__networks = {}
513
514             assert(len(xml.childNodes) == 1)
515             toplevel = xml.childNodes[0]
516
517             assert(toplevel.nodeName == "xenserver-network-configuration")
518
519             for n in toplevel.childNodes:
520                 if n.nodeName == "#text":
521                     pass
522                 elif n.nodeName == PIF_XML_TAG:
523                     (ref,rec) = self.__from_xml(n, PIF_ATTRS)
524                     self.__pifs[ref] = rec
525                 elif n.nodeName == BOND_XML_TAG:
526                     (ref,rec) = self.__from_xml(n, BOND_ATTRS)
527                     self.__bonds[ref] = rec
528                 elif n.nodeName == VLAN_XML_TAG:
529                     (ref,rec) = self.__from_xml(n, VLAN_ATTRS)
530                     self.__vlans[ref] = rec
531                 elif n.nodeName == NETWORK_XML_TAG:
532                     (ref,rec) = self.__from_xml(n, NETWORK_ATTRS)
533                     self.__networks[ref] = rec
534                 else:
535                     raise Error("Unknown XML element %s" % n.nodeName)
536
537     def save(self, cache_file):
538
539         xml = getDOMImplementation().createDocument(
540             None, "xenserver-network-configuration", None)
541         for (ref,rec) in self.__pifs.items():
542             self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS)
543         for (ref,rec) in self.__bonds.items():
544             self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS)
545         for (ref,rec) in self.__vlans.items():
546             self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS)
547         for (ref,rec) in self.__networks.items():
548             self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec,
549                           NETWORK_ATTRS)
550
551         f = open(cache_file, 'w')
552         f.write(xml.toprettyxml())
553         f.close()
554
555     def get_pif_by_uuid(self, uuid):
556         pifs = map(lambda (ref,rec): ref,
557                   filter(lambda (ref,rec): uuid == rec['uuid'],
558                          self.__pifs.items()))
559         if len(pifs) == 0:
560             raise Error("Unknown PIF \"%s\"" % uuid)
561         elif len(pifs) > 1:
562             raise Error("Non-unique PIF \"%s\"" % uuid)
563
564         return pifs[0]
565
566     def get_pifs_by_device(self, device):
567         return map(lambda (ref,rec): ref,
568                    filter(lambda (ref,rec): rec['device'] == device,
569                           self.__pifs.items()))
570
571     def get_pif_by_bridge(self, bridge):
572         networks = map(lambda (ref,rec): ref,
573                        filter(lambda (ref,rec): rec['bridge'] == bridge,
574                               self.__networks.items()))
575         if len(networks) == 0:
576             raise Error("No matching network \"%s\"" % bridge)
577
578         answer = None
579         for network in networks:
580             nwrec = self.get_network_record(network)
581             for pif in nwrec['PIFs']:
582                 pifrec = self.get_pif_record(pif)
583                 if answer:
584                     raise Error("Multiple PIFs on host for network %s" % (bridge))
585                 answer = pif
586         if not answer:
587             raise Error("No PIF on host for network %s" % (bridge))
588         return answer
589
590     def get_pif_record(self, pif):
591         if self.__pifs.has_key(pif):
592             return self.__pifs[pif]
593         raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif)
594     def get_all_pifs(self):
595         return self.__pifs
596     def pif_exists(self, pif):
597         return self.__pifs.has_key(pif)
598
599     def get_management_pif(self):
600         """ Returns the management pif on host
601         """
602         all = self.get_all_pifs()
603         for pif in all:
604             pifrec = self.get_pif_record(pif)
605             if pifrec['management']: return pif
606         return None
607
608     def get_network_record(self, network):
609         if self.__networks.has_key(network):
610             return self.__networks[network]
611         raise Error("Unknown network \"%s\"" % network)
612     def get_all_networks(self):
613         return self.__networks
614
615     def get_bond_record(self, bond):
616         if self.__bonds.has_key(bond):
617             return self.__bonds[bond]
618         else:
619             return None
620
621     def get_vlan_record(self, vlan):
622         if self.__vlans.has_key(vlan):
623             return self.__vlans[vlan]
624         else:
625             return None
626
627 #
628 # Boot from Network filesystem or device.
629 #
630
631 def check_allowed(pif):
632     """Determine whether interface-reconfigure should be manipulating this PIF.
633
634     Used to prevent system PIFs (such as network root disk) from being interfered with.
635     """
636
637     pifrec = db.get_pif_record(pif)
638     try:
639         f = open("/proc/ardence")
640         macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
641         f.close()
642         if len(macline) == 1:
643             p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
644             if p.match(macline[0]):
645                 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
646                 return False
647     except IOError:
648         pass
649     return True
650
651 #
652 # Bare Network Devices -- network devices without IP configuration
653 #
654
655 def netdev_exists(netdev):
656     return os.path.exists("/sys/class/net/" + netdev)
657
658 def pif_netdev_name(pif):
659     """Get the netdev name for a PIF."""
660
661     pifrec = db.get_pif_record(pif)
662
663     if pif_is_vlan(pif):
664         return "%(device)s.%(VLAN)s" % pifrec
665     else:
666         return pifrec['device']
667
668 def netdev_down(netdev):
669     """Bring down a bare network device"""
670     if debug_mode():
671         return
672     if not netdev_exists(netdev):
673         log("netdev: down: device %s does not exist, ignoring" % netdev)
674         return
675     run_command(["/sbin/ifconfig", netdev, 'down'])
676
677 def netdev_up(netdev, mtu=None):
678     """Bring up a bare network device"""
679     if debug_mode():
680         return
681     if not netdev_exists(netdev):
682         raise Error("netdev: up: device %s does not exist" % netdev)
683
684     if mtu:
685         mtu = ["mtu", mtu]
686     else:
687         mtu = []
688         
689     run_command(["/sbin/ifconfig", netdev, 'up'] + mtu)
690
691 def netdev_remap_name(pif, already_renamed=[]):
692     """Check whether 'pif' exists and has the correct MAC.
693     If not, try to find a device with the correct MAC and rename it.
694     'already_renamed' is used to avoid infinite recursion.
695     """
696     
697     def read1(name):
698         file = None
699         try:
700             file = open(name, 'r')
701             return file.readline().rstrip('\n')
702         finally:
703             if file != None:
704                 file.close()
705
706     def get_netdev_mac(device):
707         try:
708             return read1("/sys/class/net/%s/address" % device)
709         except:
710             # Probably no such device.
711             return None
712
713     def get_netdev_tx_queue_len(device):
714         try:
715             return int(read1("/sys/class/net/%s/tx_queue_len" % device))
716         except:
717             # Probably no such device.
718             return None
719
720     def get_netdev_by_mac(mac):
721         for device in os.listdir("/sys/class/net"):
722             dev_mac = get_netdev_mac(device)
723             if (dev_mac and mac.lower() == dev_mac.lower() and
724                 get_netdev_tx_queue_len(device)):
725                 return device
726         return None
727
728     def rename_netdev(old_name, new_name):
729         log("Changing the name of %s to %s" % (old_name, new_name))
730         run_command(['/sbin/ifconfig', old_name, 'down'])
731         if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
732             raise Error("Could not rename %s to %s" % (old_name, new_name))
733
734     pifrec = db.get_pif_record(pif)
735     device = pifrec['device']
736     mac = pifrec['MAC']
737
738     # Is there a network device named 'device' at all?
739     device_exists = netdev_exists(device)
740     if device_exists:
741         # Yes.  Does it have MAC 'mac'?
742         found_mac = get_netdev_mac(device)
743         if found_mac and mac.lower() == found_mac.lower():
744             # Yes, everything checks out the way we want.  Nothing to do.
745             return
746     else:
747         log("No network device %s" % device)
748
749     # What device has MAC 'mac'?
750     cur_device = get_netdev_by_mac(mac)
751     if not cur_device:
752         log("No network device has MAC %s" % mac)
753         return
754
755     # First rename 'device', if it exists, to get it out of the way
756     # for 'cur_device' to replace it.
757     if device_exists:
758         rename_netdev(device, "dev%d" % random.getrandbits(24))
759
760     # Rename 'cur_device' to 'device'.
761     rename_netdev(cur_device, device)
762
763 #
764 # IP Network Devices -- network devices with IP configuration
765 #
766
767 def pif_ipdev_name(pif):
768     """Return the ipdev name associated with pif"""
769     pifrec = db.get_pif_record(pif)
770     nwrec = db.get_network_record(pifrec['network'])
771
772     if nwrec['bridge']:
773         # TODO: sanity check that nwrec['bridgeless'] != 'true'
774         return nwrec['bridge']
775     else:
776         # TODO: sanity check that nwrec['bridgeless'] == 'true'
777         return pif_netdev_name(pif)
778
779 def ifdown(netdev):
780     """Bring down a network interface"""
781     if debug_mode():
782         return
783     if not netdev_exists(netdev):
784         log("ifdown: device %s does not exist, ignoring" % netdev)
785         return
786     if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
787         log("ifdown: device %s exists but ifcfg %s does not" % (netdev,netdev))
788         netdev_down(netdev)
789     run_command(["/sbin/ifdown", netdev])
790
791 def ifup(netdev):
792     """Bring up a network interface"""
793     if debug_mode():
794         return
795     if not netdev_exists(netdev):
796         raise Error("ifup: device %s does not exist, ignoring" % netdev)
797     if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
798         raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
799     run_command(["/sbin/ifup", netdev])
800
801 #
802 # Bridges
803 #
804
805 def pif_bridge_name(pif):
806     """Return the bridge name of a pif.
807
808     PIF must not be a VLAN and must be a bridged PIF."""
809
810     pifrec = db.get_pif_record(pif)
811
812     if pif_is_vlan(pif):
813         raise Error("PIF %(uuid)s cannot be a bridge, VLAN is %(VLAN)s" % pifrec)
814         
815     nwrec = db.get_network_record(pifrec['network'])
816
817     if nwrec['bridge']:
818         return nwrec['bridge']
819     else:
820         raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
821
822 #
823 # PIF miscellanea
824 #
825
826 def pif_currently_in_use(pif):
827     """Determine if a PIF is currently in use.
828
829     A PIF is determined to be currently in use if
830     - PIF.currently-attached is true
831     - Any bond master is currently attached
832     - Any VLAN master is currently attached
833     """
834     rec = db.get_pif_record(pif)
835     if rec['currently_attached']:
836         log("configure_datapath: %s is currently attached" % (pif_netdev_name(pif)))
837         return True
838     for b in pif_get_bond_masters(pif):
839         if pif_currently_in_use(b):
840             log("configure_datapath: %s is in use by BOND master %s" % (pif_netdev_name(pif),pif_netdev_name(b)))
841             return True
842     for v in pif_get_vlan_masters(pif):
843         if pif_currently_in_use(v):
844             log("configure_datapath: %s is in use by VLAN master %s" % (pif_netdev_name(pif),pif_netdev_name(v)))
845             return True
846     return False
847
848 #
849 #
850 #
851
852 def ethtool_settings(oc):
853     settings = []
854     if oc.has_key('ethtool-speed'):
855         val = oc['ethtool-speed']
856         if val in ["10", "100", "1000"]:
857             settings += ['speed', val]
858         else:
859             log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
860     if oc.has_key('ethtool-duplex'):
861         val = oc['ethtool-duplex']
862         if val in ["10", "100", "1000"]:
863             settings += ['duplex', 'val']
864         else:
865             log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
866     if oc.has_key('ethtool-autoneg'):
867         val = oc['ethtool-autoneg']
868         if val in ["true", "on"]:
869             settings += ['autoneg', 'on']
870         elif val in ["false", "off"]:
871             settings += ['autoneg', 'off']
872         else:
873             log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
874     offload = []
875     for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
876         if oc.has_key("ethtool-" + opt):
877             val = oc["ethtool-" + opt]
878             if val in ["true", "on"]:
879                 offload += [opt, 'on']
880             elif val in ["false", "off"]:
881                 offload += [opt, 'off']
882             else:
883                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
884     return settings,offload
885
886 def mtu_setting(oc):
887     if oc.has_key('mtu'):
888         try:
889             int(oc['mtu'])      # Check that the value is an integer
890             return oc['mtu']
891         except ValueError, x:
892             log("Invalid value for mtu = %s" % oc['mtu'])
893     return None
894
895 #
896 # Bonded PIFs
897 #
898 def pif_get_bond_masters(pif):
899     """Returns a list of PIFs which are bond masters of this PIF"""
900
901     pifrec = db.get_pif_record(pif)
902
903     bso = pifrec['bond_slave_of']
904
905     # bond-slave-of is currently a single reference but in principle a
906     # PIF could be a member of several bonds which are not
907     # concurrently attached. Be robust to this possibility.
908     if not bso or bso == "OpaqueRef:NULL":
909         bso = []
910     elif not type(bso) == list:
911         bso = [bso]
912
913     bondrecs = [db.get_bond_record(bond) for bond in bso]
914     bondrecs = [rec for rec in bondrecs if rec]
915
916     return [bond['master'] for bond in bondrecs]
917
918 def pif_get_bond_slaves(pif):
919     """Returns a list of PIFs which make up the given bonded pif."""
920
921     pifrec = db.get_pif_record(pif)
922
923     bmo = pifrec['bond_master_of']
924     if len(bmo) > 1:
925         raise Error("Bond-master-of contains too many elements")
926
927     if len(bmo) == 0:
928         return []
929
930     bondrec = db.get_bond_record(bmo[0])
931     if not bondrec:
932         raise Error("No bond record for bond master PIF")
933
934     return bondrec['slaves']
935
936 #
937 # VLAN PIFs
938 #
939
940 def pif_is_vlan(pif):
941     return db.get_pif_record(pif)['VLAN'] != '-1'
942
943 def pif_get_vlan_slave(pif):
944     """Find the PIF which is the VLAN slave of pif.
945
946 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
947
948     pifrec = db.get_pif_record(pif)
949
950     vlan = pifrec['VLAN_master_of']
951     if not vlan or vlan == "OpaqueRef:NULL":
952         raise Error("PIF is not a VLAN master")
953
954     vlanrec = db.get_vlan_record(vlan)
955     if not vlanrec:
956         raise Error("No VLAN record found for PIF")
957
958     return vlanrec['tagged_PIF']
959
960 def pif_get_vlan_masters(pif):
961     """Returns a list of PIFs which are VLANs on top of the given pif."""
962
963     pifrec = db.get_pif_record(pif)
964     vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
965     return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
966
967 #
968 # IP device configuration
969 #
970
971 def ipdev_configure_static_routes(interface, oc, f):
972     """Open a route-<interface> file for static routes.
973
974     Opens the static routes configuration file for interface and writes one
975     line for each route specified in the network's other config "static-routes" value.
976     E.g. if
977            interface ( RO): xenbr1
978            other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
979
980     Then route-xenbr1 should be
981           172.16.0.0/15 via 192.168.0.3 dev xenbr1
982           172.18.0.0/16 via 192.168.0.4 dev xenbr1
983     """
984     fname = "route-%s" % interface
985     if oc.has_key('static-routes'):
986         # The key is present - extract comma seperates entries
987         lines = oc['static-routes'].split(',')
988     else:
989         # The key is not present, i.e. there are no static routes
990         lines = []
991
992     child = ConfigurationFile(fname)
993     child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
994             (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
995
996     try:
997         for l in lines:
998             network, masklen, gateway = l.split('/')
999             child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1000
1001         f.attach_child(child)
1002         child.close()
1003
1004     except ValueError, e:
1005         log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1006
1007 def ipdev_open_ifcfg(pif):
1008     ipdev = pif_ipdev_name(pif)
1009
1010     log("Writing network configuration for %s" % ipdev)
1011
1012     f = ConfigurationFile("ifcfg-%s" % ipdev)
1013
1014     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1015             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1016     f.write("XEMANAGED=yes\n")
1017     f.write("DEVICE=%s\n" % ipdev)
1018     f.write("ONBOOT=no\n")
1019
1020     return f
1021
1022 def ipdev_configure_network(pif):
1023     """Write the configuration file for a network.
1024
1025     Writes configuration derived from the network object into the relevant
1026     ifcfg file.  The configuration file is passed in, but if the network is
1027     bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1028
1029     This routine may also write ifcfg files of the networks corresponding to other PIFs
1030     in order to maintain consistency.
1031
1032     params:
1033         pif:  Opaque_ref of pif
1034         f :   ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1035     """
1036
1037     pifrec = db.get_pif_record(pif)
1038     nwrec = db.get_network_record(pifrec['network'])
1039
1040     ipdev = pif_ipdev_name(pif)
1041
1042     f = ipdev_open_ifcfg(pif)
1043
1044     mode = pifrec['ip_configuration_mode']
1045     log("Configuring %s using %s configuration" % (ipdev, mode))
1046
1047     oc = None
1048     if pifrec.has_key('other_config'):
1049         oc = pifrec['other_config']
1050
1051     f.write("TYPE=Ethernet\n")
1052     if pifrec['ip_configuration_mode'] == "DHCP":
1053         f.write("BOOTPROTO=dhcp\n")
1054         f.write("PERSISTENT_DHCLIENT=yes\n")
1055     elif pifrec['ip_configuration_mode'] == "Static":
1056         f.write("BOOTPROTO=none\n")
1057         f.write("NETMASK=%(netmask)s\n" % pifrec)
1058         f.write("IPADDR=%(IP)s\n" % pifrec)
1059         f.write("GATEWAY=%(gateway)s\n" % pifrec)
1060     elif pifrec['ip_configuration_mode'] == "None":
1061         f.write("BOOTPROTO=none\n")
1062     else:
1063         raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1064
1065     if nwrec.has_key('other_config'):
1066         settings,offload = ethtool_settings(nwrec['other_config'])
1067         if len(settings):
1068             f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
1069         if len(offload):
1070             f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
1071
1072         mtu = mtu_setting(nwrec['other_config'])
1073         if mtu:
1074             f.write("MTU=%s\n" % mtu)
1075
1076         ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
1077
1078     if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1079         ServerList = pifrec['DNS'].split(",")
1080         for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1081     if oc and oc.has_key('domain'):
1082         f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1083
1084     # We only allow one ifcfg-* to have PEERDNS=yes and there can be
1085     # only one GATEWAYDEV in /etc/sysconfig/network.
1086     #
1087     # The peerdns pif will be the one with
1088     # pif::other-config:peerdns=true, or the mgmt pif if none have
1089     # this set.
1090     #
1091     # The gateway pif will be the one with
1092     # pif::other-config:defaultroute=true, or the mgmt pif if none
1093     # have this set.
1094
1095     # Work out which pif on this host should be the one with
1096     # PEERDNS=yes and which should be the GATEWAYDEV
1097     #
1098     # Note: we prune out the bond master pif (if it exists).  This is
1099     # because when we are called to bring up an interface with a bond
1100     # master, it is implicit that we should bring down that master.
1101     pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1102                      not __pif in pif_get_bond_masters(pif) ]
1103     other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1104
1105     peerdns_pif = None
1106     defaultroute_pif = None
1107
1108     # loop through all the pifs on this host looking for one with
1109     #   other-config:peerdns = true, and one with
1110     #   other-config:default-route=true
1111     for __pif in pifs_on_host:
1112         __pifrec = db.get_pif_record(__pif)
1113         __oc = __pifrec['other_config']
1114         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1115             if peerdns_pif == None:
1116                 peerdns_pif = __pif
1117             else:
1118                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1119                         (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1120         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1121             if defaultroute_pif == None:
1122                 defaultroute_pif = __pif
1123             else:
1124                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1125                         (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1126
1127     # If no pif is explicitly specified then use the mgmt pif for
1128     # peerdns/defaultroute.
1129     if peerdns_pif == None:
1130         peerdns_pif = management_pif
1131     if defaultroute_pif == None:
1132         defaultroute_pif = management_pif
1133
1134     # Update all the other network's ifcfg files and ensure
1135     # consistency.
1136     for __pif in other_pifs_on_host:
1137         __f = ipdev_open_ifcfg(__pif)
1138         peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1139         lines =  __f.readlines()
1140
1141         if not peerdns_line_wanted in lines:
1142             # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1143             for line in lines:
1144                 if not line.lstrip().startswith('PEERDNS'):
1145                     __f.write(line)
1146             log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1147             __f.write(peerdns_line_wanted)
1148             __f.close()
1149             f.attach_child(__f)
1150
1151         else:
1152             # There is no need to change this ifcfg file.  So don't attach_child.
1153             pass
1154
1155     # ... and for this pif too
1156     f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1157
1158     # Update gatewaydev
1159     fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1160     for line in fnetwork.readlines():
1161         if line.lstrip().startswith('GATEWAY') :
1162             continue
1163         fnetwork.write(line)
1164     if defaultroute_pif:
1165         gatewaydev = pif_ipdev_name(defaultroute_pif)
1166         if not gatewaydev:
1167             gatewaydev = pif_netdev_name(defaultroute_pif)
1168         fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1169     fnetwork.close()
1170     f.attach_child(fnetwork)
1171
1172     return f
1173
1174 #
1175 # Datapath Configuration
1176 #
1177
1178 def pif_datapath(pif):
1179     """Return the OpenFlow datapath name associated with pif.
1180 For a non-VLAN PIF, the datapath name is the bridge name.
1181 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
1182 """
1183     if pif_is_vlan(pif):
1184         return pif_datapath(pif_get_vlan_slave(pif))
1185     
1186     pifrec = db.get_pif_record(pif)
1187     nwrec = db.get_network_record(pifrec['network'])
1188     if not nwrec['bridge']:
1189         raise Error("datapath PIF cannot be bridgeless")
1190     else:
1191         return pif
1192
1193 def datapath_get_physical_pifs(pif):
1194     """Return the PIFs for the physical network device(s) associated with a datapath PIF.
1195 For a bond master PIF, these are the bond slave PIFs.
1196 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
1197
1198 A VLAN PIF cannot be a datapath PIF.
1199 """
1200     pifrec = db.get_pif_record(pif)
1201
1202     if pif_is_vlan(pif):
1203         raise Error("get-physical-pifs should not get passed a VLAN")
1204     elif len(pifrec['bond_master_of']) != 0:
1205         return pif_get_bond_slaves(pif)
1206     else:
1207         return [pif]
1208
1209 def datapath_deconfigure_physical(netdev):
1210     # The use of [!0-9] keeps an interface of 'eth0' from matching
1211     # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
1212     # interfaces.
1213     return ['--del-match=bridge.*.port=%s' % netdev,
1214             '--del-match=port.%s.[!0-9]*' % netdev,
1215             '--del-match=bonding.*.slave=%s' % netdev,
1216             '--del-match=iface.%s.[!0-9]*' % netdev]
1217
1218 def datapath_configure_bond(pif,slaves):
1219     pifrec = db.get_pif_record(pif)
1220     interface = pif_netdev_name(pif)
1221
1222     argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
1223     argv += ["--add=bonding.%s.slave=%s" % (interface, pif_netdev_name(slave))
1224              for slave in slaves]
1225     argv += ['--add=bonding.%s.fake-iface=true' % interface]
1226
1227     if pifrec['MAC'] != "":
1228         argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
1229
1230     # Bonding options.
1231     bond_options = {
1232         "mode":   "balance-slb",
1233         "miimon": "100",
1234         "downdelay": "200",
1235         "updelay": "31000",
1236         "use_carrier": "1",
1237         }
1238     # override defaults with values from other-config whose keys
1239     # being with "bond-"
1240     oc = pifrec['other_config']
1241     overrides = filter(lambda (key,val):
1242                            key.startswith("bond-"), oc.items())
1243     overrides = map(lambda (key,val): (key[5:], val), overrides)
1244     bond_options.update(overrides)
1245     for (name,val) in bond_options.items():
1246         argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
1247     return argv
1248
1249 def datapath_deconfigure_bond(netdev):
1250     # The use of [!0-9] keeps an interface of 'eth0' from matching
1251     # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
1252     # interfaces.
1253     return ['--del-match=bonding.%s.[!0-9]*' % netdev,
1254             '--del-match=port.%s.[!0-9]*' % netdev]
1255
1256 def datapath_deconfigure_ipdev(interface):
1257     # The use of [!0-9] keeps an interface of 'eth0' from matching
1258     # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
1259     # interfaces.
1260     return ['--del-match=bridge.*.port=%s' % interface,
1261             '--del-match=port.%s.[!0-9]*' % interface,
1262             '--del-match=iface.%s.[!0-9]*' % interface,
1263             '--del-match=vlan.%s.trunks=*' % interface,
1264             '--del-match=vlan.%s.tag=*' % interface]
1265
1266 def datapath_modify_config(commands):
1267     if debug_mode():
1268         log("modifying configuration:")
1269         for c in commands:
1270             log("  %s" % c)
1271
1272     rc = run_command(['/usr/bin/ovs-cfg-mod', '-vANY:console:emer',
1273                  '-F', '/etc/ovs-vswitchd.conf']
1274                 + [c for c in commands if c[0] != '#'] + ['-c'])
1275     if not rc:
1276         raise Error("Failed to modify vswitch configuration")
1277     run_command(['/sbin/service', 'vswitch', 'reload'])
1278     return True
1279
1280 #
1281 # Toplevel Datapath Configuration.
1282 #
1283
1284 def configure_datapath(pif):
1285     """Bring up the datapath configuration for PIF.
1286
1287     Should be careful not to glitch existing users of the datapath, e.g. other VLANs etc.
1288
1289     Should take care of tearing down other PIFs which encompass common physical devices.
1290
1291     Returns a tuple containing
1292     - A list containing the necessary cfgmod command line arguments
1293     - A list of additional devices which should be brought up after
1294       the configuration is applied.    
1295     """
1296
1297     cfgmod_argv = []
1298     extra_up_ports = []
1299
1300     bridge = pif_bridge_name(pif)
1301
1302     physical_devices = datapath_get_physical_pifs(pif)
1303
1304     # Determine additional devices to deconfigure.
1305     #
1306     # Given all physical devices which are part of this PIF we need to
1307     # consider:
1308     # - any additional bond which a physical device is part of.
1309     # - any additional physical devices which are part of an additional bond.
1310     #
1311     # Any of these which are not currently in use should be brought
1312     # down and deconfigured.
1313     extra_down_bonds = []
1314     extra_down_ports = []
1315     for p in physical_devices:
1316         for bond in pif_get_bond_masters(p):
1317             if bond == pif:
1318                 log("configure_datapath: leaving bond %s up" % pif_netdev_name(bond))
1319                 continue
1320             if bond in extra_down_bonds:
1321                 continue
1322             if db.get_pif_record(bond)['currently_attached']:
1323                 log("configure_datapath: implicitly tearing down currently-attached bond %s" % pif_netdev_name(bond))
1324
1325             extra_down_bonds += [bond]
1326
1327             for s in pif_get_bond_slaves(bond):
1328                 if s in physical_devices:
1329                     continue
1330                 if s in extra_down_ports:
1331                     continue
1332                 if pif_currently_in_use(s):
1333                     continue
1334                 extra_down_ports += [s]
1335
1336     log("configure_datapath: bridge      - %s" % bridge)
1337     log("configure_datapath: physical    - %s" % [pif_netdev_name(p) for p in physical_devices])
1338     log("configure_datapath: extra ports - %s" % [pif_netdev_name(p) for p in extra_down_ports])
1339     log("configure_datapath: extra bonds - %s" % [pif_netdev_name(p) for p in extra_down_bonds])
1340
1341     # Need to fully deconfigure any bridge which any of the:
1342     # - physical devices
1343     # - bond devices
1344     # - sibling devices
1345     # refers to
1346     for brpif in physical_devices + extra_down_ports + extra_down_bonds:
1347         if brpif == pif:
1348             continue
1349         b = pif_bridge_name(brpif)
1350         ifdown(b)
1351         cfgmod_argv += ['# remove bridge %s' % b]
1352         cfgmod_argv += ['--del-match=bridge.%s.*' % b]
1353
1354     for n in extra_down_ports:
1355         dev = pif_netdev_name(n)
1356         cfgmod_argv += ['# deconfigure sibling physical device %s' % dev]
1357         cfgmod_argv += datapath_deconfigure_physical(dev)
1358         netdev_down(dev)
1359
1360     for n in extra_down_bonds:
1361         dev = pif_netdev_name(n)
1362         cfgmod_argv += ['# deconfigure bond device %s' % dev]
1363         cfgmod_argv += datapath_deconfigure_bond(dev)
1364         netdev_down(dev)
1365
1366     for p in physical_devices:
1367         dev = pif_netdev_name(p)
1368         cfgmod_argv += ['# deconfigure physical port %s' % dev]
1369         cfgmod_argv += datapath_deconfigure_physical(dev)
1370
1371     # Check the MAC address of each network device and remap if
1372     # necessary to make names match our expectations.
1373     for p in physical_devices:
1374         netdev_remap_name(p)
1375
1376     # Bring up physical devices early, because ovs-vswitchd initially
1377     # enables or disables bond slaves based on whether carrier is
1378     # detected when they are added, and a network device that is down
1379     # always reports "no carrier".
1380     for p in physical_devices:
1381         oc = db.get_pif_record(p)['other_config']
1382
1383         dev = pif_netdev_name(p)
1384
1385         mtu = mtu_setting(oc)
1386
1387         netdev_up(dev, mtu)
1388         
1389         settings, offload = ethtool_settings(oc)
1390         if len(settings):
1391             run_command(['/sbin/ethtool', '-s', dev] + settings)
1392         if len(offload):
1393             run_command(['/sbin/ethtool', '-K', dev] + offload)
1394
1395     if len(physical_devices) > 1:
1396         cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
1397         cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
1398         cfgmod_argv += ['--del-entry=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif))]
1399         cfgmod_argv += ['# configure bond %s' % pif_netdev_name(pif)]
1400         cfgmod_argv += datapath_configure_bond(pif, physical_devices)
1401         cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif)) ]
1402         extra_up_ports += [pif_netdev_name(pif)]
1403     else:
1404         iface = pif_netdev_name(physical_devices[0])
1405         cfgmod_argv += ['# add physical device %s' % iface]
1406         cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,iface) ]
1407
1408     return cfgmod_argv,extra_up_ports
1409
1410 def deconfigure_datapath(pif):
1411     cfgmod_argv = []
1412
1413     bridge = pif_bridge_name(pif)
1414
1415     physical_devices = datapath_get_physical_pifs(pif)
1416
1417     log("deconfigure_datapath: bridge           - %s" % bridge)
1418     log("deconfigure_datapath: physical devices - %s" % [pif_netdev_name(p) for p in physical_devices])
1419
1420     for p in physical_devices:
1421         dev = pif_netdev_name(p)
1422         cfgmod_argv += ['# deconfigure physical port %s' % dev]
1423         cfgmod_argv += datapath_deconfigure_physical(dev)
1424         netdev_down(dev)
1425
1426     if len(physical_devices) > 1:
1427         cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
1428         cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
1429
1430     cfgmod_argv += ['# deconfigure bridge %s' % bridge]
1431     cfgmod_argv += ['--del-match=bridge.%s.*' % bridge]
1432     
1433     return cfgmod_argv
1434
1435 #
1436 # Toplevel actions
1437 #
1438
1439 def action_up(pif):
1440     pifrec = db.get_pif_record(pif)
1441     cfgmod_argv = []
1442     extra_ports = []
1443
1444     ipdev = pif_ipdev_name(pif)
1445     dp = pif_datapath(pif)
1446     bridge = pif_bridge_name(dp)
1447
1448     log("action_up: %s on bridge %s" % (ipdev, bridge))
1449     
1450     ifdown(ipdev)
1451
1452     if dp:
1453         c,e = configure_datapath(dp)
1454         cfgmod_argv += c
1455         extra_ports += e
1456
1457         cfgmod_argv += ['# configure xs-network-uuids']
1458         cfgmod_argv += ['--del-match=bridge.%s.xs-network-uuids=*' % bridge]
1459
1460         for nwpif in db.get_pifs_by_device(db.get_pif_record(pif)['device']):
1461             rec = db.get_pif_record(nwpif)
1462             
1463             # When state is read from dbcache PIF.currently_attached
1464             # is always assumed to be false... Err on the side of
1465             # listing even detached networks for the time being.
1466             #if nwpif != pif and not rec['currently_attached']:
1467             #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
1468             #    continue
1469             nwrec = db.get_network_record(rec['network'])
1470             cfgmod_argv += ['--add=bridge.%s.xs-network-uuids=%s' % (bridge, nwrec['uuid'])]
1471
1472         cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
1473         cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
1474         cfgmod_argv += ["# reconfigure ipdev %s" % ipdev]
1475         cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge, ipdev)]
1476
1477     f = ipdev_configure_network(pif)
1478     f.close()
1479
1480     # /etc/xensource/scripts/vif needs to know where to add VIFs.
1481     if pif_is_vlan(pif):
1482         if not bridge:
1483             raise Error("Unbridged VLAN devices not implemented yet")
1484         cfgmod_argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
1485         cfgmod_argv += ['--add=iface.%s.internal=true' % (ipdev)]
1486         cfgmod_argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
1487         
1488     # Apply updated configuration.
1489     try:
1490         f.apply()
1491
1492         datapath_modify_config(cfgmod_argv)
1493
1494         ifup(ipdev)
1495
1496         for p in extra_ports:
1497             netdev_up(p)
1498
1499         # Update /etc/issue (which contains the IP address of the management interface)
1500         os.system("/sbin/update-issue")
1501
1502         f.commit()
1503     except Error, e:
1504         log("failed to apply changes: %s" % e.msg)
1505         f.revert()
1506         raise
1507
1508 def action_down(pif):
1509     pifrec = db.get_pif_record(pif)
1510     cfgmod_argv = []
1511
1512     ipdev = pif_ipdev_name(pif)
1513     dp = pif_datapath(pif)
1514     bridge = pif_bridge_name(dp)
1515     
1516     log("action_down: %s on bridge %s" % (ipdev, bridge))
1517
1518     ifdown(ipdev)
1519
1520     if dp:
1521         nw = db.get_pif_record(pif)['network']
1522         nwrec = db.get_network_record(nw)
1523         cfgmod_argv += ['# deconfigure xs-network-uuids']
1524         cfgmod_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
1525
1526         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
1527         cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
1528         cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
1529
1530     f = ipdev_open_ifcfg(pif)
1531     f.unlink()
1532
1533     if pif_is_vlan(pif):
1534         br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir)
1535         br.unlink()
1536         f.attach_child(br)
1537
1538         # If the VLAN's slave is attached, leave datapath setup.
1539         slave = pif_get_vlan_slave(pif)
1540         if db.get_pif_record(slave)['currently_attached']:
1541             log("action_down: vlan slave is currently attached")
1542             dp = None
1543
1544         # If the VLAN's slave has other VLANs that are attached, leave datapath setup.
1545         for master in pif_get_vlan_masters(slave):
1546             if master != pif and db.get_pif_record(master)['currently_attached']:
1547                 log("action_down: vlan slave has other master: %s" % pif_netdev_name(master))
1548                 dp = None
1549
1550         # Otherwise, take down the datapath too (fall through)
1551         if dp:
1552             log("action_down: no more masters, bring down slave %s" % bridge)
1553     else:
1554         # Stop here if this PIF has attached VLAN masters.
1555         masters = [db.get_pif_record(m)['VLAN'] for m in pif_get_vlan_masters(pif) if db.get_pif_record(m)['currently_attached']]
1556         if len(masters) > 0:
1557             log("Leaving datapath %s up due to currently attached VLAN masters %s" % (bridge, masters))
1558             dp = None
1559
1560     if dp:
1561         cfgmod_argv += deconfigure_datapath(dp)
1562
1563     try:
1564         f.apply()
1565
1566         datapath_modify_config(cfgmod_argv)
1567
1568         f.commit()
1569     except Error, e:
1570         log("action_down failed to apply changes: %s" % e.msg)
1571         f.revert()
1572         raise
1573
1574 def action_rewrite(pif):
1575     f = ipdev_configure_network(pif)
1576     f.close()
1577     try:
1578         f.apply()
1579         f.commit()
1580     except Error, e:
1581         log("failed to apply changes: %s" % e.msg)
1582         f.revert()
1583         raise
1584
1585 def action_force_rewrite(bridge, config):
1586     raise Error("Force rewrite is not implemented yet.")
1587
1588 def main(argv=None):
1589     global output_directory, management_pif
1590
1591     session = None
1592     pif_uuid = None
1593     pif = None
1594
1595     force_interface = None
1596     force_management = False
1597
1598     if argv is None:
1599         argv = sys.argv
1600
1601     try:
1602         try:
1603             shortops = "h"
1604             longops = [ "output-directory=",
1605                         "pif=", "pif-uuid=",
1606                         "session=",
1607                         "force=",
1608                         "force-interface=",
1609                         "management",
1610                         "device=", "mode=", "ip=", "netmask=", "gateway=",
1611                         "help" ]
1612             arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1613         except getopt.GetoptError, msg:
1614             raise Usage(msg)
1615
1616         force_rewrite_config = {}
1617
1618         for o,a in arglist:
1619             if o == "--output-directory":
1620                 output_directory = a
1621             elif o == "--pif":
1622                 pif = a
1623             elif o == "--pif-uuid":
1624                 pif_uuid = a
1625             elif o == "--session":
1626                 session = a
1627             elif o == "--force-interface" or o == "--force":
1628                 force_interface = a
1629             elif o == "--management":
1630                 force_management = True
1631             elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1632                 force_rewrite_config[o[2:]] = a
1633             elif o == "-h" or o == "--help":
1634                 print __doc__ % {'command-name': os.path.basename(argv[0])}
1635                 return 0
1636
1637         if not debug_mode():
1638             syslog.openlog(os.path.basename(argv[0]))
1639             log("Called as " + str.join(" ", argv))
1640         if len(args) < 1:
1641             raise Usage("Required option <action> not present")
1642         if len(args) > 1:
1643             raise Usage("Too many arguments")
1644
1645         action = args[0]
1646
1647         if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
1648             raise Usage("Unknown action \"%s\"" % action)
1649
1650         # backwards compatibility
1651         if action == "rewrite-configuration": action = "rewrite"
1652
1653         if output_directory and ( session or pif ):
1654             raise Usage("--session/--pif cannot be used with --output-directory")
1655         if ( session or pif ) and pif_uuid:
1656             raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1657         if ( session and not pif ) or ( not session and pif ):
1658             raise Usage("--session and --pif must be used together.")
1659         if force_interface and ( session or pif or pif_uuid ):
1660             raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1661         if force_interface == "all" and action != "down":
1662             raise Usage("\"--force all\" only valid for down action")
1663         if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1664             raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1665
1666         global db
1667         if force_interface:
1668             log("Force interface %s %s" % (force_interface, action))
1669
1670             if action == "rewrite":
1671                 action_force_rewrite(force_interface, force_rewrite_config)
1672             elif action in ["up", "down"]:
1673                 if action == "down" and force_interface == "all":
1674                     raise Error("Force all interfaces down not implemented yet")
1675
1676                 db = DatabaseCache(cache_file=dbcache_file)
1677                 pif = db.get_pif_by_bridge(force_interface)
1678                 management_pif = db.get_management_pif()
1679
1680                 if action == "up":
1681                     action_up(pif)
1682                 elif action == "down":
1683                     action_down(pif)
1684             else:
1685                 raise Error("Unknown action %s"  % action)
1686         else:
1687             db = DatabaseCache(session_ref=session)
1688
1689             if pif_uuid:
1690                 pif = db.get_pif_by_uuid(pif_uuid)
1691
1692             if action == "rewrite" and not pif:
1693                 pass
1694             else:
1695                 if not pif:
1696                     raise Usage("No PIF given")
1697
1698                 if force_management:
1699                     # pif is going to be the management pif
1700                     management_pif = pif
1701                 else:
1702                     # pif is not going to be the management pif.
1703                     # Search DB cache for pif on same host with management=true
1704                     pifrec = db.get_pif_record(pif)
1705                     management_pif = db.get_management_pif()
1706
1707                 log_pif_action(action, pif)
1708
1709                 if not check_allowed(pif):
1710                     return 0
1711
1712                 if action == "up":
1713                     action_up(pif)
1714                 elif action == "down":
1715                     action_down(pif)
1716                 elif action == "rewrite":
1717                     action_rewrite(pif)
1718                 else:
1719                     raise Error("Unknown action %s"  % action)
1720
1721             # Save cache.
1722             db.save(dbcache_file)
1723
1724     except Usage, err:
1725         print >>sys.stderr, err.msg
1726         print >>sys.stderr, "For help use --help."
1727         return 2
1728     except Error, err:
1729         log(err.msg)
1730         return 1
1731
1732     return 0
1733
1734 if __name__ == "__main__":
1735     rc = 1
1736     try:
1737         rc = main()
1738     except:
1739         ex = sys.exc_info()
1740         err = traceback.format_exception(*ex)
1741         for exline in err:
1742             log(exline)
1743
1744     if not debug_mode():
1745         syslog.closelog()
1746
1747     sys.exit(rc)