Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit afabeed

Browse files
authored
Merge pull request #285 from zalando-stups/fix-senza-status-for-alias-records
Fix senza status for alias records
2 parents 35b73f4 + 6cefe29 commit afabeed

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

senza/cli.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import boto3
1919
import click
20-
import dns.resolver
2120
import requests
2221
import senza
2322
import yaml
@@ -43,7 +42,7 @@
4342
from .stups.piu import Piu
4443
from .templates import get_template_description, get_templates
4544
from .templates._helper import get_mint_bucket_name
46-
from .traffic import change_version_traffic, get_records, print_version_traffic
45+
from .traffic import change_version_traffic, get_records, print_version_traffic, resolve_to_ip_addresses
4746
from .utils import (camel_case_to_underscore, ensure_keys, named_value,
4847
pystache_render)
4948

@@ -937,7 +936,9 @@ def status(stack_ref, region, output, w, watch):
937936
for stack in sorted(get_stacks(stack_refs, region)):
938937
instance_health = get_instance_health(elb, stack.StackName)
939938

940-
main_dns_resolves = False
939+
main_dns_resolves = None
940+
version_addresses = set()
941+
main_addresses = set()
941942
http_status = None
942943
for res in cf.Stack(stack.StackId).resource_summaries.all():
943944
if res.resource_type == 'AWS::Route53::RecordSet':
@@ -946,22 +947,20 @@ def status(stack_ref, region, output, w, watch):
946947
# physical resource ID will be empty during stack creation
947948
continue
948949
if 'version' in res.logical_id.lower():
950+
# VersionDomain -> check HTTPS reachability
951+
# (won't work for internal ELBs, but we don't care here)
949952
try:
950-
requests.get('https://{}/'.format(name), timeout=2)
953+
requests.get('https://{}/'.format(name), timeout=1)
951954
http_status = 'OK'
952955
except:
953956
http_status = 'ERROR'
957+
version_addresses = resolve_to_ip_addresses(name)
954958
else:
955-
try:
956-
answers = dns.resolver.query(name, 'CNAME')
957-
except:
958-
answers = []
959-
for answer in answers:
960-
target = answer.target.to_text()
961-
if isinstance(target, bytes):
962-
target = target.decode()
963-
if target.startswith('{}-'.format(stack.StackName)):
964-
main_dns_resolves = True
959+
# MainDomain -> check whether DNS resolves to this stack version
960+
main_addresses = resolve_to_ip_addresses(name)
961+
962+
if version_addresses and main_addresses:
963+
main_dns_resolves = bool(version_addresses & main_addresses)
965964

966965
instances = list(ec2.instances.filter(Filters=[{'Name': 'tag:aws:cloudformation:stack-id',
967966
'Values': [stack.StackId]}]))
@@ -1025,7 +1024,7 @@ def domains(stack_ref, region, output, w, watch):
10251024
if record:
10261025
row.update({'weight': str(record.get('Weight', '')),
10271026
'type': record.get('Type'),
1028-
'value': ','.join([r['Value'] for r in record.get('ResourceRecords')])})
1027+
'value': ','.join([r['Value'] for r in record.get('ResourceRecords', [])])})
10291028
rows.append(row)
10301029

10311030
with OutputFormat(output):

senza/traffic.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import collections
2+
import dns.resolver
23
from json import JSONEncoder
34

45
import boto3
@@ -395,3 +396,15 @@ def inform_sns(arns: list, message: str, region):
395396
sns = boto3.client('sns', region_name=region)
396397
for sns_topic in sns_topics:
397398
sns.publish(TopicArn=sns_topic, Subject="SenzaTrafficRedirect", Message=jsonizer.encode((message)))
399+
400+
401+
def resolve_to_ip_addresses(dns_name: str) -> set:
402+
"""
403+
Try to resolve the given DNS name to IPv4 addresses and return empty set on ANY error.
404+
"""
405+
try:
406+
answers = dns.resolver.query(dns_name, 'A')
407+
except:
408+
return set()
409+
else:
410+
return {answer.address for answer in answers}

