Example #1
0
 def create_hosted_zone(self, caller_reference, name):
     """
     @see: L{txaws.route53.client._Route53Client.create_hosted_zone}
     """
     zone = HostedZone(
         name=name,
         reference=caller_reference,
         identifier=self._state.next_id(),
         # Hosted zones start with SOA and NS rrsets.
         rrset_count=2,
     )
     self._state.zones = self._state.zones.append(zone)
     self.change_resource_record_sets(
         zone.identifier,
         [
             create_rrset(
                 RRSet(
                     label=Name(name),
                     type="SOA",
                     ttl=900,
                     records=self._state.soa_records,
                 ), ),
             create_rrset(
                 RRSet(
                     label=Name(name),
                     type="NS",
                     ttl=172800,
                     records=self._state.ns_records,
                 ), ),
         ],
     )
     return succeed(zone)
Example #2
0
        def test_list_resource_record_sets_maxitems(self):
            """
            If C{maxitems} is used to limit the number of records returned by
            C{list_resource_record_sets}, the records returned are those that
            sort first according to the rules given by
            U{http://docs.aws.amazon.com/Route53/latest/APIReference/API_ListResourceRecordSets.html#API_ListResourceRecordSets_RequestSyntax}.
            """
            zone_name = u"{}.example.invalid.".format(uuid4())
            client = get_client(self)

            # extra sorts _after_ expected according to the AWS Route53
            # ordering rules but it sorts _before_ according to more naive
            # (incorrect) string ordering rules.
            extra = RRSet(
                Name(u"a.z.{}".format(zone_name)),
                u"A",
                60,
                {A(IPv4Address(u"10.0.0.1"))},
            )
            expected = RRSet(
                Name(u"b.y.{}".format(zone_name)),
                u"A",
                60,
                {A(IPv4Address(u"10.0.0.2"))},
            )

            d = client.create_hosted_zone(u"{}".format(time()), zone_name)

            def created_zone(zone):
                self.addCleanup(lambda: self._cleanup(client, zone.identifier))
                d = client.change_resource_record_sets(zone.identifier, [
                    create_rrset(extra),
                    create_rrset(expected),
                ])
                d.addCallback(lambda ignored: zone)
                return d

            d.addCallback(created_zone)

            def created_rrsets(zone):
                return client.list_resource_record_sets(
                    zone.identifier,
                    name=Name(u"a.{}".format(zone_name)),
                    type=u"A",
                    maxitems=1,
                )

            d.addCallback(created_rrsets)

            def listed_rrsets(rrsets):
                self.assertEqual(
                    {RRSetKey(expected.label, expected.type): expected},
                    rrsets,
                )

            d.addCallback(listed_rrsets)
            return d
def _rrset_for_subscription(subscription_id, zone_name):
    return RRSet(
        label=_introducer_name_for_subscription(subscription_id, zone_name),
        type=u"CNAME",
        ttl=60,
        records={_cname_for_subscription(zone_name)},
    )
