From a01cdd9978ff5767d4785af4a3b8f1128bb44cbc Mon Sep 17 00:00:00 2001 From: Kieron Bulloch Date: Mon, 16 Jun 2014 01:20:35 -0400 Subject: [PATCH 1/5] Moving cloud image creation script to gns3-server project. --- cloud-image/.novarc | 5 + cloud-image/create_image.py | 268 +++++++++++++++++++++++++++++++++++ cloud-image/dependencies.txt | 3 + 3 files changed, 276 insertions(+) create mode 100644 cloud-image/.novarc create mode 100644 cloud-image/create_image.py create mode 100644 cloud-image/dependencies.txt diff --git a/cloud-image/.novarc b/cloud-image/.novarc new file mode 100644 index 00000000..bab8f323 --- /dev/null +++ b/cloud-image/.novarc @@ -0,0 +1,5 @@ +export OS_USERNAME=username +export OS_PASSWORD="" +export OS_TENANT_NAME=000000 +export OS_AUTH_URL=https://identity.api.rackspacecloud.com/v2.0/ +export OS_REGION_NAME=ord diff --git a/cloud-image/create_image.py b/cloud-image/create_image.py new file mode 100644 index 00000000..b865cbf3 --- /dev/null +++ b/cloud-image/create_image.py @@ -0,0 +1,268 @@ +""" Create a new GNS3 Server Rackspace image with the provided options. """ + +import argparse +import getpass +import os +import sys +import uuid +from fabric.api import env +from fabric.contrib.files import exists +from github import Github +from novaclient.v1_1 import client +from time import sleep + +POLL_SEC = 30 +GNS3_REPO = 'gns3/gns3-server' +PLANC_REPO = 'planctechnologies/gns3-server' +OS_AUTH_URL = 'https://identity.api.rackspacecloud.com/v2.0/' +UBUNTU_BASE_ID = '5cc098a5-7286-4b96-b3a2-49f4c4f82537' + + +def main(): + """ + Get the user options and perform the image creation. + + Creates a new instance, installs the required software, creates an image + from the instance, and then deletes the instance. + + """ + + args = get_cli_args() + + g = Github() + + if args.username: + username = args.username + + else: + if 'OS_USERNAME' in os.environ: + username = os.environ.get('OS_USERNAME') + else: + username = raw_input('Enter Rackspace username: ') + + if args.password: + password = args.password + + else: + if 'OS_PASSWORD' in os.environ: + password = os.environ.get('OS_PASSWORD') + else: + password = getpass.getpass('Enter Rackspace password: ') + + if args.tenant: + tenant = args.tenant + + else: + if 'OS_TENANT_NAME' in os.environ: + tenant = os.environ.get('OS_TENANT_NAME') + else: + tenant = raw_input('Enter Rackspace Tenant ID: ') + + if args.region: + region = args.region + + else: + if 'OS_REGION_NAME' in os.environ: + region = os.environ.get('OS_REGION_NAME') + else: + region = raw_input('Enter Rackspace Region Name: ') + + if args.source == 'release': + # get the list of releases, present them to the user, save the url + repo = g.get_repo('gns3/gns3-server') + keyword = "tag" + i = 1 + branch_opts = {} + for tag in repo.get_tags(): + branch_opts[i] = tag.name + i += 1 + + elif args.source == 'dev': + # get the list of dev branches, present them to the user, save the url + repo = g.get_repo('planctechnologies/gns3-server') + keyword = "branch" + i = 1 + branch_opts = {} + for branch in repo.get_branches(): + branch_opts[i] = branch.name + i += 1 + + prompt_text = "Select a %s" % keyword + selected_branch = prompt_user_select(branch_opts, prompt_text) + + if args.image_name: + image_name = args.image_name + else: + image_name = "gns3-%s" % (uuid.uuid4().hex[0:4]) + + if args.on_boot: + on_boot = True + else: + on_boot = False + + startup_script = create_script(repo.svn_url, selected_branch, on_boot) + + server_name = uuid.uuid4().hex + instance = create_instance(username, password, tenant, region, server_name, + startup_script) + + passwd = uuid.uuid4().hex + instance.change_password(passwd) + # wait for the password change to be processed + sleep(10) + + env.host_string = str(instance.accessIPv4) + env.user = "root" + env.password = passwd + + sys.stdout.write("Installing software...") + sys.stdout.flush() + + while 1: + if exists('/tmp/gns-install-complete'): + break + + sleep(20) + sys.stdout.write(".") + sys.stdout.flush() + + print("Done.") + + create_image(username, password, tenant, region, instance, image_name) + + +def prompt_user_select(opts, text="Please select"): + """ Ask the user to select an option from the provided list. """ + + print("%s" % text) + print("=" * len(text)) + for o in opts: + print("(%s)\t%s" % (o, opts[o])) + + while 1: + selected = raw_input("Select: ") + try: + return opts[int(selected)] + except (KeyError, ValueError): + print("Invalid selection. Try again") + + +def create_instance(username, password, tenant, region, server_name, script, + auth_url=OS_AUTH_URL): + """ Create a new instance. """ + + sys.stdout.write("Creating instance...") + sys.stdout.flush() + + nc = client.Client(username, password, tenant, auth_url, + region_name=region) + server = nc.servers.create(server_name, UBUNTU_BASE_ID, 2, + config_drive=True, userdata=script) + server_id = server.id + + while 1: + server = nc.servers.get(server_id) + if server.status == 'ACTIVE': + break + + sleep(20) + sys.stdout.write(".") + sys.stdout.flush() + + print "Done." + + return server + + +def create_script(git_url, git_branch, on_boot): + """ Create the start-up script. """ + + # Consider using jinja or similar if this becomes unwieldly + script = "#!/bin/bash\n\n" + script += "export DEBIAN_FRONTEND=noninteractive\n\n" + script += "apt-get -y update\n" + script += "apt-get -o Dpkg::Options::=\"--force-confnew\" --force-yes -fuy dist-upgrade\n\n" + + reqs = ["git", "python3-setuptools", "python3-netifaces", "python3-pip"] + + for r in reqs: + script += "apt-get -y install %s\n" % r + + script += "\n" + + script += "mkdir -p /opt/gns3\n" + script += "pushd /opt/gns3\n" + script += "git clone --branch %s %s\n" % (git_branch, git_url) + script += "cd gns3-server\n" + script += "pip3 install tornado\n" + script += "pip3 install pyzmq\n" + script += "pip3 install jsonschema\n" + script += "python3 ./setup.py install\n\n" + + if on_boot: + script += "echo '/usr/local/bin/gns3-server' >> /etc/rc.local\n\n" + + script += "touch /tmp/gns-install-complete\n\n" + + return script + + +def create_image(username, password, tenant, region, server, + image_name, auth_url=OS_AUTH_URL): + """ Create a Rackspace image based on the server instance. """ + + nc = client.Client(username, password, tenant, auth_url, + region_name=region) + + sys.stdout.write("Creating image %s..." % image_name) + sys.stdout.flush() + + server.create_image(image_name) + + while 1: + server = nc.servers.get(server.id) + if getattr(server, 'OS-EXT-STS:task_state') is None: + break + + sleep(20) + sys.stdout.write(".") + sys.stdout.flush() + + print("Done.") + + +def get_cli_args(): + """ Parse the CLI input. """ + + parser = argparse.ArgumentParser( + description='Create a new GNS3 image', + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + '--rackspace_username', dest='username', action='store' + ) + parser.add_argument( + '--rackspace_password', dest='password', action='store' + ) + parser.add_argument( + '--rackspace_tenant', dest='tenant', action='store' + ) + parser.add_argument( + '--rackspace_region', dest='region', action='store' + ) + parser.add_argument('--source', dest='source', action='store', + choices=['release', 'dev'], default='release', + help='specify the gns3-server source location') + parser.add_argument('--branch', dest='branch', action='store', + help='specify the branch/tag') + parser.add_argument('--start-on-boot', dest='on_boot', action='store_true', + help='start the GNS3-server when the image boots', + default=False) + parser.add_argument('--image-name', dest='image_name', action='store', + help='the name of the image to be created') + + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/cloud-image/dependencies.txt b/cloud-image/dependencies.txt new file mode 100644 index 00000000..502c79b6 --- /dev/null +++ b/cloud-image/dependencies.txt @@ -0,0 +1,3 @@ +fabric +pygithub +python-novaclient From 8b3c5c54a487f6add0a97d4a1ce0c5e8522653f9 Mon Sep 17 00:00:00 2001 From: Kieron Bulloch Date: Tue, 17 Jun 2014 00:02:17 -0400 Subject: [PATCH 2/5] Adding GNS3-server code source info to default image name. Fixing spacing, etc before pull request. --- cloud-image/create_image.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cloud-image/create_image.py b/cloud-image/create_image.py index b865cbf3..08f6b7e4 100644 --- a/cloud-image/create_image.py +++ b/cloud-image/create_image.py @@ -27,13 +27,11 @@ def main(): """ - args = get_cli_args() - g = Github() + args = get_cli_args() if args.username: username = args.username - else: if 'OS_USERNAME' in os.environ: username = os.environ.get('OS_USERNAME') @@ -42,7 +40,6 @@ def main(): if args.password: password = args.password - else: if 'OS_PASSWORD' in os.environ: password = os.environ.get('OS_PASSWORD') @@ -51,7 +48,6 @@ def main(): if args.tenant: tenant = args.tenant - else: if 'OS_TENANT_NAME' in os.environ: tenant = os.environ.get('OS_TENANT_NAME') @@ -60,7 +56,6 @@ def main(): if args.region: region = args.region - else: if 'OS_REGION_NAME' in os.environ: region = os.environ.get('OS_REGION_NAME') @@ -93,7 +88,8 @@ def main(): if args.image_name: image_name = args.image_name else: - image_name = "gns3-%s" % (uuid.uuid4().hex[0:4]) + image_name = "gns3-%s-%s-%s" % (args.source, selected_branch, + uuid.uuid4().hex[0:4]) if args.on_boot: on_boot = True @@ -101,11 +97,9 @@ def main(): on_boot = False startup_script = create_script(repo.svn_url, selected_branch, on_boot) - server_name = uuid.uuid4().hex instance = create_instance(username, password, tenant, region, server_name, startup_script) - passwd = uuid.uuid4().hex instance.change_password(passwd) # wait for the password change to be processed @@ -128,7 +122,9 @@ def main(): print("Done.") - create_image(username, password, tenant, region, instance, image_name) + image_id = create_image(username, password, tenant, region, instance, + image_name) + instance.delete() def prompt_user_select(opts, text="Please select"): @@ -217,7 +213,7 @@ def create_image(username, password, tenant, region, server, sys.stdout.write("Creating image %s..." % image_name) sys.stdout.flush() - server.create_image(image_name) + image_id = server.create_image(image_name) while 1: server = nc.servers.get(server.id) @@ -230,6 +226,8 @@ def create_image(username, password, tenant, region, server, print("Done.") + return image_id + def get_cli_args(): """ Parse the CLI input. """ From 8dc3f288b3fae2f37034a2d55d9d8938fa40a53a Mon Sep 17 00:00:00 2001 From: Kieron Bulloch Date: Tue, 17 Jun 2014 00:11:25 -0400 Subject: [PATCH 3/5] Adding readme.txt --- cloud-image/readme.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 cloud-image/readme.txt diff --git a/cloud-image/readme.txt b/cloud-image/readme.txt new file mode 100644 index 00000000..a979aa08 --- /dev/null +++ b/cloud-image/readme.txt @@ -0,0 +1,10 @@ +create_image.py: + +- uses fabric, which doesn't support Python 3 + +- prompts for Rackspace credentials if environment variables not set + - see .novarc for example env variables + - note that the novaclient library uses the Rackspace password and -not- + the API key + +- use '--help' for help with arguments From aedd75025162f29451032c1a7e8cf1cc9b0e5b66 Mon Sep 17 00:00:00 2001 From: Kieron Bulloch Date: Tue, 24 Jun 2014 23:07:28 -0400 Subject: [PATCH 4/5] Switching script creation method to use string.Template --- cloud-image/create_image.py | 29 +++++------------------------ cloud-image/script_template | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 cloud-image/script_template diff --git a/cloud-image/create_image.py b/cloud-image/create_image.py index 08f6b7e4..4bc56b8b 100644 --- a/cloud-image/create_image.py +++ b/cloud-image/create_image.py @@ -9,6 +9,7 @@ from fabric.api import env from fabric.contrib.files import exists from github import Github from novaclient.v1_1 import client +from string import Template from time import sleep POLL_SEC = 30 @@ -173,34 +174,14 @@ def create_instance(username, password, tenant, region, server_name, script, def create_script(git_url, git_branch, on_boot): """ Create the start-up script. """ - # Consider using jinja or similar if this becomes unwieldly - script = "#!/bin/bash\n\n" - script += "export DEBIAN_FRONTEND=noninteractive\n\n" - script += "apt-get -y update\n" - script += "apt-get -o Dpkg::Options::=\"--force-confnew\" --force-yes -fuy dist-upgrade\n\n" + script_template = Template(open('script_template', 'r').read()) - reqs = ["git", "python3-setuptools", "python3-netifaces", "python3-pip"] - - for r in reqs: - script += "apt-get -y install %s\n" % r - - script += "\n" - - script += "mkdir -p /opt/gns3\n" - script += "pushd /opt/gns3\n" - script += "git clone --branch %s %s\n" % (git_branch, git_url) - script += "cd gns3-server\n" - script += "pip3 install tornado\n" - script += "pip3 install pyzmq\n" - script += "pip3 install jsonschema\n" - script += "python3 ./setup.py install\n\n" + p = dict(git_url=git_url, git_branch=git_branch, rc_local='') if on_boot: - script += "echo '/usr/local/bin/gns3-server' >> /etc/rc.local\n\n" + p['rc_local'] = "echo '/usr/local/bin/gns3-server' >> /etc/rc.local" - script += "touch /tmp/gns-install-complete\n\n" - - return script + return script_template.substitute(p) def create_image(username, password, tenant, region, server, diff --git a/cloud-image/script_template b/cloud-image/script_template new file mode 100644 index 00000000..e7c5ad56 --- /dev/null +++ b/cloud-image/script_template @@ -0,0 +1,21 @@ +#!/bin/bash +export DEBIAN_FRONTEND=noninteractive +apt-get -y update +apt-get -o Dpkg::Options::="--force-confnew" --force-yes -fuy dist-upgrade +apt-get -y install git +apt-get -y install python3-setuptools +apt-get -y install python3-netifaces +apt-get -y install python3-pip + +mkdir -p /opt/gns3 +pushd /opt/gns3 +git clone --branch ${git_branch} ${git_url} +cd gns3-server +pip3 install tornado +pip3 install pyzmq +pip3 install jsonschema +python3 ./setup.py install + +${rc_local} + +touch /tmp/gns-install-complete From 2d4656621b2dd80061b786ea999d165eff4ae71d Mon Sep 17 00:00:00 2001 From: Kieron Bulloch Date: Tue, 24 Jun 2014 23:34:50 -0400 Subject: [PATCH 5/5] Improving readability. --- cloud-image/create_image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud-image/create_image.py b/cloud-image/create_image.py index 4bc56b8b..8de039c3 100644 --- a/cloud-image/create_image.py +++ b/cloud-image/create_image.py @@ -176,12 +176,12 @@ def create_script(git_url, git_branch, on_boot): script_template = Template(open('script_template', 'r').read()) - p = dict(git_url=git_url, git_branch=git_branch, rc_local='') + params = {'git_url': git_url, 'git_branch': git_branch, 'rc_local': ''} if on_boot: - p['rc_local'] = "echo '/usr/local/bin/gns3-server' >> /etc/rc.local" + params['rc_local'] = "echo '/usr/local/bin/gns3-server' >> /etc/rc.local" - return script_template.substitute(p) + return script_template.substitute(params) def create_image(username, password, tenant, region, server,