| #!/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) |