You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2012/12/13 04:52:46 UTC

svn commit: r1421073 - in /libcloud/trunk: ./ libcloud/common/ libcloud/compute/drivers/ libcloud/dns/ libcloud/dns/drivers/ libcloud/test/ libcloud/test/compute/ libcloud/test/dns/ libcloud/test/dns/fixtures/hostvirtual/

Author: tomaz
Date: Thu Dec 13 03:52:44 2012
New Revision: 1421073

URL: http://svn.apache.org/viewvc?rev=1421073&view=rev
Log:
New driver for HostVirtual provider (www.vr.org).

Contributed by Dinesh Bhoopathy, part of LIBCLOUD-249.

Added:
    libcloud/trunk/libcloud/common/hostvirtual.py
    libcloud/trunk/libcloud/dns/drivers/hostvirtual.py
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_record.json
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_zone.json
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_records.json
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_zones.json
    libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/zone_does_not_exist.json
    libcloud/trunk/libcloud/test/dns/test_hostvirtual.py
Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/compute/drivers/hostvirtual.py
    libcloud/trunk/libcloud/dns/providers.py
    libcloud/trunk/libcloud/dns/types.py
    libcloud/trunk/libcloud/test/compute/test_hostvirtual.py
    libcloud/trunk/libcloud/test/secrets.py-dist

Modified: libcloud/trunk/CHANGES
URL: http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Thu Dec 13 03:52:44 2012
@@ -101,6 +101,9 @@ Changes with Apache Libcloud in developm
       (LIBCLOUD-247)
       [Tomaz Muraus]
 
+    - New driver for HostVirtual provider (www.vr.org).
+      [Dinesh Bhoopathy]
+
 Changes with Apache Libcloud 0.11.3:
 
   *) Storage

Added: libcloud/trunk/libcloud/common/hostvirtual.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/hostvirtual.py?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/common/hostvirtual.py (added)
+++ libcloud/trunk/libcloud/common/hostvirtual.py Thu Dec 13 03:52:44 2012
@@ -0,0 +1,77 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+from libcloud.utils.py3 import httplib
+from libcloud.common.base import ConnectionKey, JsonResponse
+from libcloud.common.types import LibcloudError
+
+API_HOST = 'www.vr.org'
+
+
+class HostVirtualException(LibcloudError):
+    def __init__(self, code, message):
+        self.code = code
+        self.message = message
+        self.args = (code, message)
+
+    def __str__(self):
+        return self.__repr__()
+
+    def __repr__(self):
+        return '<HostVirtualException in %d: %s>' % (self.code, self.message)
+
+
+class HostVirtualConnection(ConnectionKey):
+    host = API_HOST
+
+    def add_default_params(self, params):
+        params['key'] = self.key
+        return params
+
+
+class HostVirtualResponse(JsonResponse):
+    valid_response_codes = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
+                            httplib.NO_CONTENT]
+
+    def parse_body(self):
+        if not self.body:
+            return None
+
+        data = json.loads(self.body)
+        return data
+
+    def parse_error(self):
+        context = self.connection.context
+        data = self.parse_body()
+        status = int(self.status)
+
+        if self.status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError(
+                data['error']['code'] + ': ' + data['error']['message'])
+        elif self.status == httplib.PRECONDITION_FAILED:
+            raise HostVirtualException(
+                data['error']['code'], data['error']['message'])
+        elif self.status == httplib.NOT_FOUND:
+            raise HostVirtualException(
+                data['error']['code'], data['error']['message'])
+
+        return self.body
+
+    def success(self):
+        return self.status in self.valid_response_codes

Modified: libcloud/trunk/libcloud/compute/drivers/hostvirtual.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/hostvirtual.py?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/hostvirtual.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/hostvirtual.py Thu Dec 13 03:52:44 2012
@@ -3,9 +3,8 @@
 # this work for additional information regarding copyright ownership.
 # The ASF licenses this file to You 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
