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