Hello community, here is the log from the commit of package WALinuxAgent for openSUSE:Factory checked in at 2015-04-16 14:12:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/WALinuxAgent (Old) and /work/SRC/openSUSE:Factory/.WALinuxAgent.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "WALinuxAgent" Changes: -------- --- /work/SRC/openSUSE:Factory/WALinuxAgent/WALinuxAgent.changes 2015-01-23 15:46:12.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.WALinuxAgent.new/WALinuxAgent.changes 2015-04-16 14:12:32.000000000 +0200 @@ -1,0 +2,7 @@ +Wed Mar 25 07:13:39 UTC 2015 - rjsch...@suse.com + +- Update to version 2.0.12 (bnc#924135,bnc#924137i,bnc#919244) + + Add support for page blob status report + + Restart the service on upgrade + +------------------------------------------------------------------- Old: ---- WALinuxAgent-2.0.11.tar.gz New: ---- WALinuxAgent-2.0.12.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ WALinuxAgent.spec ++++++ --- /var/tmp/diff_new_pack.MKMU1p/_old 2015-04-16 14:12:32.000000000 +0200 +++ /var/tmp/diff_new_pack.MKMU1p/_new 2015-04-16 14:12:32.000000000 +0200 @@ -1,7 +1,7 @@ # # spec file for package WALinuxAgent # -# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -20,7 +20,7 @@ Summary: The Windows Azure Linux Agent License: Apache-2.0 Group: System/Daemons -Version: 2.0.11 +Version: 2.0.12 Release: 0 Url: https://github.com/Azure/WALinuxAgent Source0: https://github.com/Azure/%{name}/archive/%{name}-%{version}.tar.gz @@ -128,6 +128,7 @@ %endif %postun +%restart_on_update waagent %if 0%{?suse_version} > 1140 %service_del_postun waagent.service %else ++++++ WALinuxAgent-2.0.11.tar.gz -> WALinuxAgent-2.0.12.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/.gitignore new/WALinuxAgent-WALinuxAgent-2.0.12/.gitignore --- old/WALinuxAgent-WALinuxAgent-2.0.11/.gitignore 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/.gitignore 2015-03-15 15:16:08.000000000 +0100 @@ -1,3 +1,4 @@ waagentc *.py[cod]* +tests/status_blob_url.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/Changelog new/WALinuxAgent-WALinuxAgent-2.0.12/Changelog --- old/WALinuxAgent-WALinuxAgent-2.0.11/Changelog 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/Changelog 2015-03-15 15:16:08.000000000 +0100 @@ -1,5 +1,8 @@ WALinuxAgent Changelog ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +15 Jan 2015, WALinuxAgent 2.0.12 + . Add support for page blob status report + 11 Dec 2014, WALinuxAgent 2.0.11 . Add support for GPT(Guid Partition Table) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/README new/WALinuxAgent-WALinuxAgent-2.0.12/README --- old/WALinuxAgent-WALinuxAgent-2.0.11/README 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/README 2015-03-15 15:16:08.000000000 +0100 @@ -1,9 +1,9 @@ -Windows Azure Linux Agent README +Microsoft Azure Linux Agent README INTRODUCTION -The Windows Azure Linux Agent (waagent) manages Linux provisioning and VM -interaction with the Windows Azure Fabric Controller. It provides the following +The Microsoft Azure Linux Agent (waagent) manages Linux & FreeBSD provisioning, +and VM interaction with the Azure Fabric Controller. It provides the following functionality for Linux and FreeBSD IaaS deployments: * Image Provisioning @@ -53,12 +53,13 @@ REQUIREMENTS -The following systems have been tested and are known to work with the Windows -Azure Linux Agent. Please note that this list may differ from the official -list of supported systems on the Windows Azure Platform as described here: +The following systems have been tested and are known to work with the Azure +Linux Agent. Please note that this list may differ from the official list +of supported systems on the Windows Azure Platform as described here: http://support.microsoft.com/kb/2805216 Supported Linux Distributions: + * CoreOS * CentOS 6.2+ * Debian 7.0+ * Ubuntu 12.04+ @@ -67,14 +68,14 @@ * Oracle Linux 6.4+ Other Supported Systems: - * FreeBSD 9+ + * FreeBSD 10+ (Azure Linux Agent v2.0.10+) Waagent depends on some system packages in order to function properly: - * Python 2.5+ + * Python 2.6+ * OpenSSL 1.0+ * OpenSSH 5.3+ - * Filesystem utilities: sfdisk, fdisk, mkfs + * Filesystem utilities: sfdisk, fdisk, mkfs, parted * Password tools: chpasswd, sudo * Text processing tools: sed, grep * Network tools: ip-route @@ -87,8 +88,12 @@ files provided (see debian/README and rpm/README). If installing manually, waagent should be copied to /usr/sbin/waagent and -installed by running: /usr/sbin/waagent -install. The waagent log is kept at -/var/log/waagent.log. +installed by running: + + # sudo chmod 755 /usr/sbin/waagent + # sudo /usr/sbin/waagent -install -verbose + +The agent's log file is kept at /var/log/waagent.log. COMMAND LINE OPTIONS @@ -155,7 +160,7 @@ waagent. A sample configuration file is shown below: # -# Windows Azure Linux Agent Configuration +# Azure Linux Agent Configuration # Role.StateConsumer=None @@ -219,7 +224,7 @@ This allows the user to enable or disable the provisioning functionality in the agent. Valid values are "y" or "n". If provisioning is disabled, SSH host and user keys in the image are preserved and any configuration specified in the -Windows Azure provisioning API is ignored. +Azure provisioning API is ignored. Provisioning.DeleteRootPassword: Type: Boolean Default: n diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/get-agent.py new/WALinuxAgent-WALinuxAgent-2.0.12/get-agent.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/get-agent.py 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/get-agent.py 2015-03-15 15:16:08.000000000 +0100 @@ -28,11 +28,10 @@ import re import platform -def upgrade(): +def upgrade(account='Azure', ref='2.0'): #Define variables - account = 'Azure' agentUri = ('https://raw.githubusercontent.com/{0}/' - 'WALinuxAgent/WALinuxAgent-2.0.11/waagent').format(account) + 'WALinuxAgent/{1}/waagent').format(account, ref) distro = platform.linux_distribution() cmd = ['service', 'waagent', 'restart'] agent_file="/usr/sbin/waagent" @@ -64,4 +63,7 @@ job.wait() if __name__ == '__main__': - upgrade() + if len(sys.argv) == 3: + upgrade(sys.argv[1], sys.argv[2]) + else: + upgrade() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/test/part-gpt.py new/WALinuxAgent-WALinuxAgent-2.0.12/test/part-gpt.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/test/part-gpt.py 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/test/part-gpt.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -# 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. -# -# 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 subprocess - -if __name__ == '__main__': - subprocess.call(['umount', '/mnt/resource']) - subprocess.call(['umount', '/mnt']) - subprocess.call(['parted', '/dev/sdb', 'print']) - subprocess.call(['parted', '/dev/sdb', 'rm', '1']) - subprocess.call(['parted', '/dev/sdb', 'mklabel', 'gpt']) - subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '0%', '50%']) - subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '50%', '100%']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/tests/env.py new/WALinuxAgent-WALinuxAgent-2.0.12/tests/env.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/tests/env.py 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/tests/env.py 2015-03-15 15:16:08.000000000 +0100 @@ -19,5 +19,5 @@ projet_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) waagent = imp.load_source('waagent', os.path.join(projet_root, 'waagent')) -waagent.LoggerInit('/tmp/test.log','/dev/stdout') +waagent.LoggerInit('/dev/stdout', '/dev/null') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/tests/part-gpt.py new/WALinuxAgent-WALinuxAgent-2.0.12/tests/part-gpt.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/tests/part-gpt.py 1970-01-01 01:00:00.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/tests/part-gpt.py 2015-03-15 15:16:08.000000000 +0100 @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# +# 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. +# +# 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 subprocess + +if __name__ == '__main__': + subprocess.call(['umount', '/mnt/resource']) + subprocess.call(['umount', '/mnt']) + subprocess.call(['parted', '/dev/sdb', 'print']) + subprocess.call(['parted', '/dev/sdb', 'rm', '1']) + subprocess.call(['parted', '/dev/sdb', 'mklabel', 'gpt']) + subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '0%', '50%']) + subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '50%', '100%']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/tests/test_shared_config.py new/WALinuxAgent-WALinuxAgent-2.0.12/tests/test_shared_config.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/tests/test_shared_config.py 1970-01-01 01:00:00.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/tests/test_shared_config.py 2015-03-15 15:16:08.000000000 +0100 @@ -0,0 +1,85 @@ +# 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 + + +class TestSharedConfig(unittest.TestCase): + + def test_parse_shared_config(self): + conf = waagent.SharedConfig().Parse(SharedConfigText) + self.assertNotEquals(None, conf) + self.assertNotEquals(None, conf.RdmaMacAddress) + self.assertNotEquals(None, conf.RdmaIPv4Address) + return conf + + def test_config_rdma(self): + waagent.LoggerInit("/dev/stdout", "/dev/null", verbose=True) + testDev = "/tmp/hvnd_rdma" + waagent.SetFileContents(testDev, "") + conf = self.test_parse_shared_config() + conf.ConfigRdma(dev=testDev) + rdmaConf = waagent.GetFileContents(testDev) + self.assertNotEquals(None, rdmaConf) + self.assertNotEquals("", rdmaConf) + +SharedConfigText="""\ +<?xml version="1.0" encoding="utf-8"?> +<SharedConfig version="1.0.0.0" goalStateIncarnation="1"> + <Deployment name="698f959e434c41cc9d72a2c67c044463" guid="{ba92e945-0302-4030-9710-257c03c07e22}" incarnation="0" isNonCancellableTopologyChangeEnabled="false"> + <Service name="test-rdms" guid="{00000000-0000-0000-0000-000000000000}" /> + <ServiceInstance name="698f959e434c41cc9d72a2c67c044463.0" guid="{6f157bcb-b6ac-4fdd-9789-2ca466220e17}" /> + </Deployment> + <Incarnation number="1" instance="test-rdms" guid="{33d19bb6-f34d-4dfb-966c-2bade1714cc5}" /> + <Role guid="{dad0becc-5d1d-3c55-3285-0136e9933bbe}" name="test-rdms" settleTimeSeconds="0" /> + <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8"> + <Probes> + <Probe name="D41D8CD98F00B204E9800998ECF8427E" /> + <Probe name="423A4BBA20CEBE79BA641B20A03ED6F9" /> + </Probes> + </LoadBalancerSettings> + <OutputEndpoints> + <Endpoint name="test-rdms:openInternalEndpoint" type="SFS"> + <Target instance="test-rdms" endpoint="openInternalEndpoint" /> + </Endpoint> + </OutputEndpoints> + <Instances> + <Instance id="test-rdms" address="100.74.58.20" primaryMacAddress="000D3A101ED4" rdmaMacAddress="00155D340044" rdmaIPv4Address="172.16.2.59"> + <FaultDomains randomId="0" updateId="0" updateCount="0" /> + <InputEndpoints> + <Endpoint name="openInternalEndpoint" address="100.74.58.20" protocol="any" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false"> + <LocalPorts> + <LocalPortSelfManaged /> + </LocalPorts> + </Endpoint> + <Endpoint name="SSH" address="100.74.58.20:22" protocol="tcp" hostName="test-rdmsContractContract" isPublic="true" loadBalancedPublicAddress="104.45.128.35:22" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false"> + <LocalPorts> + <LocalPortRange from="22" to="22" /> + </LocalPorts> + </Endpoint> + <Endpoint name="test-rdms_A9_Infiniband" address="100.74.58.20" protocol="any" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false"> + <LocalPorts> + <LocalPortSelfManaged /> + </LocalPorts> + </Endpoint> + </InputEndpoints> + </Instance> + </Instances> +</SharedConfig> +""" + +if __name__ == '__main__': + unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/tests/upload_status_blob.py new/WALinuxAgent-WALinuxAgent-2.0.12/tests/upload_status_blob.py --- old/WALinuxAgent-WALinuxAgent-2.0.11/tests/upload_status_blob.py 1970-01-01 01:00:00.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/tests/upload_status_blob.py 2015-03-15 15:16:08.000000000 +0100 @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# +# 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. +# +# 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 +from env import waagent +""" +To run the test, you need to create a file under the same directory called: + status_blob_url.py +and defined the following 2 variables like: + blockBlobUrl="<sas link to a block blob with w/r access>" + pageBlobUrl="<sas link to a page blob with w/r access>" +""" +from status_blob_url import blockBlobUrl, pageBlobUrl + +if __name__ == '__main__': + waagent.LoggerInit('/dev/stdout', '/dev/null', verbose=True) + status = "a" * 512 + waagent.UploadStatusBlob(blockBlobUrl, status.encode("utf-8")) + waagent.UploadStatusBlob(pageBlobUrl, status.encode("utf-8")) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WALinuxAgent-WALinuxAgent-2.0.11/waagent new/WALinuxAgent-WALinuxAgent-2.0.12/waagent --- old/WALinuxAgent-WALinuxAgent-2.0.11/waagent 2014-12-11 10:04:31.000000000 +0100 +++ new/WALinuxAgent-WALinuxAgent-2.0.12/waagent 2015-03-15 15:16:08.000000000 +0100 @@ -2,7 +2,7 @@ # # Windows Azure Linux Agent # -# Copyright 2014 Microsoft Corporation +# Copyright 2015 Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Requires Python 2.4+ and Openssl 1.0+ +# Requires Python 2.6+ and Openssl 1.0+ # # Implements parts of RFC 2131, 1541, 1497 and # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx @@ -80,7 +80,7 @@ GuestAgentName = "WALinuxAgent" GuestAgentLongName = "Windows Azure Linux Agent" -GuestAgentVersion = "WALinuxAgent-2.0.11" +GuestAgentVersion = "WALinuxAgent-2.0.12" ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol. Config = None @@ -1060,12 +1060,9 @@ def restartSshService(self): """ - Service call to re(start) the SSH service + SSH is socket activated on CoreOS. No need to restart it. """ - retcode = Run("systemctl restart sshd") - if retcode > 0: - Error("Failed to restart SSH service with return code:" + str(retcode)) - return retcode + return 0 def sshDeployPublicKey(self,fprint,path): """ @@ -1374,7 +1371,7 @@ """ Ubuntu specific warning string from Deprovision. """ - print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.") + print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,original} will be deleted.") def deprovisionDeleteFiles(self): """ @@ -1388,7 +1385,7 @@ else: Log("resolvconf is enabled; leaving /etc/resolv.conf intact") resolvConfD = '/etc/resolvconf/resolv.conf.d/' - self.fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial']) + self.fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'original']) for f in os.listdir(LibDir)+self.fileBlackList: try: os.remove(f) @@ -1788,7 +1785,7 @@ code,output=RunGetOutput("ifconfig",chk_err=False) Log(output) retries=10 - cmd='ifconfig | grep -A1 -B2 ether | grep -B3 inet | grep -A3 UP ' + cmd='ifconfig | grep -A2 -B2 ether | grep -B3 inet | grep -A4 UP ' code=1 while code > 0 : @@ -2593,140 +2590,233 @@ Http communication class. Base of GoalState, and Agent classes. """ - def _HttpGet(self, url, headers): + __RetryWaitingInterval=10 + + def __init__(self): + self.Endpoint = None + + def _ParseUrl(self, url): + secure = False + host = self.Endpoint + action = url + + #Strip "http[s]://hostname/" from url + if url.startswith("http://"): + url = url[7:] + pos = url.index("/") + if pos > 0: + host = url[0: pos] + action = url[pos:] + 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 httplib module is not built with ssl support. Failback to http + if secure and hasattr(httplib, "HTTPSConnection"): + httpConnection = httplib.HTTPSConnection(host) + else: + httpConnection = httplib.HTTPConnection(host) + if headers == None: + httpConnection.request(method, action, data) + else: + httpConnection.request(method, action, data, headers) + resp = httpConnection.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): """ - Do HTTP get on 'url' with 'headers'. + Sending http request to server On error, sleep 10 and maxRetry times. Return the output buffer or None. """ - LogIfVerbose("HttpGet(" + url + ")") - maxRetry = 2 - if url.startswith("http://"): - url = url[7:] - url = url[url.index("/"):] - for retry in range(0, maxRetry + 1): - strRetry = str(retry) - log = [NoLog, Error][retry > 0] - log("retry HttpGet(" + url + "),retry=" + strRetry) - response = None - strStatus = "None" - try: - httpConnection = httplib.HTTPConnection(self.Endpoint) - if headers == None: - request = httpConnection.request("GET", url) - else: - request = httpConnection.request("GET", url, None, headers) - response = httpConnection.getresponse() - strStatus = str(response.status) - except httplib.HTTPException, e: - Error('HTTPException ' + e.message + ' args: ' + repr(e.args)) - except IOError, e: - Error('socket IOError ' + e.message + ' args: ' + repr(e.args)) - log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if response == None or response.status != httplib.OK: - Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if retry == maxRetry: - Error("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return None - else: - Error("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) - time.sleep(10) + 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) + for retry in range(0, maxRetry): + if resp is not None and \ + (resp.status == httplib.OK or \ + resp.status == httplib.CREATED or \ + resp.status == httplib.ACCEPTED): + return resp; + + Error("Retry={0}".format(retry)) + Error("HTTP Req: {0} {1}".format(method, url)) + Error("HTTP Req: Data={0}".format(data)) + Error("HTTP Req: Header={0}".format(headers)) + if resp is None: + Error("HTTP Err: response is empty.".format(retry)) else: - log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return response.read() + Error("HTTP Err: Status={0}".format(resp.status)) + 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) + + return None + + def HttpGet(self, url, headers=None, maxRetry=3): + return self.HttpRequest("GET", url, None, headers, maxRetry) + + def HttpHead(self, url, headers=None, maxRetry=3): + return self.HttpRequest("HEAD", url, None, headers, maxRetry) + + def HttpPost(self, url, data, headers=None, maxRetry=3): + return self.HttpRequest("POST", url, data, headers, maxRetry) + + def HttpPut(self, url, data, headers=None, maxRetry=3): + return self.HttpRequest("PUT", url, data, headers, maxRetry) - def HttpGetWithoutHeaders(self, url): + def HttpDelete(url, data, headers=None, maxRetry=3): + return self.HttpRequest("DELETE", url, data, headers, maxRetry) + + def HttpGetWithoutHeaders(self, url, maxRetry=3): """ Return data from an HTTP get on 'url'. """ - return self._HttpGet(url, None) + resp = self.HttpGet(url, None, maxRetry) + return resp.read() if resp is not None else None - def HttpGetWithHeaders(self, url): + def HttpGetWithHeaders(self, url, maxRetry=3): """ Return data from an HTTP get on 'url' with x-ms-agent-name and x-ms-version headers. """ - return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion}) + resp = self.HttpGet(url, { + "x-ms-agent-name": GuestAgentName, + "x-ms-version": ProtocolVersion + }, maxRetry) + return resp.read() if resp is not None else None - def HttpSecureGetWithHeaders(self, url, transportCert): + def HttpSecureGetWithHeaders(self, url, transportCert, maxRetry=3): """ Return output of get using ssl cert. """ - return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, - "x-ms-version": ProtocolVersion, - "x-ms-cipher-name": "DES_EDE3_CBC", - "x-ms-guest-agent-public-x509-cert": transportCert}) - - def HttpPost(self, url, data): - """ - Send http POST to server, sleeping 10 retrying maxRetry times upon error. - """ - LogIfVerbose("HttpPost(" + url + ")") - maxRetry = 2 - for retry in range(0, maxRetry + 1): - strRetry = str(retry) - log = [NoLog, Error][retry > 0] - log("retry HttpPost(" + url + "),retry=" + strRetry) - response = None - strStatus = "None" - try: - httpConnection = httplib.HTTPConnection(self.Endpoint) - request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName, - "Content-Type": "text/xml; charset=utf-8", - "x-ms-version": ProtocolVersion}) - response = httpConnection.getresponse() - strStatus = str(response.status) - except httplib.HTTPException, e: - Error('HTTPException ' + e.message + ' args: ' + repr(e.args)) - except IOError, e: - Error('socket IOError ' + e.message + ' args: ' + repr(e.args)) - log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED): - Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if retry == maxRetry: - Error("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return None - else: - Error("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) - time.sleep(10) - else: - log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return response + resp = self.HttpGet(url, { + "x-ms-agent-name": GuestAgentName, + "x-ms-version": ProtocolVersion, + "x-ms-cipher-name": "DES_EDE3_CBC", + "x-ms-guest-agent-public-x509-cert": transportCert + }, maxRetry) + return resp.read() if resp is not None else None + + def HttpPostWithHeaders(self, url, data, maxRetry=3): + header = { + "x-ms-agent-name": GuestAgentName, + "Content-Type": "text/xml; charset=utf-8", + "x-ms-version": ProtocolVersion + } + return self.HttpPost(url, data, header, maxRetry) - def HttpPutBlockBlob(self, url, data): - """ - Send http PUT to server, sleeping 10 retrying maxRetry times upon error. - """ - LogIfVerbose("HttpPutBlockBlob(" + url + ")") - maxRetry = 2 - for retry in range(0, maxRetry + 1): - strRetry = str(retry) - log = [NoLog, Error][retry > 0] - log("retry HttpPutBlockBlob(" + url + "),retry=" + strRetry) - response = None - strStatus = "None" - try: - httpConnection = httplib.HTTPConnection(self.Endpoint) - request = httpConnection.request("PUT", url, data, {"x-ms-blob-type" : "BlockBlob", "x-ms-date" : time.strftime("%Y-%M-%dT%H:%M:%SZ", time.gmtime()) ,"Content-Length": str(len(data))} ) - response = httpConnection.getresponse() - strStatus = str(response.status) - except httplib.HTTPException, e: - Error('HTTPException ' + e.message + ' args: ' + repr(e.args)) - except IOError, e: - Error('socket IOError ' + e.message + ' args: ' + repr(e.args)) - log("response HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if response == None or (response.status != httplib.OK and response.status != httplib.CREATED): - Error("HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) - if retry == maxRetry: - Error("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return None - else: - Error("sleep 10 seconds HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) - time.sleep(10) - else: - log("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) - return response +__StorageVersion="2014-02-14" + +def GetBlobType(url): + restutil = Util() + #Check blob type + LogIfVerbose("Check blob type.") + timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + blobPropResp = restutil.HttpHead(url, { + "x-ms-date" : timestamp, + 'x-ms-version' : __StorageVersion + }); + blobType = None + if blobPropResp is None: + Error("Can't get status blob type.") + return None + blobType = blobPropResp.getheader("x-ms-blob-type") + LogIfVerbose("Blob type={0}".format(blobType)) + return blobType + +def PutBlockBlob(url, data): + restutil = Util() + LogIfVerbose("Upload block blob") + timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + ret = restutil.HttpPut(url, data, { + "x-ms-date" : timestamp, + "x-ms-blob-type" : "BlockBlob", + "Content-Length": str(len(data)), + "x-ms-version" : __StorageVersion + }) + if ret is None: + Error("Failed to upload block blob for status.") + +def PutPageBlob(url, data): + restutil = Util() + LogIfVerbose("Replace old page blob") + timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + #Align to 512 bytes + pageBlobSize = ((len(data) + 511) / 512) * 512 + ret = restutil.HttpPut(url, "", { + "x-ms-date" : timestamp, + "x-ms-blob-type" : "PageBlob", + "Content-Length": "0", + "x-ms-blob-content-length" : str(pageBlobSize), + "x-ms-version" : __StorageVersion + }) + if ret is None: + Error("Failed to clean up page blob for status") + return + + if url.index('?') < 0: + url = "{0}?comp=page".format(url) + else: + url = "{0}&comp=page".format(url) + + LogIfVerbose("Upload page blob") + pageMax = 4 * 1024 * 1024 #Max page size: 4MB + start = 0 + end = 0 + while end < len(data): + end = min(len(data), start + pageMax) + contentSize = end - start + #Align to 512 bytes + pageEnd = ((end + 511) / 512) * 512 + bufSize = pageEnd - start + buf = bytearray(bufSize) + buf[0 : contentSize] = data[start : end] + ret = restutil.HttpPut(url, buf, { + "x-ms-date" : timestamp, + "x-ms-range" : "bytes={0}-{1}".format(start, pageEnd - 1), + "x-ms-page-write" : "update", + "x-ms-version" : __StorageVersion, + "Content-Length": str(pageEnd - start) + }) + if ret is None: + Error("Failed to upload page blob for status") + return + start = end + +def UploadStatusBlob(url, data): + LogIfVerbose("Upload status blob") + LogIfVerbose("Status={0}".format(data)) + blobType = GetBlobType(url) + + if blobType == "BlockBlob": + PutBlockBlob(url, data) + elif blobType == "PageBlob": + PutPageBlob(url, data) + else: + Error("Unknown blob type: {0}".format(blobType)) + return None class TCPHandler(SocketServer.BaseRequestHandler): """ @@ -3032,36 +3122,61 @@ """ Reset members. """ - self.Deployment = None - self.Incarnation = None - self.Role = None - self.LoadBalancerSettings = None - self.OutputEndpoints = None - self.Instances = None + self.RdmaMacAddress = None + self.RdmaIPv4Address = None + self.xmlText = None def Parse(self, xmlText): """ Parse and write configuration to file SharedConfig.xml. """ self.reinitialize() - SetFileContents("SharedConfig.xml", xmlText) + self.xmlText = xmlText dom = xml.dom.minidom.parseString(xmlText) for a in [ "SharedConfig", "Deployment", "Service", "ServiceInstance", "Incarnation", "Role", ]: if not dom.getElementsByTagName(a): Error("SharedConfig.Parse: Missing " + a) - return None + node = dom.childNodes[0] if node.localName != "SharedConfig": Error("SharedConfig.Parse: root not SharedConfig") - return None + + nodes = dom.getElementsByTagName("Instance") + if nodes is not None and len(nodes) != 0: + node = nodes[0] + if node.hasAttribute("rdmaMacAddress"): + self.RdmaMacAddress = node.getAttribute("rdmaMacAddress") + if node.hasAttribute("rdmaIPv4Address"): + self.RdmaIPv4Address = node.getAttribute("rdmaIPv4Address") + return self + + def Save(self): + 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 InvokeTopologyConsumer(self): program = Config.get("Role.TopologyConsumer") if program != None: try: Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"])) except OSError, e : ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program ) - return self + + def Process(self): + self.ConfigRdma() + self.InvokeTopologyConsumer() class ExtensionsConfig(object): """ @@ -3131,11 +3246,14 @@ try: self.Extensions=dom.getElementsByTagName("Extensions") pg = dom.getElementsByTagName("Plugins") - self.Plugins = pg[0].getElementsByTagName("Plugin") + if len(pg) > 0: + self.Plugins = pg[0].getElementsByTagName("Plugin") + else: + self.Plugins = [] incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText) - except: - LogIfVerbose('ERROR: Error parsing ExtensionsConfig.') + except Exception, e: + Error('ERROR: Error parsing ExtensionsConfig: {0}.'.format(e)) return None for p in self.Plugins: if len(p.getAttribute("location"))<1: # this plugin is inside the PluginSettings @@ -3173,7 +3291,7 @@ Error('Unable to disable '+name) SimpleLog(p.plugin_log,'ERROR: Unable to disable '+name) else : - self.SetHandlerState(handler, 'Installed') + self.SetHandlerState(handler, 'Disabled') Log(name+' is disabled') SimpleLog(p.plugin_log,name+' is disabled') @@ -3608,8 +3726,8 @@ except: Error('Error parsing ExtensionsConfig. Unable to send status reports') return None - self.Util.Endpoint=uri.split('/')[2] - self.Util.HttpPutBlockBlob(uri, status) + + UploadStatusBlob(uri, status.encode("utf-8")) LogIfVerbose('Status report '+status+' sent to ' + uri) return True @@ -3996,6 +4114,8 @@ Calls HostingEnvironmentConfig.Process() """ self.HostingEnvironmentConfig.Process() + self.SharedConfig.Process() + self.SharedConfig.Save() class OvfEnv(object): """ @@ -4056,7 +4176,7 @@ Return self. """ self.reinitialize() - LogIfVerbose(xmlText) + LogIfVerbose(re.sub("<UserPassword>.*?<", "<UserPassword>*<", xmlText)) dom = xml.dom.minidom.parseString(xmlText) if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1: Error("Unable to parse OVF XML.") @@ -4411,7 +4531,7 @@ dataFormat = u'<?xml version="1.0"?><TelemetryData version="1.0"><Provider id="{0}">{1}'\ '</Provider></TelemetryData>' data = dataFormat.format(providerid,events) - self.post("/machine/?comp=telemetrydata",data) + self.post("/machine/?comp=telemetrydata", data) def CollectAndSendWALAEvents(self): if not os.path.exists(self.eventdir): @@ -4893,7 +5013,7 @@ + "</ContainerId><RoleInstanceList><Role><InstanceId>" + self.GoalState.RoleInstanceId + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>") - a = self.HttpPost("/machine?comp=health", healthReport) + a = self.HttpPostWithHeaders("/machine?comp=health", healthReport) if a != None: return a.getheader("x-ms-latest-goal-state-incarnation-number") return None @@ -4912,7 +5032,7 @@ + "</InstanceId><Health><State>NotReady</State>" + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>" + "</Health></Role></RoleInstanceList></Container></Health>") - a = self.HttpPost("/machine?comp=health", healthReport) + a = self.HttpPostWithHeaders("/machine?comp=health", healthReport) if a != None: return a.getheader("x-ms-latest-goal-state-incarnation-number") return None @@ -4927,7 +5047,8 @@ + "<Id>" + self.GoalState.RoleInstanceId + "</Id>" + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>" + "</RoleInstance></RoleInstances></Container></RoleProperties>") - a = self.HttpPost("/machine?comp=roleProperties", roleProperties) + a = self.HttpPostWithHeaders("/machine?comp=roleProperties", + roleProperties) Log("Posted Role Properties. CertificateThumbprint=" + thumbprint) return a @@ -5295,7 +5416,7 @@ goalState.ExtensionsConfig.ReportHandlerStatus() if not eventMonitor: - eventMonitor = WALAEventMonitor(self.HttpPost) + eventMonitor = WALAEventMonitor(self.HttpPostWithHeaders) eventMonitor.StartEventsLoop() time.sleep(25 - sleepToReduceAccessDenied)