| #!/usr/bin/env python |
| |
| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Tool to sync lab servers to the "Allowed Networks" of a CloudSQL instance. |
| |
| For a lab server to access CloudSQL instance, the server's IP must be added to |
| the "Allowed Networks" list of the CloudSQL instance. This tool is to be used to |
| read the list of lab servers from server database and update the list of |
| "Allowed Networks" of a given CloudSQL instance. |
| |
| The tool also reads CLOUD/tko_access_servers from global config to add these |
| servers to the "Allowed Networks" list of the CloudSQL instance. This allows |
| servers that do not run Autotest code can access the CloudSQL instance. |
| |
| Note that running this tool will overwrite existing IPs in the "Allowed |
| Networks" list. Therefore, manually editing that list from CloudSQL console |
| should be prohibited. Instead, the servers should be added to |
| CLOUD/tko_access_servers in shadow_config.ini. |
| |
| """ |
| |
| import argparse |
| import socket |
| import sys |
| |
| import common |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import global_config |
| from autotest_lib.client.common_lib.cros import retry |
| from autotest_lib.server import frontend |
| |
| |
| ROLES_REQUIRE_TKO_ACCESS = { |
| 'afe', |
| 'database', |
| 'drone', |
| 'scheduler', |
| 'sentinel', |
| 'shard', |
| 'skylab_drone', |
| } |
| |
| |
| def gcloud_login(project): |
| """Login to Google Cloud service for gcloud command to run. |
| |
| @param project: Name of the Google Cloud project. |
| """ |
| # Login with user account. If the user hasn't log in yet, the script will |
| # print a url and ask for a verification code. User should load the url in |
| # browser, and copy the verification code from the web page. When private IP |
| # can be supported to be added using non-corp account, the login can be done |
| # through service account and key file, e.g., |
| # gcloud auth activate-service-account --key-file ~/key.json |
| utils.run('gcloud auth login', stdout_tee=sys.stdout, |
| stderr_tee=sys.stderr, stdin=sys.stdin) |
| |
| |
| @retry.retry(error.CmdError, timeout_min=3) |
| def _fetch_external_ip(server_name): |
| return utils.run('ssh %s curl -s ifconfig.me' % server_name).stdout.rstrip() |
| |
| |
| def update_allowed_networks(project, instance, afe=None, extra_servers=None, |
| dryrun=False): |
| """Update the "Allowed Networks" list of the given CloudSQL instance. |
| |
| @param project: Name of the Google Cloud project. |
| @param instance: Name of the CloudSQL instance. |
| @param afe: Server of the frontend RPC, default to None to use the server |
| specified in global config. |
| @param extra_servers: Extra servers to be included in the "Allowed Networks" |
| list. Default is None. |
| @param dryrun: Boolean indicating whether this is a dryrun. |
| """ |
| # Get the IP address of all servers need access to CloudSQL instance. |
| rpc = frontend.AFE(server=afe) |
| servers = [s['hostname'] for s in rpc.run('get_servers') |
| if s['status'] != 'repair_required' and |
| ROLES_REQUIRE_TKO_ACCESS.intersection(s['roles'])] |
| if extra_servers: |
| servers.extend(extra_servers.split(',')) |
| # Extra servers can be listed in CLOUD/tko_access_servers shadow config. |
| tko_servers = global_config.global_config.get_config_value( |
| 'CLOUD', 'tko_access_servers', default='') |
| if tko_servers: |
| servers.extend(tko_servers.split(',')) |
| print 'Adding servers %s to access list for projects %s' % (servers, |
| instance) |
| print 'Fetching their IP addresses...' |
| ips = [] |
| for name in servers: |
| try: |
| # collect internal ips |
| ips.append(socket.gethostbyname(name)) |
| # collect external ips |
| ips.append(_fetch_external_ip(name)) |
| except socket.gaierror: |
| print 'Failed to resolve internal IP address for name %s' % name |
| raise |
| except error.TimeoutException: |
| print 'Failed to resolve external IP address for %s' % name |
| raise |
| |
| print '...Done: %s' % ips |
| |
| cidr_ips = [str(ip) + '/32' for ip in ips] |
| |
| if dryrun: |
| print 'This is a dryrun: skip updating glcoud sql whitelists.' |
| return |
| |
| login = False |
| while True: |
| try: |
| utils.run('gcloud config set project %s -q' % project) |
| cmd = ('gcloud sql instances patch %s --authorized-networks %s ' |
| '-q' % (instance, ','.join(cidr_ips))) |
| print 'Running command to update whitelists: "%s"' % cmd |
| utils.run(cmd, stdout_tee=sys.stdout, stderr_tee=sys.stderr) |
| return |
| except error.CmdError: |
| if login: |
| raise |
| |
| # Try to login and retry if the command failed. |
| gcloud_login(project) |
| login = True |
| |
| |
| def main(): |
| """main script.""" |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--project', type=str, dest='project', |
| help='Name of the Google Cloud project.') |
| parser.add_argument('--instance', type=str, dest='instance', |
| help='Name of the CloudSQL instance.') |
| parser.add_argument('--afe', type=str, dest='afe', |
| help='Name of the RPC server to get server list.', |
| default=None) |
| parser.add_argument('--extra_servers', type=str, dest='extra_servers', |
| help=('Extra servers to be included in the "Allowed ' |
| 'Networks" list separated by comma.'), |
| default=None) |
| parser.add_argument('--dryrun', dest='dryrun', action='store_true', |
| default=False, |
| help='Fetch IPs without updating whitelists in gcloud.') |
| options = parser.parse_args() |
| |
| update_allowed_networks(options.project, options.instance, options.afe, |
| options.extra_servers, options.dryrun) |
| |
| |
| if __name__ == '__main__': |
| main() |