From 84b06ce1996bd74ed38b56e97e2e9658dac2f2d6 Mon Sep 17 00:00:00 2001 From: Yue Zhang Date: Tue, 14 Oct 2014 12:30:04 +0800 Subject: [PATCH] Add provisioning code skeleton --- test/run_all.sh | 27 +++++++++++++++ test/test_conf.py | 67 ++++++++++++++++++++++++++++++++++++ test/test_os_util.py | 44 +++++++++++++++++++++++ walinuxagent/conf.py | 44 +++++++++++++++++++++++ walinuxagent/provision.py | 65 ++++++++++++++++++++++++++++++++++ walinuxagent/utils/osutil.py | 28 ++++++++++----- 6 files changed, 266 insertions(+), 9 deletions(-) create mode 100755 test/run_all.sh create mode 100644 test/test_conf.py create mode 100644 test/test_os_util.py diff --git a/test/run_all.sh b/test/run_all.sh new file mode 100755 index 0000000..d0d9e73 --- /dev/null +++ b/test/run_all.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# This script is used to set up a test env for extensions +# +# 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. +# + +script=$(dirname $0) +root=$script +cd $root +root=`pwd` + +echo "Run unit test:" +ls test_*.py +ls test_*.py | sed -e 's/\.py//'|xargs python -m unittest diff --git a/test/test_conf.py b/test/test_conf.py new file mode 100644 index 0000000..7fc349f --- /dev/null +++ b/test/test_conf.py @@ -0,0 +1,67 @@ +# 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 +import test.tools as tools +import uuid +import unittest +import os +import walinuxagent.utils.fileutil as fileutil +import walinuxagent.conf as conf +import test + +TestConf="""\ +# +# This is comment +# +foo.bar.switch=y +foo.bar.switch2=n +foo.bar.str=foobar +foo.bar.int=300 + +""" + +class TestConfiguration(unittest.TestCase): + def test_parse_conf(self): + config = conf.ConfigurationProvider(TestConf) + self.assertEquals(True, config.getSwitch("foo.bar.switch")) + self.assertEquals(False, config.getSwitch("foo.bar.switch2")) + self.assertEquals(False, config.getSwitch("foo.bar.switch3")) + self.assertEquals(True, config.getSwitch("foo.bar.switch4", True)) + self.assertEquals("foobar", config.get("foo.bar.str")) + self.assertEquals("foobar1", config.get("foo.bar.str1", "foobar1")) + self.assertEquals(300, config.getInt("foo.bar.int")) + self.assertEquals(-1, config.getInt("foo.bar.int2")) + self.assertEquals(-1, config.getInt("foo.bar.str")) + + def test_parse_malformed_conf(self): + with self.assertRaises(Exception) as cm: + conf.ConfigurationProvider(None) + + def test_load_conf_file(self): + with open('/tmp/test_conf', 'w') as F: + F.write(TestConf) + F.close() + + config = conf.LoadConfiguration('/tmp/test_conf') + self.assertNotEquals(None, config) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_os_util.py b/test/test_os_util.py new file mode 100644 index 0000000..207b6e1 --- /dev/null +++ b/test/test_os_util.py @@ -0,0 +1,44 @@ +# 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 +import test.tools as tools +import uuid +import unittest +import os +import walinuxagent.utils.osutil as osutil +import test + +class TestOSUtil(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]) + distro = osutil.GetDistro(distroInfo) + self.assertNotEquals(None, distro) + + def test_current_distro(self): + self.assertNotEquals(None, osutil.CurrentDistroInfo) + self.assertNotEquals(None, osutil.CurrentDistro) + +if __name__ == '__main__': + unittest.main() diff --git a/walinuxagent/conf.py b/walinuxagent/conf.py index 147599c..b1b8b8c 100644 --- a/walinuxagent/conf.py +++ b/walinuxagent/conf.py @@ -17,4 +17,48 @@ # Requires Python 2.4+ and Openssl 1.0+ # +import os +import walinuxagent.utils.fileutil as fileutil + ConfFilePath = '/etc/waagent.conf' + +def LoadConfiguration(confFilePath = ConfFilePath): + if os.path.isfile(confFilePath) == False: + raise Exception("Missing configuration in {0}", confFilePath) + try: + return ConfigurationProvider(fileutil.GetFileContents(confFilePath)) + except IOError, e: + raise Exception("Failed to load conf file:{0}", confFilePath) + +class ConfigurationProvider(object): + """ + Parse amd store key:values in /etc/waagent.conf. + """ + def __init__(self, content): + self.values = dict() + if not content: + raise Exception("Can't not parse empty configuration") + for line in content.split('\n'): + if not line.startswith("#") and "=" in line: + parts = line.split()[0].split('=') + value = parts[1].strip("\" ") + if value != "None": + self.values[parts[0]] = value + else: + self.values[parts[0]] = None + + def get(self, key, defaultValue=None): + val = self.values.get(key) + return val if val else defaultValue + + def getSwitch(self, key, defaultValue=False): + val = self.values.get(key) + return True if (val and val.lower() == 'y') else defaultValue + + def getInt(self, key, defaultValue=-1): + try: + return int(self.values.get(key)) + except: + return defaultValue + + diff --git a/walinuxagent/provision.py b/walinuxagent/provision.py index 7a4980e..c60541d 100644 --- a/walinuxagent/provision.py +++ b/walinuxagent/provision.py @@ -16,3 +16,68 @@ # # Requires Python 2.4+ and Openssl 1.0+ # + +import os +import walinuxagent.logger as logger +import walinuxagent.utils.osutil as osutil +import walinuxagent.utils.shellutil as shellutil +from osutil import LibDir, OvfMountPoint + +def _GetDvdDevice(devDir = '/dev'): + patten=r'(sr[0-9]|hd[c-z]|cdrom[0-9]|cd[0-9]?)' + for dvd in [re.match(patten, dev) for dev in os.listdir(devDir)]: + if not dvd = None + return "/dev/{0}".format(dvd.group(0)) + return None + +def _MountDvd(dvd, mountPoint = OvfMountPoint): + if not os.path.exits(mountPoint): + os.makedirs(mountPoint) + for retry in range(1, 6): + retcode, output = osutil.MountDvd(dvd, mountPoint) + if retcode == 0: + logger.Info("Successfully mounted provision dvd") + return + else: + logger.Warn("Mount dvd failed: retry={0}, ret={1}", retry, retcode) + logger.Error("Failed to mount provision dvd") +#TODO raise exception + raise Exception("Failed to mount provision dvd") + +def _UmountDvd(mountPoint = OvfMountPoint): + pass + +def _CreateUserAccount(): + pass + +def _ReportSshHostkeyThumbnail(): + pass + +def _DeleteRootPassword(): + pass + +def Provision(config, libDir = LibDir): + if not config.getSwitch("Provisioning.Enabled"): + return + if os.path.exits(os.path.join(libDir, "provisioned")): + return + + logger.Info("Provisioning image started") + dvd = _GetDvdDevice() + _MountDvd(dvd) + ovfxml = _ReadOvfFile() + _SaveOvfFile(ovfxml) + _UmountDvd() + + osutil.CreateUserAccount() + + if config.getSwitch("Provisioning.RegenerateSshHostKeyPair"): + keyPairType = config.get("Provisioning.SshHostKeyPairType", "rsa") + osutil.RegenerateSshHostkey(keyPairType) + + _ReportSshHostkeyThumbnail() + + _DeleteRootPassword() + +def Deprovision(config, libDir = LibDir): + pass diff --git a/walinuxagent/utils/osutil.py b/walinuxagent/utils/osutil.py index b330e49..3636b4c 100644 --- a/walinuxagent/utils/osutil.py +++ b/walinuxagent/utils/osutil.py @@ -27,6 +27,7 @@ import walinuxagent.utils.fileutil as fileutil import walinuxagent.utils.shellutil as shellutil LibDir = '/var/lib/waagent' +OvfMountPoint='/mnt/cdrom/secure' def RestartNetwork(): CurrentDistro.restartNetwork() @@ -68,6 +69,15 @@ def RegisterAgentService(): def UnregisterAgentService(): CurrentDistro.unregisterAgentService() +def RegenerateSshHostkey(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)) + RestartSshService() + +def RestartSshService(): + pass + def SetSshClientAliveInterval(): CurrentDistro.setSshClientAliveInterval() @@ -99,28 +109,28 @@ class DefaultDistro(): fileutil.ReplaceFileContentsAtomic(filepath, '\n'.join(options)) logger.Info("Configured SSH client probing to keep connections alive.") -class DebianDistro(): +class DebianDistro(DefaultDistro): pass -class UbuntuDistro(): +class UbuntuDistro(DefaultDistro): pass -class RedHatDistro(): +class RedHatDistro(DefaultDistro): pass -class FedoraDistro(): +class FedoraDistro(DefaultDistro): pass -class CoreOSDistro(): +class CoreOSDistro(DefaultDistro): pass -class GentooDistro(): +class GentooDistro(DefaultDistro): pass -class SUSEDistro(): +class SUSEDistro(DefaultDistro): pass -def GetdistroInfo(): +def GetDistroInfo(): if 'FreeBSD' in platform.system(): release = re.sub('\-.*\Z', '', str(platform.release())) distroInfo = ['freebsd', release, ''] @@ -154,5 +164,5 @@ def GetDistro(distroInfo): else: return DefaultDistro() -CurrentDistroInfo = GetdistroInfo() +CurrentDistroInfo = GetDistroInfo() CurrentDistro = GetDistro(CurrentDistroInfo)