diff -Nru walinuxagent-2.0.12/Changelog walinuxagent-2.0.13/Changelog --- walinuxagent-2.0.12/Changelog 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/Changelog 2015-06-01 01:55:11.000000000 +0000 @@ -1,5 +1,13 @@ WALinuxAgent Changelog ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +1 Jun 2015, WALinuxAgent 2.0.13 + . Handle http 410 returned by host + . Add support for http proxy + . Add support to execute CustomData after provisioning + . Add a udev rule for product-uuid + . Fix agent path for CoreOS + . Update service start/stop command for Ubuntu + 15 Jan 2015, WALinuxAgent 2.0.12 . Add support for page blob status report diff -Nru walinuxagent-2.0.12/config/99-azure-product-uuid.rules walinuxagent-2.0.13/config/99-azure-product-uuid.rules --- walinuxagent-2.0.12/config/99-azure-product-uuid.rules 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/config/99-azure-product-uuid.rules 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,9 @@ +SUBSYSTEM!="dmi", GOTO="product_uuid-exit" +ATTR{sys_vendor}!="Microsoft Corporation", GOTO="product_uuid-exit" +ATTR{product_name}!="Virtual Machine", GOTO="product_uuid-exit" +TEST!="/sys/devices/virtual/dmi/id/product_uuid", GOTO="product_uuid-exit" + +RUN+="/bin/chmod 0444 /sys/devices/virtual/dmi/id/product_uuid" + +LABEL="product_uuid-exit" + diff -Nru walinuxagent-2.0.12/config/waagent.conf walinuxagent-2.0.13/config/waagent.conf --- walinuxagent-2.0.12/config/waagent.conf 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/config/waagent.conf 2015-06-01 01:55:11.000000000 +0000 @@ -28,6 +28,12 @@ # Monitor host name changes and publish changes via DHCP requests. Provisioning.MonitorHostName=y +# Decode CustomData from Base64. +Provisioning.DecodeCustomData=n + +# Execute CustomData after provisioning. +Provisioning.ExecuteCustomData=n + # Format if unformatted. If 'n', resource disk will not be mounted. ResourceDisk.Format=y @@ -55,3 +61,8 @@ # If "None", the system default version is used. OS.OpensslPath=None + +# If set, agent will use proxy server to access internet +#HttpProxy.Host=None +#HttpProxy.Port=None + diff -Nru walinuxagent-2.0.12/debian/changelog walinuxagent-2.0.13/debian/changelog --- walinuxagent-2.0.12/debian/changelog 2015-04-16 17:58:02.000000000 +0000 +++ walinuxagent-2.0.13/debian/changelog 2015-07-03 18:13:35.000000000 +0000 @@ -1,11 +1,26 @@ -walinuxagent (2.0.12-0ubuntu2~14.04.0) trusty; urgency=medium +walinuxagent (2.0.13-0ubuntu2~14.04.0) trusty; urgency=medium - * Backport from Ubuntu 15.04 (LP: #1442392): - - Support page blob for status blob - - Fix extension status on disable success - - Update shared config handler + * Backport to 14.04 (LP: #1449369). - -- Ben Howard Thu, 16 Apr 2015 11:53:56 -0600 + -- Ben Howard Fri, 03 Jul 2015 12:13:35 -0600 + +walinuxagent (2.0.13-0ubuntu2) wily; urgency=medium + + * Added missing udev rule for product-uuid. + + -- Ben Howard Fri, 03 Jul 2015 10:47:27 -0600 + +walinuxagent (2.0.13-0ubuntu1) wily; urgency=medium + + * New upstream release (LP: #1449369). + * Rebased patches for 2.0.12 onto 2.0.13. + * Upstream fixes: + - Handle http 410 returned by host + - Add support for http proxy + - Add support to execute CustomData after provisioning + - Update service start/stop command for Ubuntu + + -- Ben Howard Thu, 02 Jul 2015 15:14:26 -0600 walinuxagent (2.0.12-0ubuntu2) vivid; urgency=medium diff -Nru walinuxagent-2.0.12/debian/install walinuxagent-2.0.13/debian/install --- walinuxagent-2.0.12/debian/install 2015-03-25 17:35:08.000000000 +0000 +++ walinuxagent-2.0.13/debian/install 2015-07-03 16:45:13.000000000 +0000 @@ -1,3 +1,4 @@ +config/99-azure-product-uuid.rules lib/udev/rules.d config/91_walinuxagent.cfg etc/cloud/cloud.cfg.d debian/ephemeral-disk-warning.service lib/systemd/system debian/ephemeral-disk-warning.conf etc/init diff -Nru walinuxagent-2.0.12/debian/patches/build_info.txt walinuxagent-2.0.13/debian/patches/build_info.txt --- walinuxagent-2.0.12/debian/patches/build_info.txt 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/debian/patches/build_info.txt 2015-06-29 20:35:27.000000000 +0000 @@ -0,0 +1,4 @@ +serial=20150629 +orig_prefix=trusty-server-cloudimg +suite=trusty +build_name=server diff -Nru walinuxagent-2.0.12/debian/patches/disable_provisioning.patch walinuxagent-2.0.13/debian/patches/disable_provisioning.patch --- walinuxagent-2.0.12/debian/patches/disable_provisioning.patch 2015-03-25 18:29:06.000000000 +0000 +++ walinuxagent-2.0.13/debian/patches/disable_provisioning.patch 2015-07-02 21:18:10.000000000 +0000 @@ -1,6 +1,18 @@ --- a/config/waagent.conf +++ b/config/waagent.conf -@@ -14,30 +14,34 @@ +@@ -2,6 +2,11 @@ + # Windows Azure Linux Agent Configuration + # + ++# NOTE: Ubuntu uses Cloud-init for disk-provisioning. This will ++# unless you disable Cloud-init disk provisioning. Please see ++# /usr/share/doc/walinuxagent/99-cloud-init-disable-diskprovisioning.conf ++# ++ + # Specified program is invoked with the argument "Ready" when we report ready status + # to the endpoint server. + Role.StateConsumer=None +@@ -14,19 +19,19 @@ Role.TopologyConsumer=None # Enable instance creation @@ -22,6 +34,11 @@ -Provisioning.MonitorHostName=y +Provisioning.MonitorHostName=n + # Decode CustomData from Base64. + Provisioning.DecodeCustomData=n +@@ -35,14 +40,14 @@ + Provisioning.ExecuteCustomData=n + # Format if unformatted. If 'n', resource disk will not be mounted. -ResourceDisk.Format=y +ResourceDisk.Format=n @@ -34,10 +51,5 @@ -ResourceDisk.MountPoint=/mnt/resource +ResourceDisk.MountPoint=/mnt -+# NOTE: Ubuntu uses Cloud-init for disk-provisioning. This will -+# unless you disable Cloud-init disk provisioning. Please see -+# /usr/share/doc/walinuxagent/99-cloud-init-disable-diskprovisioning.conf -+# # Create and use swapfile on resource disk. ResourceDisk.EnableSwap=n - diff -Nru walinuxagent-2.0.12/debian/patches/fixup_setup_file.patch walinuxagent-2.0.13/debian/patches/fixup_setup_file.patch --- walinuxagent-2.0.12/debian/patches/fixup_setup_file.patch 2014-08-29 16:09:33.000000000 +0000 +++ walinuxagent-2.0.13/debian/patches/fixup_setup_file.patch 2015-07-02 21:15:02.000000000 +0000 @@ -1,6 +1,6 @@ --- a/setup.py +++ b/setup.py -@@ -52,7 +52,7 @@ +@@ -51,7 +51,7 @@ def initialize_options(self): install.initialize_options(self) diff -Nru walinuxagent-2.0.12/debian/patches/series walinuxagent-2.0.13/debian/patches/series --- walinuxagent-2.0.12/debian/patches/series 2015-04-09 21:57:03.000000000 +0000 +++ walinuxagent-2.0.13/debian/patches/series 2015-07-02 21:16:40.000000000 +0000 @@ -1,5 +1,6 @@ fixup_setup_file.patch cloud-init-default-cfg.patch -disable_provisioning.patch +#disable_provisioning.patch disable-udev-rules.patch fix-waagent-service.patch +disable_provisioning.patch diff -Nru walinuxagent-2.0.12/fix-gpt-ubuntu.py walinuxagent-2.0.13/fix-gpt-ubuntu.py --- walinuxagent-2.0.12/fix-gpt-ubuntu.py 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/fix-gpt-ubuntu.py 2015-06-01 01:55:11.000000000 +0000 @@ -26,7 +26,7 @@ a new one using the entire disk space. """ if __name__ == '__main__': - print 'Umnout resource disk...' + print 'Unmount resource disk...' subprocess.call(['umount', '/dev/sdb1']) print 'Remove old partitions...' subprocess.call(['parted', '/dev/sdb', 'rm', '1']) diff -Nru walinuxagent-2.0.12/README walinuxagent-2.0.13/README --- walinuxagent-2.0.12/README 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/README 2015-06-01 01:55:11.000000000 +0000 @@ -31,11 +31,11 @@ * SCVMM Deployments - Detect and bootstrap the VMM agent for Linux when running in a System - Center Virtual Machine Manager 2012R2 environment + Center Virtual Machine Manager 2012R2 environment * VM Extension - Inject component authored by Microsoft and Partners into Linux VM (IaaS) - to enable software and configuration automation + to enable software and configuration automation - VM Extension reference implementation on https://github.com/Azure/azure-linux-extensions @@ -90,8 +90,32 @@ If installing manually, waagent should be copied to /usr/sbin/waagent and installed by running: - # sudo chmod 755 /usr/sbin/waagent - # sudo /usr/sbin/waagent -install -verbose + # sudo chmod 755 /usr/sbin/waagent + # sudo /usr/sbin/waagent -install -verbose + +The agent's log file is kept at /var/log/waagent.log. + + +UPGRADE + +Upgrading via your distribution's package repository is preferred. + +If upgrading manually, same with installation above, waagent should be copied +to /usr/sbin/waagent to override original file and installed by running: + + # sudo chmod 755 /usr/sbin/waagent + +Restart waagent service,for most of linux distributions: + + #sudo service waagent restart + +For Ubuntu, use: + + #sudo service walinuxagent restart + +For CoreOS, use: + + #sudo systemctl restart waagent The agent's log file is kept at /var/log/waagent.log. @@ -171,6 +195,8 @@ Provisioning.RegenerateSshHostKeyPair=y Provisioning.SshHostKeyPairType=rsa Provisioning.MonitorHostName=y +Provisioning.DecodeCustomData=n +Provisioning.ExecuteCustomData=n ResourceDisk.Format=y ResourceDisk.Filesystem=ext4 ResourceDisk.MountPoint=/mnt/resource @@ -180,6 +206,8 @@ Logs.Verbose=n OS.RootDeviceScsiTimeout=300 OS.OpensslPath=None +HttpProxy.Host=None +HttpProxy.Port=None The various configuration options are described in detail below. Configuration options are of three types : Boolean, String or Integer. The Boolean @@ -260,6 +288,16 @@ servers, networking will be restarted in the VM. This will result in brief loss of Internet connectivity. +Provisioning.DecodeCustomData: +Type: Boolean Default: n + +If set, waagent will decode CustomData from Base64. + +Provisioning.ExecuteCustomData: +Type: Boolean Default: n + +If set, waagent will execute CustomData after provisioning. + ResourceDisk.Format: Type: Boolean Default: y @@ -317,6 +355,11 @@ This can be used to specify an alternate path for the openssl binary to use for cryptographic operations. +HttpProxy.Host=None +HttpProxy.Port=None +Type: String Default: None + +If set, agent will use proxy server to access internet APPENDIX diff -Nru walinuxagent-2.0.12/rpm/walinuxagent.spec walinuxagent-2.0.13/rpm/walinuxagent.spec --- walinuxagent-2.0.12/rpm/walinuxagent.spec 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/rpm/walinuxagent.spec 2015-06-01 01:55:11.000000000 +0000 @@ -2,19 +2,19 @@ # Name: walinuxagent.spec #------------------------------------------------------------------------------- # Purpose : RPM Spec file for Python script packaging -# Version : 2.0.8 +# Version : 2.0.13 # Created : April 20 2012 #=============================================================================== Name: WALinuxAgent Summary: The Windows Azure Linux Agent -Version: 2.0.8 +Version: 2.0.13 Release: 1 License: Apache License Version 2.0 Group: System/Daemons Url: http://go.microsoft.com/fwlink/?LinkId=250998 -Source0: WALinuxAgent-2.0.8.tar.gz -Requires: python python-pyasn1 openssh openssl util-linux sed grep sudo iptables +Source0: WALinuxAgent-2.0.13.tar.gz +Requires: python python-pyasn1 openssh openssl util-linux sed grep sudo iptables parted BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildArch: noarch Vendor: Microsoft Corporation @@ -61,7 +61,8 @@ %files -%attr(0755,root,root) %{_initddir}/waagent +%attr(0755,root,root) %{_sysconfdir}/rc.d/init.d/waagent +%attr(0755,root,root) %{_sysconfdir}/udev/rules.d/99-azure-product-uuid.rules %defattr(0644,root,root,0755) %doc Changelog LICENSE-2.0.txt NOTICE README %attr(0755,root,root) %{_sbindir}/waagent @@ -75,7 +76,7 @@ * Thu Sep 18 2014 - walinuxagent@microsoft.com - Remove NetworkManager conflict for EL7+ -* Thu Mar 25 2014 - walinuxagent@microsoft.com +* Sun Mar 25 2014 - walinuxagent@microsoft.com - Create directory /var/lib/waagent - Updated version to 2.0.4 for release @@ -91,13 +92,13 @@ * Fri Sep 20 2013 - walinuxagent@microsoft.com - Updated version to 2.0.0 for release -* Thu Aug 23 2013 - walinuxagent@microsoft.com +* Fri Aug 23 2013 - walinuxagent@microsoft.com - Updated version to 1.4.0 for release * Thu May 30 2013 - walinuxagent@microsoft.com - Updated version to 1.3.3 for release -* Fri Feb 26 2013 - walinuxagent@microsoft.com +* Tue Feb 26 2013 - walinuxagent@microsoft.com - Updated version to 1.3.2 for release * Fri Feb 15 2013 - walinuxagent@microsoft.com diff -Nru walinuxagent-2.0.12/script/buildrpm.sh walinuxagent-2.0.13/script/buildrpm.sh --- walinuxagent-2.0.12/script/buildrpm.sh 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/script/buildrpm.sh 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,31 @@ +script=$(dirname $0) +root=$script/.. +cd $root +root=`pwd` + +version=WALinuxAgent-2.0.13 + +mkdir -p ~/rpmbuild/TMP +mkdir -p ~/rpmbuild/SPECS +mkdir -p ~/rpmbuild/SOURCES + + +echo "rsync -a --exclude '.*' $root/ ~/rpmbuild/TMP/$version" +rsync -a --exclude '.*' $root/ ~/rpmbuild/TMP/$version + +echo "cd ~/rpmbuild/TMP" +cd ~/rpmbuild/TMP + +echo "tar -czf ${version}.tar.gz $version" +tar -czf ${version}.tar.gz $version + +echo "cp $root/rpm/walinuxagent.spec ~/rpmbuild/SPECS" +cp $root/rpm/walinuxagent.spec ~/rpmbuild/SPECS + +echo "cp ~/rpmbuild/TMP/${version}.tar.gz ~/rpmbuild/SOURCES" +cp ~/rpmbuild/TMP/${version}.tar.gz ~/rpmbuild/SOURCES + +echo "rpmbuild -ba ~/rpmbuild/SPECS/walinuxagent.spec" +rpmbuild -ba ~/rpmbuild/SPECS/walinuxagent.spec + + diff -Nru walinuxagent-2.0.12/setup.py walinuxagent-2.0.13/setup.py --- walinuxagent-2.0.12/setup.py 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/setup.py 2015-06-01 01:55:11.000000000 +0000 @@ -41,7 +41,6 @@ distro = 'redhat' return distro - class InstallData(install): user_options = install.user_options + [ @@ -133,19 +132,33 @@ # Configuration file if not os.path.exists(tgtDir + 'etc'): - try: - self.mkpath(tgtDir + 'etc', 0755) - except: - msg = 'Could not create config dir ' - msg += tgtDir - msg += 'etc' - print msg - sys.exit(1) + try: + self.mkpath(tgtDir + 'etc', 0755) + except: + msg = 'Could not create config dir ' + msg += tgtDir + msg += 'etc' + print msg + sys.exit(1) try: self.copy_file('config/waagent.conf', tgtDir + 'etc/waagent.conf') except: - print 'Could not install configuration file %etc' %tgtDir + print 'Could not install configuration file %setc' %tgtDir + sys.exit(1) + + if not os.path.exists(tgtDir + 'etc/udev/rules.d'): + try: + self.mkpath(tgtDir + 'etc/udev/rules.d', 0755) + except Exception as e: + print e + + try: + self.copy_file('config/99-azure-product-uuid.rules', tgtDir + 'etc/udev/rules.d/99-azure-product-uuid.rules') + except Exception as e: + print e + print 'Could not install product uuid rules file %setc' %tgtDir sys.exit(1) + if not os.path.exists(tgtDir + 'etc/logrotate.d'): try: self.mkpath(tgtDir + 'etc/logrotate.d', 0755) @@ -161,7 +174,7 @@ msg += tgtDir + 'etc/logrotate.d' print msg sys.exit(1) - + # Daemon if not os.path.exists(tgtDir + prefix + 'sbin'): try: diff -Nru walinuxagent-2.0.12/tests/env.py walinuxagent-2.0.13/tests/env.py --- walinuxagent-2.0.12/tests/env.py 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/tests/env.py 2015-06-01 01:55:11.000000000 +0000 @@ -15,9 +15,11 @@ import imp import os +import sys -projet_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -waagent = imp.load_source('waagent', os.path.join(projet_root, 'waagent')) +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +waagent = imp.load_source('waagent', os.path.join(project_root, 'waagent')) +sys.path.insert(0, project_root) waagent.LoggerInit('/dev/stdout', '/dev/null') diff -Nru walinuxagent-2.0.12/tests/__init__.py walinuxagent-2.0.13/tests/__init__.py --- walinuxagent-2.0.12/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/tests/__init__.py 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,19 @@ +# 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 diff -Nru walinuxagent-2.0.12/tests/test_http.py walinuxagent-2.0.13/tests/test_http.py --- walinuxagent-2.0.12/tests/test_http.py 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/tests/test_http.py 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,147 @@ +# 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. +# + +import unittest +from env import waagent +import sys +from tests.tools import * + +class MockHTTPResponse(object): + def __init__(self, status=200): + self.status = status + self.reason = "foo" + + def getheaders(*args, **kwargs): + return {"hehe" : "haha"} + + def read(*args, **kwargs): + return "bar" + +class MockOldHTTPConnection(object): + MockHost=None + MockPort=None + MockUrl=None + MockCallCount=0 + + def __init__(self, host, port): + self.__class__.MockHost = host + self.__class__.MockPort = port + + def request(self, method, url, data, headers = None): + self.__class__.MockUrl = url + self.__class__.MockCallCount += 1 + + def getresponse(*args, **kwargs): + return MockHTTPResponse() + +class MockHTTPConnection(MockOldHTTPConnection): + def set_tunnel(*args, **kwargs): + pass + +class MockBadHTTPConnection(MockHTTPConnection): + def getresponse(*args, **kwargs): + return MockHTTPResponse(500) + +class MockHttpLib(object): + def __init__(self): + self.HTTPConnection = MockHTTPConnection + self.OK = 200 + +MockOSEnv = { + "http_proxy":"http://httpproxy:8888", + "https_proxy":"https://httpsproxy:8888" +} + +class TestHttp(unittest.TestCase): + + def test_parseurl(self): + httputil = waagent.Util() + host, port, secure, path = httputil._ParseUrl("http://foo:8/bar?hehe") + self.assertEquals("foo", host) + self.assertEquals(8, port) + self.assertEquals(False, secure) + self.assertEquals("/bar?hehe", path) + + host, port, secure, path = httputil._ParseUrl("http://foo.bar/") + self.assertEquals("foo.bar", host) + self.assertEquals(80, port) + self.assertEquals(False, secure) + self.assertEquals("/", path) + + host, port, secure, path= httputil._ParseUrl("https://foo.bar/") + self.assertEquals("foo.bar", host) + self.assertEquals(80, port) + self.assertEquals(True, secure) + self.assertEquals("/", path) + + self.assertRaises(ValueError, httputil._ParseUrl, + "https://a:b@foo.bar/") + + host, port, secure, path = httputil._ParseUrl("https://foo.bar") + self.assertEquals("foo.bar", host) + self.assertEquals(80, port) + self.assertEquals(True, secure) + self.assertEquals("/", path) + + host, port, secure, path = httputil._ParseUrl("http://a:b@foo.bar:8888") + self.assertEquals("a:b@foo.bar", host) + self.assertEquals(8888, port) + self.assertEquals(False, secure) + self.assertEquals("/", path) + + @Mockup(waagent.httplib, "HTTPConnection", MockHTTPConnection) + @Mockup(waagent.os, "environ", MockOSEnv) + def test_http_request(self): + httputil = waagent.Util() + + #If chkProxy is on, host and port should point to proxy server + httputil.HttpRequest("GET", "http://foo.bar/get", chkProxy=True) + self.assertEquals("httpproxy", MockHTTPConnection.MockHost) + self.assertEquals(8888, MockHTTPConnection.MockPort) + self.assertEquals("http://foo.bar:80/get", MockHTTPConnection.MockUrl) + + #If chkProxy is off, ignore proxy + httputil.HttpRequest("GET", "http://foo.bar/get", chkProxy=False) + self.assertEquals("foo.bar", MockHTTPConnection.MockHost) + self.assertEquals(80, MockHTTPConnection.MockPort) + self.assertEquals("/get", MockHTTPConnection.MockUrl) + + @Mockup(waagent, "httplib" , MockHttpLib()) + def test_https_fallback(self): + httputil = waagent.Util() + print "The bellowing warning log is expected:" + httputil.HttpRequest("GET", "https://foo.bar/get") + self.assertEquals("/get", MockHTTPConnection.MockUrl) + + @Mockup(waagent.httplib, "HTTPConnection", MockOldHTTPConnection) + @Mockup(waagent.httplib, "HTTPSConnection", MockOldHTTPConnection) + @Mockup(waagent.os, "environ", MockOSEnv) + def test_https_fallback2(self): + httputil = waagent.Util() + print "The bellowing warning log is expected:" + httputil.HttpRequest("GET", "https://foo.bar/get", chkProxy=True) + self.assertEquals("http://foo.bar:80/get", MockOldHTTPConnection.MockUrl) + + @Mockup(waagent.Util, "RetryWaitingInterval", 0) + @Mockup(waagent.httplib, "HTTPConnection", MockBadHTTPConnection) + def test_retry(self): + httputil = waagent.Util() + MockBadHTTPConnection.MockCallCount=0 + print "The bellowing error log is expected:" + httputil.HttpRequest("GET", "http://foo.bar", chkProxy=False, maxRetry=1) + self.assertEquals(2, MockBadHTTPConnection.MockCallCount) + +if __name__ == '__main__': + unittest.main() diff -Nru walinuxagent-2.0.12/tests/test_shared_config.py walinuxagent-2.0.13/tests/test_shared_config.py --- walinuxagent-2.0.12/tests/test_shared_config.py 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/tests/test_shared_config.py 2015-06-01 01:55:11.000000000 +0000 @@ -16,6 +16,12 @@ import unittest from env import waagent +class MockDistro(object): + def getInterfaceNameByMac(self, mac): + pass + + def configIpV4(self, ifName, addr): + pass class TestSharedConfig(unittest.TestCase): @@ -24,10 +30,12 @@ self.assertNotEquals(None, conf) self.assertNotEquals(None, conf.RdmaMacAddress) self.assertNotEquals(None, conf.RdmaIPv4Address) + self.assertEquals("00:15:5D:34:00:44", conf.RdmaMacAddress) return conf def test_config_rdma(self): - waagent.LoggerInit("/dev/stdout", "/dev/null", verbose=True) + #waagent.LoggerInit("/dev/stdout", "/dev/null", verbose=True) + waagent.MyDistro= MockDistro() testDev = "/tmp/hvnd_rdma" waagent.SetFileContents(testDev, "") conf = self.test_parse_shared_config() diff -Nru walinuxagent-2.0.12/tests/test_util.py walinuxagent-2.0.13/tests/test_util.py --- walinuxagent-2.0.12/tests/test_util.py 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/tests/test_util.py 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,61 @@ +# 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. +# + +import unittest +from env import waagent +import sys +from tests.tools import * + +SampleInterfaceInfo="""\ +eth0 Link encap:Ethernet HWaddr ff:ff:ff:ff:ff:ff + inet addr:10.94.20.249 Bcast:10.94.23.255 Mask:255.255.252.0 + inet6 addr: fe80::215:5dff:fe5f:bf03/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:3789880 errors:0 dropped:0 overruns:0 frame:0 + TX packets:80973 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:388563383 (388.5 MB) TX bytes:21484571 (21.4 MB) + +eth1 Link encap:Ethernet HWaddr 00:00:00:00:00:00 + inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0 + inet6 addr: fe80::215:5dff:fe5f:bf08/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:386614 errors:0 dropped:0 overruns:0 frame:0 + TX packets:201356 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:32507619 (32.5 MB) TX bytes:78342503 (78.3 MB) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:2561 errors:0 dropped:0 overruns:0 frame:0 + TX packets:2561 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 +""" + +class TestUtil(unittest.TestCase): + + @Mockup(waagent, "RunGetOutput", MockFunc('', (0, SampleInterfaceInfo))) + def test_getInterfaceNameByMac(self): + distro = waagent.AbstractDistro() + ifName = distro.getInterfaceNameByMac("ff:ff:ff:ff:ff:ff") + self.assertEquals("eth0", ifName) + ifName = distro.getInterfaceNameByMac("00:00:00:00:00:00") + self.assertEquals("eth1", ifName) + + +if __name__ == '__main__': + unittest.main() diff -Nru walinuxagent-2.0.12/tests/test_utils.py walinuxagent-2.0.13/tests/test_utils.py --- walinuxagent-2.0.12/tests/test_utils.py 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/tests/test_utils.py 2015-06-01 01:55:11.000000000 +0000 @@ -14,6 +14,8 @@ # import unittest +import tempfile +import os from env import waagent sample_mount_list = """\ @@ -43,5 +45,39 @@ mp = waagent.GetMountPoint(malformed, device_name) self.assertEqual(mp, None) + def test_replace_in_file_found(self): + tmpfilename = tempfile.mkstemp('', 'tmp', None, True)[1] + try: + tmpfile = open(tmpfilename, 'w') + tmpfile.write('Replace Me') + tmpfile.close() + + result = waagent.ReplaceStringInFile(tmpfilename, r'c. ', 'ced ') + + tmpfile = open(tmpfilename, 'r') + newcontents = tmpfile.read(); + tmpfile.close() + + self.assertEqual('Replaced Me', str(newcontents)) + finally: + os.remove(tmpfilename) + + def test_replace_in_file_not_found(self): + tmpfilename = tempfile.mkstemp('', 'tmp', None, True)[1] + try: + tmpfile = open(tmpfilename, 'w') + tmpfile.write('Replace Me') + tmpfile.close() + + result = waagent.ReplaceStringInFile(tmpfilename, r'not here ', 'ced ') + + tmpfile = open(tmpfilename, 'r') + newcontents = tmpfile.read(); + tmpfile.close() + + self.assertEqual('Replace Me', str(newcontents)) + finally: + os.remove(tmpfilename) + if __name__ == '__main__': unittest.main() diff -Nru walinuxagent-2.0.12/tests/tools.py walinuxagent-2.0.13/tests/tools.py --- walinuxagent-2.0.12/tests/tools.py 1970-01-01 00:00:00.000000000 +0000 +++ walinuxagent-2.0.13/tests/tools.py 2015-06-01 01:55:11.000000000 +0000 @@ -0,0 +1,61 @@ +# 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 os +import sys + +parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(parent) + +def simple_file_grep(file_path, search_str): + for line in open(file_path): + if search_str in line: + return line + +def Mockup(target, name, mock): + def Decorator(func): + def Wrapper(*args, **kwargs): + origin = getattr(target, name) + setattr(target, name, mock) + try: + result = func(*args, **kwargs) + except: + raise + finally: + setattr(target, name, origin) + return result + return Wrapper + return Decorator + +class MockFunc(): + def __init__(self, name='', retval=None): + self.name = name + self.retval = retval + + def __call__(*args, **kwargs): + self = args[0] + self.args = args[1:] + self.kwargs = kwargs + return self.retval + +def Dummy(): + pass + diff -Nru walinuxagent-2.0.12/waagent walinuxagent-2.0.13/waagent --- walinuxagent-2.0.12/waagent 2015-03-15 14:16:08.000000000 +0000 +++ walinuxagent-2.0.13/waagent 2015-06-01 01:55:11.000000000 +0000 @@ -80,7 +80,7 @@ GuestAgentName = "WALinuxAgent" GuestAgentLongName = "Windows Azure Linux Agent" -GuestAgentVersion = "WALinuxAgent-2.0.12" +GuestAgentVersion = "WALinuxAgent-2.0.13" ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol. Config = None @@ -499,8 +499,9 @@ if existingFS == "7" and fs != "ntfs": Run("sfdisk -c " + device + " 1 83") Run("mkfs." + fs + " " + partition) - if Run("mount " + partition + " " + mountpoint): + if Run("mount " + partition + " " + mountpoint, chk_err=False): #If mount failed, try to format the partition and mount again + Warn("Failed to mount resource disk. Retry mounting.") Run("mkfs." + fs + " " + partition + " -F") if Run("mount " + partition + " " + mountpoint): Error("ActivateResourceDisk: Failed to mount resource disk (" + partition + ").") @@ -588,7 +589,7 @@ def stopDHCP(self): """ - Stop the system DHCP client so that tha agent can bind on its port. If + Stop the system DHCP client so that the agent can bind on its port. If the distro has set dhcp_enabled to True, it will need to provide an implementation of this method. """ @@ -605,6 +606,9 @@ """ Translate the custom data from a Base64 encoding. Default to no-op. """ + decodeCustomData = Config.get("Provisioning.DecodeCustomData") + if decodeCustomData != None and decodeCustomData.lower().startswith("y"): + return base64.b64decode(data) return data def getConfigurationPath(self): @@ -615,7 +619,26 @@ def getTotalMemory(self): return int(RunGetOutput("grep MemTotal /proc/meminfo |awk '{print $2}'")[1])/1024 - + + def getInterfaceNameByMac(self, mac): + ret, output = RunGetOutput("ifconfig -a") + if ret != 0: + raise Exception("Failed to get network interface info") + match = re.search(r"(eth\d)[^\n]+HWaddr {0}".format(mac), output) + if match is None: + raise Exception("Failed to get ifname with mac: {0}".format(mac)) + return match.group(1) + + def configIpV4(self, ifName, addr): + ret, output = RunGetOutput("ifconfig {0} up".format(ifName)) + if ret != 0: + raise Exception("Failed to bring up {0}: {1}".format(ifName, + output)) + ret, output = RunGetOutput("ifconfig {0} {1}/24".format(ifName, addr)) + if ret != 0: + raise Exception("Failed to config ipv4 for {0}: {1}".format(ifName, + output)) + ############################################################ # GentooDistro ############################################################ @@ -1019,7 +1042,7 @@ self.dhcp_client_name='systemd-networkd' self.getpidcmd='pidof ' self.shadow_file_mode=0640 - self.waagent_path='/usr/share/oem/waagent/bin' + self.waagent_path='/usr/share/oem/bin' self.python_path='/usr/share/oem/python/bin' self.dhcp_enabled=True if 'PATH' in os.environ: @@ -1337,18 +1360,6 @@ def registerAgentService(self): return self.installAgentServiceScriptFiles() - - def startAgentService(self): - """ - Use upstart syntax. - """ - return Run('start ' + self.agent_service_name) - - def stopAgentService(self): - """ - Use upstart syntax. - """ - return Run('stop ' + self.agent_service_name) def uninstallAgentService(self): """ @@ -2585,12 +2596,15 @@ break return device +class HttpResourceGoneError(Exception): + pass + class Util(object): """ Http communication class. Base of GoalState, and Agent classes. """ - __RetryWaitingInterval=10 + RetryWaitingInterval=10 def __init__(self): self.Endpoint = None @@ -2598,47 +2612,82 @@ def _ParseUrl(self, url): secure = False host = self.Endpoint - action = url - - #Strip "http[s]://hostname/" from url + path = url + port = None + + #"http[s]://hostname[:port][/]" if url.startswith("http://"): url = url[7:] - pos = url.index("/") - if pos > 0: - host = url[0: pos] - action = url[pos:] + if "/" in url: + host = url[0: url.index("/")] + path = url[url.index("/"):] + else: + host = url + path = "/" elif url.startswith("https://"): secure = True url = url[8:] - pos = url.index("/") - if pos > 0: - host = url[0:pos] - action = url[pos:] - return host, action, secure - - def _HttpRequest(self, method, host, action, data=None, - secure=False, headers=None): - resp = None; - try: - httpConnection = None + if "/" in url: + host = url[0: url.index("/")] + path = url[url.index("/"):] + else: + host = url + path = "/" - #If httplib module is not built with ssl support. Failback to http - if secure and hasattr(httplib, "HTTPSConnection"): - httpConnection = httplib.HTTPSConnection(host) + if host is None: + raise ValueError("Host is invalid:{0}".format(url)) + + if(":" in host): + pos = host.rfind(":") + port = int(host[pos + 1:]) + host = host[0:pos] + + return host, port, secure, path + + def GetHttpProxy(self, secure): + """ + Get http_proxy and https_proxy from environment variables. + Username and password is not supported now. + """ + host = Config.get("HttpProxy.Host") + port = Config.get("HttpProxy.Port") + return (host, port) + + def _HttpRequest(self, method, host, path, port=None, data=None, secure=False, + headers=None, proxyHost=None, proxyPort=None): + resp = None + conn = None + try: + if secure: + port = 443 if port is None else port + if proxyHost is not None and proxyPort is not None: + conn = httplib.HTTPSConnection(proxyHost, proxyPort) + conn.set_tunnel(host, port) + #If proxy is used, full url is needed. + path = "https://{0}:{1}{2}".format(host, port, path) + else: + conn = httplib.HTTPSConnection(host, port) else: - httpConnection = httplib.HTTPConnection(host) + port = 80 if port is None else port + if proxyHost is not None and proxyPort is not None: + conn = httplib.HTTPConnection(proxyHost, proxyPort) + #If proxy is used, full url is needed. + path = "http://{0}:{1}{2}".format(host, port, path) + else: + conn = httplib.HTTPConnection(host, port) if headers == None: - httpConnection.request(method, action, data) + conn.request(method, path, data) else: - httpConnection.request(method, action, data, headers) - resp = httpConnection.getresponse() + conn.request(method, path, data, headers) + resp = conn.getresponse() except httplib.HTTPException, e: Error('HTTPException {0}, args:{1}'.format(e, repr(e.args))) except IOError, e: Error('Socket IOError {0}, args:{1}'.format(e, repr(e.args))) return resp - def HttpRequest(self, method, url, data, headers=None, maxRetry=3): + def HttpRequest(self, method, url, data=None, + headers=None, maxRetry=3, chkProxy=False): """ Sending http request to server On error, sleep 10 and maxRetry times. @@ -2647,8 +2696,34 @@ LogIfVerbose("HTTP Req: {0} {1}".format(method, url)) LogIfVerbose("HTTP Req: Data={0}".format(data)) LogIfVerbose("HTTP Req: Header={0}".format(headers)) - host, action, secure = self._ParseUrl(url) - resp = self._HttpRequest(method, host, action, data, secure, headers) + try: + host, port, secure, path = self._ParseUrl(url) + except ValueError, e: + Error("Failed to parse url:{0}".format(url)) + return None + + #Check proxy + proxyHost, proxyPort = (None, None) + if chkProxy: + proxyHost, proxyPort = self.GetHttpProxy(secure) + + #If httplib module is not built with ssl support. Fallback to http + if secure and not hasattr(httplib, "HTTPSConnection"): + Warn("httplib is not built with ssl support") + secure = False + proxyHost, proxyPort = self.GetHttpProxy(secure) + + #If httplib module doesn't support https tunnelling. Fallback to http + if secure and \ + proxyHost is not None and \ + proxyPort is not None and \ + not hasattr(httplib.HTTPSConnection, "set_tunnel"): + Warn("httplib doesn't support https tunnelling(new in python 2.7)") + secure = False + proxyHost, proxyPort = self.GetHttpProxy(secure) + + resp = self._HttpRequest(method, host, path, port, data, + secure, headers, proxyHost, proxyPort) for retry in range(0, maxRetry): if resp is not None and \ (resp.status == httplib.OK or \ @@ -2656,6 +2731,9 @@ resp.status == httplib.ACCEPTED): return resp; + if resp is not None and resp.status == httplib.GONE: + raise HttpResourceGoneError("Http resource gone.") + Error("Retry={0}".format(retry)) Error("HTTP Req: {0} {1}".format(method, url)) Error("HTTP Req: Data={0}".format(data)) @@ -2667,35 +2745,36 @@ Error("HTTP Err: Reason={0}".format(resp.reason)) Error("HTTP Err: Header={0}".format(resp.getheaders())) Error("HTTP Err: Body={0}".format(resp.read())) - time.sleep(self.__class__.__RetryWaitingInterval) - resp = self._HttpRequest(method, host, action, data, secure, - headers) + + time.sleep(self.__class__.RetryWaitingInterval) + resp = self._HttpRequest(method, host, path, data, secure, + headers, proxyHost, proxyPort) return None - def HttpGet(self, url, headers=None, maxRetry=3): - return self.HttpRequest("GET", url, None, headers, maxRetry) + def HttpGet(self, url, headers=None, maxRetry=3, chkProxy=False): + return self.HttpRequest("GET", url, None, headers, maxRetry, chkProxy) - def HttpHead(self, url, headers=None, maxRetry=3): - return self.HttpRequest("HEAD", url, None, headers, maxRetry) + def HttpHead(self, url, headers=None, maxRetry=3, chkProxy=False): + return self.HttpRequest("HEAD", url, None, headers, maxRetry, chkProxy) - def HttpPost(self, url, data, headers=None, maxRetry=3): - return self.HttpRequest("POST", url, data, headers, maxRetry) + def HttpPost(self, url, data, headers=None, maxRetry=3, chkProxy=False): + return self.HttpRequest("POST", url, data, headers, maxRetry, chkProxy) - def HttpPut(self, url, data, headers=None, maxRetry=3): - return self.HttpRequest("PUT", url, data, headers, maxRetry) + def HttpPut(self, url, data, headers=None, maxRetry=3, chkProxy=False): + return self.HttpRequest("PUT", url, data, headers, maxRetry, chkProxy) - def HttpDelete(url, data, headers=None, maxRetry=3): - return self.HttpRequest("DELETE", url, data, headers, maxRetry) + def HttpDelete(self, url, headers=None, maxRetry=3, chkProxy=False): + return self.HttpRequest("DELETE", url, None, headers, maxRetry, chkProxy) - def HttpGetWithoutHeaders(self, url, maxRetry=3): + def HttpGetWithoutHeaders(self, url, maxRetry=3, chkProxy=False): """ Return data from an HTTP get on 'url'. """ - resp = self.HttpGet(url, None, maxRetry) + resp = self.HttpGet(url, None, maxRetry, chkProxy) return resp.read() if resp is not None else None - def HttpGetWithHeaders(self, url, maxRetry=3): + def HttpGetWithHeaders(self, url, maxRetry=3, chkProxy=False): """ Return data from an HTTP get on 'url' with x-ms-agent-name and x-ms-version @@ -2704,10 +2783,11 @@ resp = self.HttpGet(url, { "x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion - }, maxRetry) + }, maxRetry, chkProxy) return resp.read() if resp is not None else None - def HttpSecureGetWithHeaders(self, url, transportCert, maxRetry=3): + def HttpSecureGetWithHeaders(self, url, transportCert, maxRetry=3, + chkProxy=False): """ Return output of get using ssl cert. """ @@ -2716,16 +2796,16 @@ "x-ms-version": ProtocolVersion, "x-ms-cipher-name": "DES_EDE3_CBC", "x-ms-guest-agent-public-x509-cert": transportCert - }, maxRetry) + }, maxRetry, chkProxy) return resp.read() if resp is not None else None - def HttpPostWithHeaders(self, url, data, maxRetry=3): + def HttpPostWithHeaders(self, url, data, maxRetry=3, chkProxy=False): header = { "x-ms-agent-name": GuestAgentName, "Content-Type": "text/xml; charset=utf-8", "x-ms-version": ProtocolVersion } - return self.HttpPost(url, data, header, maxRetry) + return self.HttpPost(url, data, header, maxRetry, chkProxy) __StorageVersion="2014-02-14" @@ -2737,7 +2817,7 @@ blobPropResp = restutil.HttpHead(url, { "x-ms-date" : timestamp, 'x-ms-version' : __StorageVersion - }); + }, chkProxy=True); blobType = None if blobPropResp is None: Error("Can't get status blob type.") @@ -2755,7 +2835,7 @@ "x-ms-blob-type" : "BlockBlob", "Content-Length": str(len(data)), "x-ms-version" : __StorageVersion - }) + }, chkProxy=True) if ret is None: Error("Failed to upload block blob for status.") @@ -2771,7 +2851,7 @@ "Content-Length": "0", "x-ms-blob-content-length" : str(pageBlobSize), "x-ms-version" : __StorageVersion - }) + }, chkProxy=True) if ret is None: Error("Failed to clean up page blob for status") return @@ -2799,7 +2879,7 @@ "x-ms-page-write" : "update", "x-ms-version" : __StorageVersion, "Content-Length": str(pageEnd - start) - }) + }, chkProxy=True) if ret is None: Error("Failed to upload page blob for status") return @@ -2919,7 +2999,7 @@ Monitor dhcp client pid and hostname. If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric. """ - publish = ConfigurationProvider().get("Provisioning.MonitorHostName") + publish = Config.get("Provisioning.MonitorHostName") dhcpcmd = MyDistro.getpidcmd+ ' ' + MyDistro.getDhcpClientName() dhcppid = RunGetOutput(dhcpcmd)[1] while not self.shutdown: @@ -3130,6 +3210,7 @@ """ Parse and write configuration to file SharedConfig.xml. """ + LogIfVerbose(xmlText) self.reinitialize() self.xmlText = xmlText dom = xml.dom.minidom.parseString(xmlText) @@ -3146,25 +3227,46 @@ if nodes is not None and len(nodes) != 0: node = nodes[0] if node.hasAttribute("rdmaMacAddress"): - self.RdmaMacAddress = node.getAttribute("rdmaMacAddress") + addr = node.getAttribute("rdmaMacAddress") + self.RdmaMacAddress = addr[0:2] + for i in range(1, 6): + self.RdmaMacAddress += ":" + addr[2 * i : 2 *i + 2] if node.hasAttribute("rdmaIPv4Address"): self.RdmaIPv4Address = node.getAttribute("rdmaIPv4Address") return self def Save(self): + LogIfVerbose("Save SharedConfig.xml") SetFileContents("SharedConfig.xml", self.xmlText) - def ConfigRdma(self, dev="/dev/hvnd_rdma"): - if self.RdmaIPv4Address is not None and self.RdmaMacAddress is not None: - if os.path.isfile(dev): - data = ('rdmaMacAddress="{0}" rdmaIPv4Address="{1}"' - '').format(self.RdmaMacAddress, self.RdmaIPv4Address) - Log("Write rdma config to {0}: {1}".format(dev, data)) - try: - with open(dev, "w") as c: - c.write(data) - except IOError, e: - Error("Error writing {0}, {1}".format(dev, e)) + def ConfigRdma(self, dev="/dev/hvnd_rdma", datConf="/etc/dat.conf"): + if self.RdmaIPv4Address is None or self.RdmaMacAddress is None: + return + + if os.path.isfile(datConf): + old = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 " + "dapl.2.0 \"\S+ 0\"") + new = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 " + "dapl.2.0 \"{0} 0\"").format(self.RdmaIPv4Address) + lines = GetFileContents(datConf) + lines = re.sub(old, new, lines) + SetFileContents(lines) + + if os.path.isfile(dev): + data = ('rdmaMacAddress="{0}" rdmaIPv4Address="{1}"' + '').format(self.RdmaMacAddress, self.RdmaIPv4Address) + Log("Write rdma config to {0}: {1}".format(dev, data)) + try: + with open(dev, "w") as c: + c.write(data) + except IOError, e: + Error("Error writing {0}, {1}".format(dev, e)) + + try: + ifName = MyDistro.getInterfaceNameByMac(self.RdmaMacAddress) + MyDistro.configIpV4(ifName, self.RdmaIPv4Address) + except Exception as e: + Error("Failed to config rdma device: {0}".format(e)) def InvokeTopologyConsumer(self): program = Config.get("Role.TopologyConsumer") @@ -3331,7 +3433,7 @@ Log("Plugin server is: " + self.Util.Endpoint) SimpleLog(p.plugin_log,"Plugin server is: " + self.Util.Endpoint) - manifest=self.Util.HttpGetWithoutHeaders(location) + manifest=self.Util.HttpGetWithoutHeaders(location, chkProxy=True) if manifest == None: Error("Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.") SimpleLog(p.plugin_log,"Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.") @@ -3340,12 +3442,12 @@ Log("Plugin failover server is: " + self.Util.Endpoint) SimpleLog(p.plugin_log,"Plugin failover server is: " + self.Util.Endpoint) - manifest=self.Util.HttpGetWithoutHeaders(failoverlocation) + manifest=self.Util.HttpGetWithoutHeaders(failoverlocation, chkProxy=True) #if failoverlocation also fail what to do then? if manifest == None: AddExtensionEvent(name,WALAEventOperation.Download,False,0,version,"Download mainfest fail "+failoverlocation) - Log("Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest))) - SimpleLog(p.plugin_log,"Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest))) + Log("Plugin manifest " + name + " downloading failed from failover location.") + SimpleLog(p.plugin_log,"Plugin manifest " + name + " downloading failed from failover location.") filepath=LibDir+"/" + name + '.' + incarnation + '.manifest' if os.path.splitext(location)[-1] == '.xml' : #if this is an xml file we may have a BOM @@ -3374,7 +3476,7 @@ SimpleLog(p.plugin_log,"Bundle URI = " + bundle_uri) # Download the zipfile archive and save as '.zip' - bundle=self.Util.HttpGetWithoutHeaders(bundle_uri) + bundle=self.Util.HttpGetWithoutHeaders(bundle_uri, chkProxy=True) if bundle == None: AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download zip fail "+bundle_uri) Error("Unable to download plugin bundle" + bundle_uri ) @@ -4085,6 +4187,7 @@ LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl) self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl) self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml) + self.SharedConfig.Save() elif e.localName == "ExtensionsConfig": self.ExtensionsConfigUrl = GetNodeTextData(e) LogIfVerbose("ExtensionsConfigUrl:" + self.ExtensionsConfigUrl) @@ -4113,9 +4216,9 @@ """ Calls HostingEnvironmentConfig.Process() """ + LogIfVerbose("Process goalstate") self.HostingEnvironmentConfig.Process() self.SharedConfig.Process() - self.SharedConfig.Save() class OvfEnv(object): """ @@ -4170,7 +4273,7 @@ self.SshPublicKeys = [] self.SshKeyPairs = [] - def Parse(self, xmlText): + def Parse(self, xmlText, isDeprovision = False): """ Parse xml tree, retreiving user and ssh key information. Return self. @@ -4202,6 +4305,8 @@ return None self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0]) self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0]) + if isDeprovision == True: + return self try: self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0]) except: @@ -4450,7 +4555,7 @@ os.mkdir(eventfolder) os.chmod(eventfolder,0700) if len(os.listdir(eventfolder)) > 1000: - raise Exception("WriteToFolder:Too many file under "+datafolder+" exit") + raise Exception("WriteToFolder:Too many file under "+eventfolder+" exit") filename = os.path.join(eventfolder,str(int(time.time()*1000000))) with open(filename+".tmp",'wb+') as hfile: @@ -4594,7 +4699,7 @@ if not self.issysteminfoinitilized: self.issysteminfoinitilized=True try: - self.sysInfo["OSVersion"]=platform.system()+":"+"-".join(DistInfo())+":"+platform.release() + self.sysInfo["OSVersion"]=platform.system()+":"+"-".join(DistInfo(1))+":"+platform.release() self.sysInfo["GAVersion"]=GuestAgentVersion self.sysInfo["RAM"]=MyDistro.getTotalMemory() self.sysInfo["Processors"]=MyDistro.getProcessorCores() @@ -5346,9 +5451,17 @@ lbProbeResponder = False while True: if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation): - goalState = self.UpdateGoalState() + try: + goalState = self.UpdateGoalState() + except HttpResourceGoneError as e: + Warn("Incarnation is out of date:{0}".format(e)) + incarnation = None + continue + if goalState == None : + Warn("Failed to fetch goalstate") continue + if provisioned == False: self.ReportNotReady("Provisioning", "Starting") @@ -5368,7 +5481,15 @@ #Get Ctime of wala config, can help identify the base image of this VM AddExtensionEvent(name="WALA",op=WALAEventOperation.Provision,isSuccess=True, message="WALA Config Ctime:"+lastCtime) - + + executeCustomData = Config.get("Provisioning.ExecuteCustomData") + if executeCustomData != None and executeCustomData.lower().startswith("y"): + if os.path.exists(LibDir + '/CustomData'): + Run('chmod +x ' + LibDir + '/CustomData') + Run(LibDir + '/CustomData') + else: + Error(LibDir + '/CustomData does not exist.') + # # only one port supported # restart server if new port is different than old port @@ -5376,13 +5497,17 @@ # goalPort = goalState.LoadBalancerProbePort if currentPort != goalPort: - self.LoadBalancerProbeServer_Shutdown() - currentPort = goalPort - if currentPort != None and lbProbeResponder == True: - self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort) - if self.LoadBalancerProbeServer == None : - lbProbeResponder = False - Log("Unable to create LBProbeResponder.") + try: + self.LoadBalancerProbeServer_Shutdown() + currentPort = goalPort + if currentPort != None and lbProbeResponder == True: + self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort) + if self.LoadBalancerProbeServer == None : + lbProbeResponder = False + Log("Unable to create LBProbeResponder.") + except Exception, e: + Error("Failed to launch LBProbeResponder: {0}".format(e)) + currentPort = None # Report SSH key fingerprint type = Config.get("Provisioning.SshHostKeyPairType") @@ -5419,7 +5544,7 @@ eventMonitor = WALAEventMonitor(self.HttpPostWithHeaders) eventMonitor.StartEventsLoop() - time.sleep(25 - sleepToReduceAccessDenied) + time.sleep(25 - sleepToReduceAccessDenied) WaagentLogrotate = """\ @@ -5513,14 +5638,14 @@ """ Replace 'src' with 'repl' in file. """ - updated='' 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) + ReplaceFileContentsAtomic(fname,updated) except : raise return @@ -5619,6 +5744,7 @@ release = re.sub('\-.*\Z', '', str(platform.release())) distinfo = ['FreeBSD', release] return distinfo + if 'linux_distribution' in dir(platform): distinfo = list(platform.linux_distribution(full_distribution_name=fullname)) distinfo[0] = distinfo[0].strip() # remove trailing whitespace in distro name @@ -5679,7 +5805,7 @@ ovfxml = GetFileContents(LibDir+"/ovf-env.xml") ovfobj = None if ovfxml != None: - ovfobj = OvfEnv().Parse(ovfxml) + ovfobj = OvfEnv().Parse(ovfxml, True) print("WARNING! The waagent service will be stopped.") print("WARNING! All SSH host key pairs will be deleted.") @@ -5747,6 +5873,12 @@ LoggerInit('/var/log/waagent.log','/dev/console') global LinuxDistro LinuxDistro=DistInfo()[0] + + #The platform.py lib has issue with detecting oracle linux distribution. + #Merge the following patch provided by oracle as a temparory fix. + if os.path.exists("/etc/oracle-release"): + LinuxDistro="Oracle Linux" + global MyDistro MyDistro=GetMyDistro() if MyDistro == None :