9077742fd4
If a cloud is setup to use server groups, then select it based on the group we pass via the CLI. Change-Id: I2662af9daa39024a306d6e60eb8c71a450b67701 Signed-off-by: Paul Belanger <pabelanger@redhat.com>
270 lines
9.4 KiB
Python
Executable File
270 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright (C) 2011-2012 OpenStack LLC.
|
|
#
|
|
# 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 shutil
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import traceback
|
|
|
|
import ansible_runner
|
|
import openstack
|
|
import paramiko
|
|
|
|
import utils
|
|
|
|
SCRIPT_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
|
|
|
|
try:
|
|
# This unactionable warning does not need to be printed over and over.
|
|
import requests.packages.urllib3
|
|
requests.packages.urllib3.disable_warnings()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class AnsibleRunner(object):
|
|
def __init__(self, keep=False):
|
|
self.keep = keep
|
|
self.root = tempfile.mkdtemp()
|
|
# env directory
|
|
self.env_root = os.path.join(self.root, 'env')
|
|
os.makedirs(self.env_root)
|
|
self.ssh_key = os.path.join(self.env_root, 'ssh_key')
|
|
# inventory directory
|
|
self.inventory_root = os.path.join(self.root, 'inventory')
|
|
shutil.copytree(
|
|
os.path.expanduser('~/.config/windmill/ansible'),
|
|
self.inventory_root)
|
|
self.hosts = os.path.join(self.inventory_root, 'hosts')
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, etype, value, tb):
|
|
if not self.keep:
|
|
shutil.rmtree(self.root)
|
|
|
|
|
|
def bootstrap_server(server, key, name, group, keep, timeout):
|
|
ip = server.public_v4
|
|
ssh_kwargs = dict(pkey=key)
|
|
ansible_user = None
|
|
|
|
print("--- Running initial configuration on host %s ---" % ip)
|
|
host_key = utils.nodescan(ip)
|
|
if not host_key:
|
|
raise Exception("Unable to find ssh-ecdsa SSH host key")
|
|
|
|
for username in ['ubuntu', 'centos']:
|
|
ssh_client = utils.ssh_connect(
|
|
ip, username, ssh_kwargs, timeout=timeout)
|
|
if ssh_client:
|
|
ansible_user = username
|
|
break
|
|
|
|
if not ssh_client:
|
|
raise Exception("Unable to log in via SSH")
|
|
|
|
with AnsibleRunner(keep) as runner:
|
|
with open(runner.ssh_key, 'w') as key_file:
|
|
key.write_private_key(key_file)
|
|
os.chmod(runner.ssh_key, 0o600)
|
|
|
|
with open(runner.hosts, 'w') as inventory_file:
|
|
inventory_file.write(
|
|
"[{group}]\n{host} ansible_host={ip} "
|
|
"ansible_user={user} "
|
|
"ansible_ssh_host_key_ed25519_public={host_key}".format(
|
|
group=group, host=name, ip=server.interface_ip,
|
|
user=ansible_user, host_key=host_key))
|
|
|
|
project_dir = os.path.join(
|
|
SCRIPT_DIR, '..', 'playbooks', 'bootstrap-ansible')
|
|
|
|
r = ansible_runner.run(
|
|
inventory=runner.hosts, private_data_dir=runner.root,
|
|
playbook='site.yaml', project_dir=project_dir)
|
|
|
|
if r.rc:
|
|
raise Exception("Ansible runner failed")
|
|
|
|
|
|
def build_server(cloud, name, group, image, flavor,
|
|
volume, keep, network, boot_from_volume, config_drive,
|
|
mount_path, fs_label, availability_zone, environment,
|
|
volume_size, timeout):
|
|
key = None
|
|
server = None
|
|
_group = None
|
|
if group:
|
|
_group = cloud.get_server_group(group)
|
|
|
|
create_kwargs = dict(image=image, flavor=flavor, name=name,
|
|
reuse_ips=False, wait=True,
|
|
boot_from_volume=boot_from_volume,
|
|
volume_size=volume_size,
|
|
network=network,
|
|
config_drive=config_drive,
|
|
timeout=timeout, group=_group)
|
|
|
|
if availability_zone:
|
|
create_kwargs['availability_zone'] = availability_zone
|
|
|
|
if volume:
|
|
create_kwargs['volumes'] = [volume]
|
|
|
|
key_name = 'launch-%i' % (time.time())
|
|
key = paramiko.RSAKey.generate(2048)
|
|
public_key = key.get_name() + ' ' + key.get_base64()
|
|
cloud.create_keypair(key_name, public_key)
|
|
create_kwargs['key_name'] = key_name
|
|
|
|
try:
|
|
server = cloud.create_server(**create_kwargs)
|
|
except Exception:
|
|
try:
|
|
cloud.delete_keypair(key_name)
|
|
except Exception:
|
|
print("Exception encountered deleting keypair:")
|
|
traceback.print_exc()
|
|
raise
|
|
|
|
try:
|
|
cloud.delete_keypair(key_name)
|
|
|
|
server = cloud.get_openstack_vars(server)
|
|
|
|
bootstrap_server(server, key, name, group, keep, timeout)
|
|
|
|
except Exception:
|
|
print("****")
|
|
print("Server %s failed to build!" % (server.id))
|
|
try:
|
|
if keep:
|
|
print("Keeping as requested")
|
|
print(
|
|
"Run to delete -> openstack server delete %s" % server.id)
|
|
else:
|
|
cloud.delete_server(server.id, delete_ips=True)
|
|
except Exception:
|
|
print("Exception encountered deleting server:")
|
|
traceback.print_exc()
|
|
print("The original exception follows:")
|
|
print("****")
|
|
# Raise the important exception that started this
|
|
raise
|
|
|
|
return server
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("name", help="server name")
|
|
parser.add_argument("group", help="server group")
|
|
parser.add_argument("--cloud", dest="cloud", required=True,
|
|
help="cloud name")
|
|
parser.add_argument("--region", dest="region",
|
|
help="cloud region")
|
|
parser.add_argument("--flavor", dest="flavor", default='1GB',
|
|
help="name (or substring) of flavor")
|
|
parser.add_argument("--image", dest="image",
|
|
default="Ubuntu 18.04 LTS (Bionic Beaver) (PVHVM)",
|
|
help="image name")
|
|
parser.add_argument("--environment", dest="environment",
|
|
help="Puppet environment to use",
|
|
default=None)
|
|
parser.add_argument("--volume", dest="volume",
|
|
help="UUID of volume to attach to the new server.",
|
|
default=None)
|
|
parser.add_argument("--mount-path", dest="mount_path",
|
|
help="Path to mount cinder volume at.",
|
|
default=None)
|
|
parser.add_argument("--fs-label", dest="fs_label",
|
|
help="FS label to use when mounting cinder volume.",
|
|
default=None)
|
|
parser.add_argument("--boot-from-volume", dest="boot_from_volume",
|
|
help="Create a boot volume for the server and use it.",
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument("--volume-size", dest="volume_size",
|
|
help="Size of volume (GB) for --boot-from-volume",
|
|
default="50")
|
|
parser.add_argument("--keep", dest="keep",
|
|
help="Don't clean up or delete the server on error.",
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument("--verbose", dest="verbose", default=False,
|
|
action='store_true',
|
|
help="Be verbose about logging cloud actions")
|
|
parser.add_argument("--network", dest="network", default=None,
|
|
help="network label to attach instance to")
|
|
parser.add_argument("--config-drive", dest="config_drive",
|
|
help="Boot with config_drive attached.",
|
|
action='store_true',
|
|
default=False)
|
|
parser.add_argument("--timeout", dest="timeout",
|
|
help="Increase timeouts (default 600s)",
|
|
type=int, default=600)
|
|
parser.add_argument("--az", dest="availability_zone", default=None,
|
|
help="AZ to boot in.")
|
|
options = parser.parse_args()
|
|
|
|
openstack.enable_logging(debug=options.verbose)
|
|
|
|
cloud_kwargs = {}
|
|
if options.region:
|
|
cloud_kwargs['region_name'] = options.region
|
|
cloud = openstack.connect(cloud=options.cloud, **cloud_kwargs)
|
|
|
|
flavor = cloud.get_flavor(options.flavor)
|
|
if flavor:
|
|
print("Found flavor", flavor.name)
|
|
else:
|
|
print("Unable to find matching flavor; flavor list:")
|
|
for i in cloud.list_flavors():
|
|
print(i.name)
|
|
sys.exit(1)
|
|
|
|
image = cloud.get_image_exclude(options.image, 'deprecated')
|
|
if image:
|
|
print("Found image", image.name)
|
|
else:
|
|
print("Unable to find matching image; image list:")
|
|
for i in cloud.list_images():
|
|
print(i.name)
|
|
sys.exit(1)
|
|
|
|
server = build_server(cloud, options.name, options.group, image, flavor,
|
|
options.volume, options.keep,
|
|
options.network, options.boot_from_volume,
|
|
options.config_drive,
|
|
options.mount_path, options.fs_label,
|
|
options.availability_zone,
|
|
options.environment, options.volume_size,
|
|
options.timeout)
|
|
|
|
print('UUID=%s\nIPV4=%s\nIPV6=%s\n' % (
|
|
server.id, server.public_v4, server.public_v6))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|