blob: a9a94248e93e574430276cc08dc90d57559c8f0d [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 queue
import subprocess
import sys
import threading
import gcloud
DEBUG = True
LOCATION = 'europe-west1-d'
# Note that the hostnames are parsed and trigger specific behavior for different use cases.
# The following parts have a special meaning:
#
# - "buildkite": This is a normal production VM running the Buildkite agent.
# - "pipeline": This is a special production VM that only runs pipeline setup scripts.
# - "testing": This is a shared VM that can be used by project members for experiments.
# It does not run the Buildkite agent.
# - "$USER": This is a VM used by one specific engineer for tests. It does not run the Buildkite
# agent.
#
DEFAULT_VM = {
'boot_disk_size': '50GB',
'boot_disk_type': 'pd-ssd',
'image_project': 'bazel-public',
'machine_type': 'n1-standard-32',
'min_cpu_platform': 'Intel Skylake',
'network': 'buildkite',
'scopes': 'cloud-platform',
'service_account': 'remote-account@bazel-public.iam.gserviceaccount.com',
}
INSTANCE_GROUPS = {
'buildkite-ubuntu1404': {
'count': 8,
'image_family': 'buildkite-ubuntu1404',
'local_ssd': 'interface=nvme',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
},
'buildkite-ubuntu1604': {
'count': 8,
'image_family': 'buildkite-ubuntu1604',
'local_ssd': 'interface=nvme',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
},
'buildkite-pipeline-ubuntu1604': {
'count': 1,
'image_family': 'buildkite-pipeline-ubuntu1604',
'local_ssd': 'interface=nvme',
'machine_type': 'n1-standard-8',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
},
'buildkite-trusted-ubuntu1604': {
'count': 1,
'image_family': 'buildkite-trusted-ubuntu1604',
'local_ssd': 'interface=nvme',
'machine_type': 'n1-standard-8',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
'service_account': 'bazel-release-process@bazel-public.iam.gserviceaccount.com',
},
'buildkite-windows': {
'count': 8,
'image_family': 'buildkite-windows',
'local_ssd': 'interface=scsi',
'metadata_from_file': 'windows-startup-script-ps1=startup-windows.ps1',
},
}
SINGLE_INSTANCES = {
'testing-ubuntu1404': {
'image_family': 'buildkite-ubuntu1404',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
'disk': 'name={0},device-name={0},mode=rw,boot=no'.format('testing-ubuntu1404-persistent'),
},
'testing-ubuntu1604': {
'image_family': 'buildkite-ubuntu1604',
'metadata_from_file': 'startup-script=startup-ubuntu.sh',
'disk': 'name={0},device-name={0},mode=rw,boot=no'.format('testing-ubuntu1604-persistent'),
},
'testing-windows': {
'boot_disk_size': '500GB',
'image_family': 'buildkite-windows',
},
}
PRINT_LOCK = threading.Lock()
WORK_QUEUE = queue.Queue()
def debug(*args, **kwargs):
if DEBUG:
print(*args, **kwargs)
def run(args, **kwargs):
debug('Running: {}'.format(' '.join(args)))
return subprocess.run(args, **kwargs)
def instance_group_task(instance_group_name, count, **kwargs):
template_name = instance_group_name + '-template'
if gcloud.delete_instance_group(instance_group_name, zone=LOCATION).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, **kwargs)
gcloud.create_instance_group(instance_group_name, zone=LOCATION,
base_instance_name=instance_group_name,
template=template_name, size=count)
def single_instance_task(instance_name, **kwargs):
if gcloud.delete_instance(instance_name, zone=LOCATION).returncode == 0:
print('Deleted existing instance: {}'.format(instance_name))
gcloud.create_instance(instance_name, zone=LOCATION, **kwargs)
def worker():
while True:
item = WORK_QUEUE.get()
if not item:
break
try:
if 'instance_group_name' in item:
instance_group_task(**item)
elif 'instance_name' in item:
single_instance_task(**item)
else:
raise Exception('Unknown task: {}'.format(item))
finally:
WORK_QUEUE.task_done()
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
# Put VM creation instructions into the work queue.
for instance_group_name, params in INSTANCE_GROUPS.items():
# If the user specified instance (group) names on the command-line, we process only these
# instances, otherwise we process all.
if argv and instance_group_name not in argv:
continue
WORK_QUEUE.put({
**DEFAULT_VM,
'instance_group_name': instance_group_name,
**params
})
for instance_name, params in SINGLE_INSTANCES.items():
# If the user specified instance (group) names on the command-line, we process only these
# instances, otherwise we process all.
if argv and instance_name not in argv:
continue
WORK_QUEUE.put({
**DEFAULT_VM,
'instance_name': instance_name,
**params
})
# 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())