From c4be96c0ee316af1d2325d87bc196bf19c6a7dde Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Tue, 28 Feb 2017 11:41:54 -0500 Subject: [PATCH 1/2] upgrade to ContainerPilot 3.0.0-RC1 --- Dockerfile | 18 ++++------- bin/test.py | 2 +- docker-compose.yml | 1 - etc/containerpilot.json | 44 --------------------------- etc/containerpilot.json5 | 64 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 59 deletions(-) delete mode 100644 etc/containerpilot.json create mode 100644 etc/containerpilot.json5 diff --git a/Dockerfile b/Dockerfile index 7a852d1..6a434d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM percona:5.6 -ENV CONTAINERPILOT_VER 2.6.0 -ENV CONTAINERPILOT file:///etc/containerpilot.json +ENV CONTAINERPILOT_VER 3.0.0-RC1 +ENV CONTAINERPILOT /etc/containerpilot.json5 # By keeping a lot of discrete steps in a single RUN we can clean up after # ourselves in the same layer. This is gross but it saves ~100MB in the image @@ -23,6 +23,7 @@ RUN set -ex \ python-Consul==0.4.7 \ manta==2.5.0 \ mock==2.0.0 \ + json5==0.2.4 \ # \ # Add Consul from https://releases.hashicorp.com/consul \ # \ @@ -35,7 +36,7 @@ RUN set -ex \ # \ # Add ContainerPilot and set its configuration file path \ # \ - && export CONTAINERPILOT_CHECKSUM=c1bcd137fadd26ca2998eec192d04c08f62beb1f \ + && export CONTAINERPILOT_CHECKSUM=f67929d1c8567d31772085fc252338091a5f795c \ && curl -Lvo /tmp/containerpilot.tar.gz "https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \ && echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \ && tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \ @@ -59,13 +60,4 @@ COPY bin/manage.py /usr/local/bin/manage.py # override the parent entrypoint ENTRYPOINT [] - -# use --console to get error logs to stderr -CMD [ "containerpilot", \ - "mysqld", \ - "--console", \ - "--log-bin=mysql-bin", \ - "--log_slave_updates=ON", \ - "--gtid-mode=ON", \ - "--enforce-gtid-consistency=ON" \ -] +CMD ["/usr/local/bin/containerpilot"] diff --git a/bin/test.py b/bin/test.py index 299cc46..e8f084c 100644 --- a/bin/test.py +++ b/bin/test.py @@ -936,7 +936,7 @@ def test_env_parse(self): 'CONSUL': 'my.consul.example.com', 'CONSUL_AGENT': '1', - 'CONTAINERPILOT': 'file:///etc/containerpilot.json', + 'CONTAINERPILOT': '/etc/containerpilot.json5', 'MYSQL_DATABASE': 'test_mydb', 'MYSQL_USER': 'test_me', diff --git a/docker-compose.yml b/docker-compose.yml index 724d403..02f7b4d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,6 @@ services: env_file: _env network_mode: bridge environment: - - CONTAINERPILOT=file:///etc/containerpilot.json - CONSUL_AGENT=1 - LOG_LEVEL=INFO - SERVICE_NAME=mysql diff --git a/etc/containerpilot.json b/etc/containerpilot.json deleted file mode 100644 index 4d0cf34..0000000 --- a/etc/containerpilot.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL }}{{ end }}:8500", - "preStart": "python /usr/local/bin/manage.py", - "logging": { - "level": "{{ if .LOG_LEVEL }}{{ .LOG_LEVEL }}{{ else }}INFO{{ end }}" - }, - "services": [ - { - "name": "{{ if .SERVICE_NAME }}{{ .SERVICE_NAME }}{{ else }}mysql{{ end }}", - "port": 3306, - "health": "python /usr/local/bin/manage.py health", - "poll": 5, - "ttl": 25 - } - ], - "backends": [ - { - "name": "{{ if .SERVICE_NAME }}{{ .SERVICE_NAME }}{{ else }}mysql{{ end }}-primary", - "poll": 10, - "onChange": "python /usr/local/bin/manage.py on_change" - } - ], - "tasks": [ - { - "name": "snapshot_check", - "command": "python /usr/local/bin/manage.py snapshot_task", - "frequency": "5m", - "timeout": "10m" - } - ], - "coprocesses": [{{ if .CONSUL_AGENT }} - { - "name": "consul-agent (host:{{ .CONSUL }})", - "command": ["/usr/local/bin/consul", "agent", - "-data-dir=/data", - "-config-dir=/config", - "-rejoin", - "-retry-join", "{{ .CONSUL }}", - "-retry-max", "10", - "-retry-interval", "10s"], - "restarts": "unlimited" - }{{ end }} - ] -} diff --git a/etc/containerpilot.json5 b/etc/containerpilot.json5 new file mode 100644 index 0000000..a69b244 --- /dev/null +++ b/etc/containerpilot.json5 @@ -0,0 +1,64 @@ +{ + consul: '{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL | default "consul"}}{{ end }}:8500', + logging: { + level: '{{ .LOG_LEVEL | default "INFO" }}' + }, + jobs: [ + { + name: "preStart", + exec: "python /usr/local/bin/manage.py" + }, + { + name: '{{ .SERVICE_NAME | default "mysql" }}', + exec: ["mysqld", + "--console", + "--log-bin=mysql-bin", + "--log_slave_updates=ON", + "--gtid-mode=ON", + "--enforce-gtid-consistency=ON"], + port: 3306, + when: { + source: "preStart", + once: "exitSuccess" + }, + health: { + exec: "python /usr/local/bin/manage.py health", + interval: 5, + ttl: 25 + } + }, + { + name: "onChange", + exec: "python /usr/local/bin/manage.py on_change", + when: { + source:'watch.{{ .SERVICE_NAME | default "mysql" }}-primary' , + each: "changed" + } + }, + { + name: "snapshot-check", + exec: "python /usr/local/bin/manage.py snapshot_task", + timeout: "10m", + when: { + interval: "5m" + }, + }, + {{ if .CONSUL_AGENT }}{ + name: "consul-agent", + restarts: "unlimited", + exec: ["/usr/local/bin/consul", "agent", + "-data-dir=/data", + "-config-dir=/config", + "-rejoin", + "-retry-join", '{{ .CONSUL | default "consul"}}', + "-retry-max", "10", + "-retry-interval", "10s"] + }{{ end }} + ], + watches: [ + { + name: '{{ .SERVICE_NAME | default "mysql" }}-primary', + interval: 10 + } + ] +} From 9202b92cc7b840c2f522e02ea05755a6d6aa866f Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Thu, 1 Jun 2017 10:09:51 -0400 Subject: [PATCH 2/2] update testing and manage.py application for ContainerPilot 3.0.0-RC1 --- bin/manager/containerpilot.py | 58 ++++++++++++++--------------------- bin/test.py | 19 +++++------- docker-compose.yml | 2 +- makefile | 2 +- 4 files changed, 32 insertions(+), 49 deletions(-) diff --git a/bin/manager/containerpilot.py b/bin/manager/containerpilot.py index 6fdcc2b..45ec063 100644 --- a/bin/manager/containerpilot.py +++ b/bin/manager/containerpilot.py @@ -1,7 +1,10 @@ """ autopilotpattern/mysql ContainerPilot configuraton wrapper """ -import json import os import signal +import subprocess + +import json5 + from manager.utils import debug, env, to_flag, log, UNASSIGNED # pylint: disable=invalid-name,no-self-use,dangerous-default-value @@ -19,38 +22,20 @@ def __init__(self): def load(self, envs=os.environ): """ - Parses the ContainerPilot config file and interpolates the - environment into it the same way that ContainerPilot does. - The `state` attribute will be populated with the state - derived from the configuration file and not the state known - by Consul. + Fetches the ContainerPilot config file and asks ContainerPilot + to render it out so that all environment variables have been + interpolated. """ - self.path = env('CONTAINERPILOT', None, envs, - lambda x: x.replace('file://', '')) - with open(self.path, 'r') as f: - cfg = f.read() - - # remove templating so that we can parse it as JSON; we'll - # override the attributes directly in the resulting dict - cfg = cfg.replace('[{{ if .CONSUL_AGENT }}', '[') - cfg = cfg.replace('}{{ end }}', '}') - - # remove templating for SERVICE_NAME - service_name = env('SERVICE_NAME', 'mysql') - cfg = cfg.replace('{{ if .SERVICE_NAME }}{{ .SERVICE_NAME }}{{ else }}mysql{{ end }}',service_name) - config = json.loads(cfg) - - if env('CONSUL_AGENT', False, envs, to_flag): - config['consul'] = 'localhost:8500' - cmd = config['coprocesses'][0]['command'] - host_cfg_idx = cmd.index('-retry-join') + 1 - cmd[host_cfg_idx] = env('CONSUL', 'consul', envs) - config['coprocesses'][0]['command'] = cmd - else: - config['consul'] = env('CONSUL', 'consul', envs, - fn='{}:8500'.format) - config['coprocesses'] = [] + self.path = env('CONTAINERPILOT', None, envs) + try: + cfg = subprocess.check_output(['containerpilot', '-config', + self.path, '-template'], + env=envs.copy()) + except (subprocess.CalledProcessError, OSError) as ex: + log.error('containerpilot -template returned error: %s', ex) + raise(ex) + config = json5.loads(cfg) self.config = config @debug(log_output=True) @@ -61,8 +46,8 @@ def update(self): """ if self.state == UNASSIGNED: return False - if self.state and self.config['services'][0]['name'] != self.state: - self.config['services'][0]['name'] = self.state + if self.state and self.config['jobs'][1]['name'] != self.state: + self.config['jobs'][1]['name'] = self.state self._render() return True return False @@ -70,7 +55,7 @@ def update(self): @debug def _render(self): """ Writes the current config to file. """ - new_config = json.dumps(self.config) + new_config = json5.dumps(self.config) with open(self.path, 'w') as f: log.info('rewriting ContainerPilot config: %s', new_config) f.write(new_config) @@ -78,4 +63,7 @@ def _render(self): def reload(self): """ Force ContainerPilot to reload its configuration """ log.info('Reloading ContainerPilot configuration.') - os.kill(1, signal.SIGHUP) + try: + subprocess.check_output(['containerpilot', '-reload']) + except subprocess.CalledProcessError: + log.info("call to 'containerpilot -reload' failed") diff --git a/bin/test.py b/bin/test.py index e8f084c..8cf5912 100644 --- a/bin/test.py +++ b/bin/test.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta import fcntl -import json import logging import os import tempfile @@ -8,6 +7,7 @@ # pylint: disable=import-error import consul as pyconsul +import json5 import mock import manage @@ -835,25 +835,20 @@ def test_parse_with_consul_agent(self): self.environ['CONSUL_AGENT'] = '1' cp = ContainerPilot() cp.load(envs=self.environ) + self.assertEqual(cp.config['consul'], 'localhost:8500') - cmd = cp.config['coprocesses'][0]['command'] + cmd = cp.config['jobs'][4]['exec'] host_cfg_idx = cmd.index('-retry-join') + 1 self.assertEqual(cmd[host_cfg_idx], 'my.consul.example.com') self.assertEqual(cp.state, UNASSIGNED) def test_parse_without_consul_agent(self): - self.environ['CONSUL_AGENT'] = '0' - cp = ContainerPilot() - cp.load(envs=self.environ) - self.assertEqual(cp.config['consul'], 'my.consul.example.com:8500') - self.assertEqual(cp.config['coprocesses'], []) - self.assertEqual(cp.state, UNASSIGNED) - self.environ['CONSUL_AGENT'] = '' cp = ContainerPilot() cp.load(envs=self.environ) self.assertEqual(cp.config['consul'], 'my.consul.example.com:8500') - self.assertEqual(cp.config['coprocesses'], []) + self.assertFalse('consul-agent' in + [job['name'] for job in cp.config['jobs']]) self.assertEqual(cp.state, UNASSIGNED) def test_update(self): @@ -873,9 +868,9 @@ def test_update(self): cp.state = PRIMARY cp.update() with open(temp_file.name, 'r') as updated: - config = json.loads(updated.read()) + config = json5.loads(updated.read()) self.assertEqual(config['consul'], 'localhost:8500') - cmd = config['coprocesses'][0]['command'] + cmd = config['jobs'][4]['exec'] host_cfg_idx = cmd.index('-retry-join') + 1 self.assertEqual(cmd[host_cfg_idx], 'my.consul.example.com') diff --git a/docker-compose.yml b/docker-compose.yml index 02f7b4d..d04a836 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,4 +37,4 @@ services: dns: - 127.0.0.1 labels: - - triton.cns.services=mysql-consul + - triton.cns.services=mc diff --git a/makefile b/makefile index ec8eaaa..19982c4 100644 --- a/makefile +++ b/makefile @@ -104,7 +104,7 @@ integration-test: -e MANTA_USER=$(MANTA_USER) \ -e MANTA_SUBUSER=$(MANTA_SUBUSER) \ -e MANTA_ROLE=$(MANTA_ROLE) \ - -e CONSUL=mysql-consul.svc.$(TRITON_ACCOUNT).$(TRITON_DC).cns.joyent.com \ + -e CONSUL=mc.svc.$(TRITON_ACCOUNT).$(TRITON_DC).cns.joyent.com \ $(SDC_KEYS_VOL) -w /src \ $(test_image):$(tag) python3 tests.py