From 746bbcd76fa4e769d08aea61ebe74398baae0df6 Mon Sep 17 00:00:00 2001 From: Yue Zhang Date: Thu, 7 May 2015 14:50:51 +0800 Subject: [PATCH] Fix unittest for osinfo and osutil --- azureguestagent/agent.py | 118 +-- azureguestagent/conf.py | 33 +- azureguestagent/exception.py | 24 + azureguestagent/handler/__init__.py | 2 - .../handler/default/deprovisionHandler.py | 46 + .../handler/default/dhcpHandler.py | 16 +- .../handler/default/resourceDiskHandler.py | 34 + .../handler/default/scvmmHandler.py | 21 + azureguestagent/handler/factory.py | 5 +- azureguestagent/osinfo.py | 1 - azureguestagent/utils/default.py | 900 ------------------ azureguestagent/utils/osutil/__init__.py | 2 - azureguestagent/utils/osutil/default.py | 6 +- azureguestagent/utils/osutil/factory.py | 2 +- tests/test_conf.py | 8 +- tests/test_handlerfactory.py | 31 + tests/test_osinfo.py | 36 + tests/test_osutil.py | 134 ++- tests/tools.py | 6 +- 19 files changed, 330 insertions(+), 1095 deletions(-) create mode 100644 azureguestagent/exception.py delete mode 100644 azureguestagent/utils/default.py create mode 100644 tests/test_handlerfactory.py create mode 100644 tests/test_osinfo.py diff --git a/azureguestagent/agent.py b/azureguestagent/agent.py index ccb3edc..b6512b3 100644 --- a/azureguestagent/agent.py +++ b/azureguestagent/agent.py @@ -26,7 +26,9 @@ import traceback import threading import azureguestagent.logger as logger import azureguestagent.conf as conf -from azureguestagent.os import CurrOS, CurrOSInfo +from azureguestagent.osinfo import CurrOSInfo +from azureguestagent.handle import CurrOSHandlerFactory +from azureguestagent.utils.osutil import CurrOSUtil import azureguestagent.utils.shellutil as shellutil import azureguestagent.utils.fileutil as fileutil @@ -37,39 +39,28 @@ GuestAgentLongVersion = "{0}-{1}".format(GuestAgentName, GuestAgentVersion) GuestAgentAuthor='MS OSTC' GuestAgentUri='https://github.com/Azure/WALinuxAgent' -VmmConfigFileName = "linuxosconfiguration.xml" -VmmStartupScriptName= "install" -DataLossWarningFile="DATALOSS_WARNING_README.txt" -DataLossWarning="""\ -WARNING: THIS IS A TEMPORARY DISK. - -Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT. - -Please do not use this disk for storing any personal or application data. - -For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx -""" - -class Agent(): +class Agent(object): def __init__(self, config): self.config = config def run(self): - os.chdir(CurrOS.GetLibDir()) + os.chdir(CurrOSUtil.GetLibDir()) self.savePid() - if CurrOS.scvmmHandler.detectScvmmEnv(): - CurrOS.scvmmHandler.startScvmmAgent() + scvmmHandler = CurrOSHandlerFactory.GetScvmmHandler() + if scvmmHandler.detectScvmmEnv(): + scvmmHandler.startScvmmAgent() return - CurrOS.dhcpHandler.waitForNetwork() - CurrOS.dhcpHandler.probe() + dhcpHandler = CurrOSHandlerFactory.GetDhcpHandler() + dhcpHandler.waitForNetwork() + dhcpHandler.probe() + CurrOSUtil.SetWireServerEndpoint(dhcpHandler.getEndpoint()) - CurrOS.SetWireServerEndpoint(self.dhcpHandler.getEndpoint()) self.protocol = proto.DetectDefaultProtocol() - provisoned = os.path.join(CurrOS.GetLibDir(), "provisioned") + provisoned = os.path.join(CurrOSUtil.GetLibDir(), "provisioned") if(not os.path.isfile(provisoned)): provisionHandler = provision.ProvisionHandler(self.config, self.protocol) @@ -83,11 +74,10 @@ class Agent(): raise e if self.config.getSwitch("ResourceDisk.Format", False): - #TODO FreeBSD use Popen to open another process to do this - #Need to investigate why? - diskThread = threading.Thread(target = self.activateResourceDisk) - diskThread.start() - + rdHandler = CurrOSHandlerFactory.GetResourceDiskHandler() + rdHandler.startActivateResourceDisk(self.config) + + self.envmonitor = envmon.EnvMonitor(self.config, self.dhcpHandler) #TODO Start load balancer #Need to check whether this should be kept @@ -109,30 +99,10 @@ class Agent(): agentStatusDetail) #Wait for 25 seconds and detect protocol again. time.sleep(25) - - def activateResourceDisk(self): - mountpoint = self.config.get("ResourceDisk.MountPoint", "/mnt/resource") - fs = self.config.get("ResourceDisk.Filesystem", "ext3") - mountpoint = CurrOS.MountResourceDisk(mountpoint, fs) - warningFile = os.path.join(mountpoint, DataLossWarningFile) - fileutil.SetFileContents(warningFile, DataLossWarning) - if self.config.getSwitch("ResourceDisk.EnabledSwap", False): - sizeMB = self.config.getInt("ResourceDisk.SwapSizeMB", 0) - CurrOS.CreateSwapSpace(mountpoint, sizeMB) - - def detectScvmmEnv(self): - CurrOS.MountDvd(maxRetry=0, chk_err=False) - mountPoint = CurrOS.GetDvdMountPoint() - return os.path.isfile(os.path.join(mountPoint, VmmConfigFileName)) - - def startScvmmAgent(self): - logger.Info("Starting Microsoft System Center VMM Initialization Process") - mountPoint = CurrOS.GetDvdMountPoint() - startupScript = os.path.join(mountPoint, VmmStartupScriptName) - subprocess.Popen(["/bin/bash", startupScript, "-p " + mountPoint]) def savePid(self): - fileutil.SetFileContents(CurrOS.GetAgentPidPath(), str(os.getpid())) + fileutil.SetFileContents(CurrOSUtil.GetAgentPidPath(), + str(os.getpid())) def ParseArgs(sysArgv): cmd = None @@ -167,62 +137,28 @@ def Usage(): print ("usage: {0} [-verbose] [-force] " "[-help|-deprovision[+user]|-version|-serialconsole|-daemon]") -def Deprovision(force=False, deluser=False): - configPath = CurrOS.GetConfigurationPath() - config = conf.LoadConfiguration(configPath) - print("WARNING! The waagent service will be stopped.") - print("WARNING! All SSH host key pairs will be deleted.") - print("WARNING! Cached DHCP leases will be deleted.") - CurrOS.OnDeprovisionStart() - delRootPasswd = config.getSwitch("Provisioning.DeleteRootPassword", False) - if delRootPasswd: - print("WARNING! root password will be disabled. " - "You will not be able to login as root.") - protocol = proto.GetDefaultProtocol() - ovf = protocol.getOvf() - if ovf is not None and deluser: - print ("WARNING! {0} account and entire home directory " - "will be deleted.").format(ovf.getUserName()) - - if not force: - confirm = raw_input("Do you want to proceed (y/n)") - if not confirm.lower().startswith('y'): - return - - CurrOS.StopAgentService() - if delRootPasswd: - CurrOS.DeleteRootPassword() - if config.getSwitch("Provisioning.RegenerateSshHostkey", False): - shellutil.Run("rm -f /etc/ssh/ssh_host_*key*") - CurrOS.SetHostname('localhost.localdomain') - fileutil.CleanupDirs(CurrOS.GetLibDir(), "/var/lib/dhclient", - "/var/lib/dhcpcd", "/var/lib/dhcp") - fileutil.RemoveFiles('/root/.bash_history', '/var/log/waagent.log') - CurrOS.OnDeprovision() - - if ovf is not None and deluser: - CurrOS.DeleteAccount(ovf.getUserName()) - def Main(): command, force, verbose = ParseArgs(sys.argv[1:]) if command == "deprovision+user": - Deprovision(force=force, deluser=True) + deprovisionHandler = CurrOSHandlerFactory.GetDeprovisionHandler() + deprovisionHandler.deprovision(force=force, deluser=True) elif command == "deprovision": - Deprovision(force=force, deluser=False) + deprovisionHandler = CurrOSHandlerFactory.GetDeprovisionHandler() + deprovisionHandler.deprovision(force=force, deluser=True) elif command == "daemon": - configPath = CurrOS.GetConfigurationPath() + configPath = CurrOSUtil.GetConfigurationPath() config = conf.LoadConfiguration(configPath) verbose = config.getSwitch("Logs.Verbose", False) logger.LoggerInit('/var/log/waagent.log', '/dev/console', verbose=verbose) - fileutil.CreateDir(CurrOS.GetLibDir(), mode='0700') - os.chdir(CurrOS.GetLibDir()) + fileutil.CreateDir(CurrOSUtil.GetLibDir(), mode='0700') + os.chdir(CurrOSUtil.GetLibDir()) Agent(config).run() elif command == "serialconsole": #TODO pass elif command == "version": Version() - else:# command == 'help': + else: Usage() diff --git a/azureguestagent/conf.py b/azureguestagent/conf.py index b82555f..9a2b832 100644 --- a/azureguestagent/conf.py +++ b/azureguestagent/conf.py @@ -19,14 +19,37 @@ import os import azureguestagent.utils.fileutil as fileutil +from azureguestagent.exception import * +from azureguestagent.utils.osutil import CurrOSUtil def LoadConfiguration(confFilePath): if os.path.isfile(confFilePath) == False: - raise Exception("Missing configuration in {0}", confFilePath) + raise AgentConfigError("Missing configuration in {0}", confFilePath) try: - return ConfigurationProvider(fileutil.GetFileContents(confFilePath)) + content = fileutil.GetFileContents(confFilePath) + __Config__ = ConfigurationProvider(content) + return __Config__ except IOError, e: - raise Exception("Failed to load conf file:{0}", confFilePath) + raise AgentConfigError("Failed to load conf file:{0}", confFilePath) + +__Config__ = None +def Get(key, defaultValue=None): + if __Config__ is not None: + return __Config__.get(key, defaultValue) + else: + return defaultValue + +def GetSwitch(key, defaultValue=None): + if __Config__ is not None: + return __Config__.getSwitch(key, defaultValue) + else: + return defaultValue + +def GetInt(key, defaultValue=None): + if __Config__ is not None: + return __Config__.getInt(key, defaultValue) + else: + return defaultValue class ConfigurationProvider(object): """ @@ -35,7 +58,7 @@ class ConfigurationProvider(object): def __init__(self, content): self.values = dict() if not content: - raise Exception("Can't not parse empty configuration") + raise AgentConfigError("Can't not parse empty configuration") for line in content.split('\n'): if not line.startswith("#") and "=" in line: parts = line.split()[0].split('=') @@ -59,4 +82,4 @@ class ConfigurationProvider(object): except: return defaultValue - + diff --git a/azureguestagent/exception.py b/azureguestagent/exception.py new file mode 100644 index 0000000..5242b49 --- /dev/null +++ b/azureguestagent/exception.py @@ -0,0 +1,24 @@ +# Windows Azure Linux Agent +# +# 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.4+ and Openssl 1.0+ +# + +class AgentError(Exception): + pass + +class AgentConfigError(AgentError): + pass diff --git a/azureguestagent/handler/__init__.py b/azureguestagent/handler/__init__.py index 6ae8eba..6e9dd82 100644 --- a/azureguestagent/handler/__init__.py +++ b/azureguestagent/handler/__init__.py @@ -18,5 +18,3 @@ # from azureguestagent.handler.factory import CurrOSHandlerFactory - -__all__ = ["CurrOSHandlerFactory"] diff --git a/azureguestagent/handler/default/deprovisionHandler.py b/azureguestagent/handler/default/deprovisionHandler.py index 7a4980e..09e5f31 100644 --- a/azureguestagent/handler/default/deprovisionHandler.py +++ b/azureguestagent/handler/default/deprovisionHandler.py @@ -16,3 +16,49 @@ # # Requires Python 2.4+ and Openssl 1.0+ # + +from azureguestagent.utils.osutil import CurrOSUtilUtil +import azureguestagent.utils.fileutil as fileutil + +class Deprovisionhandler(object): + + def promptUser(self, delRootPasswd, ovf): + print("WARNING! The waagent service will be stopped.") + print("WARNING! All SSH host key pairs will be deleted.") + print("WARNING! Cached DHCP leases will be deleted.") + if delRootPasswd: + print("WARNING! root password will be disabled. " + "You will not be able to login as root.") + if ovf is not None and deluser: + print ("WARNING! {0} account and entire home directory " + "will be deleted.").format(ovf.getUserName()) + + def deprovision(force=False, deluser=False): + configPath = CurrOSUtil.GetConfigurationPath() + config = conf.LoadConfiguration(configPath) + delRootPasswd = config.getSwitch("Provisioning.DeleteRootPassword", False) + protocol = proto.GetDefaultProtocol() + ovf = protocol.getOvf() + + self.promptUser(delRootPasswd, ovf) + if not force: + confirm = raw_input("Do you want to proceed (y/n)") + if not confirm.lower().startswith('y'): + return + + self.cleanup(delRootPasswd) + + def cleanup(self, delRootPasswd, ovf) + CurrOSUtil.StopAgentService() + if delRootPasswd: + CurrOSUtil.DeleteRootPassword() + if config.getSwitch("Provisioning.RegenerateSshHostkey", False): + shellutil.Run("rm -f /etc/ssh/ssh_host_*key*") + CurrOSUtil.SetHostname('localhost.localdomain') + fileutil.CleanupDirs(CurrOSUtil.GetLibDir(), "/var/lib/dhclient", + "/var/lib/dhcpcd", "/var/lib/dhcp") + fileutil.RemoveFiles('/root/.bash_history', '/var/log/waagent.log') + + if ovf is not None and deluser: + CurrOSUtil.DeleteAccount(ovf.getUserName()) + diff --git a/azureguestagent/handler/default/dhcpHandler.py b/azureguestagent/handler/default/dhcpHandler.py index 163f9d7..7d0401b 100644 --- a/azureguestagent/handler/default/dhcpHandler.py +++ b/azureguestagent/handler/default/dhcpHandler.py @@ -22,6 +22,7 @@ import socket import array import time import azureguestagent.logger as logger +from azureguestagent.utils.osutil import CurrOSUtil import azureguestagent.utils.restutil as restutil import azureguestagent.utils.fileutil as fileutil import azureguestagent.utils.shellutil as shellutil @@ -29,22 +30,21 @@ from azureguestagent.utils.textutil import * class DhcpHandler(object): - def __init__(self, osutil): + def __init__(self): self.endpoint = None self.gateway = None self.routes = None - self.osutil = osutil def waitForNetwork(self): - ipv4 = self.osutil.GetIpv4Address() + ipv4 = CurrOSUtil.GetIpv4Address() while ipv4 == '' or ipv4 == '0.0.0.0': logger.Info("Waiting for network.") time.sleep(10) - self.osutil.StartNetwork() - ipv4 = self.osutil.GetIpv4Address() + CurrOSUtil.StartNetwork() + ipv4 = CurrOSUtil.GetIpv4Address() def probe(self): - macAddress = self.osutil.GetMacAddress() + macAddress = CurrOSUtil.GetMacAddress() req = BuildDhcpRequest(macAddress) resp = SendDhcpRequest(req) endpoint, gateway, routes = ParseDhcpResponse(resp) @@ -59,10 +59,10 @@ class DhcpHandler(object): def configRoutes(self): #Add default gateway if self.gateway is not None: - self.osutil.RouteAdd(0 , 0, self.gateway) + CurrOSUtil.RouteAdd(0 , 0, self.gateway) if self.routes is not None: for route in self.routes: - self.osutil.RouteAdd(route[0], route[1], route[2]) + CurrOSUtil.RouteAdd(route[0], route[1], route[2]) def ValidateDhcpResponse(request, response): bytesReceived = len(response) diff --git a/azureguestagent/handler/default/resourceDiskHandler.py b/azureguestagent/handler/default/resourceDiskHandler.py index 7a4980e..9e68365 100644 --- a/azureguestagent/handler/default/resourceDiskHandler.py +++ b/azureguestagent/handler/default/resourceDiskHandler.py @@ -16,3 +16,37 @@ # # Requires Python 2.4+ and Openssl 1.0+ # + +import os +from azureguestagent.utils.osutil import CurrOSUtil +import azureguestagent.utils.fileutil as fileutil + +DataLossWarningFile="DATALOSS_WARNING_README.txt" +DataLossWarning="""\ +WARNING: THIS IS A TEMPORARY DISK. + +Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT. + +Please do not use this disk for storing any personal or application data. + +For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx +""" + +class ResourceDiskHandler(object): + + def startActivateResourceDisk(self, config): + #TODO FreeBSD use Popen to open another process to do this + diskThread = threading.Thread(target = self.activateResourceDisk, + args = (config)) + diskThread.start() + + def activateResourceDisk(self, config): + mountpoint = config.get("ResourceDisk.MountPoint", "/mnt/resource") + fs = config.get("ResourceDisk.Filesystem", "ext3") + mountpoint = CurrOSUtil.MountResourceDisk(mountpoint, fs) + warningFile = os.path.join(mountpoint, DataLossWarningFile) + fileutil.SetFileContents(warningFile, DataLossWarning) + if config.getSwitch("ResourceDisk.EnabledSwap", False): + sizeMB = config.getInt("ResourceDisk.SwapSizeMB", 0) + CurrOSUtil.CreateSwapSpace(mountpoint, sizeMB) + diff --git a/azureguestagent/handler/default/scvmmHandler.py b/azureguestagent/handler/default/scvmmHandler.py index 7a4980e..1dfd0f7 100644 --- a/azureguestagent/handler/default/scvmmHandler.py +++ b/azureguestagent/handler/default/scvmmHandler.py @@ -16,3 +16,24 @@ # # Requires Python 2.4+ and Openssl 1.0+ # + +import os +from azureguestagent.utils.osutil import CurrOSUtil +import azureguestagent.utils.fileutil as fileutil + +VmmConfigFileName = "linuxosconfiguration.xml" +VmmStartupScriptName= "install" + +class ScvmmHandler(object): + + def detectScvmmEnv(self): + CurrOSUtil.MountDvd(maxRetry=0, chk_err=False) + mountPoint = CurrOSUtil.GetDvdMountPoint() + return os.path.isfile(os.path.join(mountPoint, VmmConfigFileName)) + + def startScvmmAgent(self): + logger.Info("Starting Microsoft System Center VMM Initialization Process") + mountPoint = CurrOSUtil.GetDvdMountPoint() + startupScript = os.path.join(mountPoint, VmmStartupScriptName) + subprocess.Popen(["/bin/bash", startupScript, "-p " + mountPoint]) + diff --git a/azureguestagent/handler/factory.py b/azureguestagent/handler/factory.py index bc16d9c..f3105f7 100644 --- a/azureguestagent/handler/factory.py +++ b/azureguestagent/handler/factory.py @@ -17,8 +17,7 @@ # Requires Python 2.4+ and Openssl 1.0+ # -import azureguestagent.osinfo as osinfo - +from azureguestagent.osinfo import CurrOSInfo import azureguestagent.handler.default as default def GetOSHandlerFactory(osInfo): @@ -33,5 +32,5 @@ def GetOSHandlerFactory(osInfo): #Return default implementation return default.DefaultHandlers() -CurrOSHandlerFactory = GetOSHandlerFactory() +CurrOSHandlerFactory = GetOSHandlerFactory(CurrOSInfo) diff --git a/azureguestagent/osinfo.py b/azureguestagent/osinfo.py index 47d6c6e..cdbe3c9 100644 --- a/azureguestagent/osinfo.py +++ b/azureguestagent/osinfo.py @@ -18,7 +18,6 @@ # import platform -import azureguestagent.os.baseos.DefaultDistro def GetDistroInfo(): if 'FreeBSD' in platform.system(): diff --git a/azureguestagent/utils/default.py b/azureguestagent/utils/default.py deleted file mode 100644 index c3bdf49..0000000 --- a/azureguestagent/utils/default.py +++ /dev/null @@ -1,900 +0,0 @@ -# -# 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.4+ and Openssl 1.0+ -# - -import platform -import os -import re -import pwd -import shutil -import tempfile -import subprocess -import socket -import array -import struct -import fcntl -import time -import base64 -import azureguestagent.logger as logger -import azureguestagent.utils.fileutil as fileutil -import azureguestagent.utils.shellutil as shellutil -import azureguestagent.utils.textutil as textutil - -RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules", - "/etc/udev/rules.d/70-persistent-net.rules" ] - -""" -Define distro specific behavior. DefaultDistro class defines default behavior -for all distros. Each concrete distro classes could overwrite default behavior -if needed. -""" - - -class DefaultDistro(object): - - __WireServer=None - - def __init__(self): - self.libDir = "/var/lib/waagent" - self.extLogDir = "/var/log/azure" - self.dvdMountPoint = "/mnt/cdrom/secure" - self.ovfenvPathOnDvd = "/mnt/cdrom/secure/ovf-env.xml" - self.agentPidPath = "/var/run/waagent.pid" - self.passwdPath = "/etc/shadow" - self.home = '/home' - self.sshdConfigPath = '/etc/ssh/sshd_config' - self.opensslCmd = '/usr/bin/openssl' - self.configPath = '/etc/waagent.conf' - self.selinux=None - - def GetLibDir(self): - return self.libDir - - def GetExtLogDir(self): - return self.extLogDir - - def GetDvdMountPoint(self): - return self.dvdMountPoint - - def GetConfigurationPath(self): - return self.configPath - - def GetOvfEnvPathOnDvd(self): - return self.ovfenvPathOnDvd - - def GetAgentPidPath(self): - return self.agentPidPath - - def GetOpensslCmd(self): - return self.opensslCmd - - def GetWireServerEndpoint(self): - return DefaultDistro.__WireServer - - def SetWireServerEndpoint(self, endpoint): - DefaultDistro.__WireServer = endpoint - - def UpdateUserAccount(self, userName, password, expiration=None): - """ - Update password and ssh key for user account. - New account will be created if not exists. - """ - if userName is None: - raise Exception("User name is empty") - - if self._IsSysUser(userName): - raise Exception(("User {0} is a system user. " - "Will not set passwd.").format(userName)) - - userentry = self.GetUserEntry(userName) - if userentry is None: - self._CreateUserAccount(userName, expiration) - - if password is not None: - self.ChangePassword(userName, password) - - self.ConfigSudoer(userName, password is None) - - def GetUserEntry(self, userName): - try: - return pwd.getpwnam(userName) - except KeyError: - return None - - def _IsSysUser(self, userName): - userentry = self.GetUserEntry(userName) - uidmin = None - try: - uidminDef = GetLineStartingWith("UID_MIN", "/etc/login.defs") - uidmin = int(uidminDef.split()[1]) - except: - pass - if uidmin == None: - uidmin = 100 - if userentry != None and userentry[2] < uidmin: - return True - else: - return False - - def _CreateUserAccount(self, userName, expiration=None): - cmd = "useradd -m {0}".format(userName) - if expiration is not None: - cmd = "{0} -e {1}".format(cmd, expiration) - retcode, out = shellutil.RunGetOutput(cmd) - if retcode != 0: - raise Exception(("Failed to create user account:{0}, " - "retcode:{1}, " - "output:{2}").format(userName, retcode, out)) - - def ChangePassword(self, userName, password): - shellutil.RunSendStdin("chpasswd", - "{0}:{1}\n".format(userName, password)) - - def ConfigSudoer(self, userName, nopasswd): - # for older distros create sudoers.d - if not os.path.isdir('/etc/sudoers.d/'): - # create the /etc/sudoers.d/ directory - os.mkdir('/etc/sudoers.d/') - # add the include of sudoers.d to the /etc/sudoers - sudoers = fileutil.GetFileContents('/etc/sudoers') - sudoers = sudoers + '\n' + '#includedir /etc/sudoers.d/\n' - fileutil.SetFileContents('/etc/sudoers', sudoers) - sudoer = None - if nopasswd: - sudoer = "{0} ALL = (ALL) NOPASSWD\n".format(userName) - else: - sudoer = "{0} ALL = (ALL) ALL\n".format(userName) - fileutil.SetFileContents('/etc/sudoers.d/waagent', sudoer, append=True) - fileutil.ChangeMod('/etc/sudoers.d/waagent', 0440) - - def DeleteRootPassword(self): - passwdContent = fileutil.GetFileContents(self.passwdPath) - if passwdContent is None: - raise Exception("Failed to delete root password.") - passwd = passwdContent.split('\n') - newPasswd = filter(lambda x : not x.startswith("root:"), passwd) - newPasswd.insert(0, "root:*LOCK*:14600::::::") - fileutil.ReplaceFileContentsAtomic(self.passwdPath, "\n".join(newPasswd)) - - def GetHome(self): - return self.home - - def GetPubKeyFromPrv(self, fileName): - cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.opensslCmd, - fileName) - pub = shellutil.RunGetOutput(cmd)[1] - return pub - - def GetPubKeyFromCrt(self, fileName): - cmd = "{0} x509 -in {1} -pubkey -noout".format(self.opensslCmd, - fileName) - pub = shellutil.RunGetOutput(cmd)[1] - return pub - - def _NormPath(self, filepath): - home = CurrOS.GetHome() - # Expand HOME variable if present in path - path = os.path.normpath(filepath.replace("$HOME", home)) - return path - - def GetThumbprintFromCrt(self, fileName): - cmd="{0} x509 -in {1} -fingerprint -noout".format(self.opensslCmd, - fileName) - thumbprint = shellutil.RunGetOutput(cmd)[1] - thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper() - return thumbprint - - def DeploySshKeyPair(self, userName, thumbprint, path): - """ - Deploy id_rsa and id_rsa.pub - """ - path = self._NormPath(path) - dirPath = os.path.dirname(path) - fileutil.CreateDir(dirPath, mode=0700, owner=userName) - libDir = CurrOS.GetLibDir() - prvPath = os.path.join(libDir, thumbprint + '.prv') - if not os.path.isfile(prvPath): - logger.Error("Failed to deploy key pair, thumbprint: {0}", - thumbprint) - return - shutil.copyfile(prvPath, path) - pubPath = path + '.pub' - pub = self.GetPubKeyFromPrv(prvPath) - fileutil.SetFileContents(pubPath, pub) - self.SetSelinuxContext(path, 'unconfined_u:object_r:ssh_home_t:s') - self.SetSelinuxContext(pubPath, 'unconfined_u:object_r:ssh_home_t:s') - os.chmod(path, 0600) - os.chmod(pubPath, 0600) - - def DeploySshPublicKey(self, userName, thumbprint, path): - """ - Deploy authorized_key - """ - path = self._NormPath(path) - dirPath = os.path.dirname(path) - fileutil.CreateDir(dirPath, mode=0700, owner=userName) - libDir = CurrOS.GetLibDir() - crtPath = os.path.join(libDir, thumbprint + '.crt') - if not os.path.isfile(crtPath): - logger.Error("Failed to deploy public key, thumbprint: {0}", - thumbprint) - return - pubPath = os.path.join(libDir, thumbprint + '.pub') - pub = self.GetPubKeyFromCrt(crtPath) - fileutil.SetFileContents(pubPath, pub) - self.SetSelinuxContext(pubPath, 'unconfined_u:object_r:ssh_home_t:s') - #TODO some distros doesn't support PKCS8. Need to figure out. - shellutil.Run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(pubPath, - path)) - self.SetSelinuxContext(path, 'unconfined_u:object_r:ssh_home_t:s') - os.chmod(path, 0600) - os.chmod(pubPath, 0600) - - def IsSelinuxSystem(self): - """ - Checks and sets self.selinux = True if SELinux is available on system. - """ - if self.selinux == None: - if shellutil.Run("which getenforce", chk_err=False) == 0: - self.selinux = True - else: - self.selinux = False - return self.selinux - - def IsSelinuxRunning(self): - """ - Calls shell command 'getenforce' and returns True if 'Enforcing'. - """ - if self.IsSelinuxSystem(): - output = shellutil.RunGetOutput("getenforce")[1] - return output.startswith("Enforcing") - else: - return False - - def SetSelinuxEnforce(self, state): - """ - Calls shell command 'setenforce' with 'state' - and returns resulting exit code. - """ - if self.IsSelinuxSystem(): - if state: s = '1' - else: s='0' - return shellutil.Run("setenforce "+s) - - def SetSelinuxContext(self, path, cn): - """ - Calls shell 'chcon' with 'path' and 'cn' context. - Returns exit result. - """ - if self.IsSelinuxSystem(): - return shellutil.Run('chcon ' + cn + ' ' + path) - - def GetSshdConfigPath(self): - return self.sshdConfigPath - - def SetSshClientAliveInterval(self): - configPath = self.GetSshdConfigPath() - config = fileutil.GetFileContents(configPath).split("\n") - textutil.SetSshConfig(config, "ClientAliveInterval", "180") - fileutil.ReplaceFileContentsAtomic(configPath, '\n'.join(config)) - logger.Info("Configured SSH client probing to keep connections alive.") - - def ConfigSshd(self, disablePassword): - option = "no" if disablePassword else "yes" - configPath = self.GetSshdConfigPath() - config = fileutil.GetFileContents(configPath).split("\n") - textutil.SetSshConfig(config, "PasswordAuthentication", option) - textutil.SetSshConfig(config, "ChallengeResponseAuthentication", option) - fileutil.ReplaceFileContentsAtomic(configPath, "\n".join(config)) - logger.Info("Disabled SSH password-based authentication methods.") - - def RegenerateSshHostkey(self, keyPairType): - shellutil.Run("rm -f /etc/ssh/ssh_host_*key*") - shellutil.Run("ssh-keygen -N '' -t {0} -f /etc/ssh/ssh_host_{1}_key" - .format(keyPairType, keyPairType)) - - def GetSshHostKeyThumbprint(self, keyPairType): - cmd = "ssh-keygen -lf /etc/ssh/ssh_host_{0}_key.pub".format(keyPairType) - ret = shellutil.RunGetOutput(cmd) - if ret[0] == 0: - return ret[1].rstrip().split()[1].replace(':', '') - else: - return None - - def WaitForSshHostKey(self, keyPairType, maxRetry=6): - path = '/etc/ssh/ssh_host_{0}_key'.format(keyPairType) - for retry in range(0, maxRetry): - if os.path.isfile(path): - return - logger.Info("Wait for ssh host key be generated: {0}", path) - time.sleep(1) - raise Exception("Can't find ssh host key.") - - def GetDvdDevice(self, devDir='/dev'): - patten=r'(sr[0-9]|hd[c-z]|cdrom[0-9])' - for dvd in [re.match(patten, dev) for dev in os.listdir(devDir)]: - if dvd is not None: - return "/dev/{0}".format(dvd.group(0)) - return None - - def MountDvd(self, maxRetry=6, chk_err=True): - dvd = self.GetDvdDevice() - mountPoint = self.GetDvdMountPoint() - #TODO Why do we need to load atapiix? - #self.LoadAtapiixModule() - mountlist = shellutil.RunGetOutput("mount")[1] - existing = self._GetMountPoint(mountlist, dvd) - if existing is not None: #Already mounted - return - if not os.path.isdir(mountPoint): - os.makedirs(mountPoint) - retcode = self.Mount(dvd, mountPoint, chk_err) - for retry in range(0, maxRetry): - if retcode == 0: - logger.Info("Successfully mounted provision dvd") - return - else: - logger.Warn("Mount dvd failed: retry={0}, ret={1}", - retry, - retcode) - time.sleep(5) - self.Mount(dvd, mountPoint, chk_err) - if chk_err: - raise Exception("Failed to mount provision dvd") - - def UmountDvd(self): - mountPoint = self.GetDvdMountPoint() - self.Umount(mountPoint) - - def LoadAtapiixModule(self): - if self.IsAtaPiixModuleLoaded(): - return - ret, kernVersion = shellutil.RunGetOutput("uname -r") - if ret != 0: - raise Exception("Failed to call uname -r") - modulePath = os.path.join('/lib/modules', - kernVersion.strip('\n'), - 'kernel/drivers/ata/ata_piix.ko') - if not os.path.isfile(modulePath): - raise Exception("Can't find module file:{0}".format(modulePath)) - - ret, output = shellutil.RunGetOutput("insmod " + modulePath) - if ret != 0: - raise Exception("Error calling insmod for ATAPI CD-ROM driver") - if not self.IsAtaPiixModuleLoaded(maxRetry=3): - raise Exception("Failed to load ATAPI CD-ROM driver") - - def IsAtaPiixModuleLoaded(self, maxRetry=1): - for retry in range(0, maxRetry): - ret = shellutil.Run("lsmod | grep ata_piix", chk_err=False) - if ret == 0: - logger.Info("Module driver for ATAPI CD-ROM is already present.") - return True - time.sleep(1) - return False - - def Mount(self, dvd, mountPoint, chk_err=True): - return shellutil.RunGetOutput("mount {0} {1}".format(dvd, mountPoint), - chk_err)[0] - - def Umount(self, mountPoint): - return shellutil.Run("umount {0}".format(mountPoint)) - - def OpenPortForDhcp(self): - #Open DHCP port if iptables is enabled. - # We supress error logging on error. - shellutil.Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT", - chk_err=False) - shellutil.Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT", - chk_err=False) - - def GenerateTransportCert(self): - """ - Create ssl certificate for https communication with endpoint server. - """ - cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 32768 " - "-newkey rsa:2048 -keyout TransportPrivate.pem " - "-out TransportCert.pem").format(self.opensslCmd) - shellutil.Run(cmd) - - def RemoveRulesFiles(self, rulesFiles=RulesFiles): - libDir = self.GetLibDir() - for src in rulesFiles: - fileName = fileutil.GetLastPathElement(src) - dest = os.path.join(libDir, fileName) - if os.path.isfile(dest): - os.remove(dest) - if os.path.isfile(src): - logger.Warn("Move rules file {0} to {1}", fileName, dest) - shutil.move(src, dest) - - def RestoreRulesFiles(self, rulesFiles=RulesFiles): - libDir = self.GetLibDir() - for dest in rulesFiles: - fileName = fileutil.GetLastPathElement(dest) - src = os.path.join(libDir, fileName) - if os.path.isfile(dest): - continue - if os.path.isfile(src): - logger.Warn("Move rules file {0} to {1}", fileName, dest) - shutil.move(src, dest) - - def CheckDependencies(self): - #TODO Add dependency check - pass - - def GetMacAddress(self): - """ - Convienience function, returns mac addr bound to - first non-loobback interface. - """ - ifname='' - while len(ifname) < 2 : - ifname=self.GetFirstActiveNetworkInterfaceNonLoopback()[0] - addr = self.GetInterfaceMac(ifname) - return textutil.HexStringToByteArray(addr) - - def GetInterfaceMac(self, ifname): - """ - Return the mac-address bound to the socket. - """ - sock = socket.socket(socket.AF_INET, - socket.SOCK_DGRAM, - socket.IPPROTO_UDP) - param = struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1')) - info = fcntl.ioctl(sock.fileno(), 0x8927, param) - return ''.join(['%02X' % textutil.Ord(char) for char in info[18:24]]) - - def GetFirstActiveNetworkInterfaceNonLoopback(self): - """ - Return the interface name, and ip addr of the - first active non-loopback interface. - """ - iface='' - expected=16 # how many devices should I expect... - struct_size=40 # for 64bit the size is 40 bytes - sock = socket.socket(socket.AF_INET, - socket.SOCK_DGRAM, - socket.IPPROTO_UDP) - buff=array.array('B', b'\0' * (expected * struct_size)) - param = struct.pack('iL', - expected*struct_size, - buff.buffer_info()[0]) - ret = fcntl.ioctl(sock.fileno(), 0x8912, param) - retsize=(struct.unpack('iL', ret)[0]) - if retsize == (expected * struct_size): - logger.Warn(('SIOCGIFCONF returned more than {0} up ' - 'network interfaces.'), expected) - sock = buff.tostring() - for i in range(0, struct_size * expected, struct_size): - iface=sock[i:i+16].split(b'\0', 1)[0] - if iface == b'lo': - continue - else: - break - return iface.decode('latin-1'), socket.inet_ntoa(sock[i+20:i+24]) - - def IsMissingDefaultRoute(self): - routes = shellutil.RunGetOutput("route -n")[1] - for route in routes: - if route.startswith("0.0.0.0 ") or route.startswith("default "): - return False - return True - - def GetInterfaceName(self): - return self.GetFirstActiveNetworkInterfaceNonLoopback()[0] - - def GetIpv4Address(self): - return self.GetFirstActiveNetworkInterfaceNonLoopback()[1] - - def SetBroadcastRouteForDhcp(self, ifname): - return shellutil.Run("route add 255.255.255.255 dev {0}".format(ifname), - chk_err=False) - - def RemoveBroadcastRouteForDhcp(self, ifname): - shellutil.Run("route del 255.255.255.255 dev {0}".format(ifname), - chk_err=False) - - def IsDhcpEnabled(self): - return False - - def StopDhcpService(self): - raise NotImplementedError('StopDhcpService method missing') - - def StartDhcpService(self): - raise NotImplementedError('StartDhcpService method missing') - - def StartNetwork(self): - raise NotImplementedError('StartNetwork method missing') - - def StartAgentService(self): - raise NotImplementedError('StartAgentService method missing') - - def StopAgentService(self): - raise NotImplementedError('StopAgentService method missing') - - def RegisterAgentService(self): - self.StartAgentService() - - def UnregisterAgentService(self): - self.StopAgentService() - - def RestartSshService(self): - raise NotImplementedError('RestartSshService method missing') - - def RouteAdd(self, net, mask, gateway): - """ - Add specified route using /sbin/route add -net. - """ - cmd = ("/sbin/route add -net " - "{0} netmask {1} gw {2}").format(net, mask, gateway) - return shellutil.Run(cmd, chk_err=False) - - def GetDhcpProcessId(self): - ret= shellutil.RunGetOutput("pidof dhclient") - return ret[1] if ret[0] == 0 else None - - def SetHostname(self, hostname): - fileutil.SetFileContents('/etc/hostname', hostname) - shellutil.Run("hostname {0}".format(hostname), chk_err=False) - - def SetDhcpHostname(self, hostname): - autoSend = r'^[^#]*?send\s*host-name.*?(|gethostname[(,)])' - dhclientFiles = ['/etc/dhcp/dhclient.conf', '/etc/dhcp3/dhclient.conf'] - for confFile in dhclientFiles: - if not os.path.isfile(confFile): - continue - if fileutil.FindStringInFile(confFile, autoSend): - #Return if auto send host-name is configured - return - fileutil.UpdateConfigFile(confFile, - 'send host-name', - 'send host-name {0}'.format(hostname)) - - def RestartInterface(self, ifname): - shellutil.Run("ifdown {0} && ifup {1}".format(ifname, ifname)) - - def PublishHostname(self, hostname): - self.SetDhcpHostname(hostname) - ifname = self.GetInterfaceName() - self.RestartInterface(ifname) - - def SetScsiDiskTimeout(self, timeout): - for dev in os.listdir("/sys/block"): - if dev.startswith('sd'): - self.SetBlockDeviceTimeout(dev, timeout) - - def SetBlockDeviceTimeout(self, dev, timeout): - if dev is not None and timeout is not None: - filePath = "/sys/block/{0}/device/timeout".format(dev) - original = fileutil.GetFileContents(filePath).splitlines()[0].rstrip() - if original != timeout: - fileutil.SetFileContents(filePath, timeout) - logger.Info("Set block dev timeout: {0} with timeout: {1}", - dev, - timeout) - def _GetMountPoint(self, mountlist, device): - """ - Example of mountlist: - /dev/sda1 on / type ext4 (rw) - proc on /proc type proc (rw) - sysfs on /sys type sysfs (rw) - devpts on /dev/pts type devpts (rw,gid=5,mode=620) - tmpfs on /dev/shm type tmpfs - (rw,rootcontext="system_u:object_r:tmpfs_t:s0") - none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) - /dev/sdb1 on /mnt/resource type ext4 (rw) - """ - if (mountlist and device): - for entry in mountlist.split('\n'): - if(re.search(device, entry)): - tokens = entry.split() - #Return the 3rd column of this line - return tokens[2] if len(tokens) > 2 else None - return None - - def MountResourceDisk(self, mountpoint, fs): - device = self.DeviceForIdePort(1) - if device is None: - logger.Error("Activate resource disk failed: " - "unable to detect disk topology") - return None - device = "/dev/" + device - mountlist = shellutil.RunGetOutput("mount")[1] - existing = self._GetMountPoint(mountlist, device) - if(existing): - logger.Info("Resource disk {0} is already mounted", device) - return existing - - fileutil.CreateDir(mountpoint, mode=0755) - output = shellutil.RunGetOutput("sfdisk -q -c {0} 1".format(device)) - if output[1].rstrip() == "7" and fs != "ntfs": - shellutil.Run("sfdisk -c {0} 1 83".format(device)) - shellutil.Run("mkfs.{0} {1}1".format(fs, device)) - ret = shellutil.Run("mount {0}1 {1}".format(device, mountpoint)) - if ret: - logger.Error("Failed to mount resource disk ({0})".format(device)) - raise Exception("Failed to mount resource disk") - else: - logger.Info(("Resource disk ({0}) is mounted at {1} " - "with fstype {2}").format(device, mountpoint, fs)) - return mountpoint - - def DeviceForIdePort(self, n): - """ - Return device name attached to ide port 'n'. - """ - if n > 3: - return None - g0 = "00000000" - if n > 1: - g0 = "00000001" - n = n - 2 - device = None - path = "/sys/bus/vmbus/devices/" - for vmbus in os.listdir(path): - deviceid = fileutil.GetFileContents(os.path.join(path, - vmbus, - "device_id")) - guid = deviceid.lstrip('{').split('-') - if guid[0] == g0 and guid[1] == "000" + str(n): - for root, dirs, files in os.walk(path + vmbus): - if root.endswith("/block"): - device = dirs[0] - break - else : #older distros - for d in dirs: - if ':' in d and "block" == d.split(':')[0]: - device = d.split(':')[1] - break - break - return device - - - def CreateSwapSpace(self, mountpoint, sizeMB): - sizeKB = sizeMB * 1024 - size = sizeKB * 1024 - swapfile = os.path.join(mountpoint, 'swapfile') - if os.path.isfile(swapfile) and os.path.getsize(swapfile) != size: - os.remove(swapfile) - if not os.path.isfile(swapfile): - shellutil.Run(("dd if=/dev/zero of={0} bs=1024 " - "count={1}").format(swapfile, sizeKB)) - shellutil.Run("mkswap {0}".format(swapfile)) - if shellutil.Run("swapon {0}".format(swapfile)): - logger.Error("Failed to activate swap at: {0}".format(swapfile)) - else: - logger.Info("Enabled {0}KB of swap at {1}".format(sizeKB, swapfile)) - - def DeleteAccount(self, userName): - if self._IsSysUser(userName): - logger.Error("{0} is a system user. Will not delete it.", userName) - shellutil.Run("> /var/run/utmp") - shellutil.Run("userdel -f -r " + userName) - #Remove user from suders - sudoers = fileutil.GetFileContents("/etc/sudoers.d/waagent").split("\n") - sudoers = filter(lambda x : userName not in x, sudoers) - fileutil.SetFileContents("/etc/sudoers.d/waagent", "\n".join(sudoers)) - - def OnDeprovisionStart(self): - print ("WARNING! Nameserver configuration in " - "/etc/resolv.conf will be deleted.") - - def OnDeprovision(self): - """ - Distro specific clean up work during deprovision - """ - fileutil.RemoveFiles('/etc/resolv.conf') - - def TranslateCustomData(self, data): - return data - -class DebianDistro(DefaultDistro): - def __init__(self): - super(DebianDistro, self).__init__() - - def RestartSshService(self): - return shellutil.Run("service sshd restart", chk_err=False) - - def StopAgentService(self): - return shellutil.Run("service azureguestagent stop", chk_err=False) - - def StartAgentService(self): - return shellutil.Run("service azureguestagent start", chk_err=False) - -class UbuntuDistro(DebianDistro): - def __init__(self): - super(UbuntuDistro, self).__init__() - - def StartNetwork(self): - return shellutil.Run("service networking start", chk_err=False) - - def OnDeprovisionStart(self): - print("WARNING! Nameserver configuration in " - "/etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.") - - def OnDeprovision(self): - if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf': - logger.Info("resolvconf is not configured. Removing /etc/resolv.conf") - fileutil.RemoveFiles('/etc/resolv.conf') - else: - logger.Info("resolvconf is enabled; leaving /etc/resolv.conf intact") - fileutil.RemoveFiles('/etc/resolvconf/resolv.conf.d/tail', - '/etc/resolvconf/resolv.conf.d/originial') - -class Ubuntu1204Distro(UbuntuDistro): - def __init__(self): - super(Ubuntu1204Distro, self).__init__() - - #Override - def GetDhcpProcessId(self): - ret= shellutil.RunGetOutput("pidof dhclient3") - return ret[1] if ret[0] == 0 else None - -class RedhatDistro(DefaultDistro): - def __init__(self): - super(RedhatDistro, self).__init__() - self.sshdConfigPath = '/etc/ssh/sshd_config' - self.opensslCmd = '/usr/bin/openssl' - self.configPath = '/etc/waagent.conf' - self.selinux=None - - def StartNetwork(self): - return shellutil.Run("/sbin/service networking start", chk_err=False) - - def RestartSshService(self): - return shellutil.Run("/sbin/service sshd condrestart", chk_err=False) - - def StopAgentService(self): - return shellutil.Run("/sbin/service waagent stop", chk_err=False) - - def StartAgentService(self): - return shellutil.Run("/sbin/service waagent start", chk_err=False) - - #Override - def GetDhcpProcessId(self): - ret= shellutil.RunGetOutput("pidof dhclient") - return ret[1] if ret[0] == 0 else None - -class Redhat7Distro(RedhatDistro): - def __init__(self): - super(Redhat7Distro, self).__init__() - - def SetHostname(self, hostname): - super(Redhat7Distro, self).SetHostname(hostname) - fileutil.UpdateConfigFile('/etc/sysconfig/network', - 'HOSTNAME', - 'HOSTNAME={0}'.format(hostname)) - - def SetDhcpHostname(self, hostname): - ifname = self.GetInterfaceName() - filepath = "/etc/sysconfig/network-scripts/ifcfg-{0}".format(ifname) - fileutil.UpdateConfigFile(filepath, - 'DHCP_HOSTNAME', - 'DHCP_HOSTNAME={0}'.format(hostname)) - -class FedoraDistro(DefaultDistro): - pass - -class CoreOSDistro(DefaultDistro): - def __init(self): - super(CoreOSDistro, self).__init__() - self.configPath = '/usr/share/oem/waagent.conf' - - def _IsSysUser(self, userName): - #User 'core' is not a sysuser - if userName == 'core': - return False - return super(CoreOSDistro, self).isSysUser(userName) - - def IsDhcpEnabled(self): - return True - - def StartNetwork(self) : - return shellutil.Run("systemctl start systemd-networkd", chk_err=False) - - def RestartInterface(self, iface): - Run("systemctl restart systemd-networkd") - - def RestartSshService(self): - return shellutil.Run("systemctl restart sshd", chk_err=False) - - def StopDhcpService(self): - return shellutil.Run("systemctl stop systemd-networkd", chk_err=False) - - def StartDhcpService(self): - return shellutil.Run("systemctl start systemd-networkd", chk_err=False) - - def GetDhcpProcessId(self): - ret= shellutil.RunGetOutput("pidof systemd-networkd") - return ret[1] if ret[0] == 0 else None - - def OnDeprovisionStart(self): - print "WARNING! /etc/machine-id will be removed." - - def OnDeprovision(self): - fileutil.RemoveFiles('/etc/machine-id') - - def TranslateCustomData(self, data): - return base64.b64decode(data) - -class GentooDistro(DefaultDistro): - pass - -class SUSEDistro(DefaultDistro): - def __init__(self): - super(SUSEDistro, self).__init__() - self.dhcpClientName='dhcpcd' - - def SetHostname(self, hostname): - fileutil.SetFileContents('/etc/HOSTNAME', hostname) - shellutil.Run("hostname {0}".format(hostname), chk_err=False) - - def GetDhcpProcessId(self): - ret= shellutil.RunGetOutput("pidof {0}".format(self.dhcpClientName)) - return ret[1] if ret[0] == 0 else None - - def IsDhcpEnabled(self): - return True - - def StopDhcpService(self): - cmd = "/sbin/service {0} stop".format(self.dhcpClientName) - return shellutil.Run(cmd, chk_err=False) - - def StartDhcpService(self): - cmd = "/sbin/service {0} start".format(self.dhcpClientName) - return shellutil.Run(cmd, chk_err=False) - - def StartNetwork(self) : - return shellutil.Run("/sbin/service start network", chk_err=False) - - def RestartSshService(self): - return shellutil.Run("/sbin/service sshd restart", chk_err=False) - - def StopAgentService(self): - return shellutil.Run("/sbin/service waagent stop", chk_err=False) - - def StartAgentService(self): - return shellutil.Run("/sbin/service waagent start", chk_err=False) - - def RegisterAgentService(self): - ret = shellutil.Run("insserv waagent", chk_err=False) - if ret != 0: - return ret - ret = super(SUSEDistro, self).RegisterAgentService() - return ret - - def UnregisterAgentService(self): - ret = super(SUSEDistro, self).UnregisterAgentService() - if ret != 0: - return ret - return shellutil.Run("insserv -r waagent", chk_err=False) - -class SUSE12Distro(SUSEDistro): - def __init__(self): - super(SUSE12Distro, self).__init__() - self.dhcpClientName = 'wickedd-dhcp4' - -class FreeBSDDistro(DefaultDistro): - def __init__(self): - self.scsiConfigured = False - - def SetScsiDiskTimeout(self, timeout): - if scsiConfigured: - return - shellutil.Run("sysctl kern.cam.da.default_timeout=" + timeout) - self.scsiConfigured = True diff --git a/azureguestagent/utils/osutil/__init__.py b/azureguestagent/utils/osutil/__init__.py index 0406099..abc5eb0 100644 --- a/azureguestagent/utils/osutil/__init__.py +++ b/azureguestagent/utils/osutil/__init__.py @@ -18,5 +18,3 @@ # from azureguestagent.utils.osutil.factory import CurrOSUtil - -__all__ = ['CurrOSUtil'] diff --git a/azureguestagent/utils/osutil/default.py b/azureguestagent/utils/osutil/default.py index 71d1237..fba6663 100644 --- a/azureguestagent/utils/osutil/default.py +++ b/azureguestagent/utils/osutil/default.py @@ -186,7 +186,7 @@ class DefaultOSUtil(object): return pub def _NormPath(self, filepath): - home = CurrOS.GetHome() + home = self.GetHome() # Expand HOME variable if present in path path = os.path.normpath(filepath.replace("$HOME", home)) return path @@ -205,7 +205,7 @@ class DefaultOSUtil(object): path = self._NormPath(path) dirPath = os.path.dirname(path) fileutil.CreateDir(dirPath, mode=0700, owner=userName) - libDir = CurrOS.GetLibDir() + libDir = self.GetLibDir() prvPath = os.path.join(libDir, thumbprint + '.prv') if not os.path.isfile(prvPath): logger.Error("Failed to deploy key pair, thumbprint: {0}", @@ -227,7 +227,7 @@ class DefaultOSUtil(object): path = self._NormPath(path) dirPath = os.path.dirname(path) fileutil.CreateDir(dirPath, mode=0700, owner=userName) - libDir = CurrOS.GetLibDir() + libDir = self.GetLibDir() crtPath = os.path.join(libDir, thumbprint + '.crt') if not os.path.isfile(crtPath): logger.Error("Failed to deploy public key, thumbprint: {0}", diff --git a/azureguestagent/utils/osutil/factory.py b/azureguestagent/utils/osutil/factory.py index fdeb7f2..314f80c 100644 --- a/azureguestagent/utils/osutil/factory.py +++ b/azureguestagent/utils/osutil/factory.py @@ -17,7 +17,7 @@ # Requires Python 2.4+ and Openssl 1.0+ # import azureguestagent.osinfo as osinfo -import azureguestagent.utils.osutil.default as default +from default import * def GetOSUtil(osInfo): name = osInfo[0] diff --git a/tests/test_conf.py b/tests/test_conf.py index 6c3d2c3..c4b71ce 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -18,14 +18,14 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx +import os import env -import tests.tools as tools import uuid import unittest -import os +import tests.tools as tools import azureguestagent.utils.fileutil as fileutil import azureguestagent.conf as conf -import test +from azureguestagent.exception import * TestConf="""\ # @@ -52,7 +52,7 @@ class TestConfiguration(unittest.TestCase): self.assertEquals(-1, config.getInt("foo.bar.str")) def test_parse_malformed_conf(self): - with self.assertRaises(Exception) as cm: + with self.assertRaises(AgentConfigError) as cm: conf.ConfigurationProvider(None) def test_load_conf_file(self): diff --git a/tests/test_handlerfactory.py b/tests/test_handlerfactory.py new file mode 100644 index 0000000..c399a43 --- /dev/null +++ b/tests/test_handlerfactory.py @@ -0,0 +1,31 @@ +# 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.4+ and Openssl 1.0+ +# +# Implements parts of RFC 2131, 1541, 1497 and +# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx +# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx + +import env +from tests.tools import * +import unittest +from azureguestagent.handler import CurrOSHandlerFactory + +class TestOSHandlerFactory(unittest.TestCase): + def test_curr_os_handler_factory(self): + self.assertNotEquals(None, CurrOSHandlerFactory) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_osinfo.py b/tests/test_osinfo.py new file mode 100644 index 0000000..99fe2ea --- /dev/null +++ b/tests/test_osinfo.py @@ -0,0 +1,36 @@ +# 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.4+ and Openssl 1.0+ +# +# Implements parts of RFC 2131, 1541, 1497 and +# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx +# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx + +import env +from tests.tools import * +import unittest +from azureguestagent.osinfo import CurrOSInfo + +class TestOSInfo(unittest.TestCase): + def test_curr_os_info(self): + print "hehe" + self.assertNotEquals(None, CurrOSInfo) + self.assertNotEquals(None, CurrOSInfo[0]) + self.assertNotEquals(None, CurrOSInfo[1]) + self.assertNotEquals(None, CurrOSInfo[2]) + self.assertNotEquals(None, CurrOSInfo[3]) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_osutil.py b/tests/test_osutil.py index 492f710..53acb59 100644 --- a/tests/test_osutil.py +++ b/tests/test_osutil.py @@ -28,24 +28,13 @@ import time import azureguestagent.utils.fileutil as fileutil import azureguestagent.utils.shellutil as shellutil import azureguestagent.utils.osutil as osutil -from azureguestagent.utils.osutil import CurrOS, CurrOSInfo +from azureguestagent.utils.osutil import CurrOSUtil import azureguestagent.conf as conf import test -class TestGetDistro(unittest.TestCase): - def test_get_distro(self): - distroInfo = osutil.GetDistroInfo() - self.assertNotEquals(None, distroInfo) - self.assertNotEquals(None, distroInfo[0]) - self.assertNotEquals(None, distroInfo[1]) - self.assertNotEquals(None, distroInfo[2]) - self.assertNotEquals(None, distroInfo[3]) - distro = osutil.GetDistro(distroInfo) - self.assertNotEquals(None, distro) - +class TestCurrOSUtil(unittest.TestCase): def test_current_distro(self): - self.assertNotEquals(None, osutil.CurrOSInfo) - self.assertNotEquals(None, osutil.CurrOS) + self.assertNotEquals(None, CurrOSUtil) MountlistSample="""\ /dev/sda1 on / type ext4 (rw) @@ -58,35 +47,36 @@ none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) """ class TestCurrOS(unittest.TestCase): +#class TestCurrOS(object): def test_get_paths(self): - self.assertNotEquals(None, CurrOS.GetHome()) - self.assertNotEquals(None, CurrOS.GetLibDir()) - self.assertNotEquals(None, CurrOS.GetAgentPidPath()) - self.assertNotEquals(None, CurrOS.GetConfigurationPath()) - self.assertNotEquals(None, CurrOS.GetDvdMountPoint()) - self.assertNotEquals(None, CurrOS.GetOvfEnvPathOnDvd()) + self.assertNotEquals(None, CurrOSUtil.GetHome()) + self.assertNotEquals(None, CurrOSUtil.GetLibDir()) + self.assertNotEquals(None, CurrOSUtil.GetAgentPidPath()) + self.assertNotEquals(None, CurrOSUtil.GetConfigurationPath()) + self.assertNotEquals(None, CurrOSUtil.GetDvdMountPoint()) + self.assertNotEquals(None, CurrOSUtil.GetOvfEnvPathOnDvd()) - @Mockup(osutil.shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) - @Mockup(osutil.shellutil, 'RunSendStdin', MockFunc(retval=[0, ''])) - @Mockup(osutil.fileutil, 'SetFileContents', MockFunc()) - @Mockup(osutil.fileutil, 'GetFileContents', MockFunc(retval='')) - @Mockup(osutil.fileutil, 'ChangeMod', MockFunc()) + @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) + @Mockup(shellutil, 'RunSendStdin', MockFunc(retval=[0, ''])) + @Mockup(fileutil, 'SetFileContents', MockFunc()) + @Mockup(fileutil, 'GetFileContents', MockFunc(retval='')) + @Mockup(fileutil, 'ChangeMod', MockFunc()) def test_update_user_account(self): - CurrOS.UpdateUserAccount('api', 'api') - CurrOS.DeleteAccount('api') + CurrOSUtil.UpdateUserAccount('api', 'api') + CurrOSUtil.DeleteAccount('api') - @Mockup(osutil.fileutil, 'GetFileContents', MockFunc(retval='root::::')) - @Mockup(osutil.fileutil, 'ReplaceFileContentsAtomic', MockFunc()) + @Mockup(fileutil, 'GetFileContents', MockFunc(retval='root::::')) + @Mockup(fileutil, 'ReplaceFileContentsAtomic', MockFunc()) def test_delete_root_password(self): - CurrOS.DeleteRootPassword() + CurrOSUtil.DeleteRootPassword() self.assertEquals('root:*LOCK*:14600::::::', fileutil.ReplaceFileContentsAtomic.args[1]) def test_wireserver_endpoint(self): if os.path.isfile('/tmp/wireserver'): os.remove('/tmp/wireserver') - CurrOS.SetWireServerEndpoint("wireserver") - endpoint = CurrOS.GetWireServerEndpoint() + CurrOSUtil.SetWireServerEndpoint("wireserver") + endpoint = CurrOSUtil.GetWireServerEndpoint() self.assertEquals('wireserver', endpoint) def test_cert_operation(self): @@ -98,70 +88,70 @@ class TestCurrOS(unittest.TestCase): os.remove('/tmp/test.crt') shutil.copyfile(os.path.join(env.test_root, 'test.crt'), '/tmp/test.crt') - pub1 = CurrOS.GetPubKeyFromPrv('/tmp/test.prv') - pub2 = CurrOS.GetPubKeyFromCrt('/tmp/test.crt') + pub1 = CurrOSUtil.GetPubKeyFromPrv('/tmp/test.prv') + pub2 = CurrOSUtil.GetPubKeyFromCrt('/tmp/test.crt') self.assertEquals(pub1, pub2) - thumbprint = CurrOS.GetThumbprintFromCrt('/tmp/test.crt') + thumbprint = CurrOSUtil.GetThumbprintFromCrt('/tmp/test.crt') self.assertEquals('33B0ABCE4673538650971C10F7D7397E71561F35', thumbprint) def test_selinux(self): - if not CurrOS.IsSelinuxSystem(): + if not CurrOSUtil.IsSelinuxSystem(): return - isRunning = CurrOS.IsSelinuxRunning() - if not CurrOS.IsSelinuxRunning(): - CurrOS.SetSelinuxEnforce(0) - self.assertEquals(False, CurrOS.IsSelinuxRunning()) - CurrOS.SetSelinuxEnforce(1) - self.assertEquals(True, CurrOS.IsSelinuxRunning()) + isRunning = CurrOSUtil.IsSelinuxRunning() + if not CurrOSUtil.IsSelinuxRunning(): + CurrOSUtil.SetSelinuxEnforce(0) + self.assertEquals(False, CurrOSUtil.IsSelinuxRunning()) + CurrOSUtil.SetSelinuxEnforce(1) + self.assertEquals(True, CurrOSUtil.IsSelinuxRunning()) if os.path.isfile('/tmp/abc'): os.remove('/tmp/abc') fileutil.SetFileContents('/tmp/abc', '') - CurrOS.SetSelinuxContext('/tmp/abc','unconfined_u:object_r:ssh_home_t:s') - CurrOS.SetSelinuxEnforce(1 if isRunning else 0) + CurrOSUtil.SetSelinuxContext('/tmp/abc','unconfined_u:object_r:ssh_home_t:s') + CurrOSUtil.SetSelinuxEnforce(1 if isRunning else 0) @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) @Mockup(fileutil, 'SetFileContents', MockFunc()) def test_network_operation(self): - CurrOS.StartNetwork() - CurrOS.OpenPortForDhcp() - CurrOS.GenerateTransportCert() - mac = CurrOS.GetMacAddress() + CurrOSUtil.StartNetwork() + CurrOSUtil.OpenPortForDhcp() + CurrOSUtil.GenerateTransportCert() + mac = CurrOSUtil.GetMacAddress() self.assertNotEquals(None, mac) - CurrOS.IsMissingDefaultRoute() - CurrOS.SetBroadcastRouteForDhcp('api') - CurrOS.RemoveBroadcastRouteForDhcp('api') - CurrOS.RouteAdd('', '', '') - CurrOS.GetDhcpProcessId() - CurrOS.SetHostname('api') - CurrOS.PublishHostname('api') + CurrOSUtil.IsMissingDefaultRoute() + CurrOSUtil.SetBroadcastRouteForDhcp('api') + CurrOSUtil.RemoveBroadcastRouteForDhcp('api') + CurrOSUtil.RouteAdd('', '', '') + CurrOSUtil.GetDhcpProcessId() + CurrOSUtil.SetHostname('api') + CurrOSUtil.PublishHostname('api') - @Mockup(CurrOS, 'GetHome', MockFunc(retval='/tmp/home')) + @Mockup(CurrOSUtil, 'GetHome', MockFunc(retval='/tmp/home')) def test_deploy_key(self): if os.path.isdir('/tmp/home'): shutil.rmtree('/tmp/home') user = shellutil.RunGetOutput('whoami')[1].strip() - CurrOS.DeploySshKeyPair(user, 'test', '$HOME/.ssh/id_rsa') - CurrOS.DeploySshPublicKey(user, 'test', '$HOME/.ssh/authorized_keys') + CurrOSUtil.DeploySshKeyPair(user, 'test', '$HOME/.ssh/id_rsa') + CurrOSUtil.DeploySshPublicKey(user, 'test', '$HOME/.ssh/authorized_keys') self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa')) self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa.pub')) self.assertTrue(os.path.isfile('/tmp/home/.ssh/authorized_keys')) @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) - @Mockup(CurrOS, 'GetSshdConfigPath', MockFunc(retval='/tmp/sshd_config')) + @Mockup(CurrOSUtil, 'GetSshdConfigPath', MockFunc(retval='/tmp/sshd_config')) def test_ssh_operation(self): - CurrOS.RegenerateSshHostkey('rsa') + CurrOSUtil.RegenerateSshHostkey('rsa') shellutil.RunGetOutput.retval=[0, '2048 f1:fe:14:66:9d:46:9a:60:8b:8c:' '80:43:39:1c:20:9e root@api (RSA)'] - thumbprint = CurrOS.GetSshHostKeyThumbprint('rsa') + thumbprint = CurrOSUtil.GetSshHostKeyThumbprint('rsa') self.assertEquals('f1fe14669d469a608b8c8043391c209e', thumbprint) - sshdConfig = CurrOS.GetSshdConfigPath() + sshdConfig = CurrOSUtil.GetSshdConfigPath() self.assertEquals('/tmp/sshd_config', sshdConfig) if os.path.isfile(sshdConfig): os.remove(sshdConfig) shutil.copyfile(os.path.join(env.test_root, 'sshd_config'), sshdConfig) - CurrOS.SetSshClientAliveInterval() - CurrOS.ConfigSshd(True) + CurrOSUtil.SetSshClientAliveInterval() + CurrOSUtil.ConfigSshd(True) self.assertTrue(simple_file_grep(sshdConfig, 'PasswordAuthentication no')) self.assertTrue(simple_file_grep(sshdConfig, @@ -170,25 +160,25 @@ class TestCurrOS(unittest.TestCase): 'ClientAliveInterval 180')) @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) - @Mockup(CurrOS, 'GetDvdMountPoint', MockFunc(retval='/tmp/cdrom')) + @Mockup(CurrOSUtil, 'GetDvdMountPoint', MockFunc(retval='/tmp/cdrom')) def test_mount(self): - CurrOS.MountDvd() - CurrOS.UmountDvd() - mountPoint = CurrOS._GetMountPoint(MountlistSample, '/dev/sda') + CurrOSUtil.MountDvd() + CurrOSUtil.UmountDvd() + mountPoint = CurrOSUtil._GetMountPoint(MountlistSample, '/dev/sda') self.assertNotEquals(None, mountPoint) @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) @Mockup(fileutil, 'CreateDir', MockFunc()) - @Mockup(CurrOS, 'DeviceForIdePort', MockFunc(retval='api')) + @Mockup(CurrOSUtil, 'DeviceForIdePort', MockFunc(retval='api')) def test_resource_disk(self): - CurrOS.MountResourceDisk('/tmp/resource', 'ext3') + CurrOSUtil.MountResourceDisk('/tmp/resource', 'ext3') @Mockup(shellutil, 'RunGetOutput', MockFunc(retval=[0, ''])) def test_swap(self): - CurrOS.CreateSwapSpace('/tmp', 1024) + CurrOSUtil.CreateSwapSpace('/tmp', 1024) def test_getdvd(self): - CurrOS.GetDvdDevice() + CurrOSUtil.GetDvdDevice() if __name__ == '__main__': unittest.main() diff --git a/tests/tools.py b/tests/tools.py index c9192a0..e5a86f7 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -21,7 +21,7 @@ import os import sys -from azureguestagent.utils.osutil import CurrOS, CurrOSInfo +from azureguestagent.utils.osutil import CurrOSUtil parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(parent) @@ -61,5 +61,5 @@ def Dummy(): pass #Mock CurrOS so that the test of other part will be os unrelated -CurrOS.GetLibDir = MockFunc(retval='/tmp') -CurrOS.GetExtLogDir = MockFunc(retval='/tmp/log') +CurrOSUtil.GetLibDir = MockFunc(retval='/tmp') +CurrOSUtil.GetExtLogDir = MockFunc(retval='/tmp/log')