blob: 73b54f8ce48a9edc0ab568a286a42db4197163f8 [file] [log] [blame]
srikanth116e6e82014-08-19 07:22:37 -07001#!/usr/bin/python
2#
3# Copyright (c) 2013 Big Switch Networks, Inc.
4#
5# Licensed under the Eclipse Public License, Version 1.0 (the
6# "License"); you may not use this file except in compliance with the
7# License. You may obtain a copy of the License at
8#
9# http://www.eclipse.org/legal/epl-v10.html
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16#
17
18import os, atexit
19import glob
20from subprocess import Popen, PIPE, check_output, CalledProcessError
21import sys, traceback, socket
22from optparse import OptionParser
23from types import StringType
24import datetime
25import json
26import re
27import time
28import urllib2
29import httplib
30import fcntl, shutil
31import sys
32import tempfile
33import stat
34
35from string import Template
36from django.forms import ValidationError
37
38# Can't import from `import sdncon` -- causes circular dependency!
39SDN_ROOT = "/opt/sdnplatform" if not 'SDN_ROOT' in os.environ else os.environ['SDN_ROOT']
40
41# Big Switch Networks Enterprise OID
42BSN_ENTERPRISE_OID = '.1.3.6.1.4.1.37538'
43BSN_ENTERPRISE_OID_CONTROLLER = BSN_ENTERPRISE_OID + '.1'
44
45
46class OsWrapper():
47 """ This base class abstracts executing os binaries without using shell in a secure, hardened way.
48 Things to keep in mind when composing command templates - because we dont use shell, args to the
49 binaries are presented as items in the list, so no need to de-specialize special characters , for
50 example, if you want to echo something into a file, command is "echo -e abc\ndef\n" and not
51 "echo -e \"abc\ndef\n\"", which would then result in the quotes also to be echo'ed.
52 """
53 name = "none"
54 cmds_lst_for_set = []
55 cmds_lst_for_get = []
56 sudo_required_for_set = True
57 sudo_required_for_get = False
58 def __init__(self, name, this_cmds_list_for_set, this_cmds_list_for_get, is_sudo_reqd_for_set=True, is_sudo_required_for_get=False):
59 self.name = name
60 self.cmds_lst_for_set = this_cmds_list_for_set
61 self.cmds_lst_for_get = this_cmds_list_for_get
62 self.sudo_required_for_set = is_sudo_reqd_for_set
63 self.sudo_required_for_get = is_sudo_required_for_get
64
65 self.IP_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
66 self.DomainName_RE = re.compile(r'^([a-zA-Z0-9-]+.?)+$')
67
68 def validate_ip(self, value):
69 if not self.IP_RE.match(value) or len([x for x in value.split('.') if int(x) < 256]) != 4:
70 return False, "IP must be in dotted decimal format, 234.0.59.1"
71 return True, ""
72
73 def validate_domain(self, value):
74 if not self.DomainName_RE.match(value):
75 return False, "Invalid domain name"
76 return True, ""
77
78 def exec_cmds(self, cmds_lst, cmds_args, stdout_file_lst):
79 # traverse through the list of commands needed to set this
80 # cmd_args is a list of lists of args for each of the commands in the set list.
81 ret_out_err = {'err': [], 'out': []}
82 if len(cmds_lst) != len(cmds_args):
83 # commands to args mismatch, error out and return
84 # possibly throw an exception here as well
85 ret_out_err['err'].append("Command and args mismatch")
86 return ret_out_err
87 for indx in range(len(cmds_lst)):
88 #if self.sudo_required_for_set:
89 # cmd_string = "sudo "
90 #else:
91 # cmd_string = ""
92 cmd_string = ""
93 cmd_template = Template(cmd_string + cmds_lst[indx])
94 args_map = dict({})
95 for args_indx in range(len(cmds_args[indx])):
96 args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
97 cmd_string += cmds_args[indx][args_indx] + " "
98 full_cmd_string = cmd_template.substitute(args_map)
99 file_for_stdout = PIPE
100 if stdout_file_lst != None and stdout_file_lst[indx] != "":
101 file_for_stdout = open(stdout_file_lst[indx], 'a')
102 sub_proc_output = Popen(full_cmd_string.rsplit(" "), shell=False, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
103 ret_out_err['err'].append([sub_proc_output.stderr.read()])
104 if file_for_stdout == PIPE:
105 ret_out_err['out'].append([sub_proc_output.stdout.read()])
106 else:
107 file_for_stdout.close()
108 # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
109 return ret_out_err
110
111 def exec_cmds_new(self, cmds_lst, cmds_args, useShell=False, appendStdOut=True):
112 # traverse through the list of commands needed to set this
113 # cmd_args is a list of lists of args for each of the commands in the set list.
114 ret_out_err = {'err': [], 'out': []}
115 if len(cmds_lst) != len(cmds_args):
116 # commands to args mismatch, error out and return
117 # possibly throw an exception here as wella
118 # cmds_list is a list of dict maps - [{'bin_name': <bin>, 'args_lst': <args-list>, 'stdoutfile':<filename>},...]
119 ret_out_err['err'].append("Command and args mismatch")
120 print 'cmds_lst:', cmds_lst
121 print 'cmd_args:', cmds_args
122 return ret_out_err
123 for indx in range(len(cmds_lst)):
124 #if self.sudo_required_for_set:
125 # cmd_string = "sudo "
126 #else:
127 # cmd_string = ""
128 #cmd_string = ""
129 cmd_args_lst = [cmds_lst[indx]['bin_name']]
130
131 args_map = dict({})
132 for args_indx in range(len(cmds_args[indx])):
133 args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
134 #cmd_string += cmds_args[indx][args_indx] + " "
135 if 'args_lst' in cmds_lst[indx]:
136 for args_indx in range(len(cmds_lst[indx]['args_lst'])):
137 arg_template = Template(cmds_lst[indx]['args_lst'][args_indx])
138 full_arg_string = arg_template.substitute(args_map)
139 cmd_args_lst.append(full_arg_string)
140 file_for_stdout = PIPE
141 if 'stdoutfile' in cmds_lst[indx] and cmds_lst[indx]['stdoutfile'] != "":
142 fMode = 'a'
143 if not appendStdOut:
144 fMode = 'r+'
145 file_for_stdout = open(cmds_lst[indx]['stdoutfile'] , fMode)
146 sub_proc_output = Popen(cmd_args_lst, shell=useShell, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
147 ret_out_err['err'].append(sub_proc_output.stderr.read())
148 if file_for_stdout == PIPE:
149 ret_out_err['out'].append(sub_proc_output.stdout.read())
150 else:
151 file_for_stdout.close()
152 # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
153 return ret_out_err
154
155 def set(self, cmds_args, stdout_file_lst):
156 return self.exec_cmds(self.cmds_lst_for_set, cmds_args, stdout_file_lst)
157 def get(self, cmds_args, stdout_file_lst):
158 return self.exec_cmds(self.cmds_lst_for_get, cmds_args, stdout_file_lst)
159
160 def set_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
161 if cmds_args_lst == []:
162 cmds_args_lst = self.cmds_lst_for_set
163 return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
164 def get_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
165 if cmds_args_lst == []:
166 cmds_args_lst = self.cmds_lst_for_get
167 return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
168
169
170def validate_input1(validator, value): #temporarily disabling this - TBD
171 try:
172 validator(value)
173 except ValidationError, _err:
174 return False
175 return True
176
177
178def validate_input(validator, value):
179 a = True
180 #try:
181 #a, b = validator(value)
182 #except ValidationError, err:
183 # return False
184 return a
185
186
187def dotted_decimal_to_int(ip):
188 """
189 Converts a dotted decimal IP address string to a 32 bit integer
190 """
191 bytes = ip.split('.')
192 ip_int = 0
193 for b in bytes:
194 ip_int = (ip_int << 8) + int(b)
195 return ip_int
196
197
198def same_subnet(ip1, ip2, netmask):
199 """
200 Checks whether the two ip addresses are on the same subnet as
201 determined by the netmask argument. All of the arguments are
202 dotted decimal IP address strings.
203 """
204 if ip1 == '' or ip2 == '' or netmask == '':
205 return False
206 ip1_int = dotted_decimal_to_int(ip1)
207 ip2_int = dotted_decimal_to_int(ip2)
208 netmask_int = dotted_decimal_to_int(netmask)
209 return (ip1_int & netmask_int) == (ip2_int & netmask_int)
210
211
212class NetworkConfig(OsWrapper):
213 def __init__(self, name="network_config"):
214 OsWrapper.__init__(self, name, [], [])
215
216
217 def rewrite_etc_network_interfaces(self, controller, interfaces, ret_result):
218 """
219 Return True when the /etc/network/interfaces is rewritten. Return False otherwise.
220 The file is rewritten only when the intended new contents is different from
221 the old contents, this is an attempt to not purturb the network unless something
222 really changed.
223 """
224
225 gateway = controller['fields']['default_gateway']
226 if (gateway != ''):
227 (r, m) = self.validate_ip(gateway)
228 if not r:
229 ret_result['err'].append("Default gateway: %s" % m)
230 gateway = ''
231
232 changed = False
233 new_conf = []
234 new_conf.append("# WARNING this file is automanaged by BSN controller\n")
235 new_conf.append("# DO NOT EDIT here, use CLI with 'configure'\n")
236 new_conf.append("auto lo\niface lo inet loopback\n\n")
237 for interface in interfaces:
238 if (interface['fields']['controller'] == controller['pk']):
239 num = interface['fields']['number']
240 new_conf.append("auto eth{0}\n".format(num))
241 if (interface['fields']['mode'] == 'dhcp'):
242 new_conf.append("iface eth{0} inet dhcp\n".format(num))
243 else:
244 ip = interface['fields']['ip']
245 netmask = interface['fields']['netmask']
246 if (ip != ""):
247 (r, m) = self.validate_ip(ip)
248 if not r:
249 ret_result['err'].append(
250 "Ethernet %s IP address %s: %s" % (num, ip, m))
251 ip = ""
252 if (netmask != ""):
253 (r, m) = self.validate_ip(netmask)
254 if not r:
255 ret_result['err'].append(
256 "Ethernet %s netmask %s: %s" % (num, netmask, m))
257 netmask = ""
258
259 new_conf.append("iface eth{0} inet static\n".format(num))
260 if (ip != ""):
261 new_conf.append(" address {0}\n".format(ip))
262 if (netmask != ""):
263 new_conf.append(" netmask {0}\n".format(netmask))
264 if same_subnet(gateway, ip, netmask):
265 new_conf.append(" gateway {0}\n".format(gateway))
266 new_conf.append("\n")
267
268 f = open("/etc/network/interfaces", "r")
269 if (''.join(new_conf) != f.read()):
270 f.close()
271 f = open("/etc/network/interfaces", "w")
272 f.write(''.join(new_conf))
273 changed = True
274 f.close()
275 return changed
276
277 def rewrite_etc_resolve_conf(self, controller, dns_servers, ret_result):
278 """
279 Return True when the /etc/resolv.conf is rewritten. Return False otherwise.
280 The file is rewritten only when the intended new contents is different from
281 the old contents, this is an attempt to not purturb the network unless something
282 really changed.
283 """
284
285 changed = False
286 new_conf = []
287 domain_name = controller['fields']['domain_name']
288 if (domain_name != ""):
289 new_conf.append("domain {0}\nsearch {1}\n".format(domain_name,
290 domain_name))
291
292 if (controller['fields']['domain_lookups_enabled'] == True):
293 for dns in dns_servers:
294 if (dns['fields']['controller'] == controller['pk']):
295 ip = dns['fields']['ip']
296 if (ip != ""):
297 (r, m) = self.validate_ip(ip)
298 if not r:
299 ret_result['err'].append("Name server %s: %s" % (ip, m))
300 ip = ""
301 if (ip != ""):
302 new_conf.append("nameserver {0}\n".format(ip))
303
304 f = open("/etc/resolv.conf", "r")
305 if (''.join(new_conf) != f.read()):
306 f.close()
307 f = open("/etc/resolv.conf", "w")
308 f.write(''.join(new_conf))
309 changed = True
310
311 f.close()
312 return changed
313
314 def set(self, args_list):
315 # args_list: [controllers, controlleDomainServers, controllerInterfaces]
316 # controllerInterfaces may be empty, which requess no rewrite
317 # of /etc/network/insterfaces
318 ifs_rewrite = False if len(args_list) < 3 else True
319
320 ret_result = {'err': [], 'out': []}
321 controller = json.loads(args_list[0])[0]
322 network_restart = True
323
324 rc_changed = False
325 ni_changed = False
326
327 try:
328 domain_name = controller['fields']['domain_name']
329 new_rc = True
330 if domain_name != "":
331 (r, m) = self.validate_domain(domain_name)
332 if not r:
333 ret_result['err'].append("Search domain %s: %s"
334 % (domain_name, m))
335 new_rc = False
336
337 if new_rc:
338 rc_changed = self.rewrite_etc_resolve_conf(controller,
339 json.loads(args_list[1]),
340 ret_result)
341
342 if ifs_rewrite:
343 ni_changed = self.rewrite_etc_network_interfaces(controller,
344 json.loads(args_list[2]),
345 ret_result)
346 except Exception, _e:
347 network_restart = False
348 traceback.print_exc()
349
350 # don't restart the network config if resolv.conf was only updated
351 if network_restart and ni_changed:
352 # Kill any dhclients that might be hanging around. When
353 # switching from dhcp to static config, networking restart
354 # won't kill these since the file has already been
355 # rewritten with a config that doesn't include DHCP. A
356 # cleaner fix for this is to stop networking before
357 # writing the file and then start it after writing the
358 # file. Longer-term, it might be better to use
359 # NetworkManager APIs for all of this rather than trying
360 # to manage this config file.
361 k = Popen(["/usr/bin/killall", "dhclient3"],
362 stdout=PIPE, stderr=PIPE)
363 k.wait()
364
365 p = Popen(["/usr/sbin/invoke-rc.d",
366 "networking",
367 "restart"],
368 stdout=PIPE, stderr=PIPE)
369 p.wait()
370
371 if (0 != p.returncode):
372 out = p.stdout.read()
373 ret_result['err'].append("Network restart failed:"
374 "%d: %s" % (p.returncode, out))
375
376 # Restart the discover-ip since the controller-interface table has been modified
377 ip = Popen(["/usr/sbin/service",
378 "discover-ip",
379 "restart"],
380 stdout=PIPE, stderr=PIPE)
381
382 # not concerned with error messages from the requested command
383
384 return ret_result
385
386class DirectNetworkConfig(NetworkConfig):
387 def __init__(self, name="direct_network_config"):
388 NetworkConfig.__init__(self, name)
389
390 def set(self, args_list):
391 gateway = ''
392 ip = ''
393 netmask = ''
394 domain_name = ''
395 dns1 = ''
396 dns2 = ''
397 domain_lookups_enabled = False
398 if args_list[0] == 'static':
399 ip = args_list[1]
400 netmask = args_list[2]
401 gateway = args_list[3]
402 if len(args_list) >= 5:
403 domain_name = args_list[4]
404 if len(args_list) >= 6:
405 domain_lookups_enabled = True
406 dns1 = args_list[5]
407 if len(args_list) >= 7:
408 dns2 = args_list[6]
409 else:
410 if len(args_list) >= 2:
411 domain_name = args_list[1]
412 if len(args_list) >= 3:
413 domain_lookups_enabled = True
414 dns1 = args_list[2]
415 if len(args_list) >= 4:
416 dns2 = args_list[3]
417
418 controller_interface=[{'fields' : {'id' : "localhost|ethernet|0",
419 'type' : "ethernet", 'number' : 0,
420 'mode' : args_list[0], 'ip' : ip,
421 'netmask' : netmask, 'controller' : 'localhost'}}]
422 nameservers = []
423 if dns1 != '':
424 nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 1, 'ip' : dns1}})
425 if dns2 != '':
426 nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 2, 'ip' : dns2}})
427 controller=[{'fields' : {'id' : "localhost", 'domain_name' : domain_name,
428 'default_gateway' : gateway,
429 'domain_lookups_enabled' : domain_lookups_enabled}, 'pk' : 'localhost'}]
430 return NetworkConfig.set(self, [json.dumps(controller), json.dumps(nameservers),
431 json.dumps(controller_interface)])
432
433
434class UfwCommand(OsWrapper):
435 def __init__(self, name = "executeufwcommand"):
436 OsWrapper.__init__(self, name, [], [])
437 def set(self, arg_list):
438 args = arg_list[0].split(" ")
439 self.cmds_lst_for_set = [{'bin_name' : '/usr/sbin/ufw', 'args_lst' : args}]
440 return OsWrapper.set_new(self, [], [[]])
441
442NTP_CONF = """tinker panic 0
443driftfile /var/lib/ntp/ntp.drift
444
445# Enable this if you want statistics to be logged.
446#statsdir /var/log/ntpstats/
447
448statistics loopstats peerstats clockstats
449filegen loopstats file loopstats type day enable
450filegen peerstats file peerstats type day enable
451filegen clockstats file clockstats type day enable
452
453# Specify one or more NTP servers.
454
455# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
456# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
457# more information.
458server %s
459
460# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
461# details. The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
462# might also be helpful.
463#
464# Note that "restrict" applies to both servers and clients, so a configuration
465# that might be intended to block requests from certain clients could also end
466# up blocking replies from your own upstream servers.
467
468# By default, exchange time with everybody, but don't allow configuration.
469restrict -4 default kod notrap nomodify nopeer noquery
470restrict -6 default kod notrap nomodify nopeer noquery
471
472# Local users may interrogate the ntp server more closely.
473restrict 127.0.0.1
474restrict ::1
475
476# Clients from this (example!) subnet have unlimited access, but only if
477# cryptographically authenticated.
478#restrict 192.168.123.0 mask 255.255.255.0 notrust
479
480
481# If you want to provide time to your local subnet, change the next line.
482# (Again, the address is an example only.)
483#broadcast 192.168.123.255
484
485# If you want to listen to time broadcasts on your local subnet, de-comment the
486# next lines. Please do this only if you trust everybody on the network!
487#disable auth
488#broadcastclient
489"""
490
491class SetNtpServer(OsWrapper):
492 def __init__(self, name = "setntpserver"):
493 OsWrapper.__init__(self, name, [], [])
494 def set(self, args_list):
495 ret_result = {'err': [], 'out': []}
496 server = "127.127.1.0"
497 if (len(args_list) > 0 and
498 args_list[0] is not None and
499 args_list[0] != ""):
500 server = str(args_list[0])
501 ntpconf = NTP_CONF % server
502 changed = False
503
504 f = open("/etc/ntp.conf", "r")
505 if (''.join(ntpconf) != f.read()):
506 f.close()
507 f = open("/etc/ntp.conf", "w")
508 f.write(''.join(ntpconf))
509 changed = True
510
511 if changed:
512 ntp = Popen(["/usr/sbin/service",
513 "ntp",
514 "restart"],
515 stdout=PIPE, stderr=PIPE)
516 return ret_result
517
518
519class SetTimezone(OsWrapper):
520 def __init__(self, name = "settimezone"):
521 OsWrapper.__init__(self, name, [], [])
522 def set(self, args_list):
523 self.cmds_lst_for_set = [
524 {'bin_name' : '/bin/bash',
525 'args_lst' : ['-c', 'echo "%s" >/etc/timezone' % (str(args_list[0]), )]},
526 {'bin_name' : '/bin/bash',
527 'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
528 {'bin_name' : '/sbin/initctl',
529 'args_lst' : ['restart', 'rsyslog', ]},
530 ]
531 return OsWrapper.set_new(self, [], [[], [], []])
532
533
534class UnsetTimezone(OsWrapper):
535 def __init__(self, name = "unsettimezone"):
536 OsWrapper.__init__(self, name, [], [])
537 def set(self, args_list):
538 self.cmds_lst_for_set = [
539 {'bin_name' : '/bin/bash',
540 'args_lst' : ['-c', 'echo "Etc/UTC" >/etc/timezone']},
541 {'bin_name' : '/bin/bash',
542 'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
543 ]
544 return OsWrapper.set_new(self, [], [[], []])
545
546class SetSyslogServer(OsWrapper):
547 def __init__(self, name = "setsyslogserver"):
548 OsWrapper.__init__(self, name, [], [])
549 def set(self, args_list):
550 self.cmds_lst_for_set = [
551 {'bin_name' : '/bin/sed',
552 'args_lst' : ['-i', '/^*.* @/d', '/etc/rsyslog.conf']},
553 {'bin_name' : '/bin/echo',
554 'args_lst' : ['*.' + str(args_list[1]) + ' @' + str(args_list[0])], 'stdoutfile' : '/etc/rsyslog.conf'},
555 {'bin_name' : '/sbin/initctl',
556 'args_lst' : ['restart', 'rsyslog']},
557 ]
558 return OsWrapper.set_new(self, [], [[], [], []])
559
560class UnsetSyslogServer(OsWrapper):
561 def __init__(self, name = "unsetsyslogserver"):
562 OsWrapper.__init__(self, name, [], [])
563 def set(self, args_list):
564 self.cmds_lst_for_set = [
565 {'bin_name' : '/bin/sed',
566 'args_lst' : ['-i', '/^*./d', '/etc/rsyslog.conf']},
567 {'bin_name' : '/sbin/initctl',
568 'args_lst' : ['restart', 'rsyslog']}
569 ]
570 return OsWrapper.set_new(self, [], [[], []])
571
572class DateTime(OsWrapper):
573 def __init__(self, name = "getdatetime"):
574 OsWrapper.__init__(self, name, [], [])
575 def set(self, args_list):
576 # If we're getting or setting the clock then the first argument
577 # should be either 'utc' or 'local' to indicate whether or not to
578 # use local time.
579 # If we're setting the clock there's a second argument which is the
580 # time to set, formatted as shown in set_cmd_args below.
581 set_parts = args_list[1].split(':')
582 set_param = '%2.2s%2.2s%2.2s%2.2s%4.4s.%2.2s' % (
583 set_parts[1],
584 set_parts[2],
585 set_parts[3],
586 set_parts[4],
587 set_parts[0],
588 set_parts[5],
589 )
590 cmd_args = [str(set_param)]
591 if str(args_list[0]).lower() == 'utc':
592 cmd_args.insert(0, '-u')
593 #date_info = args_list[0]
594 #is_utc = args_list[1]
595 #date_str = "%s:%s:%s:%s:%s:%s:%s"
596 self.cmds_lst_for_set = [
597 {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
598 ]
599 OsWrapper.set_new(self, [], [[]])
600 return self.get(args_list)
601
602 def get(self, args_list):
603 cmd_args = ['+%Y:%m:%d:%H:%M:%S:%Z']
604 if str(args_list[0]).lower() == 'utc':
605 cmd_args.insert(0, '-u')
606 self.cmds_lst_for_get = [
607 {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
608 ]
609 return OsWrapper.get_new(self, [], [[]])
610
611class SetControllerId(OsWrapper):
612 def __init__(self, name = "setcontrollerid"):
613 OsWrapper.__init__(self, name, [], [])
614 def set(self, args_list):
615 self.cmds_lst_for_set = [
616 {'bin_name' : '/bin/sed',
617 'args_lst' : ['-i', '/^controller-id=/d', "%s/run/boot-config" % SDN_ROOT]},
618 {'bin_name' : '/bin/bash',
619 'args_lst' : ['-c', 'echo "controller-id=%s" >> %s/run/boot-config' % (
620 str(args_list[0]), SDN_ROOT)
621 ]
622 },
623 ]
624 return OsWrapper.set_new(self, [], [[], []])
625
626class HAFailback(OsWrapper):
627 def __init__(self, name = "hafailback"):
628 OsWrapper.__init__(self, name, [], [])
629 def set(self, args_list):
630 self.cmds_lst_for_set = [
631 {'bin_name' : '/bin/touch',
632 'args_lst' : ["%s/force-one-time-health-check-failure" % SDN_ROOT]},
633 ]
634 return OsWrapper.set_new(self, [], [[]])
635
636#class HAFailover(OsWrapper):
637# def __init__(self, name = "hafailover"):
638# OsWrapper.__init__(self, name, [], [])
639# def set(self, args_list):
640# self.cmds_lst_for_set = [
641# {'bin_name' : '/bin/bash',
642# 'args_lst' : ["%s/sys/bin/ha-failover.sh" % SDN_ROOT, str(args_list[0])]},
643# ]
644# return OsWrapper.set_new(self, [], [[]])
645
646class SetVrrpVirtualRouterId(OsWrapper):
647 def __init__(self, name = "setvrrpvirtualrouterid"):
648 OsWrapper.__init__(self, name, [], [])
649 def set(self, args_list):
650 self.cmds_lst_for_set = [
651 {'bin_name' : '/bin/bash',
652 'args_lst' : ["%s/sys/bin/set-vrrp-virtual-router-id.sh" % SDN_ROOT, str(args_list[0])]},
653 ]
654 return OsWrapper.set_new(self, [], [[]])
655
656
657def write_controller_restarted():
658 """
659 Write the controller started file with the current timestamp + 5 seconds. This will
660 ensure that health check script ignores the state of sdnplatform for 5 seconds
661 from current time
662 """
663 f = open("/var/run/sdnplatform-healthcheck-disabled", "w")
664 # write time converted to int and then string
665 f.write(str(5 + long(time.time())))
666 f.close()
667
668
669class SetStaticFlowOnlyConfig(OsWrapper):
670 def __init__(self, name = "setstaticflowonlyconfig"):
671 OsWrapper.__init__(self, name, [], [])
672 def set(self, args_list):
673
674 # write the controller started file. do it before actually resetting
675 # so that there is no race condition with health check script
676 try:
677 write_controller_restarted()
678 except Exception, _e:
679 traceback.print_exc()
680
681 self.cmds_lst_for_set = [
682 {'bin_name' : '/bin/touch',
683 'args_lst' : ["%s/feature/staticflowonlyconfig" % SDN_ROOT]},
684 {'bin_name' : '/sbin/initctl',
685 'args_lst' : ['restart', 'sdnplatform']},
686 ]
687 return OsWrapper.set_new(self, [], [[], [], []])
688
689class RestartSDNPlatform(OsWrapper):
690 def __init__(self, name = "restartsdnplatform"):
691 OsWrapper.__init__(self, name, [], [])
692 def set(self, args_list):
693
694 # write the controller started file. do it before actually resetting
695 # so that there is no race condition with health check script
696 try:
697 write_controller_restarted()
698 except Exception, _e:
699 traceback.print_exc()
700
701 self.cmds_lst_for_set = [
702 {'bin_name' : '/sbin/initctl',
703 'args_lst' : ['restart', 'sdnplatform']},
704 ]
705 return OsWrapper.set_new(self, [], [[]])
706
707class AbortUpgrade(OsWrapper):
708 def __init__(self, name = "abortupgrade"):
709 OsWrapper.__init__(self, name, [], [])
710 def set(self, args_list):
711
712 self.cmds_lst_for_set = [
713 {'bin_name' : '/opt/sdnplatform/sys/bin/abort_upgrade.sh',
714 'args_lst' : []},
715 ]
716 return OsWrapper.set_new(self, [], [[]])
717
718class SetDefaultConfig(OsWrapper):
719 def __init__(self, name = "setdefaultconfig"):
720 OsWrapper.__init__(self, name, [], [])
721 def set(self, args_list):
722
723 # write the controller started file. do it before actually resetting
724 # so that there is no race condition with health check script
725 try:
726 write_controller_restarted()
727 except Exception, _e:
728 traceback.print_exc()
729
730 self.cmds_lst_for_set = [
731 {'bin_name' : '/bin/rm',
732 'args_lst' : ['-f', "%s/feature/staticflowonlyconfig" % SDN_ROOT]},
733 {'bin_name' : '/sbin/initctl',
734 'args_lst' : ['restart', 'sdnplatform']},
735 # if there are other configs their flag files need to be deleted here too
736 ]
737 return OsWrapper.set_new(self, [], [[], [], []])
738
739class SetHostname(OsWrapper):
740 def __init__(self, name = "sethostname"):
741 OsWrapper.__init__(self, name, [], [])
742 def set(self, args_list):
743 hostname = str(args_list[0])
744 self.cmds_lst_for_set = [
745 # replace hostname from /etc/hosts
746 {'bin_name' : '/bin/sed',
747 'args_lst' : ['-i',
748 r's/^127\.0\.1\.1 .*$$/127.0.1.1 %s/' % hostname,
749 '/etc/hosts']},
750 # populate /etc/hosts
751 {'bin_name' : '/bin/bash',
752 'args_lst' : ['-c', 'echo "%s" >/etc/hostname' % hostname]},
753 # tell the system about the hostname
754 {'bin_name' : '/bin/hostname',
755 'args_lst' : ['-b', '-F', '/etc/hostname'] },
756 ]
757 return OsWrapper.set_new(self, [], [[], [], [],])
758
759# In PAM, "auth" == authentication (surprise!)
760# XXX roth -- maybe use PAP instead?
761
762# 'sufficient' --> tacacs+ and local authentication are enabled
763AUTHN_TPL = """\
764auth [default=1 success=ignore] pam_succeed_if.so uid >= 10000
765auth sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login protocol=ip login=login
766"""
767
768# In PAM, "account" == authorization
769AUTHZ_TPL = """\
770account [default=1 success=ignore] pam_succeed_if.so uid >= 10000
771account sufficient pam_tacplus.so %(secrets)s %(timeout)s service=login service_av=shell protocol=ip login=login
772"""
773
774# In PAM, "session" == accounting
775ACCT_TPL = """\
776session sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login service_av=shell protocol=ip
777"""
778
779# XXX roth -- keep this in sync with the current release
780# of /etc/pam.d/sshd
781SSHD_TACPLUS_TPL = """\
782# PAM configuration for the Secure Shell service
783
784# Read environment variables from /etc/environment and
785# /etc/security/pam_env.conf.
786auth required pam_env.so # [1]
787# In Debian 4.0 (etch), locale-related environment variables were moved to
788# /etc/default/locale, so read that as well.
789auth required pam_env.so envfile=/etc/default/locale
790
791# Standard Un*x authentication.
792%(authn)s
793
794# Disallow non-root logins when /etc/nologin exists.
795account required pam_nologin.so
796
797# Uncomment and edit /etc/security/access.conf if you need to set complex
798# access limits that are hard to express in sshd_config.
799# account required pam_access.so
800
801# Standard Un*x authorization.
802%(authz)s
803
804# Standard Un*x session setup and teardown.
805%(acct)s
806
807# Print the message of the day upon successful login.
808session optional pam_motd.so # [1]
809
810# Print the status of the user's mailbox upon successful login.
811session optional pam_mail.so standard noenv # [1]
812
813# Set up user limits from /etc/security/limits.conf.
814session required pam_limits.so
815
816# Set up SELinux capabilities (need modified pam)
817# session required pam_selinux.so multiple
818
819# Standard Un*x password updating.
820@include common-password
821"""
822
823class TacacsPlusConfig(OsWrapper):
824
825 def __init__(self, name="tacacs_plus_config"):
826 OsWrapper.__init__(self, name, [], [])
827 self.config = {}
828 self.hosts = []
829 self.result = dict(err=[], out=[])
830
831 def isEnabled(self):
832 """Is TACACS+ enabled?
833
834 If any of authn/authz/acct is set, *and* there is a non-empty
835 set of TACACS+ hosts, then we should enable the PAM plugin.
836 """
837
838 if (self.hosts
839 and (self.config['fields']['tacacs_plus_authn']
840 or self.config['fields']['tacacs_plus_authz']
841 or self.config['fields']['tacacs_plus_acct'])):
842 return True
843
844 return False
845
846 def disablePamDefault(self):
847 """Disable the default PAM setup.
848
849 This is installed by the initial configuration scripts
850 for the libpam-tacplus DEB.
851 """
852
853 if not os.path.exists("/usr/share/pam-configs/tacplus"):
854 return
855
856 cmd = ("/usr/sbin/pam-auth-update", "--remove", "tacplus",)
857 pipe = Popen(cmd, stdout=PIPE, stderr=PIPE)
858 out, err = pipe.communicate()
859 code = pipe.wait()
860
861 out = (out or "").strip().split("\n")
862 err = (err or "").strip().split("\n")
863
864 if not code:
865 self.result['out'].append("disabled tacplus via pam-auth-update\n")
866 else:
867 self.result['out'].extend([l + "\n" for l in out])
868 self.result['err'].extend([l + "\n" for l in err])
869 self.result['err'].append("pam-auth-update failed\n")
870
871 def disablePam(self):
872 """Disable the TACACS+ PAM plugin."""
873 m = dict(authn="@include common-auth",
874 authz="@include common-account",
875 acct="@include common-session")
876 self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
877
878 def readLocked(self, path):
879
880 fd = open(path, "r")
881
882 fcntl.lockf(fd, fcntl.LOCK_SH)
883 try:
884 buf = fd.read()
885 except Exception, what:
886 fcntl.lockf(fd, fcntl.LOCK_UN)
887 fd.close()
888 self.result['err'].append(str(what) + "\n")
889 self.result['err'].append("cannot read %s\n" % path)
890 return None
891 fcntl.lockf(fd, fcntl.LOCK_UN)
892
893 fd.close()
894
895 return buf
896
897 def writeLocked(self, path, buf, backup=True):
898
899 if backup:
900 shutil.copy2(path, path + "-")
901
902 fd = open(path, "w")
903
904 fcntl.lockf(fd, fcntl.LOCK_EX)
905 try:
906 fd.write(buf)
907 except Exception, what:
908 fcntl.lockf(fd, fcntl.LOCK_UN)
909 fd.close()
910 self.result['err'].append(str(what) + "\n")
911 self.result['err'].append("cannot write %s\n" % path)
912 return
913 fcntl.lockf(fd, fcntl.LOCK_UN)
914
915 fd.close()
916
917 def enableNss(self):
918 """Enable the NSS plugin."""
919
920 buf = self.readLocked("/etc/nsswitch.conf")
921 if buf is None: return
922
923 p = buf.find("\npasswd:")
924 q = buf.find("\n", p+8)
925 if p < 0 or q < 0:
926 self.result['err'].append("cannot find passwd entry"
927 " in /etc/nsswitch.conf\n")
928 return
929
930 f = buf[p+8:q]
931 if "remoteuser" in f:
932 self.result['out'].append("remoteuser already enabled"
933 " in /etc/nssswitch.conf\n")
934 return
935
936 self.result['out'].append("enabling remoteuser"
937 " in /etc/nssswitch.conf\n")
938 f = " " + f.strip() + " remoteuser"
939 buf = buf[:p+8] + f + buf[q:]
940
941 self.writeLocked("/etc/nsswitch.conf", buf)
942
943 def disableNss(self):
944 """Disable the NSS plugin."""
945
946 buf = self.readLocked("/etc/nsswitch.conf")
947 if buf is None: return
948
949 p = buf.find("\npasswd:")
950 q = buf.find("\n", p+8)
951 if p < 0 or q < 0:
952 self.result['err'].append("cannot find passwd entry"
953 " in /etc/nsswitch.conf\n")
954 return
955
956 l = buf[p+8:q].strip().split()
957 if "remoteuser" not in l:
958 self.result['out'].append("remoteuser already disabled"
959 " in /etc/nssswitch.conf\n")
960 return
961
962 self.result['out'].append("disabling remoteuser"
963 " in /etc/nssswitch.conf\n")
964 l.remove("remoteuser")
965 f = " " + " ".join(l)
966 buf = buf[:p+8] + f + buf[q:]
967
968 self.writeLocked("/etc/nsswitch.conf", buf)
969
970 def enablePam(self):
971 """Enable the TACACS+ PAM plugin.
972
973 * construct consolidate server and secret lists
974 * generate a timeout line
975 * pick sufficient/required clauses as indicated by enable
976 flags in the JSON
977
978 See
979 http://tacplus.git.sourceforge.net/git/gitweb.cgi?p=tacplus/tacplus;a=blob_plain;f=README;hb=HEAD
980 """
981
982 # disable TACACS+ while updating
983 self.disableNss()
984 self.disablePam()
985
986 # XXX roth -- field is 'ip', but since it is the primary key,
987 # it get mapped to 'pk'. Go figure.
988 def svrClause(h):
989 self.result['out'].append("enabling host %s\n" % h['pk'])
990 return 'server=%s' % h['pk']
991
992 servers = " ".join([svrClause(h) for h in self.hosts])
993
994 def keyClause(h):
995 """Secret for this host, possibly the global one."""
996 if h['fields']['key']:
997 return "secret=%s" % h['fields']['key']
998 if self.config['fields']['key']:
999 return "secret=%s" % self.config['fields']['key']
1000 return ""
1001
1002 secrets = " ".join([keyClause(h) for h in self.hosts])
1003
1004 m = dict(servers=servers, secrets=secrets)
1005
1006 if self.config['fields']['timeout']:
1007 m['timeout'] = 'timeout=%s' % self.config['fields']['timeout']
1008 else:
1009 m['timeout'] = ''
1010
1011 isLocal = self.config['fields']['local_authn']
1012 isTacacs = self.config['fields']['tacacs_plus_authn']
1013
1014 authn = []
1015 if isTacacs:
1016 authn.append(AUTHN_TPL % m)
1017 if isLocal:
1018 authn.append("@include common-auth")
1019
1020 isLocal = self.config['fields']['local_authz']
1021 isTacacs = self.config['fields']['tacacs_plus_authz']
1022
1023 authz = []
1024 if isTacacs:
1025 authz.append(AUTHZ_TPL % m)
1026 if isLocal:
1027 authz.append("@include common-account")
1028
1029 isTacacs = self.config['fields']['tacacs_plus_acct']
1030
1031 acct = []
1032 if isTacacs:
1033 acct.append(ACCT_TPL % m)
1034 acct.append("@include common-session")
1035
1036 # enable userid lookups
1037 self.enableNss()
1038
1039 # write out sshd PAM config as a final step to enable it
1040 m = dict(authn="\n".join(authn),
1041 authz="\n".join(authz),
1042 acct="\n".join(acct))
1043 self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
1044
1045 def set(self, args_list):
1046
1047 self.config = json.loads(args_list[0])[0]
1048 self.hosts = json.loads(args_list[1])
1049
1050 self.disablePamDefault()
1051
1052 if not self.isEnabled():
1053 try:
1054 self.disableNss()
1055 self.disablePam()
1056 self.result['out'].append('TACACS+ (via PAM) is now disabled\n')
1057 except Exception:
1058 traceback.print_exc()
1059 self.result['err'].append('TACACS+ (via PAM) disable failed\n')
1060 return self.result
1061
1062 # else, enable the PAM module
1063 try:
1064 self.enableNss()
1065 self.enablePam()
1066 self.result['out'].append('TACACS+ (via PAM) is now enabled\n')
1067 except Exception:
1068 # XXX roth -- maybe back out here and *disable* PAM
1069 # so that we do not end up with a broken PAM config
1070 traceback.print_exc()
1071 self.result['err'].append('TACACS+ (via PAM) enable failed\n')
1072
1073 return self.result
1074
1075#
1076# get_system_version_string
1077#
1078# Gets the version string of the controller.
1079# Reference implementation is in sdncon/rest/views/do_system_version
1080#
1081def get_system_version_string():
1082 version = "SDN OS 1.0 - custom version"
1083 try:
1084 f = open("%s/release" % SDN_ROOT, 'r')
1085 version = f.read()
1086 f.close()
1087 except:
1088 pass
1089 return version
1090
1091#
1092# rewrite_etc_snmpd_conf
1093#
1094# API to rewrite the /etc/snmp/snmpd.conf file based on latest config
1095#
1096def rewrite_etc_snmpd_conf(community, location, contact, ret_result):
1097 """
1098 Return True when the /etc/snmp/snmpd.conf is rewritten. Return False
1099 otherwise. The file is rewritten only when the intended new contents
1100 is different from the old contents, this is an attempt to not restart
1101 the snmp agent unless something really changed.
1102 """
1103
1104 changed = False
1105 new_conf = []
1106 # start with default configuration of the file
1107 new_conf.append("# Default Configuration for the SNMP daemon\n")
1108 new_conf.append("# Agent address\n")
1109 new_conf.append("agentAddress udp:161,udp6:[::1]:161\n")
1110 new_conf.append("# System Object ID\n")
1111 new_conf.append("sysObjectID %s\n" % (BSN_ENTERPRISE_OID_CONTROLLER))
1112 new_conf.append("# System Description\n")
1113 new_conf.append("sysDescr %s\n"%(get_system_version_string()))
1114
1115 #add community, location, contact information to the file if not there already
1116 if community != '':
1117 new_conf.append("rocommunity %s\n" % community)
1118 if location != '':
1119 new_conf.append("sysLocation %s\n" % location)
1120 if contact != '':
1121 new_conf.append("sysContact %s\n" % contact)
1122
1123 f = open("/etc/snmp/snmpd.conf", "r")
1124 if (''.join(new_conf) != f.read()):
1125 f.close()
1126 f = open("/etc/snmp/snmpd.conf", "w")
1127 f.write(''.join(new_conf))
1128 changed = True
1129
1130 f.close()
1131
1132 return changed
1133
1134#
1135# One of the entry in the snmp server configuration changed
1136#
1137class SetSnmpServerConfig(OsWrapper):
1138 def __init__(self, name = "setsnmpserverconfig"):
1139 OsWrapper.__init__(self, name, [], [])
1140
1141
1142 def set(self, args_list):
1143 # args_list: [server_enable, community, location, contact, enable_changed]
1144 print "SnmpServerConfig Args List: ", args_list
1145 server_enable = args_list[0]
1146 community = args_list[1]
1147 location = args_list[2]
1148 contact = args_list[3]
1149 enable_changed = args_list[4]
1150
1151 ret_result = {'err': [], 'out': []}
1152
1153 try:
1154 # rewrite /etc/snmp/snmpd.conf file
1155 need_restart = rewrite_etc_snmpd_conf(community, location, contact, ret_result)
1156 except Exception, _e:
1157 need_restart = False
1158 traceback.print_exc()
1159
1160 if server_enable == 'True' and (need_restart or enable_changed == 'True'):
1161 self.cmds_lst_for_set = [
1162 # set snmpdrun=yes
1163 {'bin_name' : '/bin/sed',
1164 'args_lst' : ['-i', 's/SNMPDRUN=no/SNMPDRUN=yes/',
1165 '/etc/default/snmpd']},
1166 # restart snmpd service
1167 {'bin_name' : '/usr/sbin/service',
1168 'args_lst' : ['snmpd', 'restart']},
1169 ]
1170 return OsWrapper.set_new(self, [], [[], []])
1171
1172 elif server_enable == 'False' and enable_changed == 'True':
1173 self.cmds_lst_for_set = [
1174 # set snmpdrun=no
1175 {'bin_name' : '/bin/sed',
1176 'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
1177 '/etc/default/snmpd']},
1178 # stop snmpd service
1179 {'bin_name' : '/usr/sbin/service',
1180 'args_lst' : ['snmpd', 'stop']},
1181 ]
1182 return OsWrapper.set_new(self, [], [[], []])
1183
1184 return ret_result
1185
1186
1187#
1188# The row entry in the snmp server config table was default and deleted
1189#
1190class UnsetSnmpServerConfig(OsWrapper):
1191 def __init__(self, name = "unsetsnmpserverconfig"):
1192 OsWrapper.__init__(self, name, [], [])
1193
1194 def set(self, args_list):
1195 # args_list: []
1196 ret_result = {'err': [], 'out': []}
1197
1198 try:
1199 # rewrite /etc/snmp/snmpd.conf to default
1200 rewrite_etc_snmpd_conf('', '', '', ret_result)
1201 except Exception, _e:
1202 traceback.print_exc()
1203
1204 # now stop the server if its there
1205 self.cmds_lst_for_set = [
1206 # set snmpdrun=no
1207 {'bin_name' : '/bin/sed',
1208 'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
1209 '/etc/default/snmpd']},
1210 # stop snmpd service
1211 {'bin_name' : '/usr/sbin/service',
1212 'args_lst' : ['snmpd', 'stop']},
1213 ]
1214 return OsWrapper.set_new(self, [], [[], []])
1215
1216class SetImagesUserSSHKey(OsWrapper):
1217 def __init__(self, name = 'setimagesusersshkey'):
1218 OsWrapper.__init__(self, name, [], [])
1219
1220 def set(self, args_list):
1221 sshkey = str(args_list[0])
1222 # cat the ssh key to the file
1223 # set the images user shell to be scponly
1224 self.cmds_lst_for_set = [
1225 {'bin_name' : '/usr/sbin/usermod',
1226 'args_lst' : ['-s', '/usr/bin/scponly', 'images']},
1227 {'bin_name' : '/bin/echo',
1228 'args_lst' : [sshkey],
1229 'stdoutfile' : '/home/images/.ssh/authorized_keys'},
1230 ]
1231 return OsWrapper.set_new(self, [], [[], []])
1232
1233class ReloadController(OsWrapper):
1234 def __init__(self, name = 'reloadcontroller'):
1235 OsWrapper.__init__(self, name, [], [])
1236
1237 def set(self, args_list):
1238 self.cmds_lst_for_set = [
1239 {'bin_name' : '/sbin/reboot',
1240 'args_list' : []},
1241 ]
1242 return OsWrapper.set_new(self, [], [[]])
1243
1244UPGRADE_IMAGE_FILE_PATH = '/tmp/upgrade-images'
1245UPGRADE_IMAGE_MANIFEST = '/tmp/upgrade-image-manifest'
1246UPGRADE_PACKAGE_DIRECTORY = '/tmp/upgrade-package/'
1247
1248class ExtractUpgradePkgManifest(OsWrapper):
1249 def __init__(self, name = 'extractupgradepkg'):
1250 OsWrapper.__init__(self, name, [], [])
1251
1252 def set(self, args_list):
1253 imageName = str(args_list[0])
1254 self.cmds_lst_for_set = [
1255 {'bin_name' : '/bin/rm',
1256 'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
1257 {'bin_name' : '/bin/touch',
1258 'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
1259 {'bin_name' : '/usr/bin/unzip',
1260 'args_lst' : ['-p', imageName, 'Manifest'],
1261 'stdoutfile' : UPGRADE_IMAGE_MANIFEST},
1262 ]
1263 return OsWrapper.set_new(self, [], [[], [], []], useShell=False, appendStdOut=False)
1264
1265 def get(self, args_list):
1266 self.cmds_lst_for_get = [
1267 {'bin_name' : '/bin/cat',
1268 'args_lst': [UPGRADE_IMAGE_MANIFEST]},
1269 ]
1270 return OsWrapper.get_new(self, [], [[]])
1271
1272class GetLatestUpgradePkg(OsWrapper):
1273 def __init__(self, name = 'getlatestupgradepkg'):
1274 OsWrapper.__init__(self, name, [], [])
1275
1276 # TODO -
1277 # This is ghetto, it just finds the last zip
1278 # file in the dir. FIX THIS!
1279 def get(self, args_list):
1280 execStr = 'ls -t /home/images/*.pkg | grep pkg | head -1 > ' + UPGRADE_IMAGE_FILE_PATH
1281 self.cmds_lst_for_get = [
1282 {'bin_name' : execStr,
1283 'args_lst' : []},
1284 ]
1285 return OsWrapper.get_new(self, [], [[]], useShell=True)
1286
1287class CatUpgradeImagesFile(OsWrapper):
1288 def __init__(self, name = 'catupgradeimagesfile'):
1289 OsWrapper.__init__(self, name, [], [])
1290
1291 def get(self, args_list):
1292 self.cmds_lst_for_get = [
1293 {'bin_name' : '/bin/cat',
1294 'args_lst' : [UPGRADE_IMAGE_FILE_PATH]},
1295 ]
1296 return OsWrapper.get_new(self, [], [[]])
1297
1298class ExecuteUpgradeStep(OsWrapper):
1299 def __init__(self, name = 'executeupgradestep'):
1300 OsWrapper.__init__(self, name, [], [])
1301
1302 def get(self, args_list):
1303 ret_result = {'err': [], 'out': []}
1304
1305 try:
1306 manifest = json.loads(exec_os_wrapper("ExtractUpgradePkgManifest", 'get')['out'])
1307 except ValueError:
1308 ret_result['err'].append("Corrupted manifest!")
1309 return ret_result
1310
1311 stepToExec = None
1312 for step in manifest:
1313 if step['step'] == int(args_list[0]):
1314 stepToExec = step['action']
1315 break;
1316
1317 if stepToExec == None:
1318 ret_result['err'].append("Step %s not found in upgrade package manifest!" %
1319 str(args_list[0]))
1320 return ret_result
1321
1322 upgradePkg = args_list[1]
1323 stepScript = tempfile.NamedTemporaryFile(delete=False)
1324 scriptName = "scripts/%s" % step['action'].strip()
1325 step = check_output(["unzip", "-p", upgradePkg, scriptName])
1326 stepScript.write(step)
1327 stepScript.flush()
1328 stepScript.close()
1329 os.chmod(stepScript.name, stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR)
1330
1331 try:
1332 ret = check_output([stepScript.name] + args_list[1:],
1333 stderr=PIPE)
1334 ret_result['out'].append(stripped(ret.strip()))
1335 except CalledProcessError, exception:
1336 ret_result['err'].append("Error running %s\nreturn code %s\nOutput:\n%s" %
1337 (exception.cmd, exception.returncode, stripped(exception.output)))
1338 return ret_result
1339
1340class CleanupOldUpgradeImages(OsWrapper):
1341 def __init__(self, name = 'cleanupoldupgradeimages'):
1342 OsWrapper.__init__(self, name, [], [])
1343
1344 def get(self, args_list):
1345 # Removes all the .pkg files execpt for the newest one.
1346 # It handles the case where there is only 1 package, we don't delete it.
1347 execStr = 'c=`ls /home/images/*.pkg | wc -l`; if [ "$c" -gt 1 ]; then ls -t -r /home/images/*.pkg | head -n -1 | xargs rm; fi'
1348 self.cmds_lst_for_get = [
1349 {'bin_name' : execStr,
1350 'args_lst' : []},
1351 ]
1352 return OsWrapper.get_new(self, [], [[]], useShell=True)
1353
1354class Decommission(OsWrapper):
1355 def __init__(self, name = "decommission"):
1356 OsWrapper.__init__(self, name, [], [])
1357 def set(self, args_list):
1358 self.cmds_lst_for_set = [
1359 {'bin_name' : '/bin/bash',
1360 'args_lst' : ["%s/sys/bin/remove-node.sh" % SDN_ROOT, str(args_list[0])]},
1361 ]
1362 return OsWrapper.set_new(self, [], [[]])
1363
1364class DecommissionLocal(OsWrapper):
1365 def __init__(self, name = "decommissionlocal"):
1366 OsWrapper.__init__(self, name, [], [])
1367 def set(self, args_list):
1368 self.cmds_lst_for_set = [
1369 {'bin_name' : '/bin/bash',
1370 'args_lst' : ["%s/sys/bin/remove-node-local.sh" % SDN_ROOT, str(args_list[0])]},
1371 ]
1372 return OsWrapper.set_new(self, [], [[]])
1373
1374class ResetBsc(OsWrapper):
1375 def __init__(self, name = "resetbsc"):
1376 OsWrapper.__init__(self, name, [], [])
1377 def set(self, args_list):
1378 self.cmds_lst_for_set = [
1379 {'bin_name' : '/bin/bash',
1380 'args_lst' : ["%s/sys/bin/resetbsc" % SDN_ROOT, '--force']},
1381 ]
1382 return OsWrapper.set_new(self, [], [[]])
1383
1384class WriteDataToFile(OsWrapper):
1385 def __init__(self, name = 'writedatatofile'):
1386 OsWrapper.__init__(self, name, [], [])
1387
1388 def set(self, args_list):
1389 data = str(args_list[0])
1390 path = str(args_list[1])
1391 self.cmds_lst_for_set = [
1392 {'bin_name' : '/bin/touch',
1393 'args_lst' : [path]},
1394 {'bin_name' : '/bin/echo',
1395 'args_lst' : [data],
1396 'stdoutfile' : path},
1397 ]
1398 return OsWrapper.set_new(self, [], [[], []])
1399
1400class DiffConfig(OsWrapper):
1401 def __init__(self, name = "scpconfig"):
1402 OsWrapper.__init__(self, name, [], [])
1403 def set(self, args_list):
1404 self.cmds_lst_for_set = [
1405 {'bin_name' : '/opt/sdnplatform/sys/bin/diff_config.py',
1406 'args_lst' : [str(args_list[0]), str(args_list[1])]},
1407 ]
1408 return OsWrapper.set_new(self, [], [[]])
1409
1410class RollbackConfig(OsWrapper):
1411 def __init__(self, name = "upgradeconfig"):
1412 OsWrapper.__init__(self, name, [], [])
1413 def set(self, args_list):
1414 self.cmds_lst_for_set = [
1415 {'bin_name' : '/bin/bash',
1416 'args_lst' : ["%s/sys/bin/rollback-config.sh" % SDN_ROOT, str(args_list[0])]},
1417 ]
1418 return OsWrapper.set_new(self, [], [[]])
1419
1420def stripped(x):
1421 # remove ascii escape
1422 return "".join([i for i in x if ord(i) != 27])
1423#
1424# exec_os_wrapper
1425#
1426def exec_os_wrapper(obj_type, oper, args_list = None):
1427 """
1428 Execute the oswrapper.py using sudo(), raising an exception
1429 for any stderr output from the executed script
1430 """
1431 # Safety check; only run if this file exists
1432 if not os.path.exists("%s/con" % SDN_ROOT):
1433 print "exec_os_wrapper: not an installed controller environment"
1434 return {'out' : '', 'err' : ''}
1435 if os.path.exists('/etc/not-controller'):
1436 # XXX should issue some alert here
1437 print "exec_os_wrapper: /etc/not-controller exists"
1438 return {'out' : '', 'err' : ''}
1439
1440 oswrapper = os.path.dirname(__file__) + "/oswrapper.py"
1441 full_cmd_string = ["/usr/bin/sudo", oswrapper, obj_type, oper]
1442 if args_list:
1443 full_cmd_string += [str(arg) for arg in args_list]
1444
1445 sub_proc_output = Popen(full_cmd_string, shell=False,
1446 stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
1447
1448 sub_proc_output.wait()
1449 stderr = sub_proc_output.stderr.read()
1450 stdout = sub_proc_output.stdout.read()
1451 returncode = sub_proc_output.returncode
1452
1453 if returncode:
1454 raise Exception("oswrapper: %s %s: exit code %d: %s" %
1455 (obj_type, oper, returncode, stderr))
1456
1457 if len(stderr) != 0 and not stderr.isspace():
1458 print " ".join(full_cmd_string), stderr
1459
1460 # ?!?
1461 return {'out' : stdout, 'err' : stderr}
1462
1463
1464def main(argv):
1465 obj_type_map = {'ExecuteUfwCommand' : UfwCommand,
1466 'SetNtpServer' : SetNtpServer,
1467 'SetTimezone' : SetTimezone,
1468 'UnsetTimezone' : UnsetTimezone,
1469 'NetworkConfig' : NetworkConfig,
1470 'SetSyslogServer' : SetSyslogServer,
1471 'UnsetSyslogServer' : UnsetSyslogServer,
1472 'DateTime' : DateTime,
1473 'ControllerId' : SetControllerId,
1474 'HAFailback' : HAFailback,
1475 'SetHostname' : SetHostname,
1476 'SetVrrpVirtualRouterId' : SetVrrpVirtualRouterId,
1477 'SetStaticFlowOnlyConfig' : SetStaticFlowOnlyConfig,
1478 'SetDefaultConfig' : SetDefaultConfig,
1479 'TacacsPlusConfig' : TacacsPlusConfig,
1480 'SetSnmpServerConfig' : SetSnmpServerConfig,
1481 'UnsetSnmpServerConfig' : UnsetSnmpServerConfig,
1482 'SetImagesUserSSHKey' : SetImagesUserSSHKey,
1483 'ReloadController' : ReloadController,
1484 'ExtractUpgradePkgManifest' : ExtractUpgradePkgManifest,
1485 'GetLatestUpgradePkg' : GetLatestUpgradePkg,
1486 'CatUpgradeImagesFile' : CatUpgradeImagesFile,
1487 'ExecuteUpgradeStep' : ExecuteUpgradeStep,
1488 'DirectNetworkConfig' : DirectNetworkConfig,
1489 'CleanupOldUpgradeImages' : CleanupOldUpgradeImages,
1490 'RestartSDNPlatform' : RestartSDNPlatform,
1491 'AbortUpgrade' : AbortUpgrade,
1492 'Decommission' : Decommission,
1493 'DecommissionLocal' : DecommissionLocal,
1494 'RollbackConfig' : RollbackConfig,
1495 'DiffConfig' : DiffConfig,
1496 'ResetBsc' : ResetBsc,
1497 'WriteDataToFile' : WriteDataToFile,
1498 }
1499 ret_result = {'err': ["insufficient or invalid args"], 'out': []}
1500 if len(argv) >= 3:
1501 if argv[1] in obj_type_map:
1502 obj_type = obj_type_map[argv[1]]
1503 x = obj_type()
1504 if argv[2] == 'set':
1505 ret_result = x.set(argv[3:])
1506 elif argv[2] == 'get':
1507 ret_result = x.get(argv[3:])
1508
1509 # The ret_result entries are lists of strings from the output's of
1510 # various commands.
1511 print >>sys.stdout, ''.join(ret_result['out'])
1512 print >>sys.stderr, ''.join(ret_result['err'])
1513
1514if __name__ == '__main__':
1515 main(sys.argv)