From eb3a120db1be157af5ad76d93a42aa1d81dfc837 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 12:56:00 +0200 Subject: [PATCH 01/16] add code to set tags on AWS objects (instances and EBS storage), as well as an ability for the postgres state_handler to call a callback when the state is changed, and use the new AWS code in a callback. --- governor.py | 4 +- helpers/aws.py | 115 ++++++++++++++++++++++++++++++++++++++++++ helpers/postgresql.py | 7 ++- 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 helpers/aws.py diff --git a/governor.py b/governor.py index 193ad53e..578cf405 100755 --- a/governor.py +++ b/governor.py @@ -10,6 +10,7 @@ from helpers.etcd import Etcd from helpers.postgresql import Postgresql from helpers.ha import Ha from helpers.utils import setup_signal_handlers, sleep +import helpers.aws import AWSConnection class Governor: @@ -17,7 +18,8 @@ class Governor: def __init__(self, config): self.nap_time = config['loop_wait'] self.etcd = Etcd(config['etcd']) - self.postgresql = Postgresql(config['postgresql']) + self.aws = AWSConnection(config) + self.postgresql = Postgresql(config['postgresql'], self.aws.on_role_change) self.ha = Ha(self.postgresql, self.etcd) host, port = config['restapi']['listen'].split(':') self.api = RestApiServer(self, config['restapi']) diff --git a/helpers/aws.py b/helpers/aws.py new file mode 100644 index 00000000..75e9e7f9 --- /dev/null +++ b/helpers/aws.py @@ -0,0 +1,115 @@ +import logging +import re +import requests +from requests.exceptions import RequestException +import types +import boto.ec2 + +logger = logging.getLogger(__name__) + + +class AWSConnection: + def __init__(self, config): + self.available = False + self.config = config + + if 'cluster_name' in config: + self.cluster_name = config.get('cluster_name') + elif 'etcd' in config and type(config['etcd']) == types.DictType: + self.cluster_name = config['etcd'].get('scope', 'unknown') + else: + self.cluster_name = 'unknown' + try: + # get the instance id + r = requests.get('http://169.254.169.254/latest/meta-data/instance-id', timeout=0.1) + if r.ok: + self.instance_id = r.content.strip() + r = requests.get('http://169.254.169.254/latest/meta-data/placement/availability-zone', timeout=0.1) + if r.ok: + # get the region from the availability zone, i.e. eu-west-1 from eu-west-1c + m = re.match(r'(\w+-\w+-\d+)[a-z]+', r.content) + if m: + self.region = m.group(1) + self.available = True + except RequestException: + logger.info("cannot query AWS meta-data") + pass + + def aws_available(self): + return self.available + + def _tag_ebs(self, role): + """ set tags, carrying the cluster name, instance role and instance id for the EBS storage """ + if not self.available: + return False + + tags = {'Name': self.cluster_name, 'Role': role, 'Instance': self.instance_id} + try: + conn = boto.ec2.connect_to_region(self.region) + # get all volumes attached to the current instance + volumes = conn.get_all_volumes(filter={'attachment.instance-id': self.instance_id}) + if volumes: + conn.create_tags([v.id for v in volumes], tags) + except Exception as e: + logger.info('could not set tags for EBS storage devices attached: {}'.format(e)) + return False + return True + + def _tag_ec2(self, role): + """ tag the current EC2 instance with a cluster role """ + if not self.available: + return False + tags = {'Role', role} + try: + conn = boto.ec2.connect_to_region(self.region) + instances = conn.get_all_reservations(instance_ids=[self.instance_id]) + if instances: + conn.create_tag([instances[0].id], tags) + except Exception as e: + logger.info("could not set tags for EC2 instance {}: {}".format(self.instance_id, e)) + return False + return True + + def on_role_change(self, new_role): + self._tag_ec2(new_role) + self._tag_ebs('spilo_' + self.cluster_name, new_role) + +if __name__ == '__main__': + import yaml + config_string = """ +loop_wait: 10 +restapi: + listen: 0.0.0.0:8008 + connect_address: 127.0.0.1:5432 +etcd: + scope: test + ttl: 30 + host: 127.0.0.1:8080 +postgresql: + name: postgresql_foo + listen: 0.0.0.0:5432 + connect_address: 127.0.0.1:5432 + data_dir: /home/postgres/pgdata/data + replication: + username: standby + password: standby + network: 0.0.0.0/0 + superuser: + password: zalando + admin: + username: admin + password: admin + parameters: + archive_mode: "on" + wal_level: hot_standby + max_wal_senders: 5 + wal_keep_segments: 8 + archive_timeout: 1800s + max_replication_slots: 5 + hot_standby: "on" + ssl: "on" +""" + awsconnection = AWSConnection(yaml.load(config_string)) + print "AWS available: {}, Cluster_name: {}".format(awsconnection.available, awsconnection.cluster_name) + if awsconnection.available: + print "AWS Region: {}, Instance_id: {}".format(awsconnection.region, awsconnection.instance_id) diff --git a/helpers/postgresql.py b/helpers/postgresql.py index b9141014..db796eea 100644 --- a/helpers/postgresql.py +++ b/helpers/postgresql.py @@ -34,7 +34,7 @@ def parseurl(url): class Postgresql: - def __init__(self, config): + def __init__(self, config, on_change_callback=None): self.config = config self.name = config['name'] self.listen_addresses, self.port = config['listen'].split(':') @@ -64,6 +64,7 @@ class Postgresql: self._connection = None self._cursor_holder = None self.members = [] # list of already existing replication slots + self.on_change_callback = on_change_callback def get_local_address(self): # TODO: try to get unix_socket_directory from postmaster.pid @@ -342,6 +343,8 @@ primary_conninfo = '{}' if not self.check_recovery_conf(leader): self.write_recovery_conf(leader) self.restart() + if self.on_change_callback: + self.on_change_callback('replica') def save_configuration_files(self): """ @@ -361,6 +364,8 @@ primary_conninfo = '{}' def promote(self): self.is_promoted = subprocess.call(self._pg_ctl + ['promote']) == 0 + if self.on_change_callback: + self.on_change_callback('master') return self.is_promoted def demote(self, leader): From 0f518f87e7e2af5d93481704c2430255373a4965 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 14:45:02 +0200 Subject: [PATCH 02/16] fix a typo. --- governor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/governor.py b/governor.py index 578cf405..d405a9d0 100755 --- a/governor.py +++ b/governor.py @@ -10,7 +10,7 @@ from helpers.etcd import Etcd from helpers.postgresql import Postgresql from helpers.ha import Ha from helpers.utils import setup_signal_handlers, sleep -import helpers.aws import AWSConnection +from helpers.aws import AWSConnection class Governor: From c68beda75cab2ec3938fb748dfa2196f1490c187 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 15:49:38 +0200 Subject: [PATCH 03/16] make sure on role change is called during the initial start of the server. --- governor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/governor.py b/governor.py index d405a9d0..d2b5a37f 100755 --- a/governor.py +++ b/governor.py @@ -44,12 +44,14 @@ class Governor: self.postgresql.start() self.postgresql.create_replication_user() self.postgresql.create_connection_users() + self.aws.on_role_change('master') else: while True: leader = self.etcd.current_leader() if leader and self.postgresql.sync_from_leader(leader): self.postgresql.write_recovery_conf(leader) self.postgresql.start() + self.aws.on_role_change('replica') break sleep(5) elif self.postgresql.is_running(): From 1d3a946ffb2b6035a6dbcbc91afee08200932802 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 15:52:35 +0200 Subject: [PATCH 04/16] Fix wrong number of arguments for the EBS instance. --- helpers/aws.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/aws.py b/helpers/aws.py index 75e9e7f9..03aec108 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -43,7 +43,7 @@ class AWSConnection: if not self.available: return False - tags = {'Name': self.cluster_name, 'Role': role, 'Instance': self.instance_id} + tags = {'Name': 'spilo_'+self.cluster_name, 'Role': role, 'Instance': self.instance_id} try: conn = boto.ec2.connect_to_region(self.region) # get all volumes attached to the current instance @@ -72,7 +72,7 @@ class AWSConnection: def on_role_change(self, new_role): self._tag_ec2(new_role) - self._tag_ebs('spilo_' + self.cluster_name, new_role) + self._tag_ebs(new_role) if __name__ == '__main__': import yaml From e06cd49debe4088810092b8376f46337d62ec43d Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 16:13:03 +0200 Subject: [PATCH 05/16] autodetect the host role during start from the existence of recovery.conf. --- helpers/postgresql.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helpers/postgresql.py b/helpers/postgresql.py index db796eea..e7e509de 100644 --- a/helpers/postgresql.py +++ b/helpers/postgresql.py @@ -245,6 +245,10 @@ class Postgresql: ret = subprocess.call(self._pg_ctl + ['start', '-o', self.server_options()]) == 0 ret and self.load_replication_slots() self.save_configuration_files() + if os.path.exists(self.recovery_conf): + self.on_change_callback('replica') + else: + self.on_change_callback('master') return ret def stop(self): From fb3bf799e90d6c8b5818ea6ce521460636520e21 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 16:18:47 +0200 Subject: [PATCH 06/16] Autodetect the host role during start from the existence of recovery.conf. Fix some typos. --- governor.py | 2 -- helpers/aws.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/governor.py b/governor.py index d2b5a37f..d405a9d0 100755 --- a/governor.py +++ b/governor.py @@ -44,14 +44,12 @@ class Governor: self.postgresql.start() self.postgresql.create_replication_user() self.postgresql.create_connection_users() - self.aws.on_role_change('master') else: while True: leader = self.etcd.current_leader() if leader and self.postgresql.sync_from_leader(leader): self.postgresql.write_recovery_conf(leader) self.postgresql.start() - self.aws.on_role_change('replica') break sleep(5) elif self.postgresql.is_running(): diff --git a/helpers/aws.py b/helpers/aws.py index 03aec108..d2f2b13e 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -47,7 +47,7 @@ class AWSConnection: try: conn = boto.ec2.connect_to_region(self.region) # get all volumes attached to the current instance - volumes = conn.get_all_volumes(filter={'attachment.instance-id': self.instance_id}) + volumes = conn.get_all_volumes(filters={'attachment.instance-id': self.instance_id}) if volumes: conn.create_tags([v.id for v in volumes], tags) except Exception as e: @@ -64,7 +64,7 @@ class AWSConnection: conn = boto.ec2.connect_to_region(self.region) instances = conn.get_all_reservations(instance_ids=[self.instance_id]) if instances: - conn.create_tag([instances[0].id], tags) + conn.create_tags([instances[0].id], tags) except Exception as e: logger.info("could not set tags for EC2 instance {}: {}".format(self.instance_id, e)) return False From ac4660081618405f29f53e619562368ee0589ab7 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Wed, 3 Jun 2015 16:26:38 +0200 Subject: [PATCH 07/16] more bugfixes. --- helpers/aws.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/helpers/aws.py b/helpers/aws.py index d2f2b13e..d09da2e0 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -59,12 +59,10 @@ class AWSConnection: """ tag the current EC2 instance with a cluster role """ if not self.available: return False - tags = {'Role', role} + tags = {'Role': role} try: conn = boto.ec2.connect_to_region(self.region) - instances = conn.get_all_reservations(instance_ids=[self.instance_id]) - if instances: - conn.create_tags([instances[0].id], tags) + conn.create_tags([self.instance_id], tags) except Exception as e: logger.info("could not set tags for EC2 instance {}: {}".format(self.instance_id, e)) return False From 3d123b41dfc4d57aafe190ce14d8adbbeb8c94ef Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Thu, 4 Jun 2015 17:19:33 +0200 Subject: [PATCH 08/16] add unittests. --- helpers/aws.py | 48 ++------------------ tests/test_aws.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 45 deletions(-) create mode 100644 tests/test_aws.py diff --git a/helpers/aws.py b/helpers/aws.py index d09da2e0..3b72c492 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -46,10 +46,8 @@ class AWSConnection: tags = {'Name': 'spilo_'+self.cluster_name, 'Role': role, 'Instance': self.instance_id} try: conn = boto.ec2.connect_to_region(self.region) - # get all volumes attached to the current instance volumes = conn.get_all_volumes(filters={'attachment.instance-id': self.instance_id}) - if volumes: - conn.create_tags([v.id for v in volumes], tags) + conn.create_tags([v.id for v in volumes], tags) except Exception as e: logger.info('could not set tags for EBS storage devices attached: {}'.format(e)) return False @@ -69,45 +67,5 @@ class AWSConnection: return True def on_role_change(self, new_role): - self._tag_ec2(new_role) - self._tag_ebs(new_role) - -if __name__ == '__main__': - import yaml - config_string = """ -loop_wait: 10 -restapi: - listen: 0.0.0.0:8008 - connect_address: 127.0.0.1:5432 -etcd: - scope: test - ttl: 30 - host: 127.0.0.1:8080 -postgresql: - name: postgresql_foo - listen: 0.0.0.0:5432 - connect_address: 127.0.0.1:5432 - data_dir: /home/postgres/pgdata/data - replication: - username: standby - password: standby - network: 0.0.0.0/0 - superuser: - password: zalando - admin: - username: admin - password: admin - parameters: - archive_mode: "on" - wal_level: hot_standby - max_wal_senders: 5 - wal_keep_segments: 8 - archive_timeout: 1800s - max_replication_slots: 5 - hot_standby: "on" - ssl: "on" -""" - awsconnection = AWSConnection(yaml.load(config_string)) - print "AWS available: {}, Cluster_name: {}".format(awsconnection.available, awsconnection.cluster_name) - if awsconnection.available: - print "AWS Region: {}, Instance_id: {}".format(awsconnection.region, awsconnection.instance_id) + ret = self._tag_ec2(new_role) + return self._tag_ebs(new_role) and ret diff --git a/tests/test_aws.py b/tests/test_aws.py new file mode 100644 index 00000000..e350a5c1 --- /dev/null +++ b/tests/test_aws.py @@ -0,0 +1,112 @@ +import unittest +import requests +import boto.ec2 +from collections import namedtuple +from helpers.aws import AWSConnection +from requests.exceptions import RequestException +import yaml + + +class MockEc2Connection: + + def __init__(self, error=False): + self.error = error + + def get_all_volumes(self, filters): + if self.error: + raise Exception("get_all_volumes") + oid = namedtuple('Volume', 'id') + return [oid(id='a'), oid(id='b')] + + def create_tags(self, objects, tags): + if self.error or len(objects) == 0: + raise Exception("create_tags") + return True + + +class TestAWSConnection(unittest.TestCase): + + def __init__(self, method_name='runTest'): + super(TestAWSConnection, self).__init__(method_name) + + def set_error(self): + self.error = True + + def set_ok(self): + self.error = False + + def boto_ec2_connect_to_region(self, region): + return MockEc2Connection(self.error) + + def requests_get(self, url, **kwargs): + if self.error: + raise RequestException("foo") + result = namedtuple('Request', 'ok content') + result.ok = True + if url.split('/')[-1] == 'availability-zone': + result.content = 'eu-west-1c' + else: + result.content = 'foo' + return result + + def setUp(self): + self.error = False + requests.get = self.requests_get + boto.ec2.connect_to_region = self.boto_ec2_connect_to_region + self.config_string = """ +loop_wait: 10 +restapi: + listen: 0.0.0.0:8008 + connect_address: 127.0.0.1:5432 +etcd: + scope: test + ttl: 30 + host: 127.0.0.1:8080 +postgresql: + name: postgresql_foo + listen: 0.0.0.0:5432 + connect_address: 127.0.0.1:5432 + data_dir: /home/postgres/pgdata/data + replication: + username: standby + password: standby + network: 0.0.0.0/0 + superuser: + password: zalando + admin: + username: admin + password: admin + parameters: + archive_mode: "on" + wal_level: hot_standby + max_wal_senders: 5 + wal_keep_segments: 8 + archive_timeout: 1800s + max_replication_slots: 5 + hot_standby: "on" + ssl: "on" +""" + self.conn = AWSConnection(yaml.load(self.config_string)) + + def test_aws_available(self): + self.assertTrue(self.conn.aws_available()) + + def test_on_role_change(self): + self.assertTrue(self.conn._tag_ebs('master')) + self.assertTrue(self.conn._tag_ec2('master')) + self.assertTrue(self.conn.on_role_change('master')) + + def test_non_aws(self): + self.set_error() + conn = AWSConnection(yaml.load(self.config_string)) + self.assertFalse(conn.aws_available()) + self.assertFalse(conn._tag_ebs('master')) + self.assertFalse(conn._tag_ec2('master')) + + def test_aws_tag_ebs_error(self): + self.set_error() + self.assertFalse(self.conn._tag_ebs("master")) + + def test_aws_tag_ec2_error(self): + self.set_error() + self.assertFalse(self.conn._tag_ec2("master")) From 1ae19f758b0708dd934a76014a2a05cf3780a2f5 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 08:48:57 +0200 Subject: [PATCH 09/16] update requirements.txt with boto. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2185e5c6..3aba0fc6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ PyYAML psycopg2 requests +boto From e2de4c90454d627430bc1cf13b80a9465c667840 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 08:59:32 +0200 Subject: [PATCH 10/16] add ok flag to the MockResponse object in order to be compatible with the AWS code, which examines this flag in a callback. --- tests/test_etcd.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_etcd.py b/tests/test_etcd.py index 8f2280e6..1f280534 100644 --- a/tests/test_etcd.py +++ b/tests/test_etcd.py @@ -12,6 +12,7 @@ class MockResponse: def __init__(self): self.status_code = 200 self.content = '{}' + self.ok = True def json(self): return json.loads(self.content) @@ -34,6 +35,9 @@ def requests_get(url, **kwargs): response.status_code = 404 elif url.startswith('http://noleader'): response.content = '{"action":"get","node":{"key":"/service/batman5","dir":true,"nodes":[{"key":"/service/batman5/initialize","value":"postgresql0","modifiedIndex":1582,"createdIndex":1582},{"key":"/service/batman5/leader","value":"postgresql1","expiration":"2015-05-15T09:11:00.037397538Z","ttl":21,"modifiedIndex":20728,"createdIndex":20434},{"key":"/service/batman5/optime","dir":true,"nodes":[{"key":"/service/batman5/optime/leader","value":"2164261704","modifiedIndex":20729,"createdIndex":20729}],"modifiedIndex":20437,"createdIndex":20437},{"key":"/service/batman5/members","dir":true,"nodes":[{"key":"/service/batman5/members/postgresql0","value":"postgres://replicator:rep-pass@127.0.0.1:5433/postgres","expiration":"2015-05-15T09:11:09.611860899Z","ttl":30,"modifiedIndex":20730,"createdIndex":20730}],"modifiedIndex":1581,"createdIndex":1581}],"modifiedIndex":1581,"createdIndex":1581}}' + else: + response.status_code = 404 + response.ok = False return response From 553b26f38569c94930fd8d45c6eeccbc6457f265 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 09:01:42 +0200 Subject: [PATCH 11/16] call on_change_callback only if it has been set. --- helpers/postgresql.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/helpers/postgresql.py b/helpers/postgresql.py index e7e509de..68448cd3 100644 --- a/helpers/postgresql.py +++ b/helpers/postgresql.py @@ -245,10 +245,11 @@ class Postgresql: ret = subprocess.call(self._pg_ctl + ['start', '-o', self.server_options()]) == 0 ret and self.load_replication_slots() self.save_configuration_files() - if os.path.exists(self.recovery_conf): - self.on_change_callback('replica') - else: - self.on_change_callback('master') + if self.on_change_callback: + if os.path.exists(self.recovery_conf): + self.on_change_callback('replica') + else: + self.on_change_callback('master') return ret def stop(self): From 20c785b63a614652da4fd8c69cd2fea9701458fc Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 09:44:47 +0200 Subject: [PATCH 12/16] python3 compatibility: use isinstance instead of type() --- helpers/aws.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helpers/aws.py b/helpers/aws.py index 3b72c492..916b62c1 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -2,7 +2,6 @@ import logging import re import requests from requests.exceptions import RequestException -import types import boto.ec2 logger = logging.getLogger(__name__) @@ -15,7 +14,7 @@ class AWSConnection: if 'cluster_name' in config: self.cluster_name = config.get('cluster_name') - elif 'etcd' in config and type(config['etcd']) == types.DictType: + elif 'etcd' in config and isinstance(config['etcd'], dict): self.cluster_name = config['etcd'].get('scope', 'unknown') else: self.cluster_name = 'unknown' From 8ab7717f1321485c5624a62f84e6510c41b3d9e5 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 10:03:42 +0200 Subject: [PATCH 13/16] increase test coverage for the changes in postgresql.py --- helpers/postgresql.py | 5 +---- tests/test_postgresql.py | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/helpers/postgresql.py b/helpers/postgresql.py index 2a998e46..bcf90c59 100644 --- a/helpers/postgresql.py +++ b/helpers/postgresql.py @@ -252,10 +252,7 @@ class Postgresql: ret and self.load_replication_slots() self.save_configuration_files() if self.on_change_callback: - if os.path.exists(self.recovery_conf): - self.on_change_callback('replica') - else: - self.on_change_callback('master') + self.on_change_callback('replica' if os.path.exists(self.recovery_conf) else 'master') return ret def stop(self): diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py index e69e5703..87e207a4 100644 --- a/tests/test_postgresql.py +++ b/tests/test_postgresql.py @@ -117,7 +117,8 @@ class TestPostgresql(unittest.TestCase): 'replication': {'username': 'replicator', 'password': 'rep-pass', 'network': '127.0.0.1/32'}, - 'parameters': {'foo': 'bar'}, 'recovery_conf': {'foo': 'bar'}}) + 'parameters': {'foo': 'bar'}, 'recovery_conf': {'foo': 'bar'}}, + on_change_callback=lambda state: True) psycopg2.connect = psycopg2_connect if not os.path.exists(self.p.data_dir): os.makedirs(self.p.data_dir) From a6fcd6681969756f08177fce514d6976f03b5d5c Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 10:07:24 +0200 Subject: [PATCH 14/16] line up dependency alphabetically. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3aba0fc6..d0f5e512 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ +boto PyYAML psycopg2 requests -boto From 92d2fda4a4928322cea0419a5ecdd63cc9d8eef9 Mon Sep 17 00:00:00 2001 From: Feike Steenbergen Date: Fri, 5 Jun 2015 10:23:49 +0200 Subject: [PATCH 15/16] Dockerfile: Bump etcd version, include boto as a dependency --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 42a90964..226c1564 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update -y RUN apt-get upgrade -y ENV PGVERSION 9.4 -RUN apt-get install python python-psycopg2 python-yaml python-requests postgresql-${PGVERSION} -y +RUN apt-get install python python-psycopg2 python-yaml python-requests python-boto postgresql-${PGVERSION} -y ENV PATH /usr/lib/postgresql/${PGVERSION}/bin:$PATH @@ -19,7 +19,7 @@ ADD governor.py /governor/governor.py ADD helpers /governor/helpers ADD postgres0.yml /governor/ -ENV ETCDVERSION 2.0.10 +ENV ETCDVERSION 2.0.11 RUN curl -L https://github.com/coreos/etcd/releases/download/v${ETCDVERSION}/etcd-v${ETCDVERSION}-linux-amd64.tar.gz | tar xz -C /bin --strip=1 --wildcards --no-anchored etcd etcdctl ## Setting up a simple script that will serve as an entrypoint From 16a068f7954d834b32fe369b37b76b3ec3985c44 Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Fri, 5 Jun 2015 17:07:10 +0200 Subject: [PATCH 16/16] use instance-identity/document URL part to get instanceid and region at once. --- helpers/aws.py | 23 ++++++++++++----------- tests/test_aws.py | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/helpers/aws.py b/helpers/aws.py index 916b62c1..b47e4e0f 100644 --- a/helpers/aws.py +++ b/helpers/aws.py @@ -2,6 +2,7 @@ import logging import re import requests from requests.exceptions import RequestException +import yaml import boto.ec2 logger = logging.getLogger(__name__) @@ -20,19 +21,19 @@ class AWSConnection: self.cluster_name = 'unknown' try: # get the instance id - r = requests.get('http://169.254.169.254/latest/meta-data/instance-id', timeout=0.1) - if r.ok: - self.instance_id = r.content.strip() - r = requests.get('http://169.254.169.254/latest/meta-data/placement/availability-zone', timeout=0.1) - if r.ok: - # get the region from the availability zone, i.e. eu-west-1 from eu-west-1c - m = re.match(r'(\w+-\w+-\d+)[a-z]+', r.content) - if m: - self.region = m.group(1) - self.available = True + r = requests.get('http://169.254.169.254/latest/dynamic/instance-identity/document', timeout=0.1) except RequestException: logger.info("cannot query AWS meta-data") - pass + return + if r.ok: + try: + content = yaml.load(r.content) + self.instance_id = content['instanceId'] + self.region = content['region'] + except Exception as e: + logger.info('unable to fetch instance id and region from AWS meta-data: {}'.format(e)) + return + self.available = True def aws_available(self): return self.available diff --git a/tests/test_aws.py b/tests/test_aws.py index e350a5c1..63b50734 100644 --- a/tests/test_aws.py +++ b/tests/test_aws.py @@ -43,8 +43,8 @@ class TestAWSConnection(unittest.TestCase): raise RequestException("foo") result = namedtuple('Request', 'ok content') result.ok = True - if url.split('/')[-1] == 'availability-zone': - result.content = 'eu-west-1c' + if url.split('/')[-1] == 'document': + result.content = '{\n "instanceId" : "012345",\n "region" : "eu-west-1"\n}' else: result.content = 'foo' return result