New utility ovs-vsctl.
[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
281 Port commands:
282   list-ports BRIDGE           print the names of all the ports on BRIDGE
283   add-port BRIDGE PORT        add network device PORT to BRIDGE
284   add-bond BRIDGE PORT IFACE...  add new bonded port PORT in BRIDGE from IFACES
285   del-port BRIDGE PORT        delete PORT (which may be bonded) from BRIDGE
286   port-to-br PORT             print name of bridge that contains PORT
287 A bond is considered to be a single port.
288
289 Interface commands (a bond consists of multiple interfaces):
290   list-ifaces BRIDGE          print the names of all the interfaces on BRIDGE
291   iface-to-br IFACE           print name of bridge that contains IFACE
292 A bond is considered to consist of interfaces.
293
294 General options:
295   -c, --config=FILE           set configuration file
296                               (default: %(config)s)
297   -t, --target=PIDFILE|SOCKET set ovs-vswitchd target
298                               (default: %(target)s)
299   --no-reload                 do not make ovs-vswitchd reload its configuration
300   -h, --help                  display this help message and exit
301   -V, --version               display version information and exit
302 Report bugs to bugs@openvswitch.org.""" % {'argv0': argv0,
303                                            'config': DEFAULT_VSWITCHD_CONF,
304                                            'target': DEFAULT_VSWITCHD_TARGET}
305     sys.exit(0)
306
307 def version():
308     print "ovs-vsctl (Open vSwitch) @VERSION@"
309     sys.exit(0)
310
311 def check_conflicts(cfg, name, op):
312     bridges = get_bridge_info(cfg)
313     if name in [bridge for bridge, parent, vlan in bridges]:
314         raise Error("%s because a bridge named %s already exists" % (op, name))
315
316     for bridge, parent, vlan in bridges:
317         if name in get_bridge_ports(cfg, parent, vlan):
318             raise Error("%s because a port named %s already exists on bridge %s" % (op, name, bridge))
319         if name in get_bridge_ifaces(cfg, parent, vlan):
320             raise Error("%s because an interface named %s already exists on bridge %s" % (op, name, bridge))
321     
322 def cmd_add_br(bridge, parent=None, vlan=None):
323     cfg = cfg_read(VSWITCHD_CONF, True)
324
325     check_conflicts(cfg, bridge, "cannot create a bridge named %s" % bridge)
326     
327     if parent and vlan:
328         if parent in get_fake_bridges(cfg):
329             raise Error("cannot create bridge with fake bridge as parent")
330         if parent not in get_real_bridges(cfg):
331             raise Error("parent bridge %s does not exist" % bridge)
332         try:
333             if int(vlan) < 0 or int(vlan) > 4095:
334                 raise ValueError
335         except ValueError:
336             raise Error("invalid VLAN number %s" % vlan)
337
338         # Create fake bridge internal port.
339         cfg['iface.%s.internal' % bridge] = ['true']
340         cfg['iface.%s.fake-bridge' % bridge] = ['true']
341         cfg['vlan.%s.tag' % bridge] = [vlan]
342
343         # Add fake bridge port to parent.
344         cfg['bridge.%s.port' % parent].append(bridge)
345     else:
346         cfg['bridge.%s.port' % bridge] = [bridge]
347     cfg_save(cfg, VSWITCHD_CONF)
348
349 def cmd_del_br(bridge):
350     cfg = cfg_read(VSWITCHD_CONF, True)
351     parent, vlan = find_bridge(cfg, bridge)
352     if vlan == 0:
353         vlan = -1
354     for port in set(get_bridge_ports(cfg, parent, vlan) + [bridge]):
355         del_port(cfg, port)
356     if vlan < 0: 
357         del_matching_keys(cfg, 'bridge.%s.[!0-9]*' % bridge)
358     cfg_save(cfg, VSWITCHD_CONF)
359
360 def cmd_list_br():
361     cfg = cfg_read(VSWITCHD_CONF)
362     for bridge in get_all_bridges(cfg):
363         print bridge
364
365 def cmd_br_exists(bridge):
366     cfg = cfg_read(VSWITCHD_CONF)
367     if bridge not in get_all_bridges(cfg):
368         sys.exit(2)
369
370 def cmd_list_ports(bridge):
371     cfg = cfg_read(VSWITCHD_CONF)
372     parent, vlan = find_bridge(cfg, bridge)
373     for port in get_bridge_ports(cfg, parent, vlan):
374         if port != bridge:
375             print port
376
377 def do_add_port(cfg, bridge, parent, port, vlan):
378     check_conflicts(cfg, port, "cannot create a port named %s" % port)
379     cfg['bridge.%s.port' % parent].append(port)
380     if vlan > 0:
381         cfg['vlan.%s.tag' % port] = [vlan]
382
383 def cmd_add_port(bridge, port):
384     cfg = cfg_read(VSWITCHD_CONF, True)
385     parent, vlan = find_bridge(cfg, bridge)
386     do_add_port(cfg, bridge, parent, port, vlan)
387     cfg_save(cfg, VSWITCHD_CONF)
388
389 def cmd_add_bond(bridge, port, *slaves):
390     cfg = cfg_read(VSWITCHD_CONF, True)
391     parent, vlan = find_bridge(cfg, bridge)
392     do_add_port(cfg, bridge, parent, port, vlan)
393     cfg['bonding.%s.slave' % port] = list(slaves)
394     cfg_save(cfg, VSWITCHD_CONF)
395
396 def cmd_del_port(bridge, port):
397     cfg = cfg_read(VSWITCHD_CONF, True)
398     parent, vlan = find_bridge(cfg, bridge)
399     if port not in get_bridge_ports(cfg, parent, vlan):
400         if port in get_bridge_ports(cfg, parent, -1):
401             raise Error("bridge %s does not have a port %s (although its parent bridge %s does)" % (bridge, port, parent))
402         else:
403             raise Error("bridge %s does not have a port %s" % (bridge, port))
404     del_port(cfg, port)
405     cfg_save(cfg, VSWITCHD_CONF)
406
407 def cmd_port_to_br(port):
408     cfg = cfg_read(VSWITCHD_CONF)
409     for bridge, parent, vlan in get_bridge_info(cfg):
410         if port != bridge and port in get_bridge_ports(cfg, parent, vlan):
411             print bridge
412             return
413     raise Error("no port named %s" % port)
414
415 def cmd_list_ifaces(bridge):
416     cfg = cfg_read(VSWITCHD_CONF)
417     parent, vlan = find_bridge(cfg, bridge)
418     for iface in get_bridge_ifaces(cfg, parent, vlan):
419         if iface != bridge:
420             print iface
421
422 def cmd_iface_to_br(iface):
423     cfg = cfg_read(VSWITCHD_CONF)
424     for bridge, parent, vlan in get_bridge_info(cfg):
425         if iface != bridge and iface in get_bridge_ifaces(cfg, parent, vlan):
426             print bridge
427             return
428     raise Error("no interface named %s" % iface)
429
430 def main():
431     # Parse command line.
432     try:
433         options, args = getopt.gnu_getopt(sys.argv[1:], "c:t:hV",
434                                           ["config=",
435                                            "target=",
436                                            "no-reload",
437                                            "help",
438                                            "version"])
439     except getopt.GetoptError, msg:
440         sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
441         sys.exit(1)
442
443     # Handle options.
444     for opt, optarg in options:
445         if opt == "-c" or opt == "--config":
446             global VSWITCHD_CONF
447             VSWITCHD_CONF = optarg
448         elif opt == "-t" or opt == "--target":
449             global VSWITCHD_TARGET
450             if optarg[0] != '/':
451                 optarg = '@RUNDIR@/' + optarg
452             VSWITCHD_TARGET = optarg
453         elif opt == "--no-reload":
454             global RELOAD_VSWITCHD
455             RELOAD_VSWITCHD = False
456         elif opt == "-h" or opt == "--help":
457             usage()
458         elif opt == "-V" or opt == "--version":
459             version()
460         else:
461             raise RuntimeError("unhandled option %s" % opt)
462
463     # Execute commands.
464     if not args:
465         sys.stderr.write("%s: missing command name (use --help for help)\n"
466                          % argv0)
467         sys.exit(1)
468
469     commands = {'add-br': (cmd_add_br, lambda n: n == 1 or n == 3),
470                 'del-br': (cmd_del_br, 1),
471                 'list-br': (cmd_list_br, 0),
472                 'br-exists': (cmd_br_exists, 1),
473                 'list-ports': (cmd_list_ports, 1),
474                 'add-port': (cmd_add_port, 2),
475                 'add-bond': (cmd_add_bond, lambda n: n >= 4),
476                 'del-port': (cmd_del_port, 2),
477                 'port-to-br': (cmd_port_to_br, 1),
478                 'list-ifaces': (cmd_list_ifaces, 1),
479                 'iface-to-br': (cmd_iface_to_br, 1)}
480     command = args[0]
481     args = args[1:]
482     if command not in commands:
483         sys.stderr.write("%s: unknown command '%s' (use --help for help)\n"
484                          % (argv0, command))
485         sys.exit(1)
486
487     function, nargs = commands[command]
488     if callable(nargs) and not nargs(len(args)):
489         sys.stderr.write("%s: '%s' command does not accept %d arguments (use --help for help)\n" % (argv0, command, len(args)))
490         sys.exit(1)
491     elif not callable(nargs) and len(args) != nargs:
492         sys.stderr.write("%s: '%s' command takes %d arguments but %d were supplied (use --help for help)\n" % (argv0, command, nargs, len(args)))
493         sys.exit(1)
494     else:
495         function(*args)
496         sys.exit(0)
497
498 if __name__ == "__main__":
499     try:
500         main()
501     except Error, msg:
502         sys.stderr.write("%s: %s\n" % (argv0, msg.msg))
503         sys.exit(1)