+# 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,
@@ -26,15 +25,15 @@ except ImportError:
 
 from libcloud.utils.py3 import httplib
 
-from libcloud.common.types import LibcloudError
+from libcloud.common.hostvirtual import HostVirtualResponse
+from libcloud.common.hostvirtual import HostVirtualConnection
+from libcloud.common.hostvirtual import HostVirtualException
 from libcloud.compute.providers import Provider
-from libcloud.common.base import ConnectionKey, JsonResponse
 from libcloud.compute.types import NodeState, InvalidCredsError
 from libcloud.compute.base import Node, NodeDriver
 from libcloud.compute.base import NodeImage, NodeSize, NodeLocation
 from libcloud.compute.base import NodeAuthSSHKey, NodeAuthPassword
 
-API_HOST = 'www.vr.org'
 API_ROOT = '/vapi'
 
 #API_VERSION = '0.1'
@@ -49,64 +48,19 @@ NODE_STATE_MAP = {
 }
 
 
-class HostVirtualException(LibcloudError):
-    def __init__(self, code, message):
-        self.code = code
-        self.message = message
-        self.args = (code, message)
-
-    def __str__(self):
-        return self.__repr__()
-
-    def __repr__(self):
-        return "<HostVirtualException in %d : %s>" % (self.code, self.message)
-
-
-class HostVirtualResponse(JsonResponse):
-    valid_response_codes = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
-                            httplib.NO_CONTENT]
-
-    def parse_body(self):
-        if not self.body:
-            return None
-
-        data = json.loads(self.body)
-        return data
-
-    def parse_error(self):
-        if self.status == 401:
-            data = self.parse_body()
-            raise InvalidCredsError(
-                data['error']['code'] + ': ' + data['error']['message'])
-        elif self.status == 412:
-            data = self.parse_body()
-            raise HostVirtualException(
-                data['error']['code'], data['error']['message'])
-        elif self.status == 404:
-            data = self.parse_body()
-            raise HostVirtualException(
-                data['error']['code'], data['error']['message'])
-        else:
-            return self.body
-
-    def success(self):
-        return self.status in self.valid_response_codes
-
+class HostVirtualComputeResponse(HostVirtualResponse):
+    pass
 
-class HostVirtualConnection(ConnectionKey):
-    host = API_HOST
-    responseCls = HostVirtualResponse
 
-    def add_default_params(self, params):
-        params["key"] = self.key
-        return params
+class HostVirtualComputeConnection(HostVirtualConnection):
+    responseCls = HostVirtualComputeResponse
 
 
 class HostVirtualNodeDriver(NodeDriver):
     type = Provider.HOSTVIRTUAL
     name = 'HostVirtual'
     website = 'http://www.vr.org'
-    connectionCls = HostVirtualConnection
+    connectionCls = HostVirtualComputeConnection
 
     def __init__(self, key):
         self.location = None