tests/test_cli.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,3 +1568,58 @@ def test_failure_event():
15681568

15691569
assert failure_event({'ResourceStatusReason': 'foo',
15701570
'ResourceStatus': 'FAIL'})
1571+
1572+
1573+
def test_status_main_dns(monkeypatch):
1574+
def my_resource(rtype, *args):
1575+
if rtype == 'ec2':
1576+
ec2 = MagicMock()
1577+
instance = MagicMock()
1578+
instance.id = 'inst-123'
1579+
instance.public_ip_address = '8.8.8.8'
1580+
instance.private_ip_address = '10.0.0.1'
1581+
instance.state = {'Name': 'Test-instance'}
1582+
instance.tags = [{'Key': 'aws:cloudformation:stack-name', 'Value': 'test-1'},
1583+
{'Key': 'aws:cloudformation:logical-id', 'Value': 'local-id-123'},
1584+
{'Key': 'StackName', 'Value': 'test'},
1585+
{'Key': 'StackVersion', 'Value': '1'}]
1586+
instance.launch_time = datetime.datetime.utcnow()
1587+
ec2.instances.filter.return_value = [instance]
1588+
return ec2
1589+
elif rtype == 'cloudformation':
1590+
cf = MagicMock()
1591+
version_domain = MagicMock()
1592+
version_domain.logical_id = 'VersionDomain'
1593+
version_domain.resource_type = 'AWS::Route53::RecordSet'
1594+
version_domain.physical_resource_id = 'test-1.example.org'
1595+
main_domain = MagicMock()
1596+
main_domain.logical_id = 'MainDomain'
1597+
main_domain.resource_type = 'AWS::Route53::RecordSet'
1598+
main_domain.physical_resource_id = 'test.example.org'
1599+
cf.Stack.return_value.resource_summaries.all.return_value = [version_domain, main_domain]
1600+
return cf
1601+
return MagicMock()
1602+
1603+
def my_client(rtype, *args):
1604+
if rtype == 'cloudformation':
1605+
cf = MagicMock()
1606+
cf.list_stacks.return_value = {'StackSummaries': [{'StackName': 'test-1',
1607+
'CreationTime': '2016-06-14'}]}
1608+
return cf
1609+
return MagicMock()
1610+
1611+
def resolve_to_ip_addresses(dns_name):
1612+
return {'test-1.example.org': {'1.2.3.4', '5.6.7.8'}, 'test.example.org': {'5.6.7.8'}}.get(dns_name)
1613+
1614+
monkeypatch.setattr('boto3.resource', my_resource)
1615+
monkeypatch.setattr('boto3.client', my_client)
1616+
monkeypatch.setattr('senza.cli.resolve_to_ip_addresses', resolve_to_ip_addresses)
1617+
1618+
runner = CliRunner()
1619+
1620+
with runner.isolated_filesystem():
1621+
result = runner.invoke(cli, ['status', 'test', '--region=aa-fakeregion-1', '1', '--output=json'],
1622+
catch_exceptions=False)
1623+
1624+
data = json.loads(result.output.strip())
1625+
assert data[0]['main_dns'] == True

tests/test_traffic.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from unittest.mock import MagicMock
22
from senza.aws import SenzaStackSummary
3-
from senza.traffic import get_stack_versions, StackVersion, get_weights
3+
from senza.traffic import get_stack_versions, StackVersion, get_weights, resolve_to_ip_addresses
44
from senza.manaus.route53 import RecordType
55

66

@@ -71,3 +71,15 @@ def test_get_weights(monkeypatch):
7171
'app-3': 0},
7272
0,
7373
0)
74+
75+
76+
def test_resolve_to_ip_addresses(monkeypatch):
77+
query = MagicMock()
78+
monkeypatch.setattr('dns.resolver.query', query)
79+
80+
query.side_effect = Exception()
81+
assert resolve_to_ip_addresses('example.org') == set()
82+
83+
query.side_effect = None
84+
query.return_value = [MagicMock(address='1.2.3.4')]
85+
assert resolve_to_ip_addresses('example.org') == {'1.2.3.4'}

0 commit comments

Comments
 (0)