Skip to content
Snippets Groups Projects
Select Git revision
  • c471f7d254a4a643bafd1a9920bb6f10970c5db8
  • master default protected
2 results

JSchnable.tex

Blame
  • update-tg-config.py 6.13 KiB
    #!/usr/bin/python
    
    import boto3
    import requests
    import sys
    import re
    import tempfile
    import os
    import difflib
    import subprocess
    import stat
    import hashlib
    
    def main():
      if len(sys.argv) != 4:
        print 'Usage: ' + sys.argv[0] + ' <service-name> <tg-name> <config-file>'
        return -1
    
      service_name = sys.argv[1]
      tg_name = sys.argv[2]
      config_file = sys.argv[3]
    
      if service_name not in ['wsrep', 'garb', 'drupal_varnish', 'drupal_memcache', 'varnish', 'efs', 'lsyncd', 'haproxy']:
        sys.stderr.write('Unknown service: ' + service_name + '\n')
        return -1
    
      prefix = get_prefix()
      tg_ips = get_tg_ips(prefix + tg_name)
      
      if len(tg_ips) == 0:
        print 'No IP addresses found in target group, aborting!'
        return -1
      
      with open(config_file) as file:
        config = file.read()
        file.close()
      new_config = globals()['generate_' + service_name + '_config'](config, tg_ips)
          
      #No update needed, so we can stop
      if new_config == config:
        return 0
      
      print 'Making the following change to ' + config_file + ':'
      for line in difflib.unified_diff(config.split("\n"), new_config.split("\n")):
        print line.strip()
      
      with open(config_file, 'w') as file:
        file.write(new_config)
    
      if 'load_' + service_name + '_config' in globals():
        globals()['load_' + service_name + '_config']()
    
      return 0
    
    # Returns the region the instance is currently running in (eg: 'us-east')
    def get_region():
      session_token = requests.put('http://169.254.169.254/latest/api/token', headers={'X-aws-ec2-metadata-token-ttl-seconds': 60}).text
      instance_info = requests.get('http://169.254.169.254/2018-09-24/dynamic/instance-identity/document/', headers={'X-aws-ec2-metadata-token': session_token}).json()
      return instance_info['region']
    
    # Returns the instance_id of this VM.
    def get_instance_id():
      with open('/var/lib/cloud/data/instance-id') as file:
        instance_id = file.read().strip()
      return instance_id
    
    # Returns the value of the 'unlcms:prefix' tag on this VM.
    def get_prefix():
      ec2 = boto3.resource('ec2', region_name=get_region())
      
      dept = ''
      campus = ''
      service = ''
      env = ''
      for tag in ec2.Instance(get_instance_id()).tags:
        if tag['Key'] == 'Department':
            dept = tag['Value']
        elif tag['Key'] == 'Environment':
            env = tag['Value']
        elif tag['Key'] == 'Campus':
            campus = tag['Value']
        elif tag['Key'] == 'Service':
            service = tag['Value']
      return "{}-{}-{}-{}-".format(
        dept,
        campus,
        service,
        env,
      )
    
    # Returns a list of IP addresses registered to the named Target Group
    def get_tg_ips(tg_name):
      region = get_region()
      elb = boto3.client('elbv2', region_name=region)
      ec2 = boto3.resource('ec2', region_name=region)
      
      ips = []
    
      tg_arn = elb.describe_target_groups(Names=[tg_name])['TargetGroups'][0]['TargetGroupArn']
      for instance in elb.describe_target_health(TargetGroupArn=tg_arn)['TargetHealthDescriptions']:
        ips.append(ec2.Instance(instance['Target']['Id']).private_ip_address)
      return ips
    
    # Returns the updated contents of the wsrep.conf config file
    def generate_wsrep_config(config, tg_ips):
      galera_nodes  = 'wsrep_cluster_address=gcomm://' + ','.join(tg_ips)
      
      pattern = re.compile(r'^wsrep_cluster_address=gcomm:\/\/.*$', re.MULTILINE)
      config = re.sub(pattern, galera_nodes, config)
      return config
    
    # Updates GALERA_NODES with the list of IP Addresses
    def generate_garb_config(config, tg_ips):
      galera_nodes  = 'GALERA_NODES="'
      galera_nodes += ','.join([ip + ':4567' for ip in tg_ips])
      galera_nodes += '"'
      
      pattern = re.compile(r'^GALERA_NODES="[^"]*"$', re.MULTILINE)
      config = re.sub(pattern, galera_nodes, config)
      return config
    
    # Updates a file included by settings.php to configure varnish servers
    def generate_drupal_varnish_config(config, tg_ips):
      config  = "<?php\n"
      config += "$conf['varnish_control_terminal'] = '"
      config += ' '.join([ip + ":6082" for ip in tg_ips])
      config += "';\n"
      return config
    
    def generate_drupal_memcache_config(config, tg_ips):
      config  = "<?php\n"
      config += "$conf['memcache_servers'] = ["
      config += ','.join(["'" + ip + ":11211' => 'default'" for ip in tg_ips])
      config += '];\n'
      return config
    
    def generate_varnish_config(config, tg_ips):
      NODE_BLOCK = (
        'backend node%s {\n'
        '  .host = "%s";\n'
        '}\n'
      )
      
      config = 'vcl 4.1;\n\n'
      for i, ip in enumerate(tg_ips):
        config += NODE_BLOCK % (i, ip)
      config += (
        'probe default {\n'
        '  .url = "/status.php";\n'
        '  .timeout = 30s;\n'
        '  .window = 6;\n'
        '  .threshold = 5;\n'
        '}\n'
        '\n'
        'import directors;\n'
        'sub vcl_init {\n'
        '  new vdir = directors.round_robin();\n'
      )
      for i, ip in enumerate(tg_ips):
        config += '  vdir.add_backend(node' + str(i) + ');\n'
      config += (
        '}\n'
        '\n'
        'sub vcl_recv {\n'
        '  set req.backend_hint = vdir.backend();\n'
        '}\n'
      )
      
      try:
        tmp_vcl = tempfile.NamedTemporaryFile(delete=False)
        tmp_vcl.write(config)
        tmp_vcl.close()
        os.chmod(tmp_vcl.name, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
        subprocess.check_call(['/sbin/varnishd', '-f', tmp_vcl.name, '-C'], stderr=open('/dev/null', 'w'))
      except:
        raise Exception('Error validating new VCL!')
      finally:
        os.unlink(tmp_vcl.name)
      
      return config
    
    def generate_lsyncd_config(config, tg_ips):
      drupal_nodes  = "tg_member_ips = {'" + "','".join(tg_ips) + "'}"
      pattern = re.compile(r'^tg_member_ips = {[^}]*}$', re.MULTILINE)
      config = re.sub(pattern, drupal_nodes, config)
      return config
    
    def generate_haproxy_config(config, tg_ips):
      server_list = (
        '  #BEGIN SERVER LIST\n'
        '  #DO NOT EDIT!\n'
      )
      for i, ip in enumerate(tg_ips):
        server_list += '  server %s %s:80\n' % (hashlib.sha256(ip).hexdigest()[:8], ip)
      server_list += '  #END SERVER LIST\n'
      pattern = re.compile(r'^\s*#BEGIN SERVER LIST\n  #DO NOT EDIT!\n(.*\n)*  #END SERVER LIST\n', re.MULTILINE)
      config = re.sub(pattern, server_list, config)
      return config
      
    
    def load_varnish_config():
      subprocess.call(['/sbin/varnishreload', '-m', '0'])
    
    def load_lsyncd_config():
      subprocess.call(['/bin/systemctl', 'restart', 'lsyncd'])
      
    def load_haproxy_config():
      subprocess.call(['/bin/systemctl', 'reload', 'haproxy'])
    
    sys.exit(main())