nodemanager/scripts/Utils/AgentUtil.py (182 lines of code) (raw):
# Wrapper module for agent utility
#
# waagent is not written as a module. This wrapper module is created
# to use the waagent code as a module.
#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
#
# 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.
#
# Requires Python 2.7+
#
import subprocess
import time
import string
import re
import os
import tempfile
if not hasattr(subprocess,'check_output'):
def check_output(*popenargs, **kwargs):
r"""Backport from subprocess module from python 2.7"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise subprocess.CalledProcessError(retcode, cmd, output=output)
return output
# Exception classes used by this module.
class CalledProcessError(Exception):
def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
self.output = output
def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
subprocess.check_output=check_output
subprocess.CalledProcessError=CalledProcessError
class Logger(object):
"""
The Agent's logging assumptions are:
For Log, and LogWithPrefix all messages are logged to the
self.file_path and to the self.con_path. Setting either path
parameter to None skips that log. If Verbose is enabled, messages
calling the LogIfVerbose method will be logged to file_path yet
not to con_path. Error and Warn messages are normal log messages
with the 'ERROR:' or 'WARNING:' prefix added.
"""
def __init__(self,filepath,conpath,verbose=False):
"""
Construct an instance of Logger.
"""
self.file_path=filepath
self.con_path=conpath
self.verbose=verbose
def ThrottleLog(self,counter):
"""
Log everything up to 10, every 10 up to 100, then every 100.
"""
return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
def LogToFile(self,message):
"""
Write 'message' to logfile.
"""
if self.file_path:
try:
with open(self.file_path, "a") as F :
message = ''.join(filter(lambda x : x in string.printable, message))
F.write(message + "\n")
except IOError as e:
print(e)
pass
def LogToCon(self,message):
"""
Write 'message' to /dev/console.
This supports serial port logging if the /dev/console
is redirected to ttys0 in kernel boot options.
"""
if self.con_path:
try:
with open(self.con_path, "w") as C :
message = ''.join(filter(lambda x : x in string.printable, message))
C.write(message + "\n")
except IOError as e:
print(e)
pass
def Log(self,message):
"""
Standard Log function.
Logs to self.file_path, and con_path
"""
self.LogWithPrefix("", message)
def LogWithPrefix(self,prefix, message):
"""
Prefix each line of 'message' with current time+'prefix'.
"""
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
t += prefix
for line in message.split('\n'):
line = t + line
self.LogToFile(line)
self.LogToCon(line)
def NoLog(self,message):
"""
Don't Log.
"""
pass
def LogIfVerbose(self,message):
"""
Only log 'message' if global Verbose is True.
"""
self.LogWithPrefixIfVerbose('',message)
def LogWithPrefixIfVerbose(self,prefix, message):
"""
Only log 'message' if global Verbose is True.
Prefix each line of 'message' with current time+'prefix'.
"""
if self.verbose == True:
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
t += prefix
for line in message.split('\n'):
line = t + line
self.LogToFile(line)
self.LogToCon(line)
def Warn(self,message):
"""
Prepend the text "WARNING:" to the prefix for each line in 'message'.
"""
self.LogWithPrefix("WARNING:", message)
def Error(self,message):
"""
Call ErrorWithPrefix(message).
"""
self.ErrorWithPrefix("", message)
def ErrorWithPrefix(self,prefix, message):
"""
Prepend the text "ERROR:" to the prefix for each line in 'message'.
Errors written to logfile, and /dev/console
"""
self.LogWithPrefix("ERROR:", message)
def LoggerInit(log_file_path,log_con_path,verbose=False):
"""
Create log object and export its methods to global scope.
"""
global Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger
l=Logger(log_file_path,log_con_path,verbose)
Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger = l.Log,l.LogWithPrefix,l.LogIfVerbose,l.LogWithPrefixIfVerbose,l.Error,l.ErrorWithPrefix,l.Warn,l.NoLog,l.ThrottleLog,l
return myLogger
def GetFileContents(filepath,asbin=False):
"""
Read and return contents of 'filepath'.
"""
mode='r'
if asbin:
mode+='b'
c=None
try:
with open(filepath, mode) as F :
c=F.read()
except IOError as e:
ErrorWithPrefix('GetFileContents','Reading from file ' + filepath + ' Exception is ' + str(e))
return None
return c
def SetFileContents(filepath, contents):
"""
Write 'contents' to 'filepath'.
"""
if type(contents) == str :
contents=contents.encode('latin-1', 'ignore')
try:
with open(filepath, "wb+") as F :
F.write(contents)
except IOError as e:
ErrorWithPrefix('SetFileContents','Writing to file ' + filepath + ' Exception is ' + str(e))
return None
return 0
def AppendFileContents(filepath, contents):
"""
Append 'contents' to 'filepath'.
"""
if type(contents) == str :
contents=contents.encode('latin-1')
try:
with open(filepath, "a+") as F :
F.write(contents)
except IOError as e:
ErrorWithPrefix('AppendFileContents','Appending to file ' + filepath + ' Exception is ' + str(e))
return None
return 0
def FindStringInFile(fname,matchs):
"""
Return match object if found in file.
"""
try:
ms=re.compile(matchs)
for l in (open(fname,'r')).readlines():
m=re.search(ms,l)
if m:
return m
except:
raise
return None
def ReplaceStringInFile(fname,src,repl):
"""
Replace 'src' with 'repl' in file.
"""
try:
sr=re.compile(src)
if FindStringInFile(fname,src):
updated=''
for l in (open(fname,'r')).readlines():
n=re.sub(sr,repl,l)
updated+=n
ReplaceFileContentsAtomic(fname,updated)
except :
raise
return
def ReplaceFileContentsAtomic(filepath, contents):
"""
Write 'contents' to 'filepath' by creating a temp file, and replacing original.
"""
handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
if type(contents) == str :
contents=contents.encode('latin-1')
try:
os.write(handle, contents)
except IOError as e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Writing to file ' + filepath + ' Exception is ' + str(e))
return None
finally:
os.close(handle)
try:
os.rename(temp, filepath)
return None
except IOError as e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Renaming ' + temp + ' to ' + filepath + ' Exception is ' + str(e))
try:
os.remove(filepath)
except IOError as e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
try:
os.rename(temp,filepath)
except IOError as e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
return 1
return 0
def Run(cmd,chk_err=True):
"""
Calls RunGetOutput on 'cmd', returning only the return code.
If chk_err=True then errors will be reported in the log.
If chk_err=False then errors will be suppressed from the log.
"""
retcode,out=RunGetOutput(cmd,chk_err)
return retcode
def RunGetOutput(cmd,chk_err=True):
"""
Wrapper for subprocess.check_output.
Execute 'cmd'. Returns return code and STDOUT, trapping expected exceptions.
Reports exceptions to Error if chk_err parameter is True
"""
LogIfVerbose(cmd)
try:
output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
except subprocess.CalledProcessError as e:
if chk_err :
Error('CalledProcessError. Error Code is ' + str(e.returncode) )
Error('CalledProcessError. Command string was ' + e.cmd )
Error('CalledProcessError. Command result was ' + (e.output[:-1]).decode('latin-1'))
return e.returncode,e.output.decode('latin-1')
return 0,output.decode('latin-1')