Base net-virt CLI files on top of which ONOS specific changes will be done
diff --git a/cli/sdncon/controller/oswrapper.py b/cli/sdncon/controller/oswrapper.py
new file mode 100755
index 0000000..73b54f8
--- /dev/null
+++ b/cli/sdncon/controller/oswrapper.py
@@ -0,0 +1,1515 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2013 Big Switch Networks, Inc.
+#
+# Licensed under the Eclipse Public License, Version 1.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+#
+
+import os, atexit
+import glob
+from subprocess import Popen, PIPE, check_output, CalledProcessError
+import sys, traceback, socket
+from optparse import OptionParser
+from types import StringType
+import datetime
+import json
+import re
+import time
+import urllib2
+import httplib
+import fcntl, shutil
+import sys
+import tempfile
+import stat
+
+from string import Template
+from django.forms import ValidationError
+
+# Can't import from `import sdncon` -- causes circular dependency!
+SDN_ROOT = "/opt/sdnplatform" if not 'SDN_ROOT' in os.environ else os.environ['SDN_ROOT']
+
+# Big Switch Networks Enterprise OID
+BSN_ENTERPRISE_OID = '.1.3.6.1.4.1.37538'
+BSN_ENTERPRISE_OID_CONTROLLER = BSN_ENTERPRISE_OID + '.1'
+
+
+class OsWrapper():
+ """ This base class abstracts executing os binaries without using shell in a secure, hardened way.
+ Things to keep in mind when composing command templates - because we dont use shell, args to the
+ binaries are presented as items in the list, so no need to de-specialize special characters , for
+ example, if you want to echo something into a file, command is "echo -e abc\ndef\n" and not
+ "echo -e \"abc\ndef\n\"", which would then result in the quotes also to be echo'ed.
+ """
+ name = "none"
+ cmds_lst_for_set = []
+ cmds_lst_for_get = []
+ sudo_required_for_set = True
+ sudo_required_for_get = False
+ 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):
+ self.name = name
+ self.cmds_lst_for_set = this_cmds_list_for_set
+ self.cmds_lst_for_get = this_cmds_list_for_get
+ self.sudo_required_for_set = is_sudo_reqd_for_set
+ self.sudo_required_for_get = is_sudo_required_for_get
+
+ self.IP_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
+ self.DomainName_RE = re.compile(r'^([a-zA-Z0-9-]+.?)+$')
+
+ def validate_ip(self, value):
+ if not self.IP_RE.match(value) or len([x for x in value.split('.') if int(x) < 256]) != 4:
+ return False, "IP must be in dotted decimal format, 234.0.59.1"
+ return True, ""
+
+ def validate_domain(self, value):
+ if not self.DomainName_RE.match(value):
+ return False, "Invalid domain name"
+ return True, ""
+
+ def exec_cmds(self, cmds_lst, cmds_args, stdout_file_lst):
+ # traverse through the list of commands needed to set this
+ # cmd_args is a list of lists of args for each of the commands in the set list.
+ ret_out_err = {'err': [], 'out': []}
+ if len(cmds_lst) != len(cmds_args):
+ # commands to args mismatch, error out and return
+ # possibly throw an exception here as well
+ ret_out_err['err'].append("Command and args mismatch")
+ return ret_out_err
+ for indx in range(len(cmds_lst)):
+ #if self.sudo_required_for_set:
+ # cmd_string = "sudo "
+ #else:
+ # cmd_string = ""
+ cmd_string = ""
+ cmd_template = Template(cmd_string + cmds_lst[indx])
+ args_map = dict({})
+ for args_indx in range(len(cmds_args[indx])):
+ args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
+ cmd_string += cmds_args[indx][args_indx] + " "
+ full_cmd_string = cmd_template.substitute(args_map)
+ file_for_stdout = PIPE
+ if stdout_file_lst != None and stdout_file_lst[indx] != "":
+ file_for_stdout = open(stdout_file_lst[indx], 'a')
+ sub_proc_output = Popen(full_cmd_string.rsplit(" "), shell=False, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
+ ret_out_err['err'].append([sub_proc_output.stderr.read()])
+ if file_for_stdout == PIPE:
+ ret_out_err['out'].append([sub_proc_output.stdout.read()])
+ else:
+ file_for_stdout.close()
+ # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
+ return ret_out_err
+
+ def exec_cmds_new(self, cmds_lst, cmds_args, useShell=False, appendStdOut=True):
+ # traverse through the list of commands needed to set this
+ # cmd_args is a list of lists of args for each of the commands in the set list.
+ ret_out_err = {'err': [], 'out': []}
+ if len(cmds_lst) != len(cmds_args):
+ # commands to args mismatch, error out and return
+ # possibly throw an exception here as wella
+ # cmds_list is a list of dict maps - [{'bin_name': <bin>, 'args_lst': <args-list>, 'stdoutfile':<filename>},...]
+ ret_out_err['err'].append("Command and args mismatch")
+ print 'cmds_lst:', cmds_lst
+ print 'cmd_args:', cmds_args
+ return ret_out_err
+ for indx in range(len(cmds_lst)):
+ #if self.sudo_required_for_set:
+ # cmd_string = "sudo "
+ #else:
+ # cmd_string = ""
+ #cmd_string = ""
+ cmd_args_lst = [cmds_lst[indx]['bin_name']]
+
+ args_map = dict({})
+ for args_indx in range(len(cmds_args[indx])):
+ args_map["arg%d"%(args_indx+1)] = cmds_args[indx][args_indx]
+ #cmd_string += cmds_args[indx][args_indx] + " "
+ if 'args_lst' in cmds_lst[indx]:
+ for args_indx in range(len(cmds_lst[indx]['args_lst'])):
+ arg_template = Template(cmds_lst[indx]['args_lst'][args_indx])
+ full_arg_string = arg_template.substitute(args_map)
+ cmd_args_lst.append(full_arg_string)
+ file_for_stdout = PIPE
+ if 'stdoutfile' in cmds_lst[indx] and cmds_lst[indx]['stdoutfile'] != "":
+ fMode = 'a'
+ if not appendStdOut:
+ fMode = 'r+'
+ file_for_stdout = open(cmds_lst[indx]['stdoutfile'] , fMode)
+ sub_proc_output = Popen(cmd_args_lst, shell=useShell, stdin=PIPE, stdout=file_for_stdout, stderr=PIPE, close_fds=True)
+ ret_out_err['err'].append(sub_proc_output.stderr.read())
+ if file_for_stdout == PIPE:
+ ret_out_err['out'].append(sub_proc_output.stdout.read())
+ else:
+ file_for_stdout.close()
+ # not fit for pipe io - os.system(cmd_string) # need to check for errors in commands.
+ return ret_out_err
+
+ def set(self, cmds_args, stdout_file_lst):
+ return self.exec_cmds(self.cmds_lst_for_set, cmds_args, stdout_file_lst)
+ def get(self, cmds_args, stdout_file_lst):
+ return self.exec_cmds(self.cmds_lst_for_get, cmds_args, stdout_file_lst)
+
+ def set_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
+ if cmds_args_lst == []:
+ cmds_args_lst = self.cmds_lst_for_set
+ return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
+ def get_new(self, cmds_args_lst = [], cmds_args = [], useShell=False, appendStdOut = False):
+ if cmds_args_lst == []:
+ cmds_args_lst = self.cmds_lst_for_get
+ return self.exec_cmds_new(cmds_args_lst, cmds_args, useShell, appendStdOut)
+
+
+def validate_input1(validator, value): #temporarily disabling this - TBD
+ try:
+ validator(value)
+ except ValidationError, _err:
+ return False
+ return True
+
+
+def validate_input(validator, value):
+ a = True
+ #try:
+ #a, b = validator(value)
+ #except ValidationError, err:
+ # return False
+ return a
+
+
+def dotted_decimal_to_int(ip):
+ """
+ Converts a dotted decimal IP address string to a 32 bit integer
+ """
+ bytes = ip.split('.')
+ ip_int = 0
+ for b in bytes:
+ ip_int = (ip_int << 8) + int(b)
+ return ip_int
+
+
+def same_subnet(ip1, ip2, netmask):
+ """
+ Checks whether the two ip addresses are on the same subnet as
+ determined by the netmask argument. All of the arguments are
+ dotted decimal IP address strings.
+ """
+ if ip1 == '' or ip2 == '' or netmask == '':
+ return False
+ ip1_int = dotted_decimal_to_int(ip1)
+ ip2_int = dotted_decimal_to_int(ip2)
+ netmask_int = dotted_decimal_to_int(netmask)
+ return (ip1_int & netmask_int) == (ip2_int & netmask_int)
+
+
+class NetworkConfig(OsWrapper):
+ def __init__(self, name="network_config"):
+ OsWrapper.__init__(self, name, [], [])
+
+
+ def rewrite_etc_network_interfaces(self, controller, interfaces, ret_result):
+ """
+ Return True when the /etc/network/interfaces is rewritten. Return False otherwise.
+ The file is rewritten only when the intended new contents is different from
+ the old contents, this is an attempt to not purturb the network unless something
+ really changed.
+ """
+
+ gateway = controller['fields']['default_gateway']
+ if (gateway != ''):
+ (r, m) = self.validate_ip(gateway)
+ if not r:
+ ret_result['err'].append("Default gateway: %s" % m)
+ gateway = ''
+
+ changed = False
+ new_conf = []
+ new_conf.append("# WARNING this file is automanaged by BSN controller\n")
+ new_conf.append("# DO NOT EDIT here, use CLI with 'configure'\n")
+ new_conf.append("auto lo\niface lo inet loopback\n\n")
+ for interface in interfaces:
+ if (interface['fields']['controller'] == controller['pk']):
+ num = interface['fields']['number']
+ new_conf.append("auto eth{0}\n".format(num))
+ if (interface['fields']['mode'] == 'dhcp'):
+ new_conf.append("iface eth{0} inet dhcp\n".format(num))
+ else:
+ ip = interface['fields']['ip']
+ netmask = interface['fields']['netmask']
+ if (ip != ""):
+ (r, m) = self.validate_ip(ip)
+ if not r:
+ ret_result['err'].append(
+ "Ethernet %s IP address %s: %s" % (num, ip, m))
+ ip = ""
+ if (netmask != ""):
+ (r, m) = self.validate_ip(netmask)
+ if not r:
+ ret_result['err'].append(
+ "Ethernet %s netmask %s: %s" % (num, netmask, m))
+ netmask = ""
+
+ new_conf.append("iface eth{0} inet static\n".format(num))
+ if (ip != ""):
+ new_conf.append(" address {0}\n".format(ip))
+ if (netmask != ""):
+ new_conf.append(" netmask {0}\n".format(netmask))
+ if same_subnet(gateway, ip, netmask):
+ new_conf.append(" gateway {0}\n".format(gateway))
+ new_conf.append("\n")
+
+ f = open("/etc/network/interfaces", "r")
+ if (''.join(new_conf) != f.read()):
+ f.close()
+ f = open("/etc/network/interfaces", "w")
+ f.write(''.join(new_conf))
+ changed = True
+ f.close()
+ return changed
+
+ def rewrite_etc_resolve_conf(self, controller, dns_servers, ret_result):
+ """
+ Return True when the /etc/resolv.conf is rewritten. Return False otherwise.
+ The file is rewritten only when the intended new contents is different from
+ the old contents, this is an attempt to not purturb the network unless something
+ really changed.
+ """
+
+ changed = False
+ new_conf = []
+ domain_name = controller['fields']['domain_name']
+ if (domain_name != ""):
+ new_conf.append("domain {0}\nsearch {1}\n".format(domain_name,
+ domain_name))
+
+ if (controller['fields']['domain_lookups_enabled'] == True):
+ for dns in dns_servers:
+ if (dns['fields']['controller'] == controller['pk']):
+ ip = dns['fields']['ip']
+ if (ip != ""):
+ (r, m) = self.validate_ip(ip)
+ if not r:
+ ret_result['err'].append("Name server %s: %s" % (ip, m))
+ ip = ""
+ if (ip != ""):
+ new_conf.append("nameserver {0}\n".format(ip))
+
+ f = open("/etc/resolv.conf", "r")
+ if (''.join(new_conf) != f.read()):
+ f.close()
+ f = open("/etc/resolv.conf", "w")
+ f.write(''.join(new_conf))
+ changed = True
+
+ f.close()
+ return changed
+
+ def set(self, args_list):
+ # args_list: [controllers, controlleDomainServers, controllerInterfaces]
+ # controllerInterfaces may be empty, which requess no rewrite
+ # of /etc/network/insterfaces
+ ifs_rewrite = False if len(args_list) < 3 else True
+
+ ret_result = {'err': [], 'out': []}
+ controller = json.loads(args_list[0])[0]
+ network_restart = True
+
+ rc_changed = False
+ ni_changed = False
+
+ try:
+ domain_name = controller['fields']['domain_name']
+ new_rc = True
+ if domain_name != "":
+ (r, m) = self.validate_domain(domain_name)
+ if not r:
+ ret_result['err'].append("Search domain %s: %s"
+ % (domain_name, m))
+ new_rc = False
+
+ if new_rc:
+ rc_changed = self.rewrite_etc_resolve_conf(controller,
+ json.loads(args_list[1]),
+ ret_result)
+
+ if ifs_rewrite:
+ ni_changed = self.rewrite_etc_network_interfaces(controller,
+ json.loads(args_list[2]),
+ ret_result)
+ except Exception, _e:
+ network_restart = False
+ traceback.print_exc()
+
+ # don't restart the network config if resolv.conf was only updated
+ if network_restart and ni_changed:
+ # Kill any dhclients that might be hanging around. When
+ # switching from dhcp to static config, networking restart
+ # won't kill these since the file has already been
+ # rewritten with a config that doesn't include DHCP. A
+ # cleaner fix for this is to stop networking before
+ # writing the file and then start it after writing the
+ # file. Longer-term, it might be better to use
+ # NetworkManager APIs for all of this rather than trying
+ # to manage this config file.
+ k = Popen(["/usr/bin/killall", "dhclient3"],
+ stdout=PIPE, stderr=PIPE)
+ k.wait()
+
+ p = Popen(["/usr/sbin/invoke-rc.d",
+ "networking",
+ "restart"],
+ stdout=PIPE, stderr=PIPE)
+ p.wait()
+
+ if (0 != p.returncode):
+ out = p.stdout.read()
+ ret_result['err'].append("Network restart failed:"
+ "%d: %s" % (p.returncode, out))
+
+ # Restart the discover-ip since the controller-interface table has been modified
+ ip = Popen(["/usr/sbin/service",
+ "discover-ip",
+ "restart"],
+ stdout=PIPE, stderr=PIPE)
+
+ # not concerned with error messages from the requested command
+
+ return ret_result
+
+class DirectNetworkConfig(NetworkConfig):
+ def __init__(self, name="direct_network_config"):
+ NetworkConfig.__init__(self, name)
+
+ def set(self, args_list):
+ gateway = ''
+ ip = ''
+ netmask = ''
+ domain_name = ''
+ dns1 = ''
+ dns2 = ''
+ domain_lookups_enabled = False
+ if args_list[0] == 'static':
+ ip = args_list[1]
+ netmask = args_list[2]
+ gateway = args_list[3]
+ if len(args_list) >= 5:
+ domain_name = args_list[4]
+ if len(args_list) >= 6:
+ domain_lookups_enabled = True
+ dns1 = args_list[5]
+ if len(args_list) >= 7:
+ dns2 = args_list[6]
+ else:
+ if len(args_list) >= 2:
+ domain_name = args_list[1]
+ if len(args_list) >= 3:
+ domain_lookups_enabled = True
+ dns1 = args_list[2]
+ if len(args_list) >= 4:
+ dns2 = args_list[3]
+
+ controller_interface=[{'fields' : {'id' : "localhost|ethernet|0",
+ 'type' : "ethernet", 'number' : 0,
+ 'mode' : args_list[0], 'ip' : ip,
+ 'netmask' : netmask, 'controller' : 'localhost'}}]
+ nameservers = []
+ if dns1 != '':
+ nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 1, 'ip' : dns1}})
+ if dns2 != '':
+ nameservers.append({ 'fields' : {'controller' : "localhost", 'priority' : 2, 'ip' : dns2}})
+ controller=[{'fields' : {'id' : "localhost", 'domain_name' : domain_name,
+ 'default_gateway' : gateway,
+ 'domain_lookups_enabled' : domain_lookups_enabled}, 'pk' : 'localhost'}]
+ return NetworkConfig.set(self, [json.dumps(controller), json.dumps(nameservers),
+ json.dumps(controller_interface)])
+
+
+class UfwCommand(OsWrapper):
+ def __init__(self, name = "executeufwcommand"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, arg_list):
+ args = arg_list[0].split(" ")
+ self.cmds_lst_for_set = [{'bin_name' : '/usr/sbin/ufw', 'args_lst' : args}]
+ return OsWrapper.set_new(self, [], [[]])
+
+NTP_CONF = """tinker panic 0
+driftfile /var/lib/ntp/ntp.drift
+
+# Enable this if you want statistics to be logged.
+#statsdir /var/log/ntpstats/
+
+statistics loopstats peerstats clockstats
+filegen loopstats file loopstats type day enable
+filegen peerstats file peerstats type day enable
+filegen clockstats file clockstats type day enable
+
+# Specify one or more NTP servers.
+
+# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
+# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
+# more information.
+server %s
+
+# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
+# details. The web page <http://support.ntp.org/bin/view/Support/AccessRestrictions>
+# might also be helpful.
+#
+# Note that "restrict" applies to both servers and clients, so a configuration
+# that might be intended to block requests from certain clients could also end
+# up blocking replies from your own upstream servers.
+
+# By default, exchange time with everybody, but don't allow configuration.
+restrict -4 default kod notrap nomodify nopeer noquery
+restrict -6 default kod notrap nomodify nopeer noquery
+
+# Local users may interrogate the ntp server more closely.
+restrict 127.0.0.1
+restrict ::1
+
+# Clients from this (example!) subnet have unlimited access, but only if
+# cryptographically authenticated.
+#restrict 192.168.123.0 mask 255.255.255.0 notrust
+
+
+# If you want to provide time to your local subnet, change the next line.
+# (Again, the address is an example only.)
+#broadcast 192.168.123.255
+
+# If you want to listen to time broadcasts on your local subnet, de-comment the
+# next lines. Please do this only if you trust everybody on the network!
+#disable auth
+#broadcastclient
+"""
+
+class SetNtpServer(OsWrapper):
+ def __init__(self, name = "setntpserver"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ ret_result = {'err': [], 'out': []}
+ server = "127.127.1.0"
+ if (len(args_list) > 0 and
+ args_list[0] is not None and
+ args_list[0] != ""):
+ server = str(args_list[0])
+ ntpconf = NTP_CONF % server
+ changed = False
+
+ f = open("/etc/ntp.conf", "r")
+ if (''.join(ntpconf) != f.read()):
+ f.close()
+ f = open("/etc/ntp.conf", "w")
+ f.write(''.join(ntpconf))
+ changed = True
+
+ if changed:
+ ntp = Popen(["/usr/sbin/service",
+ "ntp",
+ "restart"],
+ stdout=PIPE, stderr=PIPE)
+ return ret_result
+
+
+class SetTimezone(OsWrapper):
+ def __init__(self, name = "settimezone"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', 'echo "%s" >/etc/timezone' % (str(args_list[0]), )]},
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'rsyslog', ]},
+ ]
+ return OsWrapper.set_new(self, [], [[], [], []])
+
+
+class UnsetTimezone(OsWrapper):
+ def __init__(self, name = "unsettimezone"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', 'echo "Etc/UTC" >/etc/timezone']},
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', '/usr/sbin/dpkg-reconfigure -f noninteractive tzdata 2>&1']},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class SetSyslogServer(OsWrapper):
+ def __init__(self, name = "setsyslogserver"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', '/^*.* @/d', '/etc/rsyslog.conf']},
+ {'bin_name' : '/bin/echo',
+ 'args_lst' : ['*.' + str(args_list[1]) + ' @' + str(args_list[0])], 'stdoutfile' : '/etc/rsyslog.conf'},
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'rsyslog']},
+ ]
+ return OsWrapper.set_new(self, [], [[], [], []])
+
+class UnsetSyslogServer(OsWrapper):
+ def __init__(self, name = "unsetsyslogserver"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', '/^*./d', '/etc/rsyslog.conf']},
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'rsyslog']}
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class DateTime(OsWrapper):
+ def __init__(self, name = "getdatetime"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ # If we're getting or setting the clock then the first argument
+ # should be either 'utc' or 'local' to indicate whether or not to
+ # use local time.
+ # If we're setting the clock there's a second argument which is the
+ # time to set, formatted as shown in set_cmd_args below.
+ set_parts = args_list[1].split(':')
+ set_param = '%2.2s%2.2s%2.2s%2.2s%4.4s.%2.2s' % (
+ set_parts[1],
+ set_parts[2],
+ set_parts[3],
+ set_parts[4],
+ set_parts[0],
+ set_parts[5],
+ )
+ cmd_args = [str(set_param)]
+ if str(args_list[0]).lower() == 'utc':
+ cmd_args.insert(0, '-u')
+ #date_info = args_list[0]
+ #is_utc = args_list[1]
+ #date_str = "%s:%s:%s:%s:%s:%s:%s"
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
+ ]
+ OsWrapper.set_new(self, [], [[]])
+ return self.get(args_list)
+
+ def get(self, args_list):
+ cmd_args = ['+%Y:%m:%d:%H:%M:%S:%Z']
+ if str(args_list[0]).lower() == 'utc':
+ cmd_args.insert(0, '-u')
+ self.cmds_lst_for_get = [
+ {'bin_name' : '/bin/date', 'args_lst' : cmd_args},
+ ]
+ return OsWrapper.get_new(self, [], [[]])
+
+class SetControllerId(OsWrapper):
+ def __init__(self, name = "setcontrollerid"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', '/^controller-id=/d', "%s/run/boot-config" % SDN_ROOT]},
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', 'echo "controller-id=%s" >> %s/run/boot-config' % (
+ str(args_list[0]), SDN_ROOT)
+ ]
+ },
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class HAFailback(OsWrapper):
+ def __init__(self, name = "hafailback"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/touch',
+ 'args_lst' : ["%s/force-one-time-health-check-failure" % SDN_ROOT]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+#class HAFailover(OsWrapper):
+# def __init__(self, name = "hafailover"):
+# OsWrapper.__init__(self, name, [], [])
+# def set(self, args_list):
+# self.cmds_lst_for_set = [
+# {'bin_name' : '/bin/bash',
+# 'args_lst' : ["%s/sys/bin/ha-failover.sh" % SDN_ROOT, str(args_list[0])]},
+# ]
+# return OsWrapper.set_new(self, [], [[]])
+
+class SetVrrpVirtualRouterId(OsWrapper):
+ def __init__(self, name = "setvrrpvirtualrouterid"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ["%s/sys/bin/set-vrrp-virtual-router-id.sh" % SDN_ROOT, str(args_list[0])]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+
+def write_controller_restarted():
+ """
+ Write the controller started file with the current timestamp + 5 seconds. This will
+ ensure that health check script ignores the state of sdnplatform for 5 seconds
+ from current time
+ """
+ f = open("/var/run/sdnplatform-healthcheck-disabled", "w")
+ # write time converted to int and then string
+ f.write(str(5 + long(time.time())))
+ f.close()
+
+
+class SetStaticFlowOnlyConfig(OsWrapper):
+ def __init__(self, name = "setstaticflowonlyconfig"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+
+ # write the controller started file. do it before actually resetting
+ # so that there is no race condition with health check script
+ try:
+ write_controller_restarted()
+ except Exception, _e:
+ traceback.print_exc()
+
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/touch',
+ 'args_lst' : ["%s/feature/staticflowonlyconfig" % SDN_ROOT]},
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'sdnplatform']},
+ ]
+ return OsWrapper.set_new(self, [], [[], [], []])
+
+class RestartSDNPlatform(OsWrapper):
+ def __init__(self, name = "restartsdnplatform"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+
+ # write the controller started file. do it before actually resetting
+ # so that there is no race condition with health check script
+ try:
+ write_controller_restarted()
+ except Exception, _e:
+ traceback.print_exc()
+
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'sdnplatform']},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class AbortUpgrade(OsWrapper):
+ def __init__(self, name = "abortupgrade"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/opt/sdnplatform/sys/bin/abort_upgrade.sh',
+ 'args_lst' : []},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class SetDefaultConfig(OsWrapper):
+ def __init__(self, name = "setdefaultconfig"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+
+ # write the controller started file. do it before actually resetting
+ # so that there is no race condition with health check script
+ try:
+ write_controller_restarted()
+ except Exception, _e:
+ traceback.print_exc()
+
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/rm',
+ 'args_lst' : ['-f', "%s/feature/staticflowonlyconfig" % SDN_ROOT]},
+ {'bin_name' : '/sbin/initctl',
+ 'args_lst' : ['restart', 'sdnplatform']},
+ # if there are other configs their flag files need to be deleted here too
+ ]
+ return OsWrapper.set_new(self, [], [[], [], []])
+
+class SetHostname(OsWrapper):
+ def __init__(self, name = "sethostname"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ hostname = str(args_list[0])
+ self.cmds_lst_for_set = [
+ # replace hostname from /etc/hosts
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i',
+ r's/^127\.0\.1\.1 .*$$/127.0.1.1 %s/' % hostname,
+ '/etc/hosts']},
+ # populate /etc/hosts
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ['-c', 'echo "%s" >/etc/hostname' % hostname]},
+ # tell the system about the hostname
+ {'bin_name' : '/bin/hostname',
+ 'args_lst' : ['-b', '-F', '/etc/hostname'] },
+ ]
+ return OsWrapper.set_new(self, [], [[], [], [],])
+
+# In PAM, "auth" == authentication (surprise!)
+# XXX roth -- maybe use PAP instead?
+
+# 'sufficient' --> tacacs+ and local authentication are enabled
+AUTHN_TPL = """\
+auth [default=1 success=ignore] pam_succeed_if.so uid >= 10000
+auth sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login protocol=ip login=login
+"""
+
+# In PAM, "account" == authorization
+AUTHZ_TPL = """\
+account [default=1 success=ignore] pam_succeed_if.so uid >= 10000
+account sufficient pam_tacplus.so %(secrets)s %(timeout)s service=login service_av=shell protocol=ip login=login
+"""
+
+# In PAM, "session" == accounting
+ACCT_TPL = """\
+session sufficient pam_tacplus.so %(servers)s %(secrets)s %(timeout)s service=login service_av=shell protocol=ip
+"""
+
+# XXX roth -- keep this in sync with the current release
+# of /etc/pam.d/sshd
+SSHD_TACPLUS_TPL = """\
+# PAM configuration for the Secure Shell service
+
+# Read environment variables from /etc/environment and
+# /etc/security/pam_env.conf.
+auth required pam_env.so # [1]
+# In Debian 4.0 (etch), locale-related environment variables were moved to
+# /etc/default/locale, so read that as well.
+auth required pam_env.so envfile=/etc/default/locale
+
+# Standard Un*x authentication.
+%(authn)s
+
+# Disallow non-root logins when /etc/nologin exists.
+account required pam_nologin.so
+
+# Uncomment and edit /etc/security/access.conf if you need to set complex
+# access limits that are hard to express in sshd_config.
+# account required pam_access.so
+
+# Standard Un*x authorization.
+%(authz)s
+
+# Standard Un*x session setup and teardown.
+%(acct)s
+
+# Print the message of the day upon successful login.
+session optional pam_motd.so # [1]
+
+# Print the status of the user's mailbox upon successful login.
+session optional pam_mail.so standard noenv # [1]
+
+# Set up user limits from /etc/security/limits.conf.
+session required pam_limits.so
+
+# Set up SELinux capabilities (need modified pam)
+# session required pam_selinux.so multiple
+
+# Standard Un*x password updating.
+@include common-password
+"""
+
+class TacacsPlusConfig(OsWrapper):
+
+ def __init__(self, name="tacacs_plus_config"):
+ OsWrapper.__init__(self, name, [], [])
+ self.config = {}
+ self.hosts = []
+ self.result = dict(err=[], out=[])
+
+ def isEnabled(self):
+ """Is TACACS+ enabled?
+
+ If any of authn/authz/acct is set, *and* there is a non-empty
+ set of TACACS+ hosts, then we should enable the PAM plugin.
+ """
+
+ if (self.hosts
+ and (self.config['fields']['tacacs_plus_authn']
+ or self.config['fields']['tacacs_plus_authz']
+ or self.config['fields']['tacacs_plus_acct'])):
+ return True
+
+ return False
+
+ def disablePamDefault(self):
+ """Disable the default PAM setup.
+
+ This is installed by the initial configuration scripts
+ for the libpam-tacplus DEB.
+ """
+
+ if not os.path.exists("/usr/share/pam-configs/tacplus"):
+ return
+
+ cmd = ("/usr/sbin/pam-auth-update", "--remove", "tacplus",)
+ pipe = Popen(cmd, stdout=PIPE, stderr=PIPE)
+ out, err = pipe.communicate()
+ code = pipe.wait()
+
+ out = (out or "").strip().split("\n")
+ err = (err or "").strip().split("\n")
+
+ if not code:
+ self.result['out'].append("disabled tacplus via pam-auth-update\n")
+ else:
+ self.result['out'].extend([l + "\n" for l in out])
+ self.result['err'].extend([l + "\n" for l in err])
+ self.result['err'].append("pam-auth-update failed\n")
+
+ def disablePam(self):
+ """Disable the TACACS+ PAM plugin."""
+ m = dict(authn="@include common-auth",
+ authz="@include common-account",
+ acct="@include common-session")
+ self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
+
+ def readLocked(self, path):
+
+ fd = open(path, "r")
+
+ fcntl.lockf(fd, fcntl.LOCK_SH)
+ try:
+ buf = fd.read()
+ except Exception, what:
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+ fd.close()
+ self.result['err'].append(str(what) + "\n")
+ self.result['err'].append("cannot read %s\n" % path)
+ return None
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+
+ fd.close()
+
+ return buf
+
+ def writeLocked(self, path, buf, backup=True):
+
+ if backup:
+ shutil.copy2(path, path + "-")
+
+ fd = open(path, "w")
+
+ fcntl.lockf(fd, fcntl.LOCK_EX)
+ try:
+ fd.write(buf)
+ except Exception, what:
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+ fd.close()
+ self.result['err'].append(str(what) + "\n")
+ self.result['err'].append("cannot write %s\n" % path)
+ return
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+
+ fd.close()
+
+ def enableNss(self):
+ """Enable the NSS plugin."""
+
+ buf = self.readLocked("/etc/nsswitch.conf")
+ if buf is None: return
+
+ p = buf.find("\npasswd:")
+ q = buf.find("\n", p+8)
+ if p < 0 or q < 0:
+ self.result['err'].append("cannot find passwd entry"
+ " in /etc/nsswitch.conf\n")
+ return
+
+ f = buf[p+8:q]
+ if "remoteuser" in f:
+ self.result['out'].append("remoteuser already enabled"
+ " in /etc/nssswitch.conf\n")
+ return
+
+ self.result['out'].append("enabling remoteuser"
+ " in /etc/nssswitch.conf\n")
+ f = " " + f.strip() + " remoteuser"
+ buf = buf[:p+8] + f + buf[q:]
+
+ self.writeLocked("/etc/nsswitch.conf", buf)
+
+ def disableNss(self):
+ """Disable the NSS plugin."""
+
+ buf = self.readLocked("/etc/nsswitch.conf")
+ if buf is None: return
+
+ p = buf.find("\npasswd:")
+ q = buf.find("\n", p+8)
+ if p < 0 or q < 0:
+ self.result['err'].append("cannot find passwd entry"
+ " in /etc/nsswitch.conf\n")
+ return
+
+ l = buf[p+8:q].strip().split()
+ if "remoteuser" not in l:
+ self.result['out'].append("remoteuser already disabled"
+ " in /etc/nssswitch.conf\n")
+ return
+
+ self.result['out'].append("disabling remoteuser"
+ " in /etc/nssswitch.conf\n")
+ l.remove("remoteuser")
+ f = " " + " ".join(l)
+ buf = buf[:p+8] + f + buf[q:]
+
+ self.writeLocked("/etc/nsswitch.conf", buf)
+
+ def enablePam(self):
+ """Enable the TACACS+ PAM plugin.
+
+ * construct consolidate server and secret lists
+ * generate a timeout line
+ * pick sufficient/required clauses as indicated by enable
+ flags in the JSON
+
+ See
+ http://tacplus.git.sourceforge.net/git/gitweb.cgi?p=tacplus/tacplus;a=blob_plain;f=README;hb=HEAD
+ """
+
+ # disable TACACS+ while updating
+ self.disableNss()
+ self.disablePam()
+
+ # XXX roth -- field is 'ip', but since it is the primary key,
+ # it get mapped to 'pk'. Go figure.
+ def svrClause(h):
+ self.result['out'].append("enabling host %s\n" % h['pk'])
+ return 'server=%s' % h['pk']
+
+ servers = " ".join([svrClause(h) for h in self.hosts])
+
+ def keyClause(h):
+ """Secret for this host, possibly the global one."""
+ if h['fields']['key']:
+ return "secret=%s" % h['fields']['key']
+ if self.config['fields']['key']:
+ return "secret=%s" % self.config['fields']['key']
+ return ""
+
+ secrets = " ".join([keyClause(h) for h in self.hosts])
+
+ m = dict(servers=servers, secrets=secrets)
+
+ if self.config['fields']['timeout']:
+ m['timeout'] = 'timeout=%s' % self.config['fields']['timeout']
+ else:
+ m['timeout'] = ''
+
+ isLocal = self.config['fields']['local_authn']
+ isTacacs = self.config['fields']['tacacs_plus_authn']
+
+ authn = []
+ if isTacacs:
+ authn.append(AUTHN_TPL % m)
+ if isLocal:
+ authn.append("@include common-auth")
+
+ isLocal = self.config['fields']['local_authz']
+ isTacacs = self.config['fields']['tacacs_plus_authz']
+
+ authz = []
+ if isTacacs:
+ authz.append(AUTHZ_TPL % m)
+ if isLocal:
+ authz.append("@include common-account")
+
+ isTacacs = self.config['fields']['tacacs_plus_acct']
+
+ acct = []
+ if isTacacs:
+ acct.append(ACCT_TPL % m)
+ acct.append("@include common-session")
+
+ # enable userid lookups
+ self.enableNss()
+
+ # write out sshd PAM config as a final step to enable it
+ m = dict(authn="\n".join(authn),
+ authz="\n".join(authz),
+ acct="\n".join(acct))
+ self.writeLocked("/etc/pam.d/sshd", SSHD_TACPLUS_TPL % m)
+
+ def set(self, args_list):
+
+ self.config = json.loads(args_list[0])[0]
+ self.hosts = json.loads(args_list[1])
+
+ self.disablePamDefault()
+
+ if not self.isEnabled():
+ try:
+ self.disableNss()
+ self.disablePam()
+ self.result['out'].append('TACACS+ (via PAM) is now disabled\n')
+ except Exception:
+ traceback.print_exc()
+ self.result['err'].append('TACACS+ (via PAM) disable failed\n')
+ return self.result
+
+ # else, enable the PAM module
+ try:
+ self.enableNss()
+ self.enablePam()
+ self.result['out'].append('TACACS+ (via PAM) is now enabled\n')
+ except Exception:
+ # XXX roth -- maybe back out here and *disable* PAM
+ # so that we do not end up with a broken PAM config
+ traceback.print_exc()
+ self.result['err'].append('TACACS+ (via PAM) enable failed\n')
+
+ return self.result
+
+#
+# get_system_version_string
+#
+# Gets the version string of the controller.
+# Reference implementation is in sdncon/rest/views/do_system_version
+#
+def get_system_version_string():
+ version = "SDN OS 1.0 - custom version"
+ try:
+ f = open("%s/release" % SDN_ROOT, 'r')
+ version = f.read()
+ f.close()
+ except:
+ pass
+ return version
+
+#
+# rewrite_etc_snmpd_conf
+#
+# API to rewrite the /etc/snmp/snmpd.conf file based on latest config
+#
+def rewrite_etc_snmpd_conf(community, location, contact, ret_result):
+ """
+ Return True when the /etc/snmp/snmpd.conf is rewritten. Return False
+ otherwise. The file is rewritten only when the intended new contents
+ is different from the old contents, this is an attempt to not restart
+ the snmp agent unless something really changed.
+ """
+
+ changed = False
+ new_conf = []
+ # start with default configuration of the file
+ new_conf.append("# Default Configuration for the SNMP daemon\n")
+ new_conf.append("# Agent address\n")
+ new_conf.append("agentAddress udp:161,udp6:[::1]:161\n")
+ new_conf.append("# System Object ID\n")
+ new_conf.append("sysObjectID %s\n" % (BSN_ENTERPRISE_OID_CONTROLLER))
+ new_conf.append("# System Description\n")
+ new_conf.append("sysDescr %s\n"%(get_system_version_string()))
+
+ #add community, location, contact information to the file if not there already
+ if community != '':
+ new_conf.append("rocommunity %s\n" % community)
+ if location != '':
+ new_conf.append("sysLocation %s\n" % location)
+ if contact != '':
+ new_conf.append("sysContact %s\n" % contact)
+
+ f = open("/etc/snmp/snmpd.conf", "r")
+ if (''.join(new_conf) != f.read()):
+ f.close()
+ f = open("/etc/snmp/snmpd.conf", "w")
+ f.write(''.join(new_conf))
+ changed = True
+
+ f.close()
+
+ return changed
+
+#
+# One of the entry in the snmp server configuration changed
+#
+class SetSnmpServerConfig(OsWrapper):
+ def __init__(self, name = "setsnmpserverconfig"):
+ OsWrapper.__init__(self, name, [], [])
+
+
+ def set(self, args_list):
+ # args_list: [server_enable, community, location, contact, enable_changed]
+ print "SnmpServerConfig Args List: ", args_list
+ server_enable = args_list[0]
+ community = args_list[1]
+ location = args_list[2]
+ contact = args_list[3]
+ enable_changed = args_list[4]
+
+ ret_result = {'err': [], 'out': []}
+
+ try:
+ # rewrite /etc/snmp/snmpd.conf file
+ need_restart = rewrite_etc_snmpd_conf(community, location, contact, ret_result)
+ except Exception, _e:
+ need_restart = False
+ traceback.print_exc()
+
+ if server_enable == 'True' and (need_restart or enable_changed == 'True'):
+ self.cmds_lst_for_set = [
+ # set snmpdrun=yes
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', 's/SNMPDRUN=no/SNMPDRUN=yes/',
+ '/etc/default/snmpd']},
+ # restart snmpd service
+ {'bin_name' : '/usr/sbin/service',
+ 'args_lst' : ['snmpd', 'restart']},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+ elif server_enable == 'False' and enable_changed == 'True':
+ self.cmds_lst_for_set = [
+ # set snmpdrun=no
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
+ '/etc/default/snmpd']},
+ # stop snmpd service
+ {'bin_name' : '/usr/sbin/service',
+ 'args_lst' : ['snmpd', 'stop']},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+ return ret_result
+
+
+#
+# The row entry in the snmp server config table was default and deleted
+#
+class UnsetSnmpServerConfig(OsWrapper):
+ def __init__(self, name = "unsetsnmpserverconfig"):
+ OsWrapper.__init__(self, name, [], [])
+
+ def set(self, args_list):
+ # args_list: []
+ ret_result = {'err': [], 'out': []}
+
+ try:
+ # rewrite /etc/snmp/snmpd.conf to default
+ rewrite_etc_snmpd_conf('', '', '', ret_result)
+ except Exception, _e:
+ traceback.print_exc()
+
+ # now stop the server if its there
+ self.cmds_lst_for_set = [
+ # set snmpdrun=no
+ {'bin_name' : '/bin/sed',
+ 'args_lst' : ['-i', 's/SNMPDRUN=yes/SNMPDRUN=no/',
+ '/etc/default/snmpd']},
+ # stop snmpd service
+ {'bin_name' : '/usr/sbin/service',
+ 'args_lst' : ['snmpd', 'stop']},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class SetImagesUserSSHKey(OsWrapper):
+ def __init__(self, name = 'setimagesusersshkey'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def set(self, args_list):
+ sshkey = str(args_list[0])
+ # cat the ssh key to the file
+ # set the images user shell to be scponly
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/usr/sbin/usermod',
+ 'args_lst' : ['-s', '/usr/bin/scponly', 'images']},
+ {'bin_name' : '/bin/echo',
+ 'args_lst' : [sshkey],
+ 'stdoutfile' : '/home/images/.ssh/authorized_keys'},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class ReloadController(OsWrapper):
+ def __init__(self, name = 'reloadcontroller'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/sbin/reboot',
+ 'args_list' : []},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+UPGRADE_IMAGE_FILE_PATH = '/tmp/upgrade-images'
+UPGRADE_IMAGE_MANIFEST = '/tmp/upgrade-image-manifest'
+UPGRADE_PACKAGE_DIRECTORY = '/tmp/upgrade-package/'
+
+class ExtractUpgradePkgManifest(OsWrapper):
+ def __init__(self, name = 'extractupgradepkg'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def set(self, args_list):
+ imageName = str(args_list[0])
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/rm',
+ 'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
+ {'bin_name' : '/bin/touch',
+ 'args_lst' : [UPGRADE_IMAGE_MANIFEST]},
+ {'bin_name' : '/usr/bin/unzip',
+ 'args_lst' : ['-p', imageName, 'Manifest'],
+ 'stdoutfile' : UPGRADE_IMAGE_MANIFEST},
+ ]
+ return OsWrapper.set_new(self, [], [[], [], []], useShell=False, appendStdOut=False)
+
+ def get(self, args_list):
+ self.cmds_lst_for_get = [
+ {'bin_name' : '/bin/cat',
+ 'args_lst': [UPGRADE_IMAGE_MANIFEST]},
+ ]
+ return OsWrapper.get_new(self, [], [[]])
+
+class GetLatestUpgradePkg(OsWrapper):
+ def __init__(self, name = 'getlatestupgradepkg'):
+ OsWrapper.__init__(self, name, [], [])
+
+ # TODO -
+ # This is ghetto, it just finds the last zip
+ # file in the dir. FIX THIS!
+ def get(self, args_list):
+ execStr = 'ls -t /home/images/*.pkg | grep pkg | head -1 > ' + UPGRADE_IMAGE_FILE_PATH
+ self.cmds_lst_for_get = [
+ {'bin_name' : execStr,
+ 'args_lst' : []},
+ ]
+ return OsWrapper.get_new(self, [], [[]], useShell=True)
+
+class CatUpgradeImagesFile(OsWrapper):
+ def __init__(self, name = 'catupgradeimagesfile'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def get(self, args_list):
+ self.cmds_lst_for_get = [
+ {'bin_name' : '/bin/cat',
+ 'args_lst' : [UPGRADE_IMAGE_FILE_PATH]},
+ ]
+ return OsWrapper.get_new(self, [], [[]])
+
+class ExecuteUpgradeStep(OsWrapper):
+ def __init__(self, name = 'executeupgradestep'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def get(self, args_list):
+ ret_result = {'err': [], 'out': []}
+
+ try:
+ manifest = json.loads(exec_os_wrapper("ExtractUpgradePkgManifest", 'get')['out'])
+ except ValueError:
+ ret_result['err'].append("Corrupted manifest!")
+ return ret_result
+
+ stepToExec = None
+ for step in manifest:
+ if step['step'] == int(args_list[0]):
+ stepToExec = step['action']
+ break;
+
+ if stepToExec == None:
+ ret_result['err'].append("Step %s not found in upgrade package manifest!" %
+ str(args_list[0]))
+ return ret_result
+
+ upgradePkg = args_list[1]
+ stepScript = tempfile.NamedTemporaryFile(delete=False)
+ scriptName = "scripts/%s" % step['action'].strip()
+ step = check_output(["unzip", "-p", upgradePkg, scriptName])
+ stepScript.write(step)
+ stepScript.flush()
+ stepScript.close()
+ os.chmod(stepScript.name, stat.S_IXUSR | stat.S_IWUSR | stat.S_IRUSR)
+
+ try:
+ ret = check_output([stepScript.name] + args_list[1:],
+ stderr=PIPE)
+ ret_result['out'].append(stripped(ret.strip()))
+ except CalledProcessError, exception:
+ ret_result['err'].append("Error running %s\nreturn code %s\nOutput:\n%s" %
+ (exception.cmd, exception.returncode, stripped(exception.output)))
+ return ret_result
+
+class CleanupOldUpgradeImages(OsWrapper):
+ def __init__(self, name = 'cleanupoldupgradeimages'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def get(self, args_list):
+ # Removes all the .pkg files execpt for the newest one.
+ # It handles the case where there is only 1 package, we don't delete it.
+ 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'
+ self.cmds_lst_for_get = [
+ {'bin_name' : execStr,
+ 'args_lst' : []},
+ ]
+ return OsWrapper.get_new(self, [], [[]], useShell=True)
+
+class Decommission(OsWrapper):
+ def __init__(self, name = "decommission"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ["%s/sys/bin/remove-node.sh" % SDN_ROOT, str(args_list[0])]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class DecommissionLocal(OsWrapper):
+ def __init__(self, name = "decommissionlocal"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ["%s/sys/bin/remove-node-local.sh" % SDN_ROOT, str(args_list[0])]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class ResetBsc(OsWrapper):
+ def __init__(self, name = "resetbsc"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ["%s/sys/bin/resetbsc" % SDN_ROOT, '--force']},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class WriteDataToFile(OsWrapper):
+ def __init__(self, name = 'writedatatofile'):
+ OsWrapper.__init__(self, name, [], [])
+
+ def set(self, args_list):
+ data = str(args_list[0])
+ path = str(args_list[1])
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/touch',
+ 'args_lst' : [path]},
+ {'bin_name' : '/bin/echo',
+ 'args_lst' : [data],
+ 'stdoutfile' : path},
+ ]
+ return OsWrapper.set_new(self, [], [[], []])
+
+class DiffConfig(OsWrapper):
+ def __init__(self, name = "scpconfig"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/opt/sdnplatform/sys/bin/diff_config.py',
+ 'args_lst' : [str(args_list[0]), str(args_list[1])]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+class RollbackConfig(OsWrapper):
+ def __init__(self, name = "upgradeconfig"):
+ OsWrapper.__init__(self, name, [], [])
+ def set(self, args_list):
+ self.cmds_lst_for_set = [
+ {'bin_name' : '/bin/bash',
+ 'args_lst' : ["%s/sys/bin/rollback-config.sh" % SDN_ROOT, str(args_list[0])]},
+ ]
+ return OsWrapper.set_new(self, [], [[]])
+
+def stripped(x):
+ # remove ascii escape
+ return "".join([i for i in x if ord(i) != 27])
+#
+# exec_os_wrapper
+#
+def exec_os_wrapper(obj_type, oper, args_list = None):
+ """
+ Execute the oswrapper.py using sudo(), raising an exception
+ for any stderr output from the executed script
+ """
+ # Safety check; only run if this file exists
+ if not os.path.exists("%s/con" % SDN_ROOT):
+ print "exec_os_wrapper: not an installed controller environment"
+ return {'out' : '', 'err' : ''}
+ if os.path.exists('/etc/not-controller'):
+ # XXX should issue some alert here
+ print "exec_os_wrapper: /etc/not-controller exists"
+ return {'out' : '', 'err' : ''}
+
+ oswrapper = os.path.dirname(__file__) + "/oswrapper.py"
+ full_cmd_string = ["/usr/bin/sudo", oswrapper, obj_type, oper]
+ if args_list:
+ full_cmd_string += [str(arg) for arg in args_list]
+
+ sub_proc_output = Popen(full_cmd_string, shell=False,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+
+ sub_proc_output.wait()
+ stderr = sub_proc_output.stderr.read()
+ stdout = sub_proc_output.stdout.read()
+ returncode = sub_proc_output.returncode
+
+ if returncode:
+ raise Exception("oswrapper: %s %s: exit code %d: %s" %
+ (obj_type, oper, returncode, stderr))
+
+ if len(stderr) != 0 and not stderr.isspace():
+ print " ".join(full_cmd_string), stderr
+
+ # ?!?
+ return {'out' : stdout, 'err' : stderr}
+
+
+def main(argv):
+ obj_type_map = {'ExecuteUfwCommand' : UfwCommand,
+ 'SetNtpServer' : SetNtpServer,
+ 'SetTimezone' : SetTimezone,
+ 'UnsetTimezone' : UnsetTimezone,
+ 'NetworkConfig' : NetworkConfig,
+ 'SetSyslogServer' : SetSyslogServer,
+ 'UnsetSyslogServer' : UnsetSyslogServer,
+ 'DateTime' : DateTime,
+ 'ControllerId' : SetControllerId,
+ 'HAFailback' : HAFailback,
+ 'SetHostname' : SetHostname,
+ 'SetVrrpVirtualRouterId' : SetVrrpVirtualRouterId,
+ 'SetStaticFlowOnlyConfig' : SetStaticFlowOnlyConfig,
+ 'SetDefaultConfig' : SetDefaultConfig,
+ 'TacacsPlusConfig' : TacacsPlusConfig,
+ 'SetSnmpServerConfig' : SetSnmpServerConfig,
+ 'UnsetSnmpServerConfig' : UnsetSnmpServerConfig,
+ 'SetImagesUserSSHKey' : SetImagesUserSSHKey,
+ 'ReloadController' : ReloadController,
+ 'ExtractUpgradePkgManifest' : ExtractUpgradePkgManifest,
+ 'GetLatestUpgradePkg' : GetLatestUpgradePkg,
+ 'CatUpgradeImagesFile' : CatUpgradeImagesFile,
+ 'ExecuteUpgradeStep' : ExecuteUpgradeStep,
+ 'DirectNetworkConfig' : DirectNetworkConfig,
+ 'CleanupOldUpgradeImages' : CleanupOldUpgradeImages,
+ 'RestartSDNPlatform' : RestartSDNPlatform,
+ 'AbortUpgrade' : AbortUpgrade,
+ 'Decommission' : Decommission,
+ 'DecommissionLocal' : DecommissionLocal,
+ 'RollbackConfig' : RollbackConfig,
+ 'DiffConfig' : DiffConfig,
+ 'ResetBsc' : ResetBsc,
+ 'WriteDataToFile' : WriteDataToFile,
+ }
+ ret_result = {'err': ["insufficient or invalid args"], 'out': []}
+ if len(argv) >= 3:
+ if argv[1] in obj_type_map:
+ obj_type = obj_type_map[argv[1]]
+ x = obj_type()
+ if argv[2] == 'set':
+ ret_result = x.set(argv[3:])
+ elif argv[2] == 'get':
+ ret_result = x.get(argv[3:])
+
+ # The ret_result entries are lists of strings from the output's of
+ # various commands.
+ print >>sys.stdout, ''.join(ret_result['out'])
+ print >>sys.stderr, ''.join(ret_result['err'])
+
+if __name__ == '__main__':
+ main(sys.argv)