| #!/usr/bin/env python3 |
| # |
| # Copyright 2018 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import os |
| import queue |
| import sys |
| import threading |
| import urllib.error |
| import urllib.request |
| import yaml |
| |
| import gcloud |
| |
| |
| CONFIG_URL = 'https://raw.githubusercontent.com/bazelbuild/continuous-integration/master/buildkite/instances.yml' |
| LOCAL_CONFIG_FILE_NAME = 'instances.yml' |
| |
| WORK_QUEUE = queue.Queue() |
| |
| |
| def worker(): |
| while True: |
| item = WORK_QUEUE.get() |
| if not item: |
| break |
| try: |
| # We take three keys out of the config item. The rest is passed |
| # as-is to create_instance_template() and thus to the gcloud |
| # command line tool. |
| count = item.pop('count') |
| instance_group_name = item.pop('name') |
| zone = item.pop('zone') |
| |
| template_name = instance_group_name + '-template' |
| |
| if gcloud.delete_instance_group(instance_group_name, zone=zone).returncode == 0: |
| print('Deleted existing instance group: {}'.format(instance_group_name)) |
| |
| if gcloud.delete_instance_template(template_name).returncode == 0: |
| print('Deleted existing VM template: {}'.format(template_name)) |
| |
| gcloud.create_instance_template(template_name, **item) |
| |
| gcloud.create_instance_group(instance_group_name, zone=zone, |
| base_instance_name=instance_group_name, |
| template=template_name, size=count) |
| finally: |
| WORK_QUEUE.task_done() |
| |
| |
| def read_config_file(use_local_config): |
| content = None |
| if use_local_config: |
| path = os.path.join(os.getcwd(), LOCAL_CONFIG_FILE_NAME) |
| with open(path, "rb") as fd: |
| content = fd.read().decode('utf-8') |
| else: |
| with urllib.request.urlopen(CONFIG_URL) as resp: |
| content = resp.read().decode('utf-8') |
| return yaml.safe_load(content) |
| |
| |
| def main(argv=None): |
| if argv is None: |
| argv = sys.argv[1:] |
| |
| parser = argparse.ArgumentParser(description="Bazel CI Instance Creation") |
| parser.add_argument('names', type=str, nargs='*', |
| help='List of instance (group) names that should be created. ' |
| 'These values must correspond to "name" entries in the ' |
| 'Yaml configuration, e.g. "bk-pipeline-ubuntu1804-java8".') |
| parser.add_argument('--local_config', action="store_true", |
| help='Whether to read the configuration from CWD/%s' % LOCAL_CONFIG_FILE_NAME) |
| |
| args = parser.parse_args(argv) |
| config = read_config_file(args.local_config) |
| |
| # Verify names passed on the command-line. |
| valid_names = [item['name'] for item in config['instance_groups']] |
| for name in args.names: |
| if name not in valid_names: |
| print("Unknown instance name: {}!".format(name)) |
| print("\nValid instance names are: {}".format(' '.join(valid_names))) |
| return 1 |
| if not args.names: |
| parser.print_help() |
| print("\nValid instance names are: {}".format(' '.join(valid_names))) |
| return 1 |
| |
| # Put VM creation instructions into the work queue. |
| for instance in config['instance_groups']: |
| if instance['name'] not in args.names: |
| continue |
| WORK_QUEUE.put({ |
| **config['default_vm'], |
| **instance |
| }) |
| |
| # Spawn worker threads that will create the VMs. |
| threads = [] |
| for _ in range(WORK_QUEUE.qsize()): |
| t = threading.Thread(target=worker) |
| t.start() |
| threads.append(t) |
| |
| # Wait for all VMs to be created. |
| WORK_QUEUE.join() |
| |
| # Signal worker threads to exit. |
| for _ in range(len(threads)): |
| WORK_QUEUE.put(None) |
| |
| # Wait for worker threads to exit. |
| for t in threads: |
| t.join() |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |