Retire old launch-node.py
It has served us well, but it's time for the new world order to take over. Change-Id: I72a9f1b1c0f1bd331b5f862f5f69966950ffe963
This commit is contained in:
parent
e14471dcc6
commit
f94594a98e
@ -14,42 +14,31 @@ to a group.)
|
||||
|
||||
To launch a node in the OpenStack CI account (production servers)::
|
||||
|
||||
. ~root/ci-launch/openstackci-rs-nova.sh
|
||||
export OS_CLOUD=openstackci-rax
|
||||
export OS_REGION=DFW
|
||||
export FLAVOR="8 GB Performance"
|
||||
export FQDN=servername.openstack.org
|
||||
sudo puppet cert generate $FQDN
|
||||
cd /opt/system-config/production/launch/
|
||||
./launch-node.py $FQDN --flavor "$FLAVOR"
|
||||
./launch-node.py $FQDN --flavor "$FLAVOR" \
|
||||
--cloud=$OS_CLOUD --region=$OS_REGION
|
||||
|
||||
To launch a node in the OpenStack Jenkins account (slave nodes)::
|
||||
|
||||
. ~root/ci-launch/openstackjenkins-rs-nova.sh
|
||||
export OS_CLOUD=openstackjenkins-rax
|
||||
export OS_REGION=DFW
|
||||
export FQDN=slavename.slave.openstack.org
|
||||
nova image-list
|
||||
openstack image list
|
||||
export IMAGE='Ubuntu 12.04 LTS (Precise Pangolin) (PVHVM)'
|
||||
nova flavor-list
|
||||
openstack flavor list
|
||||
export FLAVOR="8 GB Performance"
|
||||
sudo puppet cert generate $FQDN
|
||||
./launch-node.py $FQDN --image "$IMAGE" --flavor "$FLAVOR"
|
||||
|
||||
If you are launching a replacement server, you may skip the generate
|
||||
step and specify the name of an existing puppet cert (as long as the
|
||||
private key is on this host).
|
||||
|
||||
The server name and cert names may be different and the latter can be
|
||||
specified with --cert if needed, but launch-node.py will assume they
|
||||
are the same unless specified.
|
||||
./launch-node.py $FQDN --image "$IMAGE" --flavor "$FLAVOR" \
|
||||
--cloud=$OS_CLOUD --region=$OS_REGION
|
||||
|
||||
Manually add the hostname to DNS (the launch script does not do so
|
||||
automatically). Note that this example assumes you've already
|
||||
exported a relevant FQDN and sourced the appropriate API credentials
|
||||
above.
|
||||
|
||||
When running outside the official OpenStack CI infrastructure, you
|
||||
will want to pass --server puppetmaster.example.com otherwise the
|
||||
new node wil try to register with puppetmaster.openstack.org - and
|
||||
fail hilariously.
|
||||
|
||||
In order for Ansible to be able to send out the Puppet updates,
|
||||
you also need the puppetmaster to accept the root SSH key for the
|
||||
new server. So as root on the puppetmaster:
|
||||
|
@ -18,68 +18,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import utils
|
||||
|
||||
|
||||
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
|
||||
kwargs['service_type'] = 'compute'
|
||||
from novaclient.v1_1.client import Client
|
||||
client = Client(*args, **kwargs)
|
||||
return client
|
||||
|
||||
|
||||
def print_dns(client, name):
|
||||
for server in client.servers.list():
|
||||
if server.name != name:
|
||||
continue
|
||||
ip4 = utils.get_public_ip(server)
|
||||
ip6 = utils.get_public_ip(server, 6)
|
||||
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 get_href(server):
|
||||
@ -90,7 +29,7 @@ def get_href(server):
|
||||
return link['href']
|
||||
|
||||
|
||||
def shade_print_dns(cloud, server):
|
||||
def print_dns(cloud, server):
|
||||
ip4 = server.public_v4
|
||||
ip6 = server.public_v6
|
||||
|
||||
@ -137,8 +76,10 @@ def main():
|
||||
parser.add_argument("name", help="server name")
|
||||
options = parser.parse_args()
|
||||
|
||||
client = get_client()
|
||||
print_dns(client, options.name)
|
||||
import shade
|
||||
cloud = shade.openstack_cloud()
|
||||
server = cloud.get_server(options.name)
|
||||
print_dns(cloud, server)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
266
launch/launch-node.py
Executable file → Normal file
266
launch/launch-node.py
Executable file → Normal file
@ -18,55 +18,37 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import argparse
|
||||
|
||||
import dns
|
||||
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']
|
||||
NOVA_SERVICE_NAME = os.environ.get('OS_SERVICE_NAME')
|
||||
NOVACLIENT_INSECURE = os.getenv('NOVACLIENT_INSECURE', None)
|
||||
IPV6 = os.environ.get('IPV6', '0') is 1
|
||||
import os_client_config
|
||||
import paramiko
|
||||
import shade
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(sys.argv[0])
|
||||
|
||||
|
||||
def get_client():
|
||||
args = [NOVA_USERNAME, NOVA_PASSWORD, NOVA_PROJECT_ID, NOVA_URL]
|
||||
kwargs = {}
|
||||
kwargs['region_name'] = NOVA_REGION_NAME
|
||||
kwargs['service_type'] = 'compute'
|
||||
if NOVA_SERVICE_NAME:
|
||||
kwargs['service_name'] = NOVA_SERVICE_NAME
|
||||
|
||||
if NOVACLIENT_INSECURE:
|
||||
kwargs['insecure'] = True
|
||||
|
||||
from novaclient.v1_1.client import Client
|
||||
client = Client(*args, **kwargs)
|
||||
return client
|
||||
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, admin_pass, key, cert, environment, name,
|
||||
puppetmaster, volume, floating_ip_pool):
|
||||
ip = utils.get_public_ip(server, floating_ip_pool=floating_ip_pool)
|
||||
if not ip:
|
||||
raise Exception("Unable to find public ip of server")
|
||||
def bootstrap_server(server, key, name, volume, keep):
|
||||
|
||||
ssh_kwargs = {}
|
||||
if key:
|
||||
ssh_kwargs['pkey'] = key
|
||||
else:
|
||||
ssh_kwargs['password'] = admin_pass
|
||||
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:
|
||||
@ -86,7 +68,7 @@ def bootstrap_server(server, admin_pass, key, cert, environment, name,
|
||||
|
||||
ssh_client = utils.ssh_connect(ip, 'root', ssh_kwargs, timeout=600)
|
||||
|
||||
if IPV6:
|
||||
if server.public_v6:
|
||||
ssh_client.ssh('ping6 -c5 -Q 0x10 review.openstack.org '
|
||||
'|| ping6 -c5 -Q 0x10 wiki.openstack.org')
|
||||
|
||||
@ -99,40 +81,50 @@ def bootstrap_server(server, admin_pass, key, cert, environment, name,
|
||||
'mount_volume.sh')
|
||||
ssh_client.ssh('bash -x mount_volume.sh')
|
||||
|
||||
# This next chunk should really exist as a playbook, but whatev
|
||||
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")
|
||||
shortname = name.split('.')[0]
|
||||
with ssh_client.open('/etc/hosts', 'w') as f:
|
||||
f.write('127.0.0.1 localhost\n')
|
||||
f.write('127.0.1.1 %s %s\n' % (name, shortname))
|
||||
with ssh_client.open('/etc/hostname', 'w') as f:
|
||||
f.write('%s\n' % (shortname,))
|
||||
ssh_client.ssh("hostname %s" % (name,))
|
||||
|
||||
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))
|
||||
# Write out the private SSH key we generated
|
||||
key_file = tempfile.NamedTemporaryFile(delete=not keep)
|
||||
key.write_private_key(key_file)
|
||||
key_file.flush()
|
||||
|
||||
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")
|
||||
# Write out inventory
|
||||
inventory_file = tempfile.NamedTemporaryFile(delete=not keep)
|
||||
inventory_file.write("{host} ansible_host={ip} ansible_user=root".format(
|
||||
host=server.id, ip=server.interface_ip))
|
||||
inventory_file.flush()
|
||||
|
||||
(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)
|
||||
ansible_cmd = [
|
||||
'ansible-playbook',
|
||||
'-i', inventory_file.name, '-l', server.id,
|
||||
'--private-key={key}'.format(key=key_file.name),
|
||||
"--ssh-common-args='-o StrictHostKeyChecking=no'",
|
||||
]
|
||||
|
||||
# Run the remote puppet apply playbook limited to just this server
|
||||
# we just created
|
||||
try:
|
||||
print subprocess.check_output(
|
||||
ansible_cmd + [
|
||||
os.path.join(
|
||||
SCRIPT_DIR, '..', 'playbooks',
|
||||
'remote_puppet_adhoc.yaml')],
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print "Subprocess failed"
|
||||
print e.output
|
||||
raise
|
||||
|
||||
try:
|
||||
ssh_client.ssh("reboot")
|
||||
@ -145,94 +137,70 @@ def bootstrap_server(server, admin_pass, key, cert, environment, name,
|
||||
raise
|
||||
|
||||
|
||||
def build_server(
|
||||
client, name, image, flavor, cert, environment, puppetmaster, volume,
|
||||
keep, net_label, floating_ip_pool, boot_from_volume):
|
||||
def build_server(cloud, name, image, flavor,
|
||||
volume, keep, network, boot_from_volume, config_drive):
|
||||
key = None
|
||||
server = None
|
||||
|
||||
create_kwargs = dict(image=image, flavor=flavor, name=name)
|
||||
create_kwargs = dict(image=image, flavor=flavor, name=name,
|
||||
reuse_ips=False, wait=True,
|
||||
boot_from_volume=boot_from_volume,
|
||||
network=network,
|
||||
config_drive=config_drive)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
if volume:
|
||||
create_kwargs['volumes'] = [volume]
|
||||
|
||||
key_name = 'launch-%i' % (time.time())
|
||||
if 'os-keypairs' in utils.get_extensions(client):
|
||||
print "Adding keypair"
|
||||
key, kp = utils.add_keypair(client, key_name)
|
||||
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 = client.servers.create(**create_kwargs)
|
||||
server = cloud.create_server(**create_kwargs)
|
||||
except Exception:
|
||||
try:
|
||||
kp.delete()
|
||||
cloud.delete_keypair(key_name)
|
||||
except Exception:
|
||||
print "Exception encountered deleting keypair:"
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
try:
|
||||
admin_pass = server.adminPass
|
||||
server = utils.wait_for_resource(server)
|
||||
if volume:
|
||||
vobj = client.volumes.create_server_volume(
|
||||
server.id, volume, None)
|
||||
if not vobj:
|
||||
raise Exception("Couldn't attach volume")
|
||||
cloud.delete_keypair(key_name)
|
||||
|
||||
bootstrap_server(server, admin_pass, key, cert, environment, name,
|
||||
puppetmaster, volume, floating_ip_pool)
|
||||
print('UUID=%s\nIPV4=%s\nIPV6=%s\n' % (server.id,
|
||||
server.accessIPv4,
|
||||
server.accessIPv6))
|
||||
if key:
|
||||
kp.delete()
|
||||
server = cloud.get_openstack_vars(server)
|
||||
bootstrap_server(server, key, name, volume, keep)
|
||||
print('UUID=%s\nIPV4=%s\nIPV6=%s\n' % (
|
||||
server.id, server.public_v4, server.public_v6))
|
||||
except Exception:
|
||||
try:
|
||||
if keep:
|
||||
print "Server failed to build, keeping as requested."
|
||||
else:
|
||||
utils.delete_server(server)
|
||||
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)
|
||||
@ -244,58 +212,50 @@ def main():
|
||||
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='',
|
||||
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("--fip-pool", dest="floating_ip_pool", default=None,
|
||||
help="pool to assign floating IP from")
|
||||
parser.add_argument("--config-drive", dest="config_drive",
|
||||
help="Boot with config_drive attached.",
|
||||
action='store_true',
|
||||
default=True)
|
||||
options = parser.parse_args()
|
||||
|
||||
client = get_client()
|
||||
shade.simple_logging(debug=options.verbose)
|
||||
|
||||
if options.cert:
|
||||
cert = options.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:
|
||||
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.")
|
||||
|
||||
flavors = [f for f in client.flavors.list()
|
||||
if options.flavor in (f.name, f.id)]
|
||||
flavor = flavors[0]
|
||||
print "Found flavor", flavor
|
||||
|
||||
images = [i for i in client.images.list()
|
||||
if (options.image.lower() in (i.id, i.name.lower()) and
|
||||
not i.name.endswith('(Kernel)') and
|
||||
not i.name.endswith('(Ramdisk)'))]
|
||||
|
||||
if len(images) > 1:
|
||||
print "Ambiguous image name; matches:"
|
||||
for i in images:
|
||||
print "Unable to find matching flavor; flavor list:"
|
||||
for i in cloud.list_flavors():
|
||||
print i.name
|
||||
sys.exit(1)
|
||||
|
||||
if len(images) == 0:
|
||||
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 client.images.list():
|
||||
for i in cloud.list_images():
|
||||
print i.name
|
||||
sys.exit(1)
|
||||
|
||||
image = images[0]
|
||||
print "Found image", image
|
||||
|
||||
if options.volume:
|
||||
print "The --volume option does not support cinder; until it does"
|
||||
print "it should not be used."
|
||||
sys.exit(1)
|
||||
|
||||
build_server(client, 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.print_dns(client, options.name)
|
||||
server = build_server(cloud, options.name, image, flavor,
|
||||
options.volume, options.keep,
|
||||
options.network, options.boot_from_volume,
|
||||
options.config_drive)
|
||||
dns.print_dns(cloud, 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'):
|
||||
|
@ -1,266 +0,0 @@
|
||||
#!/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 argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
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, name, volume, keep):
|
||||
|
||||
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')
|
||||
|
||||
# This next chunk should really exist as a playbook, but whatev
|
||||
ssh_client.scp(os.path.join(SCRIPT_DIR, '..', 'install_puppet.sh'),
|
||||
'install_puppet.sh')
|
||||
ssh_client.ssh('bash -x install_puppet.sh')
|
||||
|
||||
shortname = name.split('.')[0]
|
||||
with ssh_client.open('/etc/hosts', 'w') as f:
|
||||
f.write('127.0.0.1 localhost\n')
|
||||
f.write('127.0.1.1 %s %s\n' % (name, shortname))
|
||||
with ssh_client.open('/etc/hostname', 'w') as f:
|
||||
f.write('%s\n' % (shortname,))
|
||||
ssh_client.ssh("hostname %s" % (name,))
|
||||
|
||||
# Write out the private SSH key we generated
|
||||
key_file = tempfile.NamedTemporaryFile(delete=not keep)
|
||||
key.write_private_key(key_file)
|
||||
key_file.flush()
|
||||
|
||||
# Write out inventory
|
||||
inventory_file = tempfile.NamedTemporaryFile(delete=not keep)
|
||||
inventory_file.write("{host} ansible_host={ip} ansible_user=root".format(
|
||||
host=server.id, ip=server.interface_ip))
|
||||
inventory_file.flush()
|
||||
|
||||
ansible_cmd = [
|
||||
'ansible-playbook',
|
||||
'-i', inventory_file.name, '-l', server.id,
|
||||
'--private-key={key}'.format(key=key_file.name),
|
||||
"--ssh-common-args='-o StrictHostKeyChecking=no'",
|
||||
]
|
||||
|
||||
# Run the remote puppet apply playbook limited to just this server
|
||||
# we just created
|
||||
try:
|
||||
print subprocess.check_output(
|
||||
ansible_cmd + [
|
||||
os.path.join(
|
||||
SCRIPT_DIR, '..', 'playbooks',
|
||||
'remote_puppet_adhoc.yaml')],
|
||||
stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print "Subprocess failed"
|
||||
print e.output
|
||||
raise
|
||||
|
||||
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,
|
||||
volume, keep, network, boot_from_volume, config_drive):
|
||||
key = None
|
||||
server = None
|
||||
|
||||
create_kwargs = dict(image=image, flavor=flavor, name=name,
|
||||
reuse_ips=False, wait=True,
|
||||
boot_from_volume=boot_from_volume,
|
||||
network=network,
|
||||
config_drive=config_drive)
|
||||
|
||||
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, volume, keep)
|
||||
print('UUID=%s\nIPV4=%s\nIPV6=%s\n' % (
|
||||
server.id, server.public_v4, server.public_v6))
|
||||
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("--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("--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=True)
|
||||
options = parser.parse_args()
|
||||
|
||||
shade.simple_logging(debug=options.verbose)
|
||||
|
||||
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)
|
||||
|
||||
server = build_server(cloud, options.name, image, flavor,
|
||||
options.volume, options.keep,
|
||||
options.network, options.boot_from_volume,
|
||||
options.config_drive)
|
||||
dns.shade_print_dns(cloud, 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()
|
@ -19,7 +19,6 @@
|
||||
# limitations under the License.
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import socket
|
||||
|
||||
import paramiko
|
||||
@ -37,32 +36,6 @@ def iterate_timeout(max_seconds, purpose):
|
||||
raise Exception("Timeout waiting for %s" % purpose)
|
||||
|
||||
|
||||
def wait_for_resource(wait_resource):
|
||||
last_progress = None
|
||||
last_status = None
|
||||
# It can take a _very_ long time for Rackspace 1.0 to save an image
|
||||
for count in iterate_timeout(21600, "waiting for %s" % wait_resource):
|
||||
try:
|
||||
resource = wait_resource.manager.get(wait_resource.id)
|
||||
except:
|
||||
print "Unable to list resources, will retry"
|
||||
traceback.print_exc()
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
# In Rackspace v1.0, there is no progress attribute while queued
|
||||
if hasattr(resource, 'progress'):
|
||||
if (last_progress != resource.progress
|
||||
or last_status != resource.status):
|
||||
print resource.status, resource.progress
|
||||
last_progress = resource.progress
|
||||
elif last_status != resource.status:
|
||||
print resource.status
|
||||
last_status = resource.status
|
||||
if resource.status == 'ACTIVE':
|
||||
return resource
|
||||
|
||||
|
||||
def ssh_connect(ip, username, connect_kwargs={}, timeout=60):
|
||||
# HPcloud may return errno 111 for about 30 seconds after adding the IP
|
||||
for count in iterate_timeout(timeout, "ssh access"):
|
||||
|
Loading…
Reference in New Issue
Block a user