CP-1592: interface-reconfigure: Configure network device MTU from Network.MTU field
[cascardo/ovs.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2008,2009 Citrix Systems, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; version 2.1 only. with the special
8 # exception on linking described in file LICENSE.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 """Usage:
16
17     %(command-name)s <PIF> up
18     %(command-name)s <PIF> down
19     %(command-name)s rewrite
20     %(command-name)s --force <BRIDGE> up
21     %(command-name)s --force <BRIDGE> down
22     %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> --mac=<MAC-ADDRESS> <CONFIG>
23
24     where <PIF> is one of:
25        --session <SESSION-REF> --pif <PIF-REF>
26        --pif-uuid <PIF-UUID>
27     and <CONFIG> is one of:
28        --mode=dhcp
29        --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
30
31   Options:
32     --session           A session reference to use to access the xapi DB
33     --pif               A PIF reference within the session.
34     --pif-uuid          The UUID of a PIF.
35     --force             An interface name.
36     --root-prefix=DIR   Use DIR as alternate root directory (for testing).
37     --no-syslog         Write log messages to stderr instead of system log.
38 """
39
40 # Notes:
41 # 1. Every pif belongs to exactly one network
42 # 2. Every network has zero or one pifs
43 # 3. A network may have an associated bridge, allowing vifs to be attached
44 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
45
46 from InterfaceReconfigure import *
47
48 import os, sys, getopt
49 import syslog
50 import traceback
51 import re
52 import random
53
54 management_pif = None
55
56 dbcache_file = "/var/xapi/network.dbcache"
57
58 #
59 # Logging.
60 #
61
62 def log_pif_action(action, pif):
63     pifrec = db().get_pif_record(pif)
64     rec = {}
65     rec['uuid'] = pifrec['uuid']
66     rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
67     rec['action'] = action
68     rec['pif_netdev_name'] = pif_netdev_name(pif)
69     rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
70     log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
71
72 #
73 # Exceptions.
74 #
75
76 class Usage(Exception):
77     def __init__(self, msg):
78         Exception.__init__(self)
79         self.msg = msg
80
81 #
82 # Boot from Network filesystem or device.
83 #
84
85 def check_allowed(pif):
86     """Determine whether interface-reconfigure should be manipulating this PIF.
87
88     Used to prevent system PIFs (such as network root disk) from being interfered with.
89     """
90
91     pifrec = db().get_pif_record(pif)
92     try:
93         f = open(root_prefix() + "/proc/ardence")
94         macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
95         f.close()
96         if len(macline) == 1:
97             p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
98             if p.match(macline[0]):
99                 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
100                 return False
101     except IOError:
102         pass
103     return True
104
105 #
106 # Bare Network Devices -- network devices without IP configuration
107 #
108
109 def netdev_remap_name(pif, already_renamed=[]):
110     """Check whether 'pif' exists and has the correct MAC.
111     If not, try to find a device with the correct MAC and rename it.
112     'already_renamed' is used to avoid infinite recursion.
113     """
114
115     def read1(name):
116         file = None
117         try:
118             file = open(name, 'r')
119             return file.readline().rstrip('\n')
120         finally:
121             if file != None:
122                 file.close()
123
124     def get_netdev_mac(device):
125         try:
126             return read1("%s/sys/class/net/%s/address" % (root_prefix(), device))
127         except:
128             # Probably no such device.
129             return None
130
131     def get_netdev_tx_queue_len(device):
132         try:
133             return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device)))
134         except:
135             # Probably no such device.
136             return None
137
138     def get_netdev_by_mac(mac):
139         for device in os.listdir(root_prefix() + "/sys/class/net"):
140             dev_mac = get_netdev_mac(device)
141             if (dev_mac and mac.lower() == dev_mac.lower() and
142                 get_netdev_tx_queue_len(device)):
143                 return device
144         return None
145
146     def rename_netdev(old_name, new_name):
147         log("Changing the name of %s to %s" % (old_name, new_name))
148         run_command(['/sbin/ifconfig', old_name, 'down'])
149         if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
150             raise Error("Could not rename %s to %s" % (old_name, new_name))
151
152     pifrec = db().get_pif_record(pif)
153     device = pifrec['device']
154     mac = pifrec['MAC']
155
156     # Is there a network device named 'device' at all?
157     device_exists = netdev_exists(device)
158     if device_exists:
159         # Yes.  Does it have MAC 'mac'?
160         found_mac = get_netdev_mac(device)
161         if found_mac and mac.lower() == found_mac.lower():
162             # Yes, everything checks out the way we want.  Nothing to do.
163             return
164     else:
165         log("No network device %s" % device)
166
167     # What device has MAC 'mac'?
168     cur_device = get_netdev_by_mac(mac)
169     if not cur_device:
170         log("No network device has MAC %s" % mac)
171         return
172
173     # First rename 'device', if it exists, to get it out of the way
174     # for 'cur_device' to replace it.
175     if device_exists:
176         rename_netdev(device, "dev%d" % random.getrandbits(24))
177
178     # Rename 'cur_device' to 'device'.
179     rename_netdev(cur_device, device)
180
181 #
182 # IP Network Devices -- network devices with IP configuration
183 #
184
185 def ifdown(netdev):
186     """Bring down a network interface"""
187     if not netdev_exists(netdev):
188         log("ifdown: device %s does not exist, ignoring" % netdev)
189         return
190     if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)):
191         log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
192         run_command(["/sbin/ifconfig", netdev, 'down'])
193         return
194     run_command(["/sbin/ifdown", netdev])
195
196 def ifup(netdev):
197     """Bring up a network interface"""
198     if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
199         raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
200     run_command(["/sbin/ifup", netdev])
201
202 #
203 #
204 #
205
206 def pif_rename_physical_devices(pif):
207
208     if pif_is_vlan(pif):
209         pif = pif_get_vlan_slave(pif)
210
211     if pif_is_bond(pif):
212         pifs = pif_get_bond_slaves(pif)
213     else:
214         pifs = [pif]
215
216     for pif in pifs:
217         netdev_remap_name(pif)
218
219 #
220 # IP device configuration
221 #
222
223 def ipdev_configure_static_routes(interface, oc, f):
224     """Open a route-<interface> file for static routes.
225
226     Opens the static routes configuration file for interface and writes one
227     line for each route specified in the network's other config "static-routes" value.
228     E.g. if
229            interface ( RO): xenbr1
230            other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
231
232     Then route-xenbr1 should be
233           172.16.0.0/15 via 192.168.0.3 dev xenbr1
234           172.18.0.0/16 via 192.168.0.4 dev xenbr1
235     """
236     if oc.has_key('static-routes'):
237         # The key is present - extract comma seperates entries
238         lines = oc['static-routes'].split(',')
239     else:
240         # The key is not present, i.e. there are no static routes
241         lines = []
242
243     child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
244     child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
245             (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
246
247     try:
248         for l in lines:
249             network, masklen, gateway = l.split('/')
250             child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
251
252         f.attach_child(child)
253         child.close()
254
255     except ValueError, e:
256         log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
257
258 def ipdev_open_ifcfg(pif):
259     ipdev = pif_ipdev_name(pif)
260
261     log("Writing network configuration for %s" % ipdev)
262
263     f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
264
265     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
266             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
267     f.write("XEMANAGED=yes\n")
268     f.write("DEVICE=%s\n" % ipdev)
269     f.write("ONBOOT=no\n")
270
271     return f
272
273 def ipdev_configure_network(pif, dp):
274     """Write the configuration file for a network.
275
276     Writes configuration derived from the network object into the relevant
277     ifcfg file.  The configuration file is passed in, but if the network is
278     bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
279
280     This routine may also write ifcfg files of the networks corresponding to other PIFs
281     in order to maintain consistency.
282
283     params:
284         pif:  Opaque_ref of pif
285         dp:   Datapath object
286     """
287
288     pifrec = db().get_pif_record(pif)
289     nw = pifrec['network']
290     nwrec = db().get_network_record(nw)
291
292     ipdev = pif_ipdev_name(pif)
293
294     f = ipdev_open_ifcfg(pif)
295
296     mode = pifrec['ip_configuration_mode']
297     log("Configuring %s using %s configuration" % (ipdev, mode))
298
299     oc = None
300     if pifrec.has_key('other_config'):
301         oc = pifrec['other_config']
302
303     dp.configure_ipdev(f)
304
305     if pifrec['ip_configuration_mode'] == "DHCP":
306         f.write("BOOTPROTO=dhcp\n")
307         f.write("PERSISTENT_DHCLIENT=yes\n")
308     elif pifrec['ip_configuration_mode'] == "Static":
309         f.write("BOOTPROTO=none\n")
310         f.write("NETMASK=%(netmask)s\n" % pifrec)
311         f.write("IPADDR=%(IP)s\n" % pifrec)
312         f.write("GATEWAY=%(gateway)s\n" % pifrec)
313     elif pifrec['ip_configuration_mode'] == "None":
314         f.write("BOOTPROTO=none\n")
315     else:
316         raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
317
318     if nwrec.has_key('other_config'):
319         settings,offload = ethtool_settings(nwrec['other_config'])
320         if len(settings):
321             f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
322         if len(offload):
323             f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
324
325         ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
326
327     mtu = mtu_setting(nw, "Network", nwrec['other_config'])
328     if mtu:
329         f.write("MTU=%s\n" % mtu)
330
331
332     if pifrec.has_key('DNS') and pifrec['DNS'] != "":
333         ServerList = pifrec['DNS'].split(",")
334         for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
335     if oc and oc.has_key('domain'):
336         f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
337
338     # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
339     #
340     # The peerdns pif will be the one with
341     # pif::other-config:peerdns=true, or the mgmt pif if none have
342     # this set.
343     #
344     # The gateway pif will be the one with
345     # pif::other-config:defaultroute=true, or the mgmt pif if none
346     # have this set.
347
348     # Work out which pif on this host should be the DNSDEV and which
349     # should be the GATEWAYDEV
350     #
351     # Note: we prune out the bond master pif (if it exists). This is
352     # because when we are called to bring up an interface with a bond
353     # master, it is implicit that we should bring down that master.
354
355     pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
356
357     # loop through all the pifs on this host looking for one with
358     #   other-config:peerdns = true, and one with
359     #   other-config:default-route=true
360     peerdns_pif = None
361     defaultroute_pif = None
362     for __pif in pifs_on_host:
363         __pifrec = db().get_pif_record(__pif)
364         __oc = __pifrec['other_config']
365         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
366             if peerdns_pif == None:
367                 peerdns_pif = __pif
368             else:
369                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
370                         (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
371         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
372             if defaultroute_pif == None:
373                 defaultroute_pif = __pif
374             else:
375                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
376                         (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
377
378     # If no pif is explicitly specified then use the mgmt pif for
379     # peerdns/defaultroute.
380     if peerdns_pif == None:
381         peerdns_pif = management_pif
382     if defaultroute_pif == None:
383         defaultroute_pif = management_pif
384
385     is_dnsdev = peerdns_pif == pif
386     is_gatewaydev = defaultroute_pif == pif
387
388     if is_dnsdev or is_gatewaydev:
389         fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
390         for line in fnetwork.readlines():
391             if is_dnsdev and line.lstrip().startswith('DNSDEV='):
392                 fnetwork.write('DNSDEV=%s\n' % ipdev)
393                 is_dnsdev = False
394             elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
395                 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
396                 is_gatewaydev = False
397             else:
398                 fnetwork.write(line)
399
400         if is_dnsdev:
401             fnetwork.write('DNSDEV=%s\n' % ipdev)
402         if is_gatewaydev:
403             fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
404
405         fnetwork.close()
406         f.attach_child(fnetwork)
407
408     return f
409
410 #
411 # Toplevel actions
412 #
413
414 def action_up(pif, force):
415     pifrec = db().get_pif_record(pif)
416
417     ipdev = pif_ipdev_name(pif)
418     dp = DatapathFactory(pif)
419
420     log("action_up: %s" % ipdev)
421
422     f = ipdev_configure_network(pif, dp)
423
424     dp.preconfigure(f)
425
426     f.close()
427
428     pif_rename_physical_devices(pif)
429
430     # if we are not forcing the interface up then attempt to tear down
431     # any existing devices which might interfere with brinign this one
432     # up.
433     if not force:
434         ifdown(ipdev)
435
436         dp.bring_down_existing()
437
438     try:
439         f.apply()
440
441         dp.configure()
442
443         ifup(ipdev)
444
445         dp.post()
446
447         # Update /etc/issue (which contains the IP address of the management interface)
448         os.system(root_prefix() + "/sbin/update-issue")
449
450         f.commit()
451     except Error, e:
452         log("failed to apply changes: %s" % e.msg)
453         f.revert()
454         raise
455
456 def action_down(pif):
457     ipdev = pif_ipdev_name(pif)
458     dp = DatapathFactory(pif)
459
460     log("action_down: %s" % ipdev)
461
462     ifdown(ipdev)
463
464     dp.bring_down()
465
466 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
467 def action_force_rewrite(bridge, config):
468     def getUUID():
469         import subprocess
470         uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
471         return uuid.strip()
472
473     # Notes:
474     # 1. that this assumes the interface is bridged
475     # 2. If --gateway is given it will make that the default gateway for the host
476
477     # extract the configuration
478     try:
479         mode = config['mode']
480         mac = config['mac']
481         interface = config['device']
482     except:
483         raise Usage("Please supply --mode, --mac and --device")
484
485     if mode == 'static':
486         try:
487             netmask = config['netmask']
488             ip = config['ip']
489         except:
490             raise Usage("Please supply --netmask and --ip")
491         try:
492             gateway = config['gateway']
493         except:
494             gateway = None
495     elif mode != 'dhcp':
496         raise Usage("--mode must be either static or dhcp")
497
498     if config.has_key('vlan'):
499         is_vlan = True
500         vlan_slave, vlan_vid = config['vlan'].split('.')
501     else:
502         is_vlan = False
503
504     if is_vlan:
505         raise Error("Force rewrite of VLAN not implemented")
506
507     log("Configuring %s using %s configuration" % (bridge, mode))
508
509     f = ConfigurationFile(root_prefix() + dbcache_file)
510
511     pif_uuid = getUUID()
512     network_uuid = getUUID()
513
514     f.write('<?xml version="1.0" ?>\n')
515     f.write('<xenserver-network-configuration>\n')
516     f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
517     f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
518     f.write('\t\t<management>True</management>\n')
519     f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
520     f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
521     f.write('\t\t<bond_master_of/>\n')
522     f.write('\t\t<VLAN_slave_of/>\n')
523     f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
524     f.write('\t\t<VLAN>-1</VLAN>\n')
525     f.write('\t\t<device>%s</device>\n' % interface)
526     f.write('\t\t<MAC>%s</MAC>\n' % mac)
527     f.write('\t\t<other_config/>\n')
528     if mode == 'dhcp':
529         f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
530         f.write('\t\t<IP></IP>\n')
531         f.write('\t\t<netmask></netmask>\n')
532         f.write('\t\t<gateway></gateway>\n')
533         f.write('\t\t<DNS></DNS>\n')
534     elif mode == 'static':
535         f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
536         f.write('\t\t<IP>%s</IP>\n' % ip)
537         f.write('\t\t<netmask>%s</netmask>\n' % netmask)
538         if gateway is not None:
539             f.write('\t\t<gateway>%s</gateway>\n' % gateway)
540         f.write('\t\t<DNS></DNS>\n')
541     else:
542         raise Error("Unknown mode %s" % mode)
543     f.write('\t</pif>\n')
544
545     f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
546     f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
547     f.write('\t\t<PIFs>\n')
548     f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
549     f.write('\t\t</PIFs>\n')
550     f.write('\t\t<bridge>%s</bridge>\n' % bridge)
551     f.write('\t\t<other_config/>\n')
552     f.write('\t</network>\n')
553     f.write('</xenserver-network-configuration>\n')
554
555     f.close()
556
557     try:
558         f.apply()
559         f.commit()
560     except Error, e:
561         log("failed to apply changes: %s" % e.msg)
562         f.revert()
563         raise
564
565 def main(argv=None):
566     global management_pif
567
568     session = None
569     pif_uuid = None
570     pif = None
571
572     force_interface = None
573     force_management = False
574
575     if argv is None:
576         argv = sys.argv
577
578     try:
579         try:
580             shortops = "h"
581             longops = [ "pif=", "pif-uuid=",
582                         "session=",
583                         "force=",
584                         "force-interface=",
585                         "management",
586                         "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
587                         "root-prefix=",
588                         "no-syslog",
589                         "help" ]
590             arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
591         except getopt.GetoptError, msg:
592             raise Usage(msg)
593
594         force_rewrite_config = {}
595
596         for o,a in arglist:
597             if o == "--pif":
598                 pif = a
599             elif o == "--pif-uuid":
600                 pif_uuid = a
601             elif o == "--session":
602                 session = a
603             elif o == "--force-interface" or o == "--force":
604                 force_interface = a
605             elif o == "--management":
606                 force_management = True
607             elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
608                 force_rewrite_config[o[2:]] = a
609             elif o == "--root-prefix":
610                 set_root_prefix(a)
611             elif o == "--no-syslog":
612                 set_log_destination("stderr")
613             elif o == "-h" or o == "--help":
614                 print __doc__ % {'command-name': os.path.basename(argv[0])}
615                 return 0
616
617         if get_log_destination() == "syslog":
618             syslog.openlog(os.path.basename(argv[0]))
619             log("Called as " + str.join(" ", argv))
620
621         if len(args) < 1:
622             raise Usage("Required option <action> not present")
623         if len(args) > 1:
624             raise Usage("Too many arguments")
625
626         action = args[0]
627
628         if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
629             raise Usage("Unknown action \"%s\"" % action)
630
631         # backwards compatibility
632         if action == "rewrite-configuration": action = "rewrite"
633
634         if ( session or pif ) and pif_uuid:
635             raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
636         if ( session and not pif ) or ( not session and pif ):
637             raise Usage("--session and --pif must be used together.")
638         if force_interface and ( session or pif or pif_uuid ):
639             raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
640         if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
641             raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
642         if (action == "rewrite") and (pif or pif_uuid ):
643             raise Usage("rewrite action does not take --pif or --pif-uuid")
644         
645         global db
646         if force_interface:
647             log("Force interface %s %s" % (force_interface, action))
648
649             if action == "rewrite":
650                 action_force_rewrite(force_interface, force_rewrite_config)
651             elif action in ["up", "down"]:
652                 db_init_from_cache(dbcache_file)
653                 pif = db().get_pif_by_bridge(force_interface)
654                 management_pif = db().get_management_pif()
655
656                 if action == "up":
657                     action_up(pif, True)
658                 elif action == "down":
659                     action_down(pif)
660             else:
661                 raise Error("Unknown action %s"  % action)
662         else:
663             db_init_from_xenapi(session)
664
665             if pif_uuid:
666                 pif = db().get_pif_by_uuid(pif_uuid)
667
668             if action == "rewrite":
669                 pass
670             else:
671                 if not pif:
672                     raise Usage("No PIF given")
673
674                 if force_management:
675                     # pif is going to be the management pif
676                     management_pif = pif
677                 else:
678                     # pif is not going to be the management pif.
679                     # Search DB cache for pif on same host with management=true
680                     pifrec = db().get_pif_record(pif)
681                     management_pif = db().get_management_pif()
682
683                 log_pif_action(action, pif)
684
685                 if not check_allowed(pif):
686                     return 0
687
688                 if action == "up":
689                     action_up(pif, False)
690                 elif action == "down":
691                     action_down(pif)
692                 else:
693                     raise Error("Unknown action %s"  % action)
694
695             # Save cache.
696             db().save(dbcache_file)
697
698     except Usage, err:
699         print >>sys.stderr, err.msg
700         print >>sys.stderr, "For help use --help."
701         return 2
702     except Error, err:
703         log(err.msg)
704         return 1
705
706     return 0
707
708 if __name__ == "__main__":
709     rc = 1
710     try:
711         rc = main()
712     except:
713         ex = sys.exc_info()
714         err = traceback.format_exception(*ex)
715         for exline in err:
716             log(exline)
717
718     syslog.closelog()
719
720     sys.exit(rc)