From 8ee0b319eeb497a452b8e726e3b3294c4ee4bca3 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Mon, 14 Nov 2011 11:30:03 -0800 Subject: [PATCH] Add scripts that launch/delete devstack vms. These are for use by jenkins for gating jobs based off of devstack. Change-Id: I6688eeae905b3cedd6585a2b8ae88d6e92d38c36 --- slave_scripts/devstack-vm-delete.py | 64 +++++++++++++++++ slave_scripts/devstack-vm-gate-host.sh | 50 +++++++++++++ slave_scripts/devstack-vm-gate.sh | 67 +++++++++++++++++ slave_scripts/devstack-vm-give.py | 71 ++++++++++++++++++ slave_scripts/devstack-vm-launch.py | 88 +++++++++++++++++++++++ slave_scripts/devstack-vm-reap.py | 67 +++++++++++++++++ slave_scripts/devstack-vm-update-image.py | 75 +++++++++++++++++++ slave_scripts/vmdatabase.py | 57 +++++++++++++++ 8 files changed, 539 insertions(+) create mode 100755 slave_scripts/devstack-vm-delete.py create mode 100755 slave_scripts/devstack-vm-gate-host.sh create mode 100755 slave_scripts/devstack-vm-gate.sh create mode 100755 slave_scripts/devstack-vm-give.py create mode 100755 slave_scripts/devstack-vm-launch.py create mode 100755 slave_scripts/devstack-vm-reap.py create mode 100755 slave_scripts/devstack-vm-update-image.py create mode 100644 slave_scripts/vmdatabase.py diff --git a/slave_scripts/devstack-vm-delete.py b/slave_scripts/devstack-vm-delete.py new file mode 100755 index 00000000..2f1adfb7 --- /dev/null +++ b/slave_scripts/devstack-vm-delete.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +# Delete a devstack VM. + +# Copyright (C) 2011 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. + +from libcloud.base import NodeImage, NodeSize, NodeLocation +from libcloud.types import Provider +from libcloud.providers import get_driver +from libcloud.deployment import MultiStepDeployment, ScriptDeployment, SSHKeyDeployment +from libcloud.dns.types import Provider as DnsProvider +from libcloud.dns.types import RecordType +from libcloud.dns.providers import get_driver as dns_get_driver +import libcloud +import os, sys +import getopt +import time + +import vmdatabase + +CLOUD_SERVERS_DRIVER = os.environ.get('CLOUD_SERVERS_DRIVER','rackspace') +CLOUD_SERVERS_USERNAME = os.environ['CLOUD_SERVERS_USERNAME'] +CLOUD_SERVERS_API_KEY = os.environ['CLOUD_SERVERS_API_KEY'] + +CHANGE = os.environ['GERRIT_CHANGE_NUMBER'] +PATCH = os.environ['GERRIT_PATCHSET_NUMBER'] +BUILD = os.environ['BUILD_NUMBER'] + +db = vmdatabase.VMDatabase() +machine = db.getMachine(CHANGE, PATCH, BUILD) +node_name = machine['name'] + +if CLOUD_SERVERS_DRIVER == 'rackspace': + Driver = get_driver(Provider.RACKSPACE) + conn = Driver(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + node = [n for n in conn.list_nodes() if n.id==str(machine['id'])][0] + + dns_provider = dns_get_driver(DnsProvider.RACKSPACE_US) + dns_ctx = dns_provider(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + + domain_name = ".".join(node_name.split(".")[-2:]) + domain = [z for z in dns_ctx.list_zones() if z.domain == 'openstack.org'][0] + + records = [z for z in domain.list_records() if z == node_name] + if records: + records[0].delete() + + node.destroy() + +db.delMachine(machine['id']) diff --git a/slave_scripts/devstack-vm-gate-host.sh b/slave_scripts/devstack-vm-gate-host.sh new file mode 100755 index 00000000..7cc9ed2a --- /dev/null +++ b/slave_scripts/devstack-vm-gate-host.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Script that is run on the devstack vm; configures and +# invokes devstack. + +# Copyright (C) 2011 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. + +set -o errexit +cd workspace + +DEST=/opt/stack +# create the destination directory and ensure it is writable by the user +sudo mkdir -p $DEST +if [ ! -w $DEST ]; then + sudo chown `whoami` $DEST +fi + +# The workspace has been copied over here by devstack-vm-gate.sh +mv * /opt/stack +cd /opt/stack/devstack + +cat <localrc +ACTIVE_TIMEOUT=60 +BOOT_TIMEOUT=90 +ASSOCIATE_TIMEOUT=60 +MYSQL_PASSWORD=secret +RABBIT_PASSWORD=secret +ADMIN_PASSWORD=secret +SERVICE_TOKEN=111222333444 +ROOTSLEEP=0 +ENABLED_SERVICES=g-api,g-reg,key,n-api,n-cpu,n-net,n-sch,mysql,rabbit +SKIP_EXERCISES=swift +EOF + +./stack.sh +./exercise.sh diff --git a/slave_scripts/devstack-vm-gate.sh b/slave_scripts/devstack-vm-gate.sh new file mode 100755 index 00000000..8c02fae1 --- /dev/null +++ b/slave_scripts/devstack-vm-gate.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Gate commits to several projects on a VM running those projects +# configured by devstack. + +# Copyright (C) 2011 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. + +set -o xtrace + +fi +if [[ ! -e python-novaclient ]]; then + git clone https://github.com/rackspace/python-novaclient.git +fi + +for PROJECT in $PROJECTS +do + echo "Setting up $PROJECT" + SHORT_PROJECT=`basename $PROJECT` + if [[ ! -e $SHORT_PROJECT ]]; then + echo " Need to clone" + git clone https://review.openstack.org/p/$PROJECT + fi + cd $SHORT_PROJECT + + git remote update + git checkout $GERRIT_BRANCH + git reset --hard remotes/origin/$GERRIT_BRANCH + + if [[ $GERRIT_PROJECT == $PROJECT ]]; then + echo " Merging proposed change" + git fetch https://review.openstack.org/p/$PROJECT $GERRIT_REFSPEC + git merge FETCH_HEAD + else + echo " Updating from origin" + git pull --ff-only origin $GERRIT_BRANCH + fi + cd $WORKSPACE +done + +python $CI_SCRIPT_DIR/devstack-vm-launch.py || exit $? +. $HOSTNAME.node.sh +rm $HOSTNAME.node.sh +scp -C -q $CI_SCRIPT_DIR/devstack-vm-gate-host.sh $ipAddr: +scp -C -q -r $WORKSPACE/ $ipAddr:workspace +ssh $ipAddr ./devstack-vm-gate-host.sh +RETVAL=$? +if [ $RETVAL = 0 ]; then + echo "Deleting host" + python $CI_SCRIPT_DIR/devstack-vm-delete.py +else + echo "Giving host to developer" + python $CI_SCRIPT_DIR/devstack-vm-give.py +fi diff --git a/slave_scripts/devstack-vm-give.py b/slave_scripts/devstack-vm-give.py new file mode 100755 index 00000000..83511534 --- /dev/null +++ b/slave_scripts/devstack-vm-give.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Turn over a devstack configured machine to the developer who +# proposed the change that is being tested. + +# Copyright (C) 2011 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 os, sys, time +import getopt +import commands +import json +import urllib2 +import tempfile + +import vmdatabase + +CHANGE = os.environ['GERRIT_CHANGE_NUMBER'] +PATCH = os.environ['GERRIT_PATCHSET_NUMBER'] +BUILD = os.environ['BUILD_NUMBER'] + +db = vmdatabase.VMDatabase() +machine = db.getMachine(CHANGE, PATCH, BUILD) + +stat, out = commands.getstatusoutput("ssh -p 29418 review.openstack.org gerrit query --format=JSON change:%s" % os.environ['GERRIT_CHANGE_NUMBER']) + +data = json.loads(out.split('\n')[0]) +username = data['owner']['username'] + +f = urllib2.urlopen('https://launchpad.net/~%s/+sshkeys'%username) +keys = f.read() + +tmp = tempfile.NamedTemporaryFile(delete=False) +try: + tmp.write("""#!/bin/bash +chmod u+w ~/.ssh/authorized_keys +cat <>~/.ssh/authorized_keys +""") + tmp.write(keys) + tmp.write("\nEOF\n") + tmp.close() + stat, out = commands.getstatusoutput("scp %s %s:/var/tmp/keys.sh" % + (tmp.name, machine['ip'])) + if stat: + print out + raise Exception("Unable to copy keys") + + stat, out = commands.getstatusoutput("ssh %s /bin/sh /var/tmp/keys.sh" % + machine['ip']) + + if stat: + print out + raise Exception("Unable to add keys") +finally: + os.unlink(tmp.name) + +db.setMachineUser(machine['id'], username) +print "Added %s to authorized_keys on %s" % (username, machine['ip']) diff --git a/slave_scripts/devstack-vm-launch.py b/slave_scripts/devstack-vm-launch.py new file mode 100755 index 00000000..120392cf --- /dev/null +++ b/slave_scripts/devstack-vm-launch.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +# Launch a VM for use by devstack. + +# Copyright (C) 2011 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. + +from libcloud.base import NodeImage, NodeSize, NodeLocation +from libcloud.types import Provider +from libcloud.providers import get_driver +from libcloud.deployment import MultiStepDeployment, ScriptDeployment, SSHKeyDeployment +from libcloud.dns.types import Provider as DnsProvider +from libcloud.dns.types import RecordType +from libcloud.dns.providers import get_driver as dns_get_driver +import libcloud +import os, sys +import getopt +import time + +import vmdatabase + +CLOUD_SERVERS_DRIVER = os.environ.get('CLOUD_SERVERS_DRIVER','rackspace') +CLOUD_SERVERS_USERNAME = os.environ['CLOUD_SERVERS_USERNAME'] +CLOUD_SERVERS_API_KEY = os.environ['CLOUD_SERVERS_API_KEY'] +CLOUD_SERVERS_HOST = os.environ.get('CLOUD_SERVERS_HOST', None) +CLOUD_SERVERS_PATH = os.environ.get('CLOUD_SERVERS_PATH', None) +IMAGE_NAME = 'devstack-oneiric' +MIN_RAM = 1024 + +CHANGE = os.environ['GERRIT_CHANGE_NUMBER'] +PATCH = os.environ['GERRIT_PATCHSET_NUMBER'] +BUILD = os.environ['BUILD_NUMBER'] + +db = vmdatabase.VMDatabase() +node_name = 'devstack-%s-%s-%s.slave.openstack.org' % (CHANGE, PATCH, BUILD) + +if CLOUD_SERVERS_DRIVER == 'rackspace': + Driver = get_driver(Provider.RACKSPACE) + conn = Driver(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + images = conn.list_images() + + sizes = [sz for sz in conn.list_sizes() if sz.ram >= MIN_RAM] + sizes.sort(lambda a,b: cmp(a.ram, b.ram)) + size = sizes[0] + image = [img for img in conn.list_images() if img.name==IMAGE_NAME][0] +else: + raise Exception ("Driver not supported") + +if CLOUD_SERVERS_DRIVER == 'rackspace': + node = conn.create_node(name=node_name, image=image, size=size) + # A private method, Tomaz Muraus says he's thinking of making it public + node = conn._wait_until_running(node=node, wait_period=3, + timeout=600) + + dns_provider = dns_get_driver(DnsProvider.RACKSPACE_US) + dns_ctx = dns_provider(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + + domain_name = ".".join(node_name.split(".")[-2:]) + domain = [z for z in dns_ctx.list_zones() if z.domain == 'openstack.org'][0] + + records = [z for z in domain.list_records() if z == node_name] + if len(records) == 0: + domain.create_record(node_name, RecordType.A, node.public_ip[0]) + else: + records[0].update(data=node.public_ip[0]) + +print "Node ID:", node.id +print "Node IP:", node.public_ip[0] + +db.addMachine(node.id, node_name, node.public_ip[0], CHANGE, PATCH, BUILD) + +with open("%s.node.sh" % node_name,"w") as node_file: + node_file.write("ipAddr=%s\n" % node.public_ip[0]) + node_file.write("nodeId=%s\n" % node.id) + diff --git a/slave_scripts/devstack-vm-reap.py b/slave_scripts/devstack-vm-reap.py new file mode 100755 index 00000000..55c34c91 --- /dev/null +++ b/slave_scripts/devstack-vm-reap.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +# Remove old devstack VMs that have been given to developers. + +# Copyright (C) 2011 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 os, sys, time +import getopt + +from libcloud.base import NodeImage, NodeSize, NodeLocation +from libcloud.types import Provider +from libcloud.providers import get_driver +from libcloud.deployment import MultiStepDeployment, ScriptDeployment, SSHKeyDeployment +from libcloud.dns.types import Provider as DnsProvider +from libcloud.dns.types import RecordType +from libcloud.dns.providers import get_driver as dns_get_driver +import libcloud + +import vmdatabase + +CLOUD_SERVERS_DRIVER = os.environ.get('CLOUD_SERVERS_DRIVER','rackspace') +CLOUD_SERVERS_USERNAME = os.environ['CLOUD_SERVERS_USERNAME'] +CLOUD_SERVERS_API_KEY = os.environ['CLOUD_SERVERS_API_KEY'] + +db = vmdatabase.VMDatabase() + +if CLOUD_SERVERS_DRIVER == 'rackspace': + Driver = get_driver(Provider.RACKSPACE) + conn = Driver(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + +def delete(machine): + node = [n for n in conn.list_nodes() if n.id==str(machine['id'])][0] + node_name = machine['name'] + + dns_provider = dns_get_driver(DnsProvider.RACKSPACE_US) + dns_ctx = dns_provider(CLOUD_SERVERS_USERNAME, CLOUD_SERVERS_API_KEY) + + domain_name = ".".join(node_name.split(".")[-2:]) + domain = [z for z in dns_ctx.list_zones() if z.domain == 'openstack.org'][0] + + records = [z for z in domain.list_records() if z == node_name] + if records: + records[0].delete() + + node.destroy() + + db.delMachine(machine['id']) + +now = time.time() +for machine in db.getMachines(): + if now-machine['created'] > 24*60*60: + print 'Deleting', machine['name'] + delete(machine) diff --git a/slave_scripts/devstack-vm-update-image.py b/slave_scripts/devstack-vm-update-image.py new file mode 100755 index 00000000..66d79e6c --- /dev/null +++ b/slave_scripts/devstack-vm-update-image.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Update the base image that is used for devstack VMs. + +# Copyright (C) 2011 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 openstack.compute +import commands +import time + +USERNAME = os.environ['CLOUD_SERVERS_USERNAME'] +API_KEY = os.environ['CLOUD_SERVERS_API_KEY'] + +SERVER_NAME = 'devstack-oneiric.slave.openstack.org' +IMAGE_NAME = 'devstack-oneiric' + +compute = openstack.compute.Compute(username=USERNAME, apikey=API_KEY, + cloud_api='RACKSPACE') + +print "Searching for %s server" % SERVER_NAME +node = compute.servers.find(name=SERVER_NAME) +print "Searching for %s image" % IMAGE_NAME +try: + image = compute.images.find(name=IMAGE_NAME) +except openstack.compute.exceptions.NotFound: + image = None + +stat, out = commands.getstatusoutput("ssh %s sudo apt-get -y dist-upgrade" % + node.public_ip) +if stat: + print out + raise Exception("Unable to upgrade server") +stat, out = commands.getstatusoutput("ssh %s sudo /etc/init.d/mysql stop" % + node.public_ip) +if stat: + print out + raise Exception("Unable to stop mysql") +stat, out = commands.getstatusoutput("ssh %s sudo /etc/init.d/rabbitmq-server stop" % + node.public_ip) +if stat: + print out + raise Exception("Unable to stop rabbitmq") + +if image: + image.delete() +image = compute.images.create(name=IMAGE_NAME, server=node) + +last_status = None +while True: + if image.status != last_status: + print + print time.ctime(), image.status, + last_status = image.status + if image.status == u'ACTIVE': + break + sys.stdout.write('.') + sys.stdout.flush() + time.sleep(1) + image = compute.images.get(image.id) diff --git a/slave_scripts/vmdatabase.py b/slave_scripts/vmdatabase.py new file mode 100644 index 00000000..a20aa1b4 --- /dev/null +++ b/slave_scripts/vmdatabase.py @@ -0,0 +1,57 @@ +import sqlite3 +import os +import time + +class VMDatabase(object): + def __init__(self, path=os.path.expanduser("~/vm.db")): + if not os.path.exists(path): + conn = sqlite3.connect(path) + c = conn.cursor() + c.execute('''create table machines +(id int, name text, ip text, change_number, patch_number, build_number, created int, user text)''') + conn.commit() + c.close() + self.conn = sqlite3.connect(path) + + def addMachine(self, mid, name, ip, change, patch, build): + c = self.conn.cursor() + c.execute("insert into machines (id, name, ip, change_number, patch_number, build_number, created) values (?, ?, ?, ?, ?, ?, ?)", + (mid, name, ip, change, patch, build, int(time.time()))) + self.conn.commit() + c.close() + + def delMachine(self, mid): + c = self.conn.cursor() + c.execute("delete from machines where id=?", (mid,)) + self.conn.commit() + c.close() + + def setMachineUser(self, mid, user): + c = self.conn.cursor() + c.execute("update machines set user=? where id=?", (user, mid)) + self.conn.commit() + c.close() + + def getMachines(self): + c = self.conn.cursor() + c.execute("select * from machines") + names = [col[0] for col in c.description] + data = [dict(zip(names, row)) for row in c] + c.close() + return data + + def getMachine(self, change, patch, build): + c = self.conn.cursor() + c.execute("select * from machines where change_number=? and patch_number=? and build_number=?", (change, patch, build)) + names = [col[0] for col in c.description] + data = [row for row in c] + c.close() + return dict(zip(names, data[0])) + +if __name__=='__main__': + db = VMDatabase() + db.addMachine(1, 'foo', '1.2.3.4', 88, 2, 1) + db.setMachineUser(1, 'jeblair') + print db.getMachines() + print db.getMachine(88,2,1) + db.delMachine(1)