blob: 13e287e775bc4f0b1e8eb4df5a6c3703e8956cef [file] [log] [blame]
Sreeju Sreedhare3fefd92019-04-02 15:57:15 -07001
2# Copyright 2017-present Open Networking Foundation
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
17#!/usr/bin/python
18
19import os
20import subprocess
21import argparse
22import sys
23import signal
24import time
25import ConfigParser
26import pprint
27
28###############################################################################
29#
30# Arguments
31#
32# Arguments can be specified directly, or via config file.
33# TODO -- Make this a separate reusable class
34#
35################################################################################
36gBasename = "ovs-ctl"
37
38gConfigParser = argparse.ArgumentParser(description=gBasename, add_help=False)
39
40gConfigParser.add_argument('-cf', '--config-file',
41 help="Load settings from the given config file",
42 nargs='+', metavar="FILE",
43 default=os.path.expanduser("~/."+gBasename))
44
45gConfigParser.add_argument('-c', '--config',
46 help="Use the specified configuration section",
47 default=None)
48
49gConfigParser.add_argument('-d', '--default-config-file',
50 help="Location of the local default config file",
51 metavar="FILE",
52 default="/opt/ovs/%s-default.conf" % (gBasename))
53
54gConfigParser.add_argument('-nd', '--no-default',
55 help="Do not load the default config file",
56 action='store_true', default=False)
57gConfigParser.add_argument('--dump-config',
58 help="Dump the loaded configuration settings",
59 action='store_true', default=False)
60
61#
62# Parse the config files first, then parse remaining command line arguments
63#
64gConfig = ConfigParser.SafeConfigParser()
65configArgs, remainingArgs = gConfigParser.parse_known_args()
66
67if not configArgs.no_default:
68 gConfig.read([configArgs.default_config_file])
69
70if isinstance(configArgs.config_file, str):
71 configFiles = [ configArgs.config_file]
72else:
73 configFiles = configArgs.config_file
74
75configFiles = [ os.path.expanduser(x) if x.startswith('~') else x
76 for x in configFiles ]
77
78gConfig.read(configFiles)
79
80# Dump loaded config if requested
81if configArgs.dump_config:
82 for section in gConfig.sections():
83 print section
84 for option in gConfig.options(section):
85 print " ", option, "=", gConfig.get(section, option)
86 sys.exit()
87
88
89
90#
91# Functional arguments go here
92#
93
94#
95# OVS target binaries -- these can all be specified individually.
96# If not specified, they are determined by the base directory settings
97#
98gParser = argparse.ArgumentParser(parents=[gConfigParser])
99
100gParser.add_argument('--ovs-vswitchd-schema',
101 help="""Path to the vswitchd.ovsschema file""")
102gParser.add_argument('--ovs-vswitchd-log',
103 help="""Path to the vswitchd log file""")
104gParser.add_argument('--ovs-vswitchd-verbosity',
105 help="""Set vswitchd logging level (off/emer/err/warn/info/dbg)""",
106 default="dbg")
107gParser.add_argument('--ovs-vswitchd',
108 help="""Path to the target ovs-vswitchd binary""")
109gParser.add_argument('--ovs-vsctl',
110 help="""Path to the target ovs-vsctl binary""")
111gParser.add_argument('--ovs-ofctl',
112 help="""Path to the target ovs-ofctl binary""")
113gParser.add_argument('--ovsdb-tool',
114 help="""Path to the target ovsdb-tool binary""")
115gParser.add_argument('--ovsdb-server',
116 help="""Path to the target ovsdb-server binary""")
117gParser.add_argument('--ovs-kmod',
118 help="""Path to the OVS kernel module""")
119gParser.add_argument('--ovs-src-dir',
120 help="""Directory for the OVS Source Files""")
121
122gParser.add_argument('--ovs-log-dir',
123 help="""Directory for the OVS Runtime Log Files""")
124
125gParser.add_argument('--ovs-version')
126
127gParser.add_argument("--ovs-base-dir", help="OVS Base Installation Directory")
128
129gParser.add_argument("--ovs-runtime-dir",
130 help="OVS Runtime Directory",
131 default="/var/run/ovs")
132
133gParser.add_argument('--ovs-db-sock',
134 help="Domain Socket Location")
135
136gParser.add_argument('--ovs-db-file',
137 help="Location for the OVS database file")
138
139
140#
141# Logging/Debugging/Testing options
142#
143gParser.add_argument('--dry',
144 help="""Dry run only. Don't actually do anything""",
145 action='store_true', default=False)
146
147gParser.add_argument('--log',
148 help='Enable logging',
149 action='store_true', default=False)
150
151gParser.add_argument('--verbose',
152 help='Enable verbose output information',
153 action='store_true', default=False)
154
155gParser.add_argument("--kill", help="Kill running OVS",
156 default=False, action='store_true')
157
158gParser.add_argument("--keep-veths",
159 help="""By default, all existing veths will be destroyed and
160the veth module removed before initializing. If you don't want the veth module
161removed, specify this argument. Your mileage may vary if you use this.""",
162 default=False, action='store_true')
163
164gParser.add_argument("--no-kmod",
165 help="""Do not attempt to insert or remove the OVS kernel module.
166Your mileage may vary.""",
167 default=False, action='store_true')
168
169gParser.add_argument("--vlog",
170 help="""Tail the running vswitch.log file in a new xterm""",
171 default=False, action='store_true')
172
173#
174# Runtime and setup arguments
175#
176gParser.add_argument('-p', '--port-count', type=int,
177 help="Number of veth ports to connect.",
178 default='4')
179
180
181gParser.add_argument("--bridge", help="Name of OF OVS Bridge",
182 default="ofbr0")
183
184gParser.add_argument("--cip", help="Controller Connection",
185 default="127.0.0.1")
186
187gParser.add_argument("--cport", type=int, help="Controller Port",
188 default=6653)
189gParser.add_argument("--dpid", help="DPID")
190
191gParser.add_argument("--max_backoff", help="VSwitchD max backoff value",
192 default=1000, type=int)
193
194
195gParser.add_argument("--keep-db",
196 help="""By default, a new database is initialized at each
197execution. If you want to keep and use the old database (if it exists),
198use this option""",
199 action='store_true', default=False)
200
201gParser.add_argument('-lb', '--loopback',
202 help="Create a loopback pair. The port numbers are port_count+1 and port_count+2.",
203 default=False, action='store_true')
204
205
206gParser.add_argument("--cli",
207 help="Run the ovs-ctl cli after initialization",
208 action='store_true', default=False)
209
210gParser.add_argument("--teardown",
211 help="Kill OVS instance after CLI exits",
212 action='store_true', default=False)
213
214#
215# Reset defaults based on config files and override
216#
217# Anything in the "Defaults" section gets slurped first:
218defaults = {}
219if gConfig.has_section('Defaults'):
220 defaults = dict(gConfig.items('Defaults'))
221 gParser.set_defaults(**defaults)
222
223#
224# The configuration to execute might be specified in on the command line, or in the Defaults section(s)
225# of the config files.
226#
227# If its specified on the command line, it will be present in configArgs.config
228# If its specified in the section, it will only be present in the defaults dict.
229# Need to check both. Command line takes precedence.
230#
231gConfigSection = None
232if configArgs.config != None:
233 gConfigSection = configArgs.config
234elif defaults.has_key('config'):
235 gConfigSection = defaults['config']
236
237
238if gConfigSection != None:
239 if gConfig.has_section(gConfigSection):
240 section = dict(gConfig.items(gConfigSection))
241 gParser.set_defaults(**section)
242 else:
243 print >>sys.stderr, "Requestion configuration '%s' does not exist" % (configArgs.config)
244 sys.exit()
245
246
247###############################################################################
248#
249# Done with argument setup. Parser remaining arguments
250#
251###############################################################################
252gArgs = gParser.parse_args(remainingArgs)
253
254
255#
256# At the end of all of this, we need the following things to be defined
257# or derived:
258#
259gRequiredOptions = [
260 [ 'ovs_vswitchd_schema', 'ovs_src_dir', '/vswitchd/vswitch.ovsschema', True, True ],
261 [ 'ovs_vswitchd', 'ovs_base_dir', '/sbin/ovs-vswitchd', True, True ],
262 [ 'ovs_vsctl', 'ovs_base_dir', '/bin/ovs-vsctl', True, True ],
263 [ 'ovs_ofctl', 'ovs_base_dir', '/bin/ovs-ofctl', True, True ],
264 [ 'ovsdb_tool', 'ovs_base_dir', '/bin/ovsdb-tool', True, True, ],
265 [ 'ovsdb_server', 'ovs_base_dir', '/sbin/ovsdb-server', True, True, ],
266 [ 'ovs_db_file', 'ovs_base_dir', '/ovs.db', False, True, ],
267 [ 'ovs_db_sock', 'ovs_runtime_dir', '/db.sock', False, True, ],
268 [ 'ovs_kmod', 'ovs_base_dir', '/sbin/openvswitch_mod.ko', True, not gArgs.no_kmod ],
269]
270
271def __require_option(key, basedir, path, exists=True):
272 value = getattr(gArgs, key)
273 if value is None:
274 # Unspecified -- try to default based on given path
275 value = getattr(gArgs, basedir)
276
277 if value is None:
278 return False
279
280 value += path
281
282 if exists and not os.path.exists(value):
283 return False
284
285 if gArgs.verbose:
286 print '--v-- option: %s @ %s' % (key, value)
287
288 setattr(gArgs, key, value)
289
290
291Validated = True
292# Try to validate as many things as we can
293for option in gRequiredOptions:
294 if option[4]:
295 if __require_option(option[0], option[1], option[2], option[3]) == False:
296 Validated = False
297
298# If any of them failed, try to be as helpful as possible
299if Validated == False:
300 print >>sys.stdout, "\nConfiguration problem. One or more required settings are missing or could not be derived:\n"
301 for option in gRequiredOptions:
302 if option[4] is False:
303 continue
304
305 value = getattr(gArgs, option[0])
306 if value:
307 print >>sys.stdout, " %s: %s" % (option[0], value),
308 if option[3] and not os.path.exists(value):
309 print >>sys.stdout, "-- does not exist"
310 else:
311 print "(OK)"
312 else:
313 print >>sys.stdout, " %s: Unknown. " % (option[0]),
314 base = getattr(gArgs, option[1])
315 if base:
316 print >>sys.stdout, "Search path was '%s'." % (base + option[2])
317 else:
318 print >>sys.stdout, "Could not derive path because '%s' was also unspecified." % (option[1])
319 print >>sys.stdout
320 sys.exit()
321
322
323
324
325#
326# If we aren't in a dry run, you must execute as root to accompish anything
327#
328if not os.geteuid() == 0 and gArgs.dry == False:
329 print >>sys.stderr, "Must run as root to accomplish any of this."
330 sys.exit()
331
332
333###############################################################################
334#
335# Helpers
336#
337###############################################################################
338
339def createVeths(count):
340 for idx in range(0, count):
341 lcall(["/sbin/ip", "link", "add", "type", "veth"])
342
343def vethsUp(count):
344 for idx in range(0, count*2):
345 lcall(["/sbin/ifconfig", 'veth%s' % (idx), "up"])
346
347def lcall(cmd, log=gArgs.log, dry=gArgs.dry, popen=False,
348 pidFile=None):
349
350 if log or gArgs.verbose:
351 print "%s: %s" % ('popen' if popen else 'call', cmd)
352
353 if not dry:
354 if not popen:
355 subprocess.call(cmd)
356 else:
357 p = subprocess.Popen(cmd)
358 if pidFile != None:
359 pidf = open(pidFile, "w");
360 print >>pidf, p.pid
361 pidf.close()
362
363
364
365def vsctl(argsList):
366 argsList.insert(0, "--db=unix:%s" % (gArgs.ovs_db_sock))
367 argsList.insert(0, gArgs.ovs_vsctl)
368 lcall(argsList)
369
370def ofctl(argsList):
371 argsList.insert(0, gArgs.ovs_ofctl)
372 lcall(argsList)
373
374def killpid(pid):
375 try:
376 os.kill(pid, signal.SIGTERM)
377 return False
378 except OSError, e:
379 return True
380
381
382def killp(pidfile):
383 if os.path.exists(pidfile):
384 pid=int(open(pidfile).read())
385 print "Killing %s, pid=%s..." % (pidfile, pid),
386 if not gArgs.dry:
387 while not killpid(pid):
388 time.sleep(1)
389 pass
390 print "dead"
391
392
393###############################################################################
394#
395# main
396#
397###############################################################################
398
399gServerPid = gArgs.ovs_runtime_dir + "/ovsdb-server.pid"
400gSwitchPid = gArgs.ovs_runtime_dir + "/ovs-vswitchd.pid"
401gLogPid= gArgs.ovs_runtime_dir + "/ovs-vswitchd-tail.pid"
402
403# Kill previous execution based on contents of the runtime dir
404if os.path.exists(gServerPid):
405 print gServerPid
406 vsctl(["del-br", gArgs.bridge])
407
408
409def killall():
410 # Kill existing DB/vswitchd
411 killp(gSwitchPid)
412 killp(gServerPid)
413 killp(gLogPid)
414
415 # Remove old logpid file, since this does not happen automagically
416 if os.path.exists(gLogPid):
417 os.remove(gLogPid)
418
419 if gArgs.keep_veths == False:
420 lcall(['/sbin/rmmod', 'veth'])
421 lcall(['/sbin/modprobe', 'veth'])
422
423 # Remove kmod
424 lcall(['/sbin/rmmod', gArgs.ovs_kmod])
425
426
427killall()
428if gArgs.kill == True:
429 # Don't do anything else
430 sys.exit()
431
432
433# Remove bridge module
434lcall(['/sbin/rmmod', 'bridge'])
435# Insert openvswitch module
436lcall(['/sbin/insmod', gArgs.ovs_kmod])
437
438port_count = gArgs.port_count
439if gArgs.loopback:
440 port_count += 1
441createVeths(port_count)
442vethsUp(port_count)
443
444if not os.path.exists(gArgs.ovs_db_file) or gArgs.keep_db == False:
445 print "Initializing DB @ %s" % (gArgs.ovs_db_file)
446 if os.path.exists(gArgs.ovs_db_file) and not gArgs.dry:
447 os.remove(gArgs.ovs_db_file)
448
449 lcall([gArgs.ovsdb_tool, "create", gArgs.ovs_db_file,
450 gArgs.ovs_vswitchd_schema])
451else:
452 print "Keeping existing DB @ %s" % (gArgs.ovs_db_file)
453
454
455if not os.path.exists(gArgs.ovs_runtime_dir):
456 os.makedirs(gArgs.ovs_runtime_dir)
457
458# Start dbserver
459lcall([gArgs.ovsdb_server, gArgs.ovs_db_file,
460 "--remote=punix:%s" % (gArgs.ovs_db_sock),
461 "--detach", "--pidfile=%s" % (gServerPid)])
462
463# Init db
464vsctl(["--no-wait", "init"])
465
466# Start vswitchd
467startV = [ gArgs.ovs_vswitchd,
468 "unix:%s" % (gArgs.ovs_db_sock),
469 "--verbose=%s" % gArgs.ovs_vswitchd_verbosity,
470 "--detach",
471 "--pidfile=%s" % (gSwitchPid) ]
472
473if gArgs.ovs_vswitchd_log:
474 startV.append("--log-file=%s" % (gArgs.ovs_vswitchd_log))
475
476lcall(startV)
477
478if gArgs.vlog:
479 lcall(["xterm", "-T", "vswitchd-log", "-e", "tail", "-f",
480 gArgs.ovs_vswitchd_log],
481 popen=True, pidFile=gLogPid)
482
483
484# Add a bridge
485vsctl(["add-br", gArgs.bridge])
486ofctl(["show", gArgs.bridge])
487
488# Add Veths to bridge
489for idx in range(0, gArgs.port_count):
490 vsctl(["add-port", gArgs.bridge, "veth%s" % (idx*2)])
491
492# Check if loopback port added
493if gArgs.loopback:
494 lb_idx = gArgs.port_count * 2
495 vsctl(["add-port", gArgs.bridge, "veth%d" % (lb_idx)])
496 vsctl(["add-port", gArgs.bridge, "veth%d" % (lb_idx+1)])
497
498if gArgs.dpid:
499 vsctl(["set", "bridge", gArgs.bridge, "other-config:datapath-id=%s" % (
500 gArgs.dpid)])
501
502# Set controller
503vsctl(["set-controller", gArgs.bridge, "tcp:%s:%s" % (
504 gArgs.cip, gArgs.cport)])
505
506# Minimize default backoff for controller connections
507vsctl(["set", "Controller", gArgs.bridge,
508 "max_backoff=%s" % (gArgs.max_backoff)])
509
510
511ofctl(["show", gArgs.bridge])
512
513
514if gArgs.cli:
515 while True:
516 cmd = raw_input("[%s] ovs-ctl> " % gConfigSection)
517 if cmd and cmd != "":
518 args = cmd.split(" ")
519 if args[0] == "vsctl" or args[0] == "ovs-vsctl":
520 vsctl(args[1:])
521 elif args[0] == "ofctl" or args[0] == "ovs-ofctl":
522 ofctl(args[1:])
523 elif args[0] == "dumpflows" or args[0] == "flowtable":
524 ofctl(["dump-flows", "%s" % gArgs.bridge])
525 elif args[0] == "exit" or args[0] == "quit":
526 break;
527 elif args[0] == "kill":
528 gArgs.teardown = True
529 break
530 else:
531 print "unknown command '%s'" % args[0]
532
533
534if gArgs.teardown:
535 print "Killing OVS"
536 killall()
537
538
539
540
541
542