Example #4
0
        def test_delete_missing_rrset(self):
            """
            It is an error to attempt to delete an rrset which does not exist.
            """
            zone_name = u"{}.test_delete_missing_rrset.invalid.".format(
                uuid4())
            rrset = RRSet(
                label=Name(u"foo.{}".format(zone_name)),
                type=u"CNAME",
                ttl=60,
                records={CNAME(canonical_name=Name(u"bar.example.invalid."))},
            )

            client = get_client(self)
            d = client.create_hosted_zone(u"{}".format(time()), zone_name)

            def created_zone(zone):
                self.addCleanup(lambda: self._cleanup(client, zone.identifier))
                d = client.change_resource_record_sets(zone.identifier,
                                                       [delete_rrset(rrset)])
                self.assertFailure(d, Route53Error)
                return d

            d.addCallback(created_zone)

            def got_error(error):
                self.assertEqual(BAD_REQUEST, int(error.status))

            d.addCallback(got_error)

            return d
    def check_route53(self, database, config, subscriptions, k8s_state, aws):
        expected_rrsets = pmap()

        service = k8s_state.services.item_by_name(S4_CUSTOMER_GRID_NAME)
        if service.status is not None and service.status.loadBalancer.ingress:
            # TODO: It would be slightly nicer to make this a Route53 Alias
            # instead of a CNAME.  txAWS needs support for creating Route53 Alias
            # rrsets first, though.
            introducer = RRSetKey(
                label=Name(
                    u"introducer.{domain}".format(domain=config.domain)),
                type=u"CNAME",
            )
            service_ingress = RRSet(
                label=introducer.label,
                type=introducer.type,
                ttl=60,
                records={
                    CNAME(canonical_name=Name(
                        service.status.loadBalancer.ingress[0].hostname, ), ),
                },
            )
            expected_rrsets = expected_rrsets.set(introducer, service_ingress)

        for sid in subscriptions:
            label = _introducer_name_for_subscription(sid, config.domain)
            key = RRSetKey(label=label, type=u"CNAME")
            cname = CNAME(
                Name(u"introducer.{domain}".format(domain=config.domain)))
            rrset = RRSet(label=label, type=u"CNAME", ttl=60, records={cname})
            expected_rrsets = expected_rrsets.set(key, rrset)

        route53 = aws.get_route53_client()
        d = route53.list_resource_record_sets(self.zone.identifier)
        result = self.case.successResultOf(d)

        actual_rrsets = pmap({
            key: rrset
            for (key, rrset) in result.iteritems()
            # Don't care about these infrastructure rrsets.
            if key.type not in (u"SOA", u"NS")
        })
        assert_that(
            actual_rrsets,
            GoodEquals(expected_rrsets),
        )
Example #6
0
 def test_soa_ns_cname(self):
     zone_id = b"ABCDEF1234"
     client = self._client_for_rrsets(
         zone_id,
         sample_list_resource_record_sets_result.xml,
     )
     rrsets = self.successResultOf(
         client.list_resource_record_sets(zone_id=zone_id, ))
     expected = {
         RRSetKey(
             label=sample_list_resource_record_sets_result.label,
             type=u"SOA",
         ):
         RRSet(
             label=sample_list_resource_record_sets_result.label,
             type=u"SOA",
             ttl=sample_list_resource_record_sets_result.soa_ttl,
             records={sample_list_resource_record_sets_result.soa},
         ),
         RRSetKey(
             label=sample_list_resource_record_sets_result.label,
             type=u"NS",
         ):
         RRSet(
             label=sample_list_resource_record_sets_result.label,
             type=u"NS",
             ttl=sample_list_resource_record_sets_result.ns_ttl,
             records={
                 sample_list_resource_record_sets_result.ns1,
                 sample_list_resource_record_sets_result.ns2,
             },
         ),
         RRSetKey(
             label=sample_list_resource_record_sets_result.label,
             type=u"CNAME",
         ):
         RRSet(
             label=sample_list_resource_record_sets_result.label,
             type=u"CNAME",
             ttl=sample_list_resource_record_sets_result.cname_ttl,
             records={sample_list_resource_record_sets_result.cname},
         ),
     }
     self.assertEquals(rrsets, expected)
Example #7
0
class sample_change_resource_record_sets_result(object):
    rrset = RRSet(
        label=Name(u"example.invalid."),
        type=u"NS",
        ttl=86400,
        records={
            NS(Name(u"ns1.example.invalid.")),
            NS(Name(u"ns2.example.invalid.")),
        },
    )
    xml = b"""\
Example #8
0
class sample_list_resource_records_with_alias_result(object):
    normal_target = Name(u"bar.example.invalid.")
    normal = RRSet(
        label=Name(u"foo.example.invalid."),
        type=u"CNAME",
        ttl=60,
        records={CNAME(canonical_name=normal_target)},
    )

    normal_xml = u"""\
