ovs-vsctl: Log changes to configuration file to syslog.
[sliver-openvswitch.git] / utilities / ovs-vsctl.in
1 #! @PYTHON@
2 # Copyright (c) 2009 Nicira Networks.                       -*- python -*-
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import errno
17 import fcntl
18 import fnmatch
19 import getopt
20 import os
21 import re
22 import socket
23 import stat
24 import sys
25 import syslog
26
27 argv0 = sys.argv[0]
28 if argv0.find('/') >= 0:
29     argv0 = argv0[argv0.rfind('/') + 1:]
30
31 DEFAULT_VSWITCHD_CONF = "@sysconfdir@/ovs-vswitchd.conf"
32 VSWITCHD_CONF = DEFAULT_VSWITCHD_CONF
33
34 DEFAULT_VSWITCHD_TARGET = "@RUNDIR@/ovs-vswitchd.pid"
35 VSWITCHD_TARGET = DEFAULT_VSWITCHD_TARGET
36
37 RELOAD_VSWITCHD = True
38
39 SYSLOG = True
40
41 class Error(Exception):
42     def __init__(self, msg):
43         Exception.__init__(self)
44         self.msg = msg
45
46 def log(message):
47     if SYSLOG:
48         syslog.syslog(message)
49
50 # XXX Most of the functions below should be integrated into a
51 # VSwitchConfiguration object with logically named fields and methods
52 # instead of this mishmash of functionality.
53
54 # Locks 'filename' for writing.
55 def cfg_lock(filename):
56     if filename == '-':
57         return
58
59     if '/' in filename:
60         lastSlash = filename.rfind('/')
61         prefix = filename[:lastSlash]
62         suffix = filename[lastSlash + 1:]
63         lock_name = "%s/.%s.~lock~" % (prefix, suffix)
64     else:
65         lock_name = ".%s.~lock~" % filename
66
67     while True:
68         # Try to open an existing lock file.
69         try:
70             f = open(lock_name, 'r')
71         except IOError, e:
72             if e.errno != errno.ENOENT:
73                 raise
74
75             # Try to create a new lock file.
76             try:
77                 fd = os.open(lock_name, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0600)
78             except OSError, e:
79                 if e.errno != errno.EEXIST:
80                     raise
81                 # Someone else created the lock file, try again.
82             os.close(fd)
83             continue
84     
85         fcntl.flock(f, fcntl.LOCK_EX)
86         return
87
88 # Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
89 # dictionary that maps from string keys to lists of string values.  (Even
90 # singleton values are represented as lists.)
91 def cfg_read(filename, lock=False):
92     if lock:
93         cfg_lock(filename)
94
95     try:
96         if filename == '-':
97             f = open('/dev/stdin')
98         else:
99             f = open(filename)
100     except IOError, e:
101         sys.stderr.write("%s: could not open %s (%s)\n"
102                          % (argv0, filename, e.strerror))
103         sys.exit(1)
104
105     cfg = {}
106     rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
107     for line in f:
108         line = line.strip()
109         if len(line) == 0 or line[0] == '#':
110             continue
111
112         match = rx.match(line)
113         if match == None:
114             continue
115
116         key, value = match.groups()
117         if key not in cfg:
118             cfg[key] = []
119         cfg[key].append(value)
120
121     global orig_cfg
122     orig_cfg = cfg_clone(cfg)
123
124     return cfg
125
126 # Returns a deep copy of 'cfg', which must be in the format returned
127 # by cfg_read().
128 def cfg_clone(cfg):
129     new = {}
130     for key in cfg:
131         new[key] = list(cfg[key])
132     return new
133
134 # Returns a list of all the configuration lines that are in 'a' but
135 # not in 'b'.
136 def cfg_subtract(a, b):
137     difference = []
138     for key in a:
139         for value in a[key]:
140             if key not in b or value not in b[key]:
141                 difference.append("%s=%s" % (key, value))
142     return difference
143
144 def do_cfg_save(cfg, file):
145     # Log changes.
146     added = cfg_subtract(cfg, orig_cfg)
147     removed = cfg_subtract(orig_cfg, cfg)
148     if added or removed:
149         log("configuration changes:")
150         for line in removed:
151             log("-%s\n" % line)
152         for line in added:
153             log("+%s\n" % line)
154
155     # Write changes.
156     for key in sorted(cfg.keys()):
157         for value in sorted(cfg[key]):
158             file.write("%s=%s\n" % (key, value))
159
160 def cfg_reload():
161     target = VSWITCHD_TARGET
162     s = os.stat(target)
163     if stat.S_ISREG(s.st_mode):
164         pid = read_first_line_of_file(target)
165         target = "@RUNDIR@/ovs-vswitchd.%s.ctl" % pid
166         s = os.stat(target)
167     if not stat.S_ISSOCK(s.st_mode):
168         raise Error("%s is not a Unix domain socket, cannot reload" % target)
169     skt = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
170     skt.connect(target)
171     f = os.fdopen(skt.fileno(), "r+")
172     f.write("vswitchd/reload\n")
173     f.flush()
174     f.readline()
175     f.close()
176
177 def cfg_save(cfg, filename):
178     if filename == '-':
179         do_cfg_save(cfg, sys.stdout)
180     else:
181         tmp_name = filename + ".~tmp~"
182         f = open(tmp_name, 'w')
183         do_cfg_save(cfg, f)
184         f.close()
185         os.rename(tmp_name, filename)
186         if RELOAD_VSWITCHD:
187             cfg_reload()
188
189 # Returns a set of the immediate subsections of 'section' within 'cfg'.  For
190 # example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
191 # and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
192 def cfg_get_subsections(cfg, section):
193     subsections = set()
194     for key in cfg:
195         if key.startswith(section + "."):
196             dot = key.find(".", len(section) + 1)
197             if dot == -1:
198                 dot = len(key)
199             subsections.add(key[len(section) + 1:dot])
200     return subsections
201
202 # Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
203 # returns False.
204 def cfg_get_bool(cfg, name):
205     return name in cfg and cfg[name] == ['true']
206
207 # If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
208 # that VLAN number.  Otherwise, returns 0.
209 def get_port_vlan(cfg, port):
210     try:
211         return int(cfg["vlan.%s.tag" % port][0])
212     except (ValueError, KeyError):
213         return 0
214
215 # Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
216 # the ports returned are only those configured with implicit VLAN 'vlan'.
217 def get_bridge_ports(cfg, bridge, vlan):
218     ports = []
219     for port in cfg["bridge.%s.port" % bridge]:
220         if vlan < 0 or get_port_vlan(cfg, port) == vlan:
221             ports.append(port)
222     return ports
223
224 # Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
225 # nonnegative, the interfaces returned are only those whose ports are
226 # configured with implicit VLAN 'vlan'.
227 def get_bridge_ifaces(cfg, bridge, vlan):
228     ifaces = []
229     for port in get_bridge_ports(cfg, bridge, vlan):
230         ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
231     return ifaces
232
233 # Returns the first line of the file named 'name', with the trailing new-line
234 # (if any) stripped off.
235 def read_first_line_of_file(name):
236     file = None
237     try:
238         file = open(name, 'r')
239         return file.readline().rstrip('\n')
240     finally:
241         if file != None:
242             file.close()
243
244 # Returns a bridge ID constructed from the MAC address of network device
245 # 'netdev', in the format "8000.000102030405".
246 def get_bridge_id(netdev):
247     try:
248         hwaddr = read_first_line_of_file("/sys/class/net/%s/address" % netdev)
249         return "8000.%s" % (hwaddr.replace(":", ""))
250     except:
251         return "8000.002320ffffff"
252
253 # Returns a list of 3-tuples based on 'cfg'.  Each 3-tuple represents
254 # one real bridge or one fake bridge and has the form (bridge, parent,
255 # vlan), where 'bridge' is the real or fake bridge name, 'parent' is
256 # the same as 'bridge' for a real bridge or the name of the containing
257 # bridge for a fake bridge, and 'vlan' is 0 for a real bridge or a
258 # VLAN number for a fake bridge.
259 def get_bridge_info(cfg):
260     real_bridges = [(br, br, 0) for br in get_real_bridges(cfg)]
261     fake_bridges = []
262     for linux_bridge, ovs_bridge, vlan in real_bridges:
263         for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
264             if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
265                 fake_bridges.append((iface, ovs_bridge,
266                                      get_port_vlan(cfg, iface)))
267     return real_bridges + fake_bridges
268
269 # Returns the real bridges configured in 'cfg'.
270 def get_real_bridges(cfg):
271     return cfg_get_subsections(cfg, "bridge")
272
273 # Returns the fake bridges configured in 'cfg'.
274 def get_fake_bridges(cfg):
275     return [bridge for bridge, parent, vlan in get_bridge_info(cfg)
276             if bridge != parent]
277
278 # Returns all the real and fake bridges configured in 'cfg'.
279 def get_all_bridges(cfg):
280     return [bridge for bridge, parent, vlan in get_bridge_info(cfg)]
281
282 # Returns the parent bridge and VLAN of real or fake 'bridge' in
283 # 'cfg', where the parent bridge and VLAN are as defined in the
284 # description of get_bridge_info().  Raises an error if no bridge
285 # named 'bridge' exists in 'cfg'.
286 def find_bridge(cfg, bridge):
287     for br, parent, vlan in get_bridge_info(cfg):
288         if br == bridge:
289             return parent, vlan
290     raise Error("no bridge named %s" % bridge)
291
292 def del_matching_keys(cfg, pattern):
293     for key in [key for key in cfg.keys() if fnmatch.fnmatch(key, pattern)]:
294         del cfg[key]
295
296 # Deletes anything related to a port named 'port' from 'cfg'.  No port
297 # named 'port' need actually exist; this function will clean up
298 # regardless.
299 def del_port(cfg, port):
300     # The use of [!0-9] keeps an interface of 'eth0' from matching
301     # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
302     # interfaces.
303     for iface in cfg.get('bonding.%s.slave' % port, [port]):
304         del_matching_keys(cfg, 'iface.%s.[!0-9]*' % iface)
305         # Yes, this "port" setting applies to interfaces, not ports, *sigh*.
306         del_matching_keys(cfg, 'port.%s.ingress-policing*' % iface)
307     del_matching_keys(cfg, 'bonding.%s.[!0-9]*' % port)
308     del_matching_keys(cfg, 'vlan.%s.[!0-9]*' % port)
309     for key in cfg.keys():
310         if fnmatch.fnmatch(key, 'bridge.*.port'):
311             cfg[key] = [s for s in cfg[key] if s != port]
312
313 def usage():
314     print """%(argv0)s: ovs-vswitchd management utility
315 usage: %(argv0)s [OPTIONS] COMMAND [ARG...]
316
317 Bridge commands:
318   add-br BRIDGE               create a new bridge named BRIDGE
319   add-br BRIDGE PARENT VLAN   create new fake bridge BRIDGE in PARENT on VLAN
320   del-br BRIDGE               delete BRIDGE and all of its ports
321   list-br                     print the names of all the bridges
322   br-exists BRIDGE            test whether BRIDGE exists
323   br-to-vlan BRIDGE           print the VLAN which BRIDGE is on
324   br-to-parent BRIDGE         print the parent of BRIDGE
325   
326 Port commands:
327   list-ports BRIDGE           print the names of all the ports on BRIDGE
328   add-port BRIDGE PORT        add network device PORT to BRIDGE
329   add-bond BRIDGE PORT IFACE...  add new bonded port PORT in BRIDGE from IFACES
330   del-port BRIDGE PORT        delete PORT (which may be bonded) from BRIDGE
331   port-to-br PORT             print name of bridge that contains PORT
332 A bond is considered to be a single port.
333
334 Interface commands (a bond consists of multiple interfaces):
335   list-ifaces BRIDGE          print the names of all the interfaces on BRIDGE
336   iface-to-br IFACE           print name of bridge that contains IFACE
337 A bond is considered to consist of interfaces.
338
339 General options:
340   --no-syslog                 do not write mesages to syslog
341   -c, --config=FILE           set configuration file
342                               (default: %(config)s)
343   -t, --target=PIDFILE|SOCKET set ovs-vswitchd target
344                               (default: %(target)s)
345   --no-reload                 do not make ovs-vswitchd reload its configuration
346   -h, --help                  display this help message and exit
347   -V, --version               display version information and exit
348 Report bugs to bugs@openvswitch.org.""" % {'argv0': argv0,
349                                            'config': DEFAULT_VSWITCHD_CONF,
350                                            'target': DEFAULT_VSWITCHD_TARGET}
351     sys.exit(0)
352
353 def version():
354     print "ovs-vsctl (Open vSwitch) @VERSION@"
355     sys.exit(0)
356
357 def check_conflicts(cfg, name, op):
358     bridges = get_bridge_info(cfg)
359     if name in [bridge for bridge, parent, vlan in bridges]:
360         raise Error("%s because a bridge named %s already exists" % (op, name))
361
362     for bridge, parent, vlan in bridges:
363         if name in get_bridge_ports(cfg, parent, vlan):
364             raise Error("%s because a port named %s already exists on bridge %s" % (op, name, bridge))
365         if name in get_bridge_ifaces(cfg, parent, vlan):
366             raise Error("%s because an interface named %s already exists on bridge %s" % (op, name, bridge))
367     
368 def cmd_add_br(bridge, parent=None, vlan=None):
369     cfg = cfg_read(VSWITCHD_CONF, True)
370
371     check_conflicts(cfg, bridge, "cannot create a bridge named %s" % bridge)
372     
373     if parent and vlan:
374         if parent in get_fake_bridges(cfg):
375             raise Error("cannot create bridge with fake bridge as parent")
376         if parent not in get_real_bridges(cfg):
377             raise Error("parent bridge %s does not exist" % bridge)
378         try:
379             if int(vlan) < 0 or int(vlan) > 4095:
380                 raise ValueError
381         except ValueError:
382             raise Error("invalid VLAN number %s" % vlan)
383
384         # Create fake bridge internal port.
385         cfg['iface.%s.internal' % bridge] = ['true']
386         cfg['iface.%s.fake-bridge' % bridge] = ['true']
387         cfg['vlan.%s.tag' % bridge] = [vlan]
388
389         # Add fake bridge port to parent.
390         cfg['bridge.%s.port' % parent].append(bridge)
391     else:
392         cfg['bridge.%s.port' % bridge] = [bridge]
393     cfg_save(cfg, VSWITCHD_CONF)
394
395 def cmd_del_br(bridge):
396     cfg = cfg_read(VSWITCHD_CONF, True)
397     parent, vlan = find_bridge(cfg, bridge)
398     if vlan == 0:
399         vlan = -1
400     for port in set(get_bridge_ports(cfg, parent, vlan) + [bridge]):
401         del_port(cfg, port)
402     if vlan < 0: 
403         del_matching_keys(cfg, 'bridge.%s.[!0-9]*' % bridge)
404     cfg_save(cfg, VSWITCHD_CONF)
405
406 def cmd_list_br():
407     cfg = cfg_read(VSWITCHD_CONF)
408     for bridge in get_all_bridges(cfg):
409         print bridge
410
411 def cmd_br_exists(bridge):
412     cfg = cfg_read(VSWITCHD_CONF)
413     if bridge not in get_all_bridges(cfg):
414         sys.exit(2)
415
416 def cmd_list_ports(bridge):
417     cfg = cfg_read(VSWITCHD_CONF)
418     parent, vlan = find_bridge(cfg, bridge)
419     for port in get_bridge_ports(cfg, parent, vlan):
420         if port != bridge:
421             print port
422
423 def do_add_port(cfg, bridge, parent, port, vlan):
424     check_conflicts(cfg, port, "cannot create a port named %s" % port)
425     cfg['bridge.%s.port' % parent].append(port)
426     if vlan > 0:
427         cfg['vlan.%s.tag' % port] = [vlan]
428
429 def cmd_add_port(bridge, port):
430     cfg = cfg_read(VSWITCHD_CONF, True)
431     parent, vlan = find_bridge(cfg, bridge)
432     do_add_port(cfg, bridge, parent, port, vlan)
433     cfg_save(cfg, VSWITCHD_CONF)
434
435 def cmd_add_bond(bridge, port, *slaves):
436     cfg = cfg_read(VSWITCHD_CONF, True)
437     parent, vlan = find_bridge(cfg, bridge)
438     do_add_port(cfg, bridge, parent, port, vlan)
439     cfg['bonding.%s.slave' % port] = list(slaves)
440     cfg_save(cfg, VSWITCHD_CONF)
441
442 def cmd_del_port(bridge, port):
443     cfg = cfg_read(VSWITCHD_CONF, True)
444     parent, vlan = find_bridge(cfg, bridge)
445     if port not in get_bridge_ports(cfg, parent, vlan):
446         if port in get_bridge_ports(cfg, parent, -1):
447             raise Error("bridge %s does not have a port %s (although its parent bridge %s does)" % (bridge, port, parent))
448         else:
449             raise Error("bridge %s does not have a port %s" % (bridge, port))
450     del_port(cfg, port)
451     cfg_save(cfg, VSWITCHD_CONF)
452
453 def cmd_port_to_br(port):
454     cfg = cfg_read(VSWITCHD_CONF)
455     for bridge, parent, vlan in get_bridge_info(cfg):
456         if port != bridge and port in get_bridge_ports(cfg, parent, vlan):
457             print bridge
458             return
459     raise Error("no port named %s" % port)
460
461 def cmd_list_ifaces(bridge):
462     cfg = cfg_read(VSWITCHD_CONF)
463     parent, vlan = find_bridge(cfg, bridge)
464     for iface in get_bridge_ifaces(cfg, parent, vlan):
465         if iface != bridge:
466             print iface
467
468 def cmd_iface_to_br(iface):
469     cfg = cfg_read(VSWITCHD_CONF)
470     for bridge, parent, vlan in get_bridge_info(cfg):
471         if iface != bridge and iface in get_bridge_ifaces(cfg, parent, vlan):
472             print bridge
473             return
474     raise Error("no interface named %s" % iface)
475
476 def cmd_br_to_vlan(bridge):
477     cfg = cfg_read(VSWITCHD_CONF)
478     parent, vlan = find_bridge(cfg, bridge)
479     print vlan
480
481 def cmd_br_to_parent(bridge):
482     cfg = cfg_read(VSWITCHD_CONF)
483     parent, vlan = find_bridge(cfg, bridge)
484     print parent
485     
486 def main():
487     # Parse command line.
488     try:
489         options, args = getopt.gnu_getopt(sys.argv[1:], "c:t:hV",
490                                           ["config=",
491                                            "target=",
492                                            "no-reload",
493                                            "no-syslog",
494                                            "help",
495                                            "version"])
496     except getopt.GetoptError, msg:
497         sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
498         sys.exit(1)
499
500     # Handle options.
501     for opt, optarg in options:
502         if opt == "-c" or opt == "--config":
503             global VSWITCHD_CONF
504             VSWITCHD_CONF = optarg
505         elif opt == "-t" or opt == "--target":
506             global VSWITCHD_TARGET
507             if optarg[0] != '/':
508                 optarg = '@RUNDIR@/' + optarg
509             VSWITCHD_TARGET = optarg
510         elif opt == "--no-reload":
511             global RELOAD_VSWITCHD
512             RELOAD_VSWITCHD = False
513         elif opt == "-h" or opt == "--help":
514             usage()
515         elif opt == "-V" or opt == "--version":
516             version()
517         elif opt == "--no-syslog":
518             global SYSLOG
519             SYSLOG = False
520         else:
521             raise RuntimeError("unhandled option %s" % opt)
522
523     if SYSLOG:
524         syslog.openlog("ovs-vsctl")
525         log("Called as %s" % ' '.join(sys.argv[1:]))
526
527     # Execute commands.
528     if not args:
529         sys.stderr.write("%s: missing command name (use --help for help)\n"
530                          % argv0)
531         sys.exit(1)
532
533     commands = {'add-br': (cmd_add_br, lambda n: n == 1 or n == 3),
534                 'del-br': (cmd_del_br, 1),
535                 'list-br': (cmd_list_br, 0),
536                 'br-exists': (cmd_br_exists, 1),
537                 'list-ports': (cmd_list_ports, 1),
538                 'add-port': (cmd_add_port, 2),
539                 'add-bond': (cmd_add_bond, lambda n: n >= 4),
540                 'del-port': (cmd_del_port, 2),
541                 'port-to-br': (cmd_port_to_br, 1),
542                 'br-to-vlan': (cmd_br_to_vlan, 1),
543                 'br-to-parent': (cmd_br_to_parent, 1),
544                 'list-ifaces': (cmd_list_ifaces, 1),
545                 'iface-to-br': (cmd_iface_to_br, 1)}
546     command = args[0]
547     args = args[1:]
548     if command not in commands:
549         sys.stderr.write("%s: unknown command '%s' (use --help for help)\n"
550                          % (argv0, command))
551         sys.exit(1)
552
553     function, nargs = commands[command]
554     if callable(nargs) and not nargs(len(args)):
555         sys.stderr.write("%s: '%s' command does not accept %d arguments (use --help for help)\n" % (argv0, command, len(args)))
556         sys.exit(1)
557     elif not callable(nargs) and len(args) != nargs:
558         sys.stderr.write("%s: '%s' command takes %d arguments but %d were supplied (use --help for help)\n" % (argv0, command, nargs, len(args)))
559         sys.exit(1)
560     else:
561         function(*args)
562         sys.exit(0)
563
564 if __name__ == "__main__":
565     try:
566         main()
567     except Error, msg:
568         sys.stderr.write("%s: %s\n" % (argv0, msg.msg))
569         sys.exit(1)