Return-Path: X-Original-To: apmail-libcloud-commits-archive@www.apache.org Delivered-To: apmail-libcloud-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id BF2BCD2E5 for ; Thu, 23 May 2013 23:25:11 +0000 (UTC) Received: (qmail 90116 invoked by uid 500); 23 May 2013 23:25:11 -0000 Delivered-To: apmail-libcloud-commits-archive@libcloud.apache.org Received: (qmail 90091 invoked by uid 500); 23 May 2013 23:25:11 -0000 Mailing-List: contact commits-help@libcloud.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@libcloud.apache.org Delivered-To: mailing list commits@libcloud.apache.org Received: (qmail 90080 invoked by uid 99); 23 May 2013 23:25:11 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 May 2013 23:25:11 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 May 2013 23:25:08 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 77B272388962; Thu, 23 May 2013 23:24:46 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1485898 - in /libcloud/branches/0.12.x: ./ CHANGES libcloud/compute/drivers/cloudstack.py libcloud/test/compute/test_cloudstack.py libcloud/utils/iso8601.py Date: Thu, 23 May 2013 23:24:46 -0000 To: commits@libcloud.apache.org From: tomaz@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130523232446.77B272388962@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: tomaz Date: Thu May 23 23:24:45 2013 New Revision: 1485898 URL: http://svn.apache.org/r1485898 Log: Backport commits from trunk. Added: libcloud/branches/0.12.x/libcloud/utils/iso8601.py Modified: libcloud/branches/0.12.x/ (props changed) libcloud/branches/0.12.x/CHANGES libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py Propchange: libcloud/branches/0.12.x/ ------------------------------------------------------------------------------ Merged /libcloud/trunk:r1485843-1485894 Modified: libcloud/branches/0.12.x/CHANGES URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/CHANGES?rev=1485898&r1=1485897&r2=1485898&view=diff ============================================================================== --- libcloud/branches/0.12.x/CHANGES (original) +++ libcloud/branches/0.12.x/CHANGES Thu May 23 23:24:45 2013 @@ -50,6 +50,10 @@ Changes with Apache Libcloud in deveplom return value. (LIBCLOUD-326) [Andre Merzky, Tomaz Muraus] + - Populate private_ips attribute in the CloudStack drive when returning + a Node object from the create_node method. (LIBCLOUD-329) + [Sebastien Goasguen, Tomaz Muraus] + *) Storage - Fix an issue with double encoding the container name in the CloudFiles Modified: libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py?rev=1485898&r1=1485897&r2=1485898&view=diff ============================================================================== --- libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py (original) +++ libcloud/branches/0.12.x/libcloud/compute/drivers/cloudstack.py Thu May 23 23:24:45 2013 @@ -170,35 +170,40 @@ class CloudStackNodeDriver(CloudStackDri vms = self._sync_request('listVirtualMachines') addrs = self._sync_request('listPublicIpAddresses') - public_ips = {} + public_ips_map = {} for addr in addrs.get('publicipaddress', []): if 'virtualmachineid' not in addr: continue vm_id = addr['virtualmachineid'] - if vm_id not in public_ips: - public_ips[vm_id] = {} - public_ips[vm_id][addr['ipaddress']] = addr['id'] + if vm_id not in public_ips_map: + public_ips_map[vm_id] = {} + public_ips_map[vm_id][addr['ipaddress']] = addr['id'] nodes = [] for vm in vms.get('virtualmachine', []): + state = self.NODE_STATE_MAP[vm['state']] + + public_ips = [] private_ips = [] for nic in vm['nic']: if 'ipaddress' in nic: private_ips.append(nic['ipaddress']) + public_ips = public_ips_map.get(vm['id'], {}).keys() + node = CloudStackNode( id=vm['id'], name=vm.get('displayname', None), - state=self.NODE_STATE_MAP[vm['state']], - public_ips=public_ips.get(vm['id'], {}).keys(), + state=state, + public_ips=public_ips, private_ips=private_ips, driver=self, extra={'zoneid': vm['zoneid'], } ) - addrs = public_ips.get(vm['id'], {}).items() + addrs = public_ips_map.get(vm['id'], {}).items() addrs = [CloudStackAddress(node, v, k) for k, v in addrs] node.extra['ip_addresses'] = addrs @@ -244,13 +249,17 @@ class CloudStackNodeDriver(CloudStackDri ) node = result['virtualmachine'] + state = self.NODE_STATE_MAP[node['state']] + + public_ips = [] + private_ips = [nic['ipaddress'] for nic in node['nic']] return Node( id=node['id'], name=node['displayname'], - state=self.NODE_STATE_MAP[node['state']], - public_ips=[], - private_ips=[], + state=state, + public_ips=public_ips, + private_ips=private_ips, driver=self, extra={ 'zoneid': location.id, Modified: libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py?rev=1485898&r1=1485897&r2=1485898&view=diff ============================================================================== --- libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py (original) +++ libcloud/branches/0.12.x/libcloud/test/compute/test_cloudstack.py Thu May 23 23:24:45 2013 @@ -58,6 +58,20 @@ class CloudStackNodeDriverTest(unittest. return self.assertTrue(False) + def test_create_node_default_location_success(self): + size = self.driver.list_sizes()[0] + image = self.driver.list_images()[0] + default_location = self.driver.list_locations()[0] + + node = self.driver.create_node(name='fred', + image=image, + size=size) + + self.assertEqual(node.name, 'fred') + self.assertEqual(node.public_ips, []) + self.assertEqual(node.private_ips, ['1.1.1.2']) + self.assertEqual(node.extra['zoneid'], default_location.id) + def test_list_images_no_images_available(self): CloudStackMockHttp.fixture_tag = 'notemplates' Added: libcloud/branches/0.12.x/libcloud/utils/iso8601.py URL: http://svn.apache.org/viewvc/libcloud/branches/0.12.x/libcloud/utils/iso8601.py?rev=1485898&view=auto ============================================================================== --- libcloud/branches/0.12.x/libcloud/utils/iso8601.py (added) +++ libcloud/branches/0.12.x/libcloud/utils/iso8601.py Thu May 23 23:24:45 2013 @@ -0,0 +1,122 @@ +""" +Copyright (c) 2007 Michael Twomey + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +ISO 8601 date time string parsing + +Basic usage: +>>> import iso8601 +>>> iso8601.parse_date("2007-01-25T12:00:00Z") +datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) +>>> +""" + +# Taken from pyiso8601 which is licensed under the MIT license. + +from datetime import datetime, timedelta, tzinfo +import re + +__all__ = ["parse_date", "ParseError"] + +# Adapted from http://delete.me.uk/2005/03/iso8601.html +ISO8601_REGEX = re.compile(r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" + r"((?P.)(?P[0-9]{2}):(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?" + r"(?PZ|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" +) +TIMEZONE_REGEX = re.compile("(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") + +class ParseError(Exception): + """Raised when there is a problem parsing a date string""" + +# Yoinked from python docs +ZERO = timedelta(0) +class Utc(tzinfo): + """UTC + + """ + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO +UTC = Utc() + +class FixedOffset(tzinfo): + """Fixed offset in hours and minutes from UTC + + """ + def __init__(self, offset_hours, offset_minutes, name): + self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + def __repr__(self): + return "" % self.__name + +def parse_timezone(tzstring, default_timezone=UTC): + """Parses ISO 8601 time zone specs into tzinfo offsets + + """ + if tzstring == "Z": + return default_timezone + # This isn't strictly correct, but it's common to encounter dates without + # timezones so I'll assume the default (which defaults to UTC). + # Addresses issue 4. + if tzstring is None: + return default_timezone + m = TIMEZONE_REGEX.match(tzstring) + prefix, hours, minutes = m.groups() + hours, minutes = int(hours), int(minutes) + if prefix == "-": + hours = -hours + minutes = -minutes + return FixedOffset(hours, minutes, tzstring) + +def parse_date(datestring, default_timezone=UTC): + """Parses ISO 8601 dates into datetime objects + + The timezone is parsed from the date string. However it is quite common to + have dates without a timezone (not strictly correct). In this case the + default timezone specified in default_timezone is used. This is UTC by + default. + """ + m = ISO8601_REGEX.match(datestring) + if not m: + raise ParseError("Unable to parse date string %r" % datestring) + groups = m.groupdict() + tz = parse_timezone(groups["timezone"], default_timezone=default_timezone) + if groups["fraction"] is None: + groups["fraction"] = 0 + else: + groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6) + return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]), + int(groups["hour"]), int(groups["minute"]), int(groups["second"]), + int(groups["fraction"]), tz)