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