From 387d4a2b6a3e25eff5fce8cbffbdafc67908cc03 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Wed, 14 Oct 2015 13:41:40 -0700 Subject: [PATCH] Add a shade launch-node Created as a new script so that we aren't hosed if this doesn't work with current providers. Change-Id: Ia8d35d0acbfb773ca710c9383d9b746e786766bc Depends-On: I26bc94408441edf067493b7ffd50eebd9dd95e75 --- launch/dns.py | 55 +++++-- launch/shade-launch-node.py | 297 ++++++++++++++++++++++++++++++++++++ launch/utils.py | 2 + 3 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 launch/shade-launch-node.py diff --git a/launch/dns.py b/launch/dns.py index c4f516df39..eb962692e9 100755 --- a/launch/dns.py +++ b/launch/dns.py @@ -18,21 +18,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys import os import argparse import utils -NOVA_USERNAME = os.environ['OS_USERNAME'] -NOVA_PASSWORD = os.environ['OS_PASSWORD'] -NOVA_URL = os.environ['OS_AUTH_URL'] -NOVA_PROJECT_ID = os.environ['OS_TENANT_NAME'] -NOVA_REGION_NAME = os.environ['OS_REGION_NAME'] - -SCRIPT_DIR = os.path.dirname(sys.argv[0]) - def get_client(): + #TODO use shade + NOVA_USERNAME = os.environ['OS_USERNAME'] + NOVA_PASSWORD = os.environ['OS_PASSWORD'] + NOVA_URL = os.environ['OS_AUTH_URL'] + NOVA_PROJECT_ID = os.environ['OS_TENANT_NAME'] + NOVA_REGION_NAME = os.environ['OS_REGION_NAME'] + args = [NOVA_USERNAME, NOVA_PASSWORD, NOVA_PROJECT_ID, NOVA_URL] kwargs = {} kwargs['region_name'] = NOVA_REGION_NAME @@ -84,6 +82,45 @@ def print_dns(client, name): server.name, ip4)) +def shade_print_dns(server): + ip4 = server.public_v4 + ip6 = server.public_v6 + href = utils.get_href(server) + + print + print "Run the following commands to set up DNS:" + print + print ". ~root/rackdns-venv/bin/activate" + print + print ( + "rackdns rdns-create --name %s \\\n" + " --data %s \\\n" + " --server-href %s \\\n" + " --ttl 3600" % ( + server.name, ip6, href)) + print + print ( + "rackdns rdns-create --name %s \\\n" + " --data %s \\\n" + " --server-href %s \\\n" + " --ttl 3600" % ( + server.name, ip4, href)) + print + print ". ~root/ci-launch/openstack-rs-nova.sh" + print + print ( + "rackdns record-create --name %s \\\n" + " --type AAAA --data %s \\\n" + " --ttl 3600 openstack.org" % ( + server.name, ip6)) + print + print ( + "rackdns record-create --name %s \\\n" + " --type A --data %s \\\n" + " --ttl 3600 openstack.org" % ( + server.name, ip4)) + + def main(): parser = argparse.ArgumentParser() parser.add_argument("name", help="server name") diff --git a/launch/shade-launch-node.py b/launch/shade-launch-node.py new file mode 100644 index 0000000000..f8f3392d4b --- /dev/null +++ b/launch/shade-launch-node.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python + +# Launch a new OpenStack project infrastructure node. + +# 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 sys +import os +import time +import traceback +import argparse + +import dns +import utils + +import os_client_config +import paramiko +import shade + +SCRIPT_DIR = 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: + pass + + +def bootstrap_server(server, key, cert, environment, name, + puppetmaster, volume, floating_ip_pool): + ip = server.public_v4 + ssh_kwargs = dict(pkey=key) + + print 'Public IP', ip + for username in ['root', 'ubuntu', 'centos', 'admin']: + ssh_client = utils.ssh_connect(ip, username, ssh_kwargs, timeout=600) + if ssh_client: + break + + if not ssh_client: + raise Exception("Unable to log in via SSH") + + # cloud-init puts the "please log in as user foo" message and + # subsequent exit() in root's authorized_keys -- overwrite it with + # a normal version to get root login working again. + if username != 'root': + ssh_client.ssh("sudo cp ~/.ssh/authorized_keys" + " ~root/.ssh/authorized_keys") + ssh_client.ssh("sudo chmod 644 ~root/.ssh/authorized_keys") + ssh_client.ssh("sudo chown root.root ~root/.ssh/authorized_keys") + + ssh_client = utils.ssh_connect(ip, 'root', ssh_kwargs, timeout=600) + + if server.public_v6: + ssh_client.ssh('ping6 -c5 -Q 0x10 review.openstack.org ' + '|| ping6 -c5 -Q 0x10 wiki.openstack.org') + + ssh_client.scp(os.path.join(SCRIPT_DIR, '..', 'make_swap.sh'), + 'make_swap.sh') + ssh_client.ssh('bash -x make_swap.sh') + + if volume: + ssh_client.scp(os.path.join(SCRIPT_DIR, '..', 'mount_volume.sh'), + 'mount_volume.sh') + ssh_client.ssh('bash -x mount_volume.sh') + + ssh_client.scp(os.path.join(SCRIPT_DIR, '..', 'install_puppet.sh'), + 'install_puppet.sh') + ssh_client.ssh('bash -x install_puppet.sh') + + certname = cert[:(0 - len('.pem'))] + ssh_client.ssh("mkdir -p /var/lib/puppet/ssl/certs") + ssh_client.ssh("mkdir -p /var/lib/puppet/ssl/private_keys") + ssh_client.ssh("mkdir -p /var/lib/puppet/ssl/public_keys") + ssh_client.ssh("chown -R puppet:root /var/lib/puppet/ssl") + ssh_client.ssh("chown -R puppet:puppet /var/lib/puppet/ssl/private_keys") + ssh_client.ssh("chmod 0771 /var/lib/puppet/ssl") + ssh_client.ssh("chmod 0755 /var/lib/puppet/ssl/certs") + ssh_client.ssh("chmod 0750 /var/lib/puppet/ssl/private_keys") + ssh_client.ssh("chmod 0755 /var/lib/puppet/ssl/public_keys") + + for ssldir in ['/var/lib/puppet/ssl/certs/', + '/var/lib/puppet/ssl/private_keys/', + '/var/lib/puppet/ssl/public_keys/']: + ssh_client.scp(os.path.join(ssldir, cert), + os.path.join(ssldir, cert)) + + ssh_client.scp("/var/lib/puppet/ssl/crl.pem", + "/var/lib/puppet/ssl/crl.pem") + ssh_client.scp("/var/lib/puppet/ssl/certs/ca.pem", + "/var/lib/puppet/ssl/certs/ca.pem") + + (rc, output) = ssh_client.ssh( + "puppet agent " + "--environment %s " + "--server %s " + "--detailed-exitcodes " + "--no-daemonize --verbose --onetime --pluginsync true " + "--certname %s" % (environment, puppetmaster, certname), error_ok=True) + utils.interpret_puppet_exitcodes(rc, output) + + try: + ssh_client.ssh("reboot") + except Exception as e: + # Some init system kill the connection too fast after reboot. + # Deal with it by ignoring ssh errors when rebooting. + if e.rc == -1: + pass + else: + raise + + +def build_server(cloud, name, image, flavor, cert, environment, + puppetmaster, volume, keep, net_label, + floating_ip_pool, boot_from_volume): + key = None + server = None + + create_kwargs = dict(image=image, flavor=flavor, name=name, + reuse_ips=False, wait=True) + + #TODO: test with rax + #TODO: use shade + if boot_from_volume: + block_mapping = [{ + 'boot_index': '0', + 'delete_on_termination': True, + 'destination_type': 'volume', + 'uuid': image.id, + 'source_type': 'image', + 'volume_size': '50', + }] + create_kwargs['image'] = None + create_kwargs['block_device_mapping_v2'] = block_mapping + + #TODO: use shade + #if net_label: + # nics = [] + # for net in client.networks.list(): + # if net.label == net_label: + # nics.append({'net-id': net.id}) + # create_kwargs['nics'] = nics + + 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) + + # TODO: use shade + if volume: + raise Exception("not implemented") + #vobj = client.volumes.create_server_volume( + # server.id, volume, None) + #if not vobj: + # raise Exception("Couldn't attach volume") + + server = cloud.get_openstack_vars(server) + bootstrap_server(server, key, cert, environment, name, + puppetmaster, volume, floating_ip_pool) + print('UUID=%s\nIPV4=%s\nIPV6=%s\n' % (server.id, + server.accessIPv4, + server.accessIPv6)) + except Exception: + try: + if keep: + print "Server failed to build, keeping as requested." + else: + cloud.delete_server(server.id, delete_ips=True) + except Exception: + print "Exception encountered deleting server:" + traceback.print_exc() + # 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("--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 14.04 LTS (Trusty Tahr) (PVHVM)", + help="image name") + parser.add_argument("--environment", dest="environment", + default="production", + help="puppet environment name") + parser.add_argument("--cert", dest="cert", + help="name of signed puppet certificate file (e.g., " + "hostname.example.com.pem)") + parser.add_argument("--server", dest="server", help="Puppetmaster to use.", + default="puppetmaster.openstack.org") + parser.add_argument("--volume", dest="volume", + help="UUID of volume to attach to the new server.", + 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("--keep", dest="keep", + help="Don't clean up or delete the server on error.", + action='store_true', + default=False) + parser.add_argument("--net-label", dest="net_label", default='', + help="network label to attach instance to") + parser.add_argument("--fip-pool", dest="floating_ip_pool", default=None, + help="pool to assign floating IP from") + options = parser.parse_args() + + if options.cert: + cert = options.cert + else: + cert = options.name + ".pem" + + if not os.path.exists(os.path.join("/var/lib/puppet/ssl/private_keys", + cert)): + raise Exception("Please specify the name of a signed puppet cert.") + + cloud_kwargs = {} + if options.region: + cloud_kwargs['region_name'] = options.region + cloud_config = os_client_config.OpenStackConfig().get_one_cloud( + options.cloud, **cloud_kwargs) + + cloud = shade.OpenStackCloud(cloud_config) + + 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) + + if options.volume: + print "The --volume option does not support cinder; until it does" + print "it should not be used." + sys.exit(1) + + server = build_server(cloud, options.name, image, flavor, cert, + options.environment, options.server, + options.volume, options.keep, + options.net_label, options.floating_ip_pool, + options.boot_from_volume) + dns.shade_print_dns(server) + # Remove the ansible inventory cache so that next run finds the new + # server + if os.path.exists('/var/cache/ansible-inventory/ansible-inventory.cache'): + os.unlink('/var/cache/ansible-inventory/ansible-inventory.cache') + os.system('/usr/local/bin/expand-groups.sh') + +if __name__ == '__main__': + main() diff --git a/launch/utils.py b/launch/utils.py index 1cb835fc10..a41e485583 100644 --- a/launch/utils.py +++ b/launch/utils.py @@ -115,6 +115,8 @@ def get_public_ip(server, version=4, floating_ip_pool=None): def get_href(server): + if not hasattr(server, 'links'): + return None for link in server.links: if link['rel'] == 'self': return link['href']