ovs-vsctl: Allow bridge name to be omitted from del-port command.
[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 # Returns the name of the (real or fake) bridge in 'cfg' that contains
314 # port 'port', or None if there is no such port.
315 def port_to_bridge(cfg, port):
316     for bridge, parent, vlan in get_bridge_info(cfg):
317         if port != bridge and port in get_bridge_ports(cfg, parent, vlan):
318             return bridge
319     return None
320
321 def usage():
322     print """%(argv0)s: ovs-vswitchd management utility
323 usage: %(argv0)s [OPTIONS] COMMAND [ARG...]
324
325 Bridge commands:
326   add-br BRIDGE               create a new bridge named BRIDGE
327   add-br BRIDGE PARENT VLAN   create new fake bridge BRIDGE in PARENT on VLAN
328   del-br BRIDGE               delete BRIDGE and all of its ports
329   list-br                     print the names of all the bridges
330   br-exists BRIDGE            test whether BRIDGE exists
331   br-to-vlan BRIDGE           print the VLAN which BRIDGE is on
332   br-to-parent BRIDGE         print the parent of BRIDGE
333   
334 Port commands:
335   list-ports BRIDGE           print the names of all the ports on BRIDGE
336   add-port BRIDGE PORT        add network device PORT to BRIDGE
337   add-bond BRIDGE PORT IFACE...  add new bonded port PORT in BRIDGE from IFACES
338   del-port [BRIDGE] PORT      delete PORT (which may be bonded) from BRIDGE
339   port-to-br PORT             print name of bridge that contains PORT
340 A bond is considered to be a single port.
341
342 Interface commands (a bond consists of multiple interfaces):
343   list-ifaces BRIDGE          print the names of all the interfaces on BRIDGE
344   iface-to-br IFACE           print name of bridge that contains IFACE
345 A bond is considered to consist of interfaces.
346
347 General options:
348   --no-syslog                 do not write mesages to syslog
349   -c, --config=FILE           set configuration file
350                               (default: %(config)s)
351   -t, --target=PIDFILE|SOCKET set ovs-vswitchd target
352                               (default: %(target)s)
353   --no-reload                 do not make ovs-vswitchd reload its configuration
354   -h, --help                  display this help message and exit
355   -V, --version               display version information and exit
356 Report bugs to bugs@openvswitch.org.""" % {'argv0': argv0,
357                                            'config': DEFAULT_VSWITCHD_CONF,
358                                            'target': DEFAULT_VSWITCHD_TARGET}
359     sys.exit(0)
360
361 def version():
362     print "ovs-vsctl (Open vSwitch) @VERSION@"
363     sys.exit(0)
364
365 def check_conflicts(cfg, name, op):
366     bridges = get_bridge_info(cfg)
367     if name in [bridge for bridge, parent, vlan in bridges]:
368         raise Error("%s because a bridge named %s already exists" % (op, name))
369
370     for bridge, parent, vlan in bridges:
371         if name in get_bridge_ports(cfg, parent, vlan):
372             raise Error("%s because a port named %s already exists on bridge %s" % (op, name, bridge))
373         if name in get_bridge_ifaces(cfg, parent, vlan):
374             raise Error("%s because an interface named %s already exists on bridge %s" % (op, name, bridge))
375     
376 def cmd_add_br(bridge, parent=None, vlan=None):
377     cfg = cfg_read(VSWITCHD_CONF, True)
378
379     check_conflicts(cfg, bridge, "cannot create a bridge named %s" % bridge)
380     
381     if parent and vlan:
382         if parent in get_fake_bridges(cfg):
383             raise Error("cannot create bridge with fake bridge as parent")
384         if parent not in get_real_bridges(cfg):
385             raise Error("parent bridge %s does not exist" % bridge)
386         try:
387             if int(vlan) < 0 or int(vlan) > 4095:
388                 raise ValueError
389         except ValueError:
390             raise Error("invalid VLAN number %s" % vlan)
391
392         # Create fake bridge internal port.
393         cfg['iface.%s.internal' % bridge] = ['true']
394         cfg['iface.%s.fake-bridge' % bridge] = ['true']
395         cfg['vlan.%s.tag' % bridge] = [vlan]
396
397         # Add fake bridge port to parent.
398         cfg['bridge.%s.port' % parent].append(bridge)
399     else:
400         cfg['bridge.%s.port' % bridge] = [bridge]
401     cfg_save(cfg, VSWITCHD_CONF)
402
403 def cmd_del_br(bridge):
404     cfg = cfg_read(VSWITCHD_CONF, True)
405     parent, vlan = find_bridge(cfg, bridge)
406     if vlan == 0:
407         vlan = -1
408     for port in set(get_bridge_ports(cfg, parent, vlan) + [bridge]):
409         del_port(cfg, port)
410     if vlan < 0: 
411         del_matching_keys(cfg, 'bridge.%s.[!0-9]*' % bridge)
412     cfg_save(cfg, VSWITCHD_CONF)
413
414 def cmd_list_br():
415     cfg = cfg_read(VSWITCHD_CONF)
416     for bridge in get_all_bridges(cfg):
417         print bridge
418
419 def cmd_br_exists(bridge):
420     cfg = cfg_read(VSWITCHD_CONF)
421     if bridge not in get_all_bridges(cfg):
422         sys.exit(2)
423
424 def cmd_list_ports(bridge):
425     cfg = cfg_read(VSWITCHD_CONF)
426     parent, vlan = find_bridge(cfg, bridge)
427     for port in get_bridge_ports(cfg, parent, vlan):
428         if port != bridge:
429             print port
430
431 def do_add_port(cfg, bridge, parent, port, vlan):
432     check_conflicts(cfg, port, "cannot create a port named %s" % port)
433     cfg['bridge.%s.port' % parent].append(port)
434     if vlan > 0:
435         cfg['vlan.%s.tag' % port] = [vlan]
436
437 def cmd_add_port(bridge, port):
438     cfg = cfg_read(VSWITCHD_CONF, True)
439     parent, vlan = find_bridge(cfg, bridge)
440     do_add_port(cfg, bridge, parent, port, vlan)
441     cfg_save(cfg, VSWITCHD_CONF)
442
443 def cmd_add_bond(bridge, port, *slaves):
444     cfg = cfg_read(VSWITCHD_CONF, True)
445     parent, vlan = find_bridge(cfg, bridge)
446     do_add_port(cfg, bridge, parent, port, vlan)
447     cfg['bonding.%s.slave' % port] = list(slaves)
448     cfg_save(cfg, VSWITCHD_CONF)
449
450 def cmd_del_port(*args):
451     cfg = cfg_read(VSWITCHD_CONF, True)
452     if len(args) == 2:
453         bridge, port = args
454         parent, vlan = find_bridge(cfg, bridge)
455         if port not in get_bridge_ports(cfg, parent, vlan):
456             if port in get_bridge_ports(cfg, parent, -1):
457                 raise Error("bridge %s does not have a port %s (although its parent bridge %s does)" % (bridge, port, parent))
458             else:
459                 raise Error("bridge %s does not have a port %s" % (bridge, port))
460     else:
461         port, = args
462         if not port_to_bridge(cfg, port):
463             raise Error("no port %s on any bridge" % port)
464     del_port(cfg, port)
465     cfg_save(cfg, VSWITCHD_CONF)
466
467 def cmd_port_to_br(port):
468     cfg = cfg_read(VSWITCHD_CONF)
469     bridge = port_to_bridge(cfg, port)
470     if bridge:
471         print bridge
472     else:
473         raise Error("no port named %s" % port)
474
475 def cmd_list_ifaces(bridge):
476     cfg = cfg_read(VSWITCHD_CONF)
477     parent, vlan = find_bridge(cfg, bridge)
478     for iface in get_bridge_ifaces(cfg, parent, vlan):
479         if iface != bridge:
480             print iface
481
482 def cmd_iface_to_br(iface):
483     cfg = cfg_read(VSWITCHD_CONF)
484     for bridge, parent, vlan in get_bridge_info(cfg):
485         if iface != bridge and iface in get_bridge_ifaces(cfg, parent, vlan):
486             print bridge
487             return
488     raise Error("no interface named %s" % iface)
489
490 def cmd_br_to_vlan(bridge):
491     cfg = cfg_read(VSWITCHD_CONF)
492     parent, vlan = find_bridge(cfg, bridge)
493     print vlan
494
495 def cmd_br_to_parent(bridge):
496     cfg = cfg_read(VSWITCHD_CONF)
497     parent, vlan = find_bridge(cfg, bridge)
498     print parent
499     
500 def main():
501     # Parse command line.
502     try:
503         options, args = getopt.gnu_getopt(sys.argv[1:], "c:t:hV",
504                                           ["config=",
505                                            "target=",
506                                            "no-reload",
507                                            "no-syslog",
508                                            "help",
509                                            "version"])
510     except getopt.GetoptError, msg:
511         sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
512         sys.exit(1)
513
514     # Handle options.
515     for opt, optarg in options:
516         if opt == "-c" or opt == "--config":
517             global VSWITCHD_CONF
518             VSWITCHD_CONF = optarg
519         elif opt == "-t" or opt == "--target":
520             global VSWITCHD_TARGET
521             if optarg[0] != '/':
522                 optarg = '@RUNDIR@/' + optarg
523             VSWITCHD_TARGET = optarg
524         elif opt == "--no-reload":
525             global RELOAD_VSWITCHD
526             RELOAD_VSWITCHD = False
527         elif opt == "-h" or opt == "--help":
528             usage()
529         elif opt == "-V" or opt == "--version":
530             version()
531         elif opt == "--no-syslog":
532             global SYSLOG
533             SYSLOG = False
534         else:
535             raise RuntimeError("unhandled option %s" % opt)
536
537     if SYSLOG:
538         syslog.openlog("ovs-vsctl")
539         log("Called as %s" % ' '.join(sys.argv[1:]))
540
541     # Execute commands.
542     if not args:
543         sys.stderr.write("%s: missing command name (use --help for help)\n"
544                          % argv0)
545         sys.exit(1)
546
547     commands = {'add-br': (cmd_add_br, lambda n: n == 1 or n == 3),
548                 'del-br': (cmd_del_br, 1),
549                 'list-br': (cmd_list_br, 0),
550                 'br-exists': (cmd_br_exists, 1),
551                 'list-ports': (cmd_list_ports, 1),
552                 'add-port': (cmd_add_port, 2),
553                 'add-bond': (cmd_add_bond, lambda n: n >= 4),
554                 'del-port': (cmd_del_port, lambda n: n == 1 or n == 2),
555                 'port-to-br': (cmd_port_to_br, 1),
556                 'br-to-vlan': (cmd_br_to_vlan, 1),
557                 'br-to-parent': (cmd_br_to_parent, 1),
558                 'list-ifaces': (cmd_list_ifaces, 1),
559                 'iface-to-br': (cmd_iface_to_br, 1)}
560     command = args[0]
561     args = args[1:]
562     if command not in commands:
563         sys.stderr.write("%s: unknown command '%s' (use --help for help)\n"
564                          % (argv0, command))
565         sys.exit(1)
566
567     function, nargs = commands[command]
568     if callable(nargs) and not nargs(len(args)):
569         sys.stderr.write("%s: '%s' command does not accept %d arguments (use --help for help)\n" % (argv0, command, len(args)))
570         sys.exit(1)
571     elif not callable(nargs) and len(args) != nargs:
572         sys.stderr.write("%s: '%s' command takes %d arguments but %d were supplied (use --help for help)\n" % (argv0, command, nargs, len(args)))
573         sys.exit(1)
574     else:
575         function(*args)
576         sys.exit(0)
577
578 if __name__ == "__main__":
579     try:
580         main()
581     except Error, msg:
582         sys.stderr.write("%s: %s\n" % (argv0, msg.msg))
583         sys.exit(1)