Added: libcloud/trunk/libcloud/dns/drivers/hostvirtual.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/drivers/hostvirtual.py?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/dns/drivers/hostvirtual.py (added)
+++ libcloud/trunk/libcloud/dns/drivers/hostvirtual.py Thu Dec 13 03:52:44 2012
@@ -0,0 +1,241 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+__all__ = [
+    'HostVirtualDNSDriver'
+]
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.misc import merge_valid_keys, get_new_obj
+from libcloud.common.hostvirtual import HostVirtualResponse
+from libcloud.common.hostvirtual import HostVirtualConnection
+from libcloud.compute.drivers.hostvirtual import API_ROOT
+from libcloud.compute.drivers.hostvirtual import HostVirtualConnection
+from libcloud.compute.drivers.hostvirtual import HostVirtualResponse
+from libcloud.dns.types import Provider, RecordType
+from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError
+from libcloud.dns.base import DNSDriver, Zone, Record
+
+try:
+    import simplejson as json
+except:
+    import json
+
+VALID_RECORD_EXTRA_PARAMS = ['prio', 'ttl']
+
+
+class HostVirtualDNSResponse(HostVirtualResponse):
+    def parse_error(self):
+        context = self.connection.context
+        status = int(self.status)
+
+        if status == httplib.NOT_FOUND:
+            if context['resource'] == 'zone':
+                raise ZoneDoesNotExistError(value='', driver=self,
+                                            zone_id=context['id'])
+            elif context['resource'] == 'record':
+                raise RecordDoesNotExistError(value='', driver=self,
+                                              record_id=context['id'])
+
+        super(HostVirtualDNSResponse, self).parse_error()
+        return self.body
+
+
+class HostVirtualDNSConnection(HostVirtualConnection):
+    responseCls = HostVirtualDNSResponse
+
+
+class HostVirtualDNSDriver(DNSDriver):
+    type = Provider.HOSTVIRTUAL
+    name = 'Host Virtual DNS'
+    website = 'http://www.vr.org/'
+    connectionCls = HostVirtualDNSConnection
+
+    RECORD_TYPE_MAP = {
+        RecordType.A: 'A',
+        RecordType.AAAA: 'AAAA',
+        RecordType.CNAME: 'CNAME',
+        RecordType.MX: 'MX',
+        RecordType.TXT: 'TXT',
+        RecordType.NS: 'SPF',
+        RecordType.SRV: 'SRV',
+    }
+
+    def _to_zones(self, items):
+        zones = []
+        for item in items:
+            zones.append(self._to_zone(item))
+        return zones
+
+    def _to_zone(self, item):
+        extra = {}
+        if 'records' in item:
+            extra['records'] = item['records']
+        if item['type'] == 'NATIVE':
+            item['type'] = 'master'
+        zone = Zone(id=item['id'], domain=item['name'],
+                    type=item['type'], ttl=item['ttl'],
+                    driver=self, extra=extra)
+        return zone
+
+    def _to_records(self, items, zone=None):
+        records = []
+
+        for item in items:
+            records.append(self._to_record(item=item, zone=zone))
+        return records
+
+    def _to_record(self, item, zone=None):
+        extra = {'ttl': item['ttl']}
+        type = self._string_to_record_type(item['type'])
+        record = Record(id=item['id'], name=item['name'],
+                        type=type, data=item['content'],
+                        zone=zone, driver=self, extra=extra)
+        return record
+
+    def list_zones(self):
+        result = self.connection.request(
+            API_ROOT + '/dns/zones/').object
+        zones = self._to_zones(result)
+        return zones
+
+    def list_records(self, zone):
+        params = {'zone_id': zone.id}
+        self.connection.set_context({'resource': 'zone', 'id': zone.id})
+        result = self.connection.request(
+            API_ROOT + '/dns/records/', data=json.dumps(params)).object
+        records = self._to_records(items=result, zone=zone)
+        return records
+
+    def get_zone(self, zone_id):
+        params = {'id': zone_id}
+        self.connection.set_context({'resource': 'zone', 'id': zone_id})
+        result = self.connection.request(
+            API_ROOT + '/dns/zone/', params=params).object
+        if 'id' not in result:
+            raise ZoneDoesNotExistError(value='', driver=self, zone_id=zone_id)
+        zone = self._to_zone(result)
+        return zone
+
+    def get_record(self, zone_id, record_id):
+        zone = self.get_zone(zone_id=zone_id)
+        params = {'id': record_id}
+        self.connection.set_context({'resource': 'record', 'id': record_id})
+        result = self.connection.request(
+            API_ROOT + '/dns/record/', params=params).object
+        if 'id' not in result:
+            raise RecordDoesNotExistError(value='',
+                                          driver=self, record_id=record_id)
+        record = self._to_record(item=result, zone=zone)
+        return record
+
+    def delete_zone(self, zone):
+        params = {'zone_id': zone.id}
+        self.connection.set_context({'resource': 'zone', 'id': zone.id})
+        result = self.connection.request(
+            API_ROOT + '/dns/zone/', params=params, method='DELETE').object
+        return bool(result)
+
+    def delete_record(self, record):
+        params = {'id': record.id}
+        self.connection.set_context({'resource': 'record', 'id': record.id})
+        result = self.connection.request(
+            API_ROOT + '/dns/record/', params=params, method='DELETE').object
+
+        return bool(result)
+
+    def create_zone(self, domain, type='NATIVE', ttl=None, extra=None):
+        if type == 'master':
+            type = 'NATIVE'
+        elif type == 'slave':
+            type = 'SLAVE'
+        params = {'name': domain, 'type': type, 'ttl': ttl}
+        result = self.connection.request(
+            API_ROOT + '/dns/zone/',
+            data=json.dumps(params), method='POST').object
+        extra = {
+            'soa': result['soa'],
+            'ns': result['ns']
+        }
+        zone = Zone(id=result['id'], domain=domain,
+                    type=type, ttl=ttl, extra=extra, driver=self)
+        return zone
+
+    def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None):
+        params = {'id': zone.id}
+        if domain:
+            params['name'] = domain
+        if type:
+            params['type'] = type
+        self.connection.set_context({'resource': 'zone', 'id': zone.id})
+        self.connection.request(API_ROOT + '/dns/zone/',
+                                data=json.dumps(params), method='PUT').object
+        updated_zone = get_new_obj(
+            obj=zone, klass=Zone,
+            attributes={
+                'domain': domain,
+                'type': type,
+                'ttl': ttl,
+                'extra': extra
+            })
+        return updated_zone
+
+    def create_record(self, name, zone, type, data, extra=None):
+        params = {
+            'name': name,
+            'type': self.RECORD_TYPE_MAP[type],
+            'domain_id': zone.id,
+            'content': data
+        }
+        merged = merge_valid_keys(
+            params=params,
+            valid_keys=VALID_RECORD_EXTRA_PARAMS,
+            extra=extra
+        )
+        self.connection.set_context({'resource': 'zone', 'id': zone.id})
+        result = self.connection.request(
+            API_ROOT + '/dns/record/',
+            data=json.dumps(params), method='POST').object
+        record = Record(id=result['id'], name=name,
+                        type=type, data=data,
+                        extra=merged, zone=zone, driver=self)
+        return record
+
+    def update_record(self, record, name=None, type=None,
+                      data=None, extra=None):
+        params = {
+            'domain_id': record.zone.id,
+            'record_id': record.id
+        }
+        if name:
+            params['name'] = name
+        if data:
+            params['content'] = data
+        if type is not None:
+            params['type'] = self.RECORD_TYPE_MAP[type]
+            merged = merge_valid_keys(
+                params=params,
+                valid_keys=VALID_RECORD_EXTRA_PARAMS,
+                extra=extra
+            )
+        self.connection.set_context({'resource': 'record', 'id': record.id})
+        self.connection.request(API_ROOT + '/dns/record/',
+                                data=json.dumps(params), method='PUT').object
+        updated_record = get_new_obj(
+            obj=record, klass=Record, attributes={
+                'name': name, 'data': data,
+                'type': type,
+                'extra': merged
+            })
+        return updated_record

