blob: 4993a755afe996b9e0027fdbc8b05f454820c8e5 [file] [log] [blame]
# 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 json
import sys
import unittest
from libcloud.utils.py3 import httplib
from libcloud.common.gandi_live import (
GandiLiveBaseError,
JsonParseError,
InvalidRequestError,
)
from libcloud.dns.types import (
RecordType,
RecordError,
ZoneDoesNotExistError,
RecordDoesNotExistError,
ZoneAlreadyExistsError,
RecordAlreadyExistsError,
)
from libcloud.dns.drivers.gandi_live import GandiLiveDNSDriver
from libcloud.dns.base import Zone, Record
from libcloud.test.file_fixtures import DNSFileFixtures
from libcloud.test.secrets import DNS_GANDI_LIVE
from libcloud.test.common.test_gandi_live import BaseGandiLiveMockHttp
class GandiLiveTests(unittest.TestCase):
def setUp(self):
GandiLiveDNSDriver.connectionCls.conn_class = GandiLiveMockHttp
GandiLiveMockHttp.type = None
self.driver = GandiLiveDNSDriver(*DNS_GANDI_LIVE)
self.test_zone = Zone(
id="example.com",
type="master",
ttl=None,
domain="example.com",
extra={"zone_uuid": "a53re"},
driver=self,
)
self.test_bad_zone = Zone(
id="badexample.com",
type="master",
ttl=None,
domain="badexample.com",
extra={"zone_uuid": "a53rf"},
driver=self,
)
self.test_record = Record(
id="A:bob",
type=RecordType.A,
name="bob",
zone=self.test_zone,
data="127.0.0.1",
driver=self,
extra={},
)
self.test_bad_record = Record(
id="A:jane",
type=RecordType.A,
name="jane",
zone=self.test_bad_zone,
data="127.0.0.1",
driver=self,
extra={},
)
def test_list_zones(self):
zones = self.driver.list_zones()
self.assertEqual(len(zones), 2)
zone = zones[0]
self.assertEqual(zone.id, "example.com")
self.assertEqual(zone.type, "master")
self.assertEqual(zone.domain, "example.com")
self.assertIsNone(zone.ttl)
zone = zones[1]
self.assertEqual(zone.id, "example.net")
self.assertEqual(zone.type, "master")
self.assertEqual(zone.domain, "example.net")
self.assertIsNone(zone.ttl)
def test_create_zone(self):
zone = self.driver.create_zone("example.org", extra={"name": "Example"})
self.assertEqual(zone.id, "example.org")
self.assertEqual(zone.domain, "example.org")
self.assertEqual(zone.extra["zone_uuid"], "54321")
def test_create_zone_without_name(self):
zone = self.driver.create_zone("example.org")
self.assertEqual(zone.id, "example.org")
self.assertEqual(zone.domain, "example.org")
self.assertEqual(zone.extra["zone_uuid"], "54321")
def test_get_zone(self):
zone = self.driver.get_zone("example.com")
self.assertEqual(zone.id, "example.com")
self.assertEqual(zone.type, "master")
self.assertEqual(zone.domain, "example.com")
self.assertIsNone(zone.ttl)
def test_list_records(self):
records = self.driver.list_records(self.test_zone)
self.assertEqual(len(records), 3)
record = records[0]
self.assertEqual(record.id, "A:@")
self.assertEqual(record.name, "@")
self.assertEqual(record.type, RecordType.A)
self.assertEqual(record.data, "127.0.0.1")
record = records[1]
self.assertEqual(record.id, "CNAME:www")
self.assertEqual(record.name, "www")
self.assertEqual(record.type, RecordType.CNAME)
self.assertEqual(record.data, "bob.example.com.")
record = records[2]
self.assertEqual(record.id, "A:bob")
self.assertEqual(record.name, "bob")
self.assertEqual(record.type, RecordType.A)
self.assertEqual(record.data, "127.0.1.1")
def test_get_record(self):
record = self.driver.get_record(self.test_zone.id, "A:bob")
self.assertEqual(record.id, "A:bob")
self.assertEqual(record.name, "bob")
self.assertEqual(record.type, RecordType.A)
self.assertEqual(record.data, "127.0.1.1")
def test_create_record(self):
record = self.driver.create_record(
"alice", self.test_zone, "AAAA", "::1", extra={"ttl": 400}
)
self.assertEqual(record.id, "AAAA:alice")
self.assertEqual(record.name, "alice")
self.assertEqual(record.type, RecordType.AAAA)
self.assertEqual(record.data, "::1")
def test_create_record_doesnt_throw_if_ttl_is_not_provided(self):
record = self.driver.create_record("alice", self.test_zone, "AAAA", "::1")
self.assertEqual(record.id, "AAAA:alice")
self.assertEqual(record.name, "alice")
self.assertEqual(record.type, RecordType.AAAA)
self.assertEqual(record.data, "::1")
def test_bad_record_validation(self):
with self.assertRaises(RecordError) as ctx:
self.driver.create_record(
"alice", self.test_zone, "AAAA", "1" * 1025, extra={"ttl": 400}
)
self.assertTrue("Record data must be" in str(ctx.exception))
with self.assertRaises(RecordError) as ctx:
self.driver.create_record(
"alice", self.test_zone, "AAAA", "::1", extra={"ttl": 10}
)
self.assertTrue("TTL must be at least" in str(ctx.exception))
with self.assertRaises(RecordError) as ctx:
self.driver.create_record(
"alice", self.test_zone, "AAAA", "::1", extra={"ttl": 31 * 24 * 60 * 60}
)
self.assertTrue("TTL must not exceed" in str(ctx.exception))
def test_update_record(self):
record = self.driver.update_record(
self.test_record, "bob", RecordType.A, "192.168.0.2", {"ttl": 500}
)
self.assertEqual(record.id, "A:bob")
self.assertEqual(record.name, "bob")
self.assertEqual(record.type, RecordType.A)
self.assertEqual(record.data, "192.168.0.2")
def test_delete_record(self):
success = self.driver.delete_record(self.test_record)
self.assertTrue(success)
def test_export_bind(self):
bind_export = self.driver.export_zone_to_bind_format(self.test_zone)
bind_lines = bind_export.decode("utf8").split("\n")
self.assertEqual(bind_lines[0], "@ 10800 IN A 127.0.0.1")
def test_bad_json_response(self):
with self.assertRaises(JsonParseError):
self.driver.get_zone("badexample.com")
def test_no_record_found(self):
with self.assertRaises(RecordDoesNotExistError):
self.driver.get_record(self.test_zone.id, "A:none")
def test_record_already_exists(self):
with self.assertRaises(RecordAlreadyExistsError):
self.driver.create_record(
"bob", self.test_bad_zone, "A", "127.0.0.1", extra={"ttl": 400}
)
def test_no_zone_found(self):
with self.assertRaises(ZoneDoesNotExistError):
self.driver.get_zone("nosuchzone.com")
def test_zone_already_exists(self):
with self.assertRaises(ZoneAlreadyExistsError):
self.driver.create_zone("badexample.com")
# This is somewhat spurious - the driver should be preventing any invalid
# request error from arising.
def test_suberrors(self):
with self.assertRaises(InvalidRequestError) as ctx:
self.driver.update_record(
self.test_bad_record, "jane", RecordType.A, "192.168.0.2", {"ttl": 500}
)
self.assertTrue("is not a foo" in str(ctx.exception))
def test_other_error(self):
with self.assertRaises(GandiLiveBaseError):
self.driver.list_records(self.test_bad_zone)
def test_mx_record(self):
record = self.driver.get_record(self.test_zone.id, "MX:lists")
self.assertEqual(record.extra["priority"], "10")
self.assertTrue("_other_records" in record.extra)
other_record = record.extra["_other_records"][0]
self.assertEqual(other_record["extra"]["priority"], "20")
def test_ex_create_multivalue_record(self):
records = self.driver.ex_create_multi_value_record(
"alice", self.test_zone, "AAAA", ["::1", "::2"], extra={"ttl": 400}
)
self.assertEqual(records[0].id, "AAAA:alice")
self.assertEqual(records[0].name, "alice")
self.assertEqual(records[0].type, RecordType.AAAA)
self.assertEqual(records[0].data, "::1")
self.assertEqual(records[1].id, "AAAA:alice")
self.assertEqual(records[1].name, "alice")
self.assertEqual(records[1].type, RecordType.AAAA)
self.assertEqual(records[1].data, "::2")
def test_update_multivalue_record(self):
record = self.driver.get_record(self.test_zone.id, "MX:lists")
updated = self.driver.update_record(
record, None, None, "mail1", {"ttl": 400, "priority": 10}
)
self.assertEqual(updated.extra["priority"], "10")
self.assertEqual(updated.data, "mail1")
self.assertTrue("_other_records" in record.extra)
other_record = record.extra["_other_records"][0]
self.assertEqual(other_record["extra"]["priority"], "20")
def test_ex_update_gandi_zone_name(self):
updated = self.driver.ex_update_gandi_zone_name("111111", "Foo")
self.assertTrue(updated)
def test_ex_delete_gandi_zone(self):
deleted = self.driver.ex_delete_gandi_zone("111111")
self.assertTrue(deleted)
class GandiLiveMockHttp(BaseGandiLiveMockHttp):
fixtures = DNSFileFixtures("gandi_live")
def _json_api_v5_domains_get(self, method, url, body, headers):
body = self.fixtures.load("list_zones.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_get(self, method, url, body, headers):
body = self.fixtures.load("get_zone.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_badexample_com_get(self, method, url, body, headers):
body = self.fixtures.load("get_bad_zone.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_nosuchzone_com_get(self, method, url, body, headers):
body = self.fixtures.load("get_nonexistent_zone.json")
return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.NOT_FOUND])
def _json_api_v5_zones_post(self, method, url, body, headers):
input = json.loads(body)
if "badexample" in input["name"]:
body = self.fixtures.load("create_bad_zone.json")
return (httplib.CONFLICT, body, {}, httplib.responses[httplib.CONFLICT])
else:
body = self.fixtures.load("create_zone.json")
return (
httplib.OK,
body,
{"Location": "/zones/54321"},
httplib.responses[httplib.OK],
)
def _json_api_v5_zones_111111_patch(self, method, url, body, headers):
body = self.fixtures.load("update_gandi_zone.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_zones_111111_delete(self, method, url, body, headers):
return (httplib.NO_CONTENT, "", {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_org_patch(self, method, url, body, headers):
body = self.fixtures.load("create_domain.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_get(self, method, url, body, headers):
body = self.fixtures.load("list_records.json")
resp_headers = {}
if (
headers is not None
and "Accept" in headers
and headers["Accept"] == "text/plain"
):
body = self.fixtures.load("list_records_bind.txt")
resp_headers["Content-Type"] = "text/plain"
return (httplib.OK, body, resp_headers, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_bob_A_get(
self, method, url, body, headers
):
body = self.fixtures.load("get_record.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_none_A_get(
self, method, url, body, headers
):
body = self.fixtures.load("get_nonexistent_record.json")
return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.NOT_FOUND])
def _json_api_v5_domains_example_com_records_lists_MX_get(
self, method, url, body, headers
):
body = self.fixtures.load("get_mx_record.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_lists_MX_put(
self, method, url, body, headers
):
body = self.fixtures.load("update_mx_record.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_post(self, method, url, body, headers):
body = self.fixtures.load("create_record.json")
return (
httplib.OK,
body,
{"Location": "/zones/12345/records/alice/AAAA"},
httplib.responses[httplib.OK],
)
def _json_api_v5_domains_badexample_com_records_post(
self, method, url, body, headers
):
body = self.fixtures.load("create_existing_record.json")
return (httplib.CONFLICT, body, {}, httplib.responses[httplib.CONFLICT])
def _json_api_v5_domains_badexample_com_records_get(
self, method, url, body, headers
):
return (
httplib.INTERNAL_SERVER_ERROR,
body,
{},
httplib.responses[httplib.INTERNAL_SERVER_ERROR],
)
def _json_api_v5_domains_badexample_com_records_jane_A_put(
self, method, url, body, headers
):
body = self.fixtures.load("update_bad_record.json")
return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.BAD_REQUEST])
def _json_api_v5_domains_example_com_records_bob_A_put(
self, method, url, body, headers
):
body = self.fixtures.load("update_record.json")
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
def _json_api_v5_domains_example_com_records_bob_A_delete(
self, method, url, body, headers
):
return (httplib.NO_CONTENT, "", {}, httplib.responses[httplib.OK])
if __name__ == "__main__":
sys.exit(unittest.main())