blob: 00e755c1c4ce06d70a991c472762e55a19a9d2b7 [file] [log] [blame]
#!/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())