Modified: libcloud/trunk/libcloud/dns/providers.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/providers.py?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/providers.py (original)
+++ libcloud/trunk/libcloud/dns/providers.py Thu Dec 13 03:52:44 2012
@@ -27,7 +27,9 @@ DRIVERS = {
     Provider.RACKSPACE_US:
         ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'),
     Provider.RACKSPACE_UK:
-        ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver')
+        ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver'),
+    Provider.HOSTVIRTUAL:
+        ('libcloud.dns.drivers.hostvirtual', 'HostVirtualDNSDriver'),
 }
 
 

Modified: libcloud/trunk/libcloud/dns/types.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/types.py?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/types.py (original)
+++ libcloud/trunk/libcloud/dns/types.py Thu Dec 13 03:52:44 2012
@@ -34,6 +34,7 @@ class Provider(object):
     RACKSPACE_US = 'rackspace_us'
     RACKSPACE_UK = 'rackspace_uk'
     ROUTE53 = 'route53'
+    HOSTVIRTUAL = 'hostvirtual'
 
 
 class RecordType(object):

Modified: libcloud/trunk/libcloud/test/compute/test_hostvirtual.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/compute/test_hostvirtual.py?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/compute/test_hostvirtual.py (original)
+++ libcloud/trunk/libcloud/test/compute/test_hostvirtual.py Thu Dec 13 03:52:44 2012
@@ -15,11 +15,6 @@
 import sys
 import unittest
 