<ResourceRecordSet><Name>{label}</Name><Type>{type}</Type><TTL>{ttl}</TTL><ResourceRecords><ResourceRecord><Value>{value}</Value></ResourceRecord></ResourceRecords></ResourceRecordSet>
""".format(
        label=normal.label,
        type=normal.type,
        ttl=normal.ttl,
        value=normal_target,
    )

    alias = AliasRRSet(
        label=Name(u"bar.example.invalid."),
        type=u"A",
        dns_name=Name(
            u"dualstack.a952f315901e6b3c812e57076f5b4138-0795221525.us-east-1.elb.amazonaws.com.",
        ),
        evaluate_target_health=False,
        hosted_zone_id=u"ZSXD5Q7O3X7TRK",
    )

    alias_xml = u"""\
<ResourceRecordSet><Name>{label}</Name><Type>{type}</Type><AliasTarget><HostedZoneId>{hosted_zone_id}</HostedZoneId><DNSName>{dns_name}</DNSName><EvaluateTargetHealth>{evaluate_target_health}</EvaluateTargetHealth></AliasTarget></ResourceRecordSet>
""".format(
        label=alias.label,
        type=alias.type,
        hosted_zone_id=alias.hosted_zone_id,
        dns_name=alias.dns_name,
        evaluate_target_health=[u"false",
                                u"true"][alias.evaluate_target_health],
    )

    xml = u"""\
<?xml version="1.0"?>\n
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><ResourceRecordSets>{normal}{alias}</ResourceRecordSets><IsTruncated>false</IsTruncated><MaxItems>100</MaxItems></ListResourceRecordSetsResponse>
""".format(normal=normal_xml, alias=alias_xml).encode("utf-8")
Example #9
0
class sample_create_resource_record_sets_error_result:
    label = Name("duplicate.example.invalid.")
    type = "CNAME"

    cname = CNAME(canonical_name=Name("somewhere.example.invalid."), )
    rrset = RRSet(
        label=label,
        type="CNAME",
        ttl=600,
        records={cname},
    )

    xml = """\
<?xml version="1.0"?>
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><Error><Type>Sender</Type><Code>InvalidChangeBatch</Code><Message>[Tried to create resource record set [name='{label}', type='{type}'] but it already exists]</Message></Error><RequestId>9197fef4-03cc-11e9-b35f-7947070744f2</RequestId></ErrorResponse>
""".format(
        label=label,
        type=type,
    )
Example #10
0
        def test_change_resource_record_sets_nonexistent_zone(self):
            """
            You cannot interact with resource record sets for a non-existent
            zone.
            """
            rrset = RRSet(
                label=Name(u"foo.example.invalid."),
                type=u"CNAME",
                ttl=60,
                records={CNAME(canonical_name=Name(u"bar.example.invalid."))},
            )
            client = get_client(self)
            d = client.change_resource_record_sets(u"abcdefg12345678",
                                                   [create_rrset(rrset)])
            self.assertFailure(d, Route53Error)

            def got_error(error):
                self.assertEqual(NOT_FOUND, int(error.status))

            d.addCallback(got_error)
            return d
Example #11
0
    def test_unknown_record_type(self):
        zone_id = b"ABCDEF1234"
        template = u"""\
<?xml version="1.0"?>
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
  <ResourceRecordSets>
    <ResourceRecordSet>
      <Name>{label}</Name>
      <Type>{type}</Type>
      <TTL>{ttl}</TTL>
      <ResourceRecords>
        <ResourceRecord><Value>{record}</Value></ResourceRecord>
      </ResourceRecords>
    </ResourceRecordSet>
  </ResourceRecordSets>
  <IsTruncated>false</IsTruncated>
  <MaxItems>100</MaxItems>
</ListResourceRecordSetsResponse>
"""
        label = Name(u"foo")
        client = self._client_for_rrsets(
            zone_id,
            template.format(
                label=label,
                type=u"X-TXAWS-FICTIONAL",
                ttl=60,
                record=u"good luck interpreting this",
            ).encode("utf-8"))
        expected = {
            RRSetKey(label=label, type=u"X-TXAWS-FICTIONAL"):
            RRSet(
                label=label,
                type=u"X-TXAWS-FICTIONAL",
                ttl=60,
                records={UnknownRecordType(u"good luck interpreting this")},
            ),
        }
        rrsets = self.successResultOf(
            client.list_resource_record_sets(zone_id=zone_id), )
        self.assertEquals(expected, rrsets)
Example #12
0
    def _simple_record_test(self, record_type, record):
        zone_id = b"ABCDEF1234"
        template = u"""\
<?xml version="1.0"?>
<ListResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
  <ResourceRecordSets>
    <ResourceRecordSet>
      <Name>{label}</Name>
      <Type>{type}</Type>
      <TTL>{ttl}</TTL>
      <ResourceRecords>
        <ResourceRecord><Value>{record}</Value></ResourceRecord>
      </ResourceRecords>
    </ResourceRecordSet>
  </ResourceRecordSets>
  <IsTruncated>false</IsTruncated>
  <MaxItems>100</MaxItems>
</ListResourceRecordSetsResponse>
"""
        label = Name(u"foo")
        client = self._client_for_rrsets(
            zone_id,
            template.format(
                label=label,
                type=record_type,
                ttl=60,
                record=record.to_text(),
            ).encode("utf-8"))
        expected = {
            RRSetKey(label=label, type=record_type):
            RRSet(
                label=label,
                type=record_type,
                ttl=60,
                records={record},
            ),
        }
        rrsets = self.successResultOf(
            client.list_resource_record_sets(zone_id=zone_id), )
        self.assertEquals(expected, rrsets)
def _converge_route53_infrastructure(actual, config, subscriptions, k8s, aws):
    """
    Converge on the desired Route53 state relating to general S4
    infrastructure.

    Specifically, make sure there is an rrset for the ``introducer`` subdomain
    which points at the customer grid service's load balancer endpoint.
    """
    if actual.service is None or actual.service.status is None:
        # Cannot do anything without a v1.Service or one without a populated
        # v1.ServiceStatus field.
        return []

    if not actual.service.status.loadBalancer.ingress:
        # Also cannot do anything if we don't yet know what our ingress
        # address is.
        return []

    loadbalancer_hostname = actual.service.status.loadBalancer.ingress[0].hostname
    introducer_key = RRSetKey(label=_introducer_domain(config.domain), type=u"CNAME")
    desired_rrset = RRSet(
        label=introducer_key.label,
        type=introducer_key.type,
        ttl=60,
        records={
            CNAME(canonical_name=Name(loadbalancer_hostname)),
        },
    )

    actual_rrset = actual.zone.rrsets.get(introducer_key, None)
    if actual_rrset == desired_rrset:
        # Nothing to do.
        return []

    # Create it or change it to what we want.
    route53 = aws.get_route53_client()
    return [
        lambda: change_route53_rrsets(route53, actual.zone.zone, desired_rrset),
    ]
Example #14
0
def _safe_get_rrset_RESOURCE(self, label, type, rrset):
    # http://docs.aws.amazon.com/Route53/latest/APIReference/API_ResourceRecord.html
    resourcerecords = rrset.find("./ResourceRecords")
    if resourcerecords is None:
        return None
    records = resourcerecords.iterfind("./ResourceRecord")
    # The docs say TTL is optional but I think that means rrsets that
    # contain something other than ResourceRecord may not have it.
    # Hopefully it's always present for ResourceRecord-tyle
    # ResourceRecordSets?
    ttl = int(rrset.find("TTL").text)
    return RRSet(
        label=label,
        type=type,
        ttl=ttl,
        records={
            RECORD_TYPES[type].basic_from_element(element)
            for element
            in records
            # This is what's changed from upstream.  We'll just drop anything
            # we don't recognize.  That's sufficient for our purposes for now.
            if type in RECORD_TYPES
        },
    )
Example #15
0
        def test_resource_record_sets(self):
            zone_name = u"{}.example.invalid.".format(uuid4())
            cname = CNAME(canonical_name=Name(u"example.invalid."))
            client = get_client(self)

            zone = yield client.create_hosted_zone(u"{}".format(time()),
                                                   zone_name)

            # At least try to clean up, to be as nice as possible.
            # This might fail and someone else might have to do the
            # cleanup - but it might not!
            self.addCleanup(lambda: self._cleanup(client, zone.identifier))

            cname_label = Name(u"foo.\N{SNOWMAN}.{}".format(zone_name))
            create = create_rrset(
                RRSet(
                    label=cname_label,
                    type=u"CNAME",
                    ttl=60,
                    records={cname},
                ))
            yield client.change_resource_record_sets(zone.identifier, [create])
            initial = yield client.list_resource_record_sets(zone.identifier)

            key = RRSetKey(cname_label, u"CNAME")
            self.assertIn(key, initial)
            cname_rrset = initial[key]
            self.assertEqual(
                RRSet(label=cname_label,
                      type=u"CNAME",
                      ttl=60,
                      records={cname}),
                cname_rrset,
            )

            # Zones start with an SOA and some NS records.
            key = RRSetKey(Name(zone_name), u"SOA")
            self.assertIn(key, initial)
            soa = initial[key]
            self.assertEqual(
                len(soa.records), 1,
                "Expected one SOA record, got {}".format(soa.records))

            key = RRSetKey(Name(zone_name), u"NS")
            self.assertIn(key, initial)
            ns = initial[key]
            self.assertNotEqual(set(), ns.records,
                                "Expected some NS records, got none")

            # Unrecognized change type
            # XXX This depends on _ChangeRRSet using attrs.
            bogus = attr.assoc(create, action=u"BOGUS")
            d = client.change_resource_record_sets(zone.identifier, [bogus])
            error = yield self.assertFailure(d, Route53Error)
            self.assertEqual(BAD_REQUEST, int(error.status))

            created_a = A(IPv4Address(u"10.0.0.1"))
            upsert_label = Name(u"upsert.{}".format(zone_name))
            upsert_create = upsert_rrset(
                RRSet(
                    upsert_label,
                    u"A",
                    60,
                    {created_a},
                ))
            updated_a = A(IPv4Address(u"10.0.0.2"))
            upsert_update = upsert_rrset(
                RRSet(
                    upsert_create.rrset.label,
                    upsert_create.rrset.type,
                    upsert_create.rrset.ttl,
                    {updated_a},
                ))
            yield client.change_resource_record_sets(zone.identifier,
                                                     [upsert_create])
            rrsets = yield client.list_resource_record_sets(zone.identifier)
            self.assertEqual(rrsets[RRSetKey(upsert_label, u"A")].records,
                             {created_a})

            yield client.change_resource_record_sets(zone.identifier,
                                                     [upsert_update])
            rrsets = yield client.list_resource_record_sets(zone.identifier)
            self.assertEqual(rrsets[RRSetKey(upsert_label, u"A")].records,
                             {updated_a})

            # Use the name and maxitems parameters to select exactly one resource record.
            rrsets = yield client.list_resource_record_sets(
                zone.identifier,
                maxitems=1,
                name=upsert_label,
                type=u"A",
            )
            self.assertEqual(1, len(rrsets), "Expected 1 rrset")
            self.assertEqual({updated_a}, rrsets[RRSetKey(upsert_label,
                                                          u"A")].records)

            # It's invalid to specify type without name.
            d = client.list_resource_record_sets(zone.identifier, type=u"A")
            error = yield self.assertFailure(d, Route53Error)
            self.assertEqual(BAD_REQUEST, int(error.status))

            # It's invalid to delete the SOA record.
            d = client.change_resource_record_sets(
                zone.identifier,
                [delete_rrset(soa)],
            )
            error = yield self.assertFailure(d, Route53Error)
            self.assertEqual(BAD_REQUEST, int(error.status))

            # Likewise, the NS records.
            d = client.change_resource_record_sets(
                zone.identifier,
                [delete_rrset(ns)],
            )
            error = yield self.assertFailure(d, Route53Error)
            self.assertEqual(BAD_REQUEST, int(error.status))

            # Test deletion at the end so the zone is clean for the
            # naive cleanup logic.
            yield client.change_resource_record_sets(
                zone.identifier,
                [
                    delete_rrset(cname_rrset),
                    delete_rrset(upsert_update.rrset),
                ],
            )
            rrsets = yield client.list_resource_record_sets(zone.identifier)
            self.assertNotIn(cname_label, rrsets)
            self.assertNotIn(upsert_label, rrsets)