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