-try:
-    import simplejson as json
-except ImportError:
-    import json
-
 from libcloud.utils.py3 import httplib
 
 from libcloud.compute.drivers.hostvirtual import HostVirtualNodeDriver
@@ -79,7 +74,7 @@ class HostVirtualTest(unittest.TestCase)
         node = self.driver.list_nodes()[0]
         self.assertTrue(self.driver.ex_stop_node(node))
 
-    def test_start_node_response(self):
+    def test_start_node(self):
         node = self.driver.list_nodes()[0]
         self.assertTrue(self.driver.ex_start_node(node))
 

Added: libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_record.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_record.json?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_record.json (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_record.json Thu Dec 13 03:52:44 2012
@@ -0,0 +1,8 @@
+{
+	"id": "300377",
+	"name": "*.t.com",
+	"type": "CNAME",
+	"content": "t.com",
+	"ttl": "86400",
+	"prio": null
+}

Added: libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_zone.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_zone.json?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_zone.json (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/get_zone.json Thu Dec 13 03:52:44 2012
@@ -0,0 +1,47 @@
+{
+  "id": "47234",
+  "name": "t.com",
+  "type": "NATIVE",
+  "ttl": "3600",
+  "soa": {
+    "primary": "ns1.hostvirtual.com",
+    "hostmaster": "support@HOSTVIRTUAL.COM",
+    "serial": "2012100901",
+    "refresh": "10800",
+    "retry": "3600",
+    "expire": "604800",
+    "default_ttl": "3600"
+  },
+  "ns": [
+    "ns4.hostvirtual.com",
+    "ns3.hostvirtual.com",
+    "ns2.hostvirtual.com",
+    "ns1.hostvirtual.com"
+  ],
+  "records": [
+    {
+      "id": "300377",
+      "name": "*.t.com",
+      "type": "CNAME",
+      "content": "t.com",
+      "ttl": "86400",
+      "prio": null
+    },
+    {
+      "id": "300719",
+      "name": "blah.com.",
+      "type": "A",
+      "content": "0.0.0.0",
+      "ttl": null,
+      "prio": null
+    },
+    {
+      "id": "300728",
+      "name": "blahblah.com.t.com",
+      "type": "A",
+      "content": "1.1.1.1",
+      "ttl": null,
+      "prio": "10"
+    }
+  ]
+}

Added: libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_records.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_records.json?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_records.json (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_records.json Thu Dec 13 03:52:44 2012
@@ -0,0 +1,26 @@
+[
+  {
+    "id": "300377",
+    "name": "*.t.com",
+    "type": "CNAME",
+    "content": "t.com",
+    "ttl": "86400",
+    "prio": null
+  },
+  {
+    "id": "300719",
+    "name": "www.t.com",
+    "type": "A",
+    "content": "208.111.35.173",
+    "ttl": null,
+    "prio": null
+  },
+  {
+    "id": "300728",
+    "name": "blahblah.t.com",
+    "type": "A",
+    "content": "208.111.35.173",
+    "ttl": null,
+    "prio": "10"
+  }
+]

Added: libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_zones.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_zones.json?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_zones.json (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/list_zones.json Thu Dec 13 03:52:44 2012
@@ -0,0 +1,32 @@
+[
+  {
+    "id": "47234",
+    "name": "t.com",
+    "type": "NATIVE",
+    "ttl": "3600"
+  },
+  {
+    "id": "48170",
+    "name": "newbug.net",
+    "type": "NATIVE",
+    "ttl": "3600"
+  },
+  {
+    "id": "48017",
+    "name": "newblah.com",
+    "type": "NATIVE",
+    "ttl": "3600"
+  },
+  {
+    "id": "47288",
+    "name": "fromapi.com",
+    "type": "NATIVE",
+    "ttl": "3600"
+  },
+  {
+    "id": "48008",
+    "name": "blahnew.com",
+    "type": "NATIVE",
+    "ttl": "3600"
+  }
+]

Added: libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/zone_does_not_exist.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/zone_does_not_exist.json?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/zone_does_not_exist.json (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/hostvirtual/zone_does_not_exist.json Thu Dec 13 03:52:44 2012
@@ -0,0 +1,6 @@
+{
+  "error": {
+    "code": 404,
+    "message": "Not Found: id, validate_dns_zone_owner"
+  }
+}

Added: libcloud/trunk/libcloud/test/dns/test_hostvirtual.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/test_hostvirtual.py?rev=1421073&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/test_hostvirtual.py (added)
+++ libcloud/trunk/libcloud/test/dns/test_hostvirtual.py Thu Dec 13 03:52:44 2012
@@ -0,0 +1,258 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 sys
+import unittest
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.dns.types import RecordType, ZoneDoesNotExistError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.drivers.hostvirtual import HostVirtualDNSDriver
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import DNSFileFixtures
+from libcloud.test.secrets import DNS_PARAMS_HOSTVIRTUAL
+
+
+class HostVirtualTests(unittest.TestCase):
+    def setUp(self):
+        HostVirtualDNSDriver.connectionCls.conn_classes = (
+            None, HostVirtualMockHttp)
+        HostVirtualMockHttp.type = None
+        self.driver = HostVirtualDNSDriver(*DNS_PARAMS_HOSTVIRTUAL)
+
+    def test_list_record_types(self):
+        record_types = self.driver.list_record_types()
+        self.assertEqual(len(record_types), 7)
+        self.assertTrue(RecordType.A in record_types)
+
+    def test_list_zones(self):
+        zones = self.driver.list_zones()
+        self.assertEqual(len(zones), 5)
+
+        zone = zones[0]
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+        self.assertEqual(zone.ttl, '3600')
+
+    def test_list_records(self):
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 3)
+
+        record = records[1]
+        self.assertEqual(record.name, 'www.t.com')
+        self.assertEqual(record.id, '300719')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '208.111.35.173')
+
+    def test_get_zone(self):
+        zone = self.driver.get_zone(zone_id='47234')
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+        self.assertEqual(zone.ttl, '3600')
+
+    def test_get_record(self):
+        record = self.driver.get_record(zone_id='47234', record_id='300377')
+        self.assertEqual(record.id, '300377')
+        self.assertEqual(record.name, '*.t.com')
+        self.assertEqual(record.type, RecordType.CNAME)
+        self.assertEqual(record.data, 't.com')
+
+    def test_list_records_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        HostVirtualMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.list_records(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_zone_does_not_exist(self):
+        HostVirtualMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_zone(zone_id='4444')
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, '4444')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_zone_does_not_exist(self):
+        HostVirtualMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_record(zone_id='4444', record_id='28536')
+        except ZoneDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_record_does_not_exist(self):
+        HostVirtualMockHttp.type = 'RECORD_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_record(zone_id='47234', record_id='4444')
+        except RecordDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_create_zone(self):
+        zone = self.driver.create_zone(domain='t.com', type='master',
+                                       ttl=None, extra=None)
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_update_zone(self):
+        zone = self.driver.list_zones()[0]
+        updated_zone = self.driver.update_zone(zone=zone, domain='tt.com')
+
+        self.assertEqual(updated_zone.id, zone.id)
+        self.assertEqual(updated_zone.domain, 'tt.com')
+        self.assertEqual(updated_zone.type, zone.type)
+        self.assertEqual(updated_zone.ttl, '3600')
+
+    def test_create_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.create_record(
+            name='www', zone=zone,
+            type=RecordType.A, data='127.0.0.1'
+        )
+
+        self.assertEqual(record.id, '300377')
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.zone, zone)
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '127.0.0.1')
+
+    def test_update_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[1]
+        updated_record = self.driver.update_record(record=record, name='www',
+                                                   type=RecordType.AAAA,
+                                                   data='::1')
+        self.assertEqual(record.data, '208.111.35.173')
+
+        self.assertEqual(updated_record.id, record.id)
+        self.assertEqual(updated_record.name, 'www')
+        self.assertEqual(updated_record.zone, record.zone)
+        self.assertEqual(updated_record.type, RecordType.AAAA)
+        self.assertEqual(updated_record.data, '::1')
+
+    def test_delete_zone(self):
+        zone = self.driver.list_zones()[0]
+        status = self.driver.delete_zone(zone=zone)
+        self.assertTrue(status)
+
+    def test_delete_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        HostVirtualMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.delete_zone(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_delete_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        status = self.driver.delete_record(record=record)
+        self.assertTrue(status)
+
+    def test_delete_record_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        HostVirtualMockHttp.type = 'RECORD_DOES_NOT_EXIST'
+        try:
+            self.driver.delete_record(record=record)
+        except RecordDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.record_id, record.id)
+        else:
+            self.fail('Exception was not thrown')
+
+
+class HostVirtualMockHttp(MockHttp):
+    fixtures = DNSFileFixtures('hostvirtual')
+
+    def _vapi_dns_zone(self, method, url, body, headers):
+        body = self.fixtures.load('get_zone.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _vapi_dns_zones(self, method, url, body, headers):
+        body = self.fixtures.load('list_zones.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _vapi_dns_record(self, method, url, body, headers):
+        body = self.fixtures.load('get_record.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _vapi_dns_records(self, method, url, body, headers):
+        body = self.fixtures.load('list_records.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _vapi_dns_zone_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _vapi_dns_zone_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('get_zone.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _vapi_dns_zones_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _vapi_dns_record_ZONE_DOES_NOT_EXIST(self, method,
+                                             url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _vapi_dns_record_RECORD_DOES_NOT_EXIST(self, method,
+                                               url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _vapi_dns_records_ZONE_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _vapi_dns_zones_RECORD_DOES_NOT_EXIST(self, method,
+                                              url, body, headers):
+        body = self.fixtures.load('zone_does_not_exist.json')
+        return (httplib.NOT_FOUND, body,
+                {}, httplib.responses[httplib.NOT_FOUND])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

Modified: libcloud/trunk/libcloud/test/secrets.py-dist
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/secrets.py-dist?rev=1421073&r1=1421072&r2=1421073&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/secrets.py-dist (original)
+++ libcloud/trunk/libcloud/test/secrets.py-dist Thu Dec 13 03:52:44 2012
@@ -51,3 +51,4 @@ LB_BRIGHTBOX_PARAMS = ('user', 'key')
 DNS_PARAMS_LINODE = ('user', 'key')
 DNS_PARAMS_ZERIGO = ('email', 'api token')
 DNS_PARAMS_RACKSPACE = ('user', 'key')
+DNS_PARAMS_HOSTVIRTUAL = ('key',)