# Software License Agreement (BSD License)
#
# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
# All rights reserved.
#
# Redistribution and use of this software in source and binary forms, with or
# without modification, are permitted provided that the following conditions
# are met:
#
# Redistributions of source code must retain the above
# copyright notice, this list of conditions and the
# following disclaimer.
#
# Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the
# following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author: vic.iglesias@eucalyptus.com
import select
import threading
import time
import eulogger
import sshconnection
import re
import os
import sys
from repoutils import RepoUtils
[docs]class DistroName:
ubuntu = "ubuntu"
rhel = "rhel"
centos = "centos"
fedora = "fedora"
debian = "debian"
vmware = "vmware"
[docs]class DistroRelease:
def __init__(self, distro_name, distro_number, distro_release = "", package_manager= None):
self.name = distro_name
self.number = distro_number
self.release = distro_release
self.package_manager = package_manager
[docs]class Distro:
ubuntu_lucid = DistroRelease(DistroName.ubuntu,"10.04", "lucid", package_manager="apt")
ubuntu_precise = DistroRelease(DistroName.ubuntu, "12.04", "precise", package_manager="apt")
debian_squeeze = DistroRelease(DistroName.debian, "6", "squeeze", package_manager="apt")
debian_wheezy = DistroRelease(DistroName.debian, "7", "wheezy", package_manager="apt")
rhel_6 = DistroRelease(DistroName.rhel, "6", package_manager="yum")
centos_6 = DistroRelease(DistroName.centos, "6", package_manager="yum")
rhel_5 = DistroRelease(DistroName.rhel, "5", package_manager="yum")
centos_5 = DistroRelease(DistroName.centos, "5", package_manager="yum")
fedora_18 = DistroRelease(DistroName.fedora, "18", package_manager="yum")
vmware_5 = DistroRelease(DistroName.vmware, "5")
vmware_4 = DistroRelease(DistroName.vmware, "4")
@classmethod
[docs] def get_distros(Distro):
distros = []
for distro in Distro.__dict__:
if isinstance(Distro.__dict__[distro], DistroRelease):
distros.append(Distro.__dict__[distro])
return distros
[docs]class Machine:
def __init__(self,
hostname,
distro="",
distro_ver="",
arch="",
source="",
components="",
connect=True,
password=None,
keypath=None,
username="root",
timeout=120,
retry=2,
debugmethod=None,
verbose = True ):
self.hostname = hostname
self.distro_ver = distro_ver
self.distro = self.convert_to_distro(distro, distro_ver)
if self.distro.package_manager is not None:
self.repo_utils = RepoUtils(self, self.distro.package_manager)
self.package_manager = self.repo_utils.package_manager
self.arch = arch
self.source = source
self.components = components
self.connect = connect
self.password = password
self.keypath = keypath
self.username = username
self.timeout = timeout
self.retry = retry
self.debugmethod = debugmethod
self.verbose = verbose
self.log_threads = {}
self.log_buffers = {}
self.log_active = {}
self.wget_last_status = 0
if self.debugmethod is None:
logger = eulogger.Eulogger(identifier= str(hostname) + ":" + str(components))
self.debugmethod = logger.log.debug
if self.connect:
self.ssh = sshconnection.SshConnection( hostname,
keypath=keypath,
password=password,
username=username,
timeout=timeout,
retry=retry,
debugmethod=self.debugmethod,
verbose=True)
self.sftp = self.ssh.connection.open_sftp()
[docs] def convert_to_distro(self, distro_name, distro_release):
distro_name = distro_name.lower()
distro_release = distro_release.lower()
for distro in Distro.get_distros():
if re.search( distro.name, distro_name,re.IGNORECASE) and (re.search( distro.release, distro_release,re.IGNORECASE) or re.search( distro.number, distro_release,re.IGNORECASE)) :
return distro
raise Exception("Unable to find distro " + str(distro_name) + " and version " + str(distro_release) + " for hostname " + str(self.hostname))
[docs] def refresh_ssh(self):
self.ssh.refresh_connection()
[docs] def debug(self,msg):
"""
Used to print debug, defaults to print() but over ridden by self.debugmethod if not None
msg - mandatory -string, message to be printed
"""
if self.verbose is True:
self.debugmethod(msg)
[docs] def refresh_connection(self):
self.ssh.refresh_connection()
[docs] def reboot(self, force=True):
if force:
try:
self.sys("reboot -f", timeout=3)
except Exception, e:
pass
else:
try:
self.sys("reboot", timeout=3)
except Exception, e:
pass
[docs] def interrupt_network(self, time = 120, interface = "eth0"):
try:
self.sys("ifdown " + interface + " && sleep " + str(time) + " && ifup eth0", timeout=3)
except Exception,e:
pass
[docs] def sys(self, cmd, verbose=True, timeout=120, listformat=True, code=None):
'''
Issues a command against the ssh connection to this instance
Returns a list of the lines from stdout+stderr as a result of the command
'''
out = self.cmd(cmd, verbose=verbose, timeout=timeout, listformat=listformat)
output = out['output']
if code is not None:
if out['status'] != code:
self.debug(output)
raise Exception('Cmd:'+str(cmd)+' failed with status code:'+str(out['status']))
return output
[docs] def cmd(self, cmd, verbose=True, timeout=120, listformat=False, cb=None, cbargs=[]):
'''
Issues a command against the ssh connection to this instance
returns dict containing:
['cmd'] - The command which was executed
['output'] - The std out/err from the executed command
['status'] - The exit (exitcode) of the command, in the case a call back fires, this status code is unreliable.
['cbfired'] - Boolean to indicate whether or not the provided callback fired (ie returned False)
['elapsed'] - Time elapsed waiting for command loop to end.
cmd - mandatory - string, the command to be executed
verbose - optional - boolean flag to enable debug
timeout - optional - command timeout in seconds
listformat -optional - specifies returned output in list of lines, or single string buffer
cb - optional - call back function, accepting string buffer, returning true false see sshconnection for more info
'''
if (self.ssh is not None):
return self.ssh.cmd(cmd, verbose=verbose, timeout=timeout, listformat=listformat, cb=cb, cbargs=cbargs)
else:
raise Exception("Euinstance ssh connection is None")
[docs] def sys_until_found(self, cmd, regex, verbose=True, timeout=120, listformat=True):
'''
Run a command until output of command satisfies/finds regex or EOF is found.
returns dict containing:
['cmd'] - The command which was executed
['output'] - The std out/err from the executed command
['status'] - The exit (exitcode) of the command, in the case a call back fires, this status code is unreliable.
['cbfired'] - Boolean to indicate whether or not the provided callback fired (ie returned False)
['elapsed'] - Time elapsed waiting for command loop to end.
cmd - mandatory - string, the command to be executed
regex - mandatory - regex to look for
verbose - optional - boolean flag to enable debug
timeout - optional - command timeout in seconds
listformat -optional - specifies returned output in list of lines, or single string buffer
'''
return self.cmd(cmd, verbose=verbose,timeout=timeout,listformat=listformat,cb=self.str_found_cb, cbargs=[regex, verbose])
[docs] def str_found_cb(self,buf,regex,verbose,search=True):
'''
Return sshcbreturn type setting stop to True if given regex matches against given string buf
'''
if verbose:
self.debug(str(buf))
return sshconnection.SshCbReturn( stop=self.str_found(buf, regex=regex, search=search))
[docs] def str_found(self, buf, regex, search=True):
'''
Return True if given regex matches against given string
'''
if search:
found = re.search(regex,buf)
else:
found = re.match(regex, buf)
if found:
return True
else:
return False
[docs] def get_file_stat(self,path):
return self.sftp.lstat(path)
[docs] def get_file_size(self, path):
return self.sftp.lstat(path).st_size
[docs] def get_file_perms_flag(self,path):
return self.sftp.lstat(path).FLAG_PERMISSIONS
[docs] def get_file_groupid(self, path):
return self.sftp.lstat(path).st_gid
[docs] def get_file_userid(self,path):
return self.sftp.lstat(path).st_uid
[docs] def get_masked_pass(self, pwd, firstlast=True, charcount=True, show=False):
'''
format password for printing
options:
pwd - string- the text password to format
firstlast -boolean - show the first and last characters in pwd
charcount -boolean - print a "*" for each char in pwd, otherwise return fixed string '**hidden**'
show - boolean - convert pwd to str() and return it in plain text
'''
ret =""
if pwd is None:
return ""
if show is True:
return str(pwd)
if charcount is False:
return "**hidden**"
for x in xrange(0,len(pwd)):
if (x == 0 or x == len(pwd)) and firstlast:
ret = ret+pwd[x]
else:
ret += "*"
[docs] def mkfs(self, partition, type="ext3"):
self.sys("mkfs."+ type + " -F " + partition)
[docs] def mount(self, device, path):
self.sys("mount "+ device + " " + path)
[docs] def chown(self, user, path):
self.sys("chwon "+ user + ":" + user + " " + path)
[docs] def ping_check(self,host):
out = self.ping_cmd(host)
self.debug('Ping attempt to host:'+str(host)+", status code:"+str(out['status']))
if out['status'] != 0:
raise Exception('Ping returned error:'+str(out['status'])+' to host:'+str(host))
[docs] def ping_cmd(self, host, count=2, pingtimeout=10, commandtimeout=120, listformat=False, verbose=True):
cmd = 'ping -c ' +str(count)+' -t '+str(pingtimeout)
if verbose:
cmd += ' -v '
cmd = cmd + ' '+ str(host)
out = self.cmd(cmd, verbose=verbose, timeout=commandtimeout, listformat=listformat)
if verbose:
#print all returned attributes from ping command dict
for item in sorted(out):
self.debug(str(item)+" = "+str(out[item]) )
return out
[docs] def dump_netfail_info(self,ip=None, mac=None, pass1=None, pass2=None, showpass=True, taillength=50):
"""
Debug method to provide potentially helpful info from current machine when debugging connectivity issues.
"""
self.debug('Attempting to dump network information, args: ip:'+str(ip)+' mac:'+str(mac)+' pass1:'+self.get_masked_pass(pass1,show=True)+' pass2:'+self.get_masked_pass(pass2,show=True))
self.ping_cmd(ip,verbose=True)
self.sys('arp -a')
self.sys('dmesg | tail -'+str(taillength))
self.sys('cat /var/log/messages | tail -'+str(taillength))
[docs] def found(self, command, regex, verbose=True):
""" Returns a Boolean of whether the result of the command contains the regex"""
result = self.sys(command, verbose=verbose)
if result is None or result == []:
return False
for line in result:
found = re.search(regex,line)
if found:
return True
return False
[docs] def wget_remote_image(self,url,path=None, user=None, password=None, retryconn=True, timeout=300):
self.debug('wget_remote_image, url:'+str(url)+", path:"+str(path))
cmd = 'wget '
if path:
cmd = cmd + " -P " + str(path)
if user:
cmd = cmd + " --user " + str(user)
if password:
cmd = cmd + " --password " + str(password)
if retryconn:
cmd += ' --retry-connrefused '
cmd = cmd + ' ' + str(url)
self.debug('wget_remote_image cmd: '+str(cmd))
ret = self.cmd(cmd, timeout=timeout, cb=self.wget_status_cb )
if ret['status'] != 0:
raise Exception('wget_remote_image failed with status:'+str(ret['status']))
self.debug('wget_remote_image succeeded')
[docs] def wget_status_cb(self, buf):
ret = sshconnection.SshCbReturn(stop=False)
try:
buf = buf.strip()
val = buf.split()[0]
if val != self.wget_last_status:
if re.match('^\d+\%',buf):
sys.stdout.write("\r\x1b[K"+str(buf))
sys.stdout.flush()
self.wget_last_status = val
else:
print buf
except Exception, e:
pass
finally:
return ret
[docs] def get_df_info(self, path=None, verbose=True):
"""
Return df's output in dict format for a given path.
If path is not given will give the df info for the current working dir used in the ssh
session this command is executed in (ie: /home/user or /root).
path - optional -string, used to specifiy path to use in df command. Default is PWD of ssh shelled command
verbose - optional -boolean, used to specify whether or debug is printed during this command.
Example:
dirpath = '/disk1/storage'
dfout = self.get_df_info(path=dirpath)
available_space = dfout['available']
mounted_on = dfout['mounted']
filesystem = dfout['filesystem']
"""
ret = {}
if path is None:
path = '${PWD}'
cmd = 'df '+str(path)
if verbose:
self.debug('get_df_info cmd:'+str(cmd))
output = self.sys(cmd, code=0)
# Get the presented fields from commands output,
# Convert to lowercase, use this as our dict keys
fields=[]
line = 0
for field in str(output[line]).split():
fields.append(str(field).lower())
# Move line forward and gather columns into the dict to be returned
x = 0
line += 1
# gather columns equal to the number of column headers accounting for newlines...
while x < (len(fields)-1):
for value in str(output[line]).split():
ret[fields[x]]=value
if verbose:
self.debug(str('DF FIELD: '+fields[x])+' = '+str(value))
x += 1
line += 1
return ret
[docs] def upgrade(self, package=None, nogpg=False):
self.package_manager.upgrade(package, nogpg=nogpg)
[docs] def add_repo(self, url, name="test-repo"):
self.package_manager.add_repo(url,name)
[docs] def install(self, package, nogpg=False):
self.package_manager.install(package,nogpg=nogpg)
[docs] def update_repos(self):
self.package_manager.update_repos()
[docs] def get_package_info(self):
self.package_manager.get_package_info()
[docs] def get_installed_packages(self):
self.package_manager.get_installed_packages()
[docs] def get_available(self, path, unit=1):
"""
Return df output's available field. By default this is KB.
path - optional -string.
unit - optional -integer used to divide return value. Can be used to convert KB to MB, GB, TB, etc..
"""
size = int(self.get_df_info(path=path)['available'])
return size/unit
[docs] def poll_log(self, log_file="/var/log/messages"):
self.debug( "Starting to poll " + log_file )
self.log_channel = self.ssh.connection.invoke_shell()
self.log_channel.send("tail -f " + log_file + " \n")
### Begin polling channel for any new data
while self.log_active[log_file]:
### CLOUD LOG
rl, wl, xl = select.select([self.log_channel],[],[],0.0)
if len(rl) > 0:
self.log_buffers[log_file] += self.log_channel.recv(1024)
time.sleep(1)
[docs] def start_log(self, log_file="/var/log/messages"):
"""Start thread to poll logs"""
thread = threading.Thread(target=self.poll_log, args=log_file)
thread.daemon = True
self.log_threads[log_file]= thread.start()
self.log_active[log_file] = True
[docs] def stop_log(self, log_file="/var/log/messages"):
"""Terminate thread that is polling logs"""
self.log_active[log_file] = False
[docs] def save_log(self, log_file, path="logs"):
"""Save log buffer for log_file to the path to a file"""
if not os.path.exists(path):
os.mkdir(path)
FILE = open( path + '/' + log_file,"w")
FILE.writelines(self.log_buffers[log_file])
FILE.close()
[docs] def save_all_logs(self, path="logs"):
"""Save log buffers to a file"""
for log_file in self.log_buffers.keys():
self.save_log(log_file,path)
def __str__(self):
s = "+++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
s += "+" + "Hostname:" + str(self.hostname) + "\n"
dname = self.distro.name if self.distro else ""
s += "+" + "Distro: " + str(dname) +"\n"
s += "+" + "Distro Version: " + str(self.distro_ver) +"\n"
s += "+" + "Install Type: " + str(self.source) +"\n"
s += "+" + "Components: " + str(self.components) +"\n"
s += "+++++++++++++++++++++++++++++++++++++++++++++++++++++"
return s