summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Howard <ben.howard@ubuntu.com>2015-07-02 15:14:26 -0600
committerusd-importer <ubuntu-server@lists.ubuntu.com>2015-07-03 16:03:20 +0000
commite1187be5b25316b2a1a9250b2aa4450179fe1f7d (patch)
tree88d6c1409913e3a630a8e82c5aed5edd26ab16ef
parent5af8d1f80caf5d49988dbb0cf95ad7c58250b90f (diff)
downloadvyos-walinuxagent-e1187be5b25316b2a1a9250b2aa4450179fe1f7d.tar.gz
vyos-walinuxagent-e1187be5b25316b2a1a9250b2aa4450179fe1f7d.zip
Import patches-unapplied version 2.0.13-0ubuntu1 to ubuntu/wily-proposed
Imported using git-ubuntu import. Changelog parent: 5af8d1f80caf5d49988dbb0cf95ad7c58250b90f New changelog entries: * New upstream release (LP: #1449369). * Rebased patches for 2.0.12 onto 2.0.13.
-rw-r--r--Changelog8
-rw-r--r--README51
-rw-r--r--config/99-azure-product-uuid.rules9
-rw-r--r--config/waagent.conf11
-rw-r--r--debian/changelog7
-rw-r--r--debian/control2
-rw-r--r--debian/patches/build_info.txt4
-rw-r--r--debian/patches/disable_provisioning.patch24
-rw-r--r--debian/patches/fixup_setup_file.patch2
-rw-r--r--debian/patches/series3
-rw-r--r--fix-gpt-ubuntu.py2
-rw-r--r--rpm/walinuxagent.spec17
-rw-r--r--script/buildrpm.sh31
-rwxr-xr-xsetup.py35
-rw-r--r--tests/__init__.py19
-rw-r--r--tests/env.py6
-rw-r--r--tests/test_http.py147
-rw-r--r--tests/test_shared_config.py10
-rw-r--r--tests/test_util.py61
-rw-r--r--tests/test_utils.py36
-rw-r--r--tests/tools.py61
-rw-r--r--waagent344
22 files changed, 748 insertions, 142 deletions
diff --git a/Changelog b/Changelog
index 90ea978..44788c8 100644
--- a/Changelog
+++ b/Changelog
@@ -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 --git a/README b/README
index 6a5a2b7..59fab95 100644
--- a/README
+++ b/README
@@ -31,11 +31,11 @@ functionality for Linux and FreeBSD IaaS deployments:
* 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 @@ files provided (see debian/README and rpm/README).
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.DeleteRootPassword=n
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 @@ LBProbeResponder=y
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 @@ the image to reflect the change. In order to push the name change to the DNS
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 @@ Type: String Default: None
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 --git a/config/99-azure-product-uuid.rules b/config/99-azure-product-uuid.rules
new file mode 100644
index 0000000..a5af9b1
--- /dev/null
+++ b/config/99-azure-product-uuid.rules
@@ -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 --git a/config/waagent.conf b/config/waagent.conf
index 6a74125..756eb4d 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -28,6 +28,12 @@ Provisioning.SshHostKeyPairType=rsa
# 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 @@ OS.RootDeviceScsiTimeout=300
# 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 --git a/debian/changelog b/debian/changelog
index c4dddd5..87408ae 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+walinuxagent (2.0.13-0ubuntu1) wily; urgency=medium
+
+ * New upstream release (LP: #1449369).
+ * Rebased patches for 2.0.12 onto 2.0.13.
+
+ -- Ben Howard <ben.howard@ubuntu.com> Thu, 02 Jul 2015 15:14:26 -0600
+
walinuxagent (2.0.12-0ubuntu2) vivid; urgency=medium
* Fixed systemd unit file which caused SharedConfig.xml to be deleted by
diff --git a/debian/control b/debian/control
index a167101..226c96f 100644
--- a/debian/control
+++ b/debian/control
@@ -4,7 +4,7 @@ Priority: extra
Maintainer: Ben Howard <ben.howard@ubuntu.com>
XSBC-Original-Maintainer: Microsoft Corporation <walinuxagent@microsoft.com>
Build-Depends: debhelper (>= 8), python-all, python-setuptools, dh-systemd, dh-python
-Standards-Version: 3.9.5
+Standards-Version: 3.9.6
XS-Python-Version: all
Homepage: http://go.microsoft.com/fwlink/?LinkId=250998
diff --git a/debian/patches/build_info.txt b/debian/patches/build_info.txt
new file mode 100644
index 0000000..f02ab12
--- /dev/null
+++ b/debian/patches/build_info.txt
@@ -0,0 +1,4 @@
+serial=20150629
+orig_prefix=trusty-server-cloudimg
+suite=trusty
+build_name=server
diff --git a/debian/patches/disable_provisioning.patch b/debian/patches/disable_provisioning.patch
index 08d8ee0..956d30b 100644
--- a/debian/patches/disable_provisioning.patch
+++ b/debian/patches/disable_provisioning.patch
@@ -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 --git a/debian/patches/fixup_setup_file.patch b/debian/patches/fixup_setup_file.patch
index 17a29b9..845607d 100644
--- a/debian/patches/fixup_setup_file.patch
+++ b/debian/patches/fixup_setup_file.patch
@@ -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 --git a/debian/patches/series b/debian/patches/series
index 60e6520..bddf5a0 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -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 --git a/fix-gpt-ubuntu.py b/fix-gpt-ubuntu.py
index 85c3fd5..dd061f7 100644
--- a/fix-gpt-ubuntu.py
+++ b/fix-gpt-ubuntu.py
@@ -26,7 +26,7 @@ WARNING: This script will remove all partitions in resource disk and create
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 --git a/rpm/walinuxagent.spec b/rpm/walinuxagent.spec
index 45a2b42..1d6f794 100644
--- a/rpm/walinuxagent.spec
+++ b/rpm/walinuxagent.spec
@@ -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 @@ fi
%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 @@ fi
* 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 @@ fi
* 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 --git a/script/buildrpm.sh b/script/buildrpm.sh
new file mode 100644
index 0000000..509a950
--- /dev/null
+++ b/script/buildrpm.sh
@@ -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 --git a/setup.py b/setup.py
index 846d8e7..d2a2416 100755
--- a/setup.py
+++ b/setup.py
@@ -41,7 +41,6 @@ def getDistro():
distro = 'redhat'
return distro
-
class InstallData(install):
user_options = install.user_options + [
@@ -133,19 +132,33 @@ class InstallData(install):
# 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 @@ class InstallData(install):
msg += tgtDir + 'etc/logrotate.d'
print msg
sys.exit(1)
-
+
# Daemon
if not os.path.exists(tgtDir + prefix + 'sbin'):
try:
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..9bdb27e
--- /dev/null
+++ b/tests/__init__.py
@@ -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 --git a/tests/env.py b/tests/env.py
index 4e9e052..513df7f 100644
--- a/tests/env.py
+++ b/tests/env.py
@@ -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 --git a/tests/test_http.py b/tests/test_http.py
new file mode 100644
index 0000000..eab1382
--- /dev/null
+++ b/tests/test_http.py
@@ -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 --git a/tests/test_shared_config.py b/tests/test_shared_config.py
index 75220b8..ede2766 100644
--- a/tests/test_shared_config.py
+++ b/tests/test_shared_config.py
@@ -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 @@ class TestSharedConfig(unittest.TestCase):
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 --git a/tests/test_util.py b/tests/test_util.py
new file mode 100644
index 0000000..6e3ff27
--- /dev/null
+++ b/tests/test_util.py
@@ -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 --git a/tests/test_utils.py b/tests/test_utils.py
index 00feb6c..e4c1c45 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -14,6 +14,8 @@
#
import unittest
+import tempfile
+import os
from env import waagent
sample_mount_list = """\
@@ -43,5 +45,39 @@ class TestWAAgentUtils(unittest.TestCase):
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 --git a/tests/tools.py b/tests/tools.py
new file mode 100644
index 0000000..27e16d3
--- /dev/null
+++ b/tests/tools.py
@@ -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 --git a/waagent b/waagent
index cbd005b..dd67353 100644
--- a/waagent
+++ b/waagent
@@ -80,7 +80,7 @@ if not hasattr(subprocess,'check_output'):
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 @@ class AbstractDistro(object):
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 @@ class AbstractDistro(object):
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 @@ class AbstractDistro(object):
"""
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 @@ class AbstractDistro(object):
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 @@ class CoreOSDistro(AbstractDistro):
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 @@ class UbuntuDistro(debianDistro):
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 @@ def DeviceForIdePort(n):
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 @@ class Util(object):
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 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
- #If httplib module is not built with ssl support. Failback to http
- if secure and hasattr(httplib, "HTTPSConnection"):
- httpConnection = httplib.HTTPSConnection(host)
+ 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 @@ class Util(object):
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 @@ class Util(object):
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 @@ class Util(object):
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 @@ class Util(object):
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 @@ class Util(object):
"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 @@ def GetBlobType(url):
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 @@ def PutBlockBlob(url, data):
"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 @@ def PutPageBlob(url, data):
"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 @@ def PutPageBlob(url, data):
"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 @@ class EnvMonitor(object):
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 @@ class SharedConfig(object):
"""
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 @@ class SharedConfig(object):
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 @@ class ExtensionsConfig(object):
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 @@ class ExtensionsConfig(object):
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 @@ class ExtensionsConfig(object):
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 @@ class GoalState(Util):
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 @@ class GoalState(Util):
"""
Calls HostingEnvironmentConfig.Process()
"""
+ LogIfVerbose("Process goalstate")
self.HostingEnvironmentConfig.Process()
self.SharedConfig.Process()
- self.SharedConfig.Save()
class OvfEnv(object):
"""
@@ -4170,7 +4273,7 @@ class OvfEnv(object):
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 @@ class OvfEnv(object):
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 @@ class WALAEvent(object):
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 @@ class WALAEventMonitor(WALAEvent):
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 @@ class Agent(Util):
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 @@ class Agent(Util):
#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 @@ class Agent(Util):
#
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 @@ class Agent(Util):
eventMonitor = WALAEventMonitor(self.HttpPostWithHeaders)
eventMonitor.StartEventsLoop()
- time.sleep(25 - sleepToReduceAccessDenied)
+ time.sleep(25 - sleepToReduceAccessDenied)
WaagentLogrotate = """\
@@ -5513,14 +5638,14 @@ def ReplaceStringInFile(fname,src,repl):
"""
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 @@ def DistInfo(fullname=0):
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 @@ def Deprovision(force, deluser):
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 @@ def main():
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 :