To reduce the number of redundant calls and reduce the code complexity.
Signed-off-by: Loic Dachary <loic@dachary.org>
# THE SOFTWARE.
#
import copy
+import datetime
import json
import logging
import os
log = logging.getLogger(__name__)
+class OpenStackInstance(object):
+
+ def __init__(self, name_or_id, info=None):
+ self.name_or_id = name_or_id
+ if info is None:
+ self.set_info()
+ else:
+ self.info = dict(map(lambda (k,v): (k.lower(), v), info.iteritems()))
+
+ def set_info(self):
+ try:
+ info = json.loads(
+ misc.sh("openstack server show -f json " + self.name_or_id))
+ self.info = dict(map(
+ lambda p: (p['Field'].lower(), p['Value']), info))
+ except CalledProcessError:
+ self.info = None
+
+ def __getitem__(self, name):
+ return self.info[name.lower()]
+
+ def get_created(self):
+ now = datetime.datetime.now()
+ created = datetime.datetime.strptime(
+ self['created'], '%Y-%m-%dT%H:%M:%SZ')
+ return (now - created).total_seconds()
+
+ def exists(self):
+ return self.info is not None
+
+ def get_volumes(self):
+ """
+ Return the uuid of the volumes attached to the name_or_id
+ OpenStack instance.
+ """
+ volumes = self['os-extended-volumes:volumes_attached']
+ return [volume['id'] for volume in volumes ]
+
+ def get_addresses(self):
+ """
+ Return the list of IPs associated with instance_id in OpenStack.
+ """
+ with safe_while(sleep=2, tries=30,
+ action="get ip " + self['id']) as proceed:
+ while proceed():
+ found = re.match('.*\d+', self['addresses'])
+ if found:
+ return self['addresses']
+ self.set_info()
+
+ def get_ip_neutron(self):
+ subnets = json.loads(misc.sh("neutron subnet-list -f json -c id -c ip_version"))
+ subnet_id = None
+ for subnet in subnets:
+ if subnet['ip_version'] == 4:
+ subnet_id = subnet['id']
+ break
+ if not subnet_id:
+ raise Exception("no subnet with ip_version == 4")
+ ports = json.loads(misc.sh("neutron port-list -f json -c fixed_ips -c device_id"))
+ fixed_ips = None
+ for port in ports:
+ if port['device_id'] == self['id']:
+ fixed_ips = port['fixed_ips'].split("\n")
+ break
+ if not fixed_ips:
+ raise Exception("no fixed ip record found")
+ ip = None
+ for fixed_ip in fixed_ips:
+ record = json.loads(fixed_ip)
+ if record['subnet_id'] == subnet_id:
+ ip = record['ip_address']
+ break
+ if not ip:
+ raise Exception("no ip")
+ return ip
+
+ def get_ip(self, network):
+ """
+ Return the private IP of the OpenStack instance_id.
+ """
+ try:
+ return self.get_ip_neutron()
+ except Exception as e:
+ log.debug("ignoring get_ip_neutron exception " + str(e))
+ return re.findall(network + '=([\d.]+)',
+ self.get_addresses())[0]
+
+ def destroy(self):
+ """
+ Delete the name_or_id OpenStack instance.
+ """
+ if not self.exists():
+ return True
+ volumes = self.get_volumes()
+ misc.sh("openstack server set --name REMOVE-ME-" + self.name_or_id +
+ " " + self['id'])
+ misc.sh("openstack server delete --wait " + self['id'] +
+ " || true")
+ for volume in volumes:
+ misc.sh("openstack volume set --name REMOVE-ME " + volume)
+ misc.sh("openstack volume delete " + volume + " || true")
+ return True
+
+
class OpenStack(object):
# wget -O debian-8.0.qcow2 http://cdimage.debian.org/cdimage/openstack/current/debian-8.1.0-openstack-amd64.qcow2
current[key] = max(current[key], new[key])
return result
+ @staticmethod
+ def list_instances():
+ ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'"
+ all = json.loads(misc.sh(
+ "openstack server list -f json --long --name 'target'"))
+ return filter(lambda instance: ownedby in instance['Properties'], all)
+
+ @staticmethod
+ def list_volumes():
+ ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'"
+ all = json.loads(misc.sh(
+ "openstack volume list -f json --long"))
+ def select(volume):
+ return (ownedby in volume['Properties'] and
+ volume['Display Name'].startswith('target'))
+ return filter(select, all)
+
def cloud_init_wait(self, name_or_ip):
"""
Wait for cloud-init to complete on the name_or_ip OpenStack instance.
break
return success
- @staticmethod
- def show(name_or_id):
- """
- Run "openstack server show -f json <name_or_id>" and return the result.
-
- Does not handle exceptions.
- """
- try:
- return json.loads(
- misc.sh("openstack server show -f json %s" % name_or_id)
- )
- except CalledProcessError:
- return False
-
- @classmethod
- def exists(cls, name_or_id, server_info=None):
- """
- Return true if the OpenStack name_or_id instance exists,
- false otherwise.
-
- :param name_or_id: The name or ID of the server to query
- :param server_info: Optionally, use already-retrieved results of
- self.show()
- """
- if server_info is None:
- server_info = cls.show(name_or_id)
- if not server_info:
- return False
- if (cls.get_value(server_info, 'Name') == name_or_id or
- cls.get_value(server_info, 'ID') == name_or_id):
- return True
- return False
-
- @staticmethod
- def get_addresses(instance_id):
- """
- Return the list of IPs associated with instance_id in OpenStack.
- """
- with safe_while(sleep=2, tries=30,
- action="get ip " + instance_id) as proceed:
- while proceed():
- instance = misc.sh("openstack server show -f json " +
- instance_id)
- addresses = OpenStack.get_value(json.loads(instance),
- 'addresses')
- found = re.match('.*\d+', addresses)
- if found:
- return addresses
-
- @staticmethod
- def get_ip_neutron(instance_id):
- subnets = json.loads(misc.sh("neutron subnet-list -f json -c id -c ip_version"))
- subnet_id = None
- for subnet in subnets:
- if subnet['ip_version'] == 4:
- subnet_id = subnet['id']
- break
- if not subnet_id:
- raise Exception("no subnet with ip_version == 4")
- ports = json.loads(misc.sh("neutron port-list -f json -c fixed_ips -c device_id"))
- fixed_ips = None
- for port in ports:
- if port['device_id'] == instance_id:
- fixed_ips = port['fixed_ips'].split("\n")
- break
- if not fixed_ips:
- raise Exception("no fixed ip record found")
- ip = None
- for fixed_ip in fixed_ips:
- record = json.loads(fixed_ip)
- if record['subnet_id'] == subnet_id:
- ip = record['ip_address']
- break
- if not ip:
- raise Exception("no ip")
- return ip
-
def get_ip(self, instance_id, network):
- """
- Return the private IP of the OpenStack instance_id.
- """
- try:
- return self.get_ip_neutron(instance_id)
- except Exception as e:
- log.debug("ignoring get_ip_neutron exception " + str(e))
- return re.findall(network + '=([\d.]+)',
- self.get_addresses(instance_id))[0]
+ return OpenStackInstance(instance_id).get_ip(network)
class TeuthologyOpenStack(OpenStack):
ip = TeuthologyOpenStack.get_floating_ip(instance_id)
if not ip:
ip = re.findall('([\d.]+)$',
- TeuthologyOpenStack.get_addresses(instance_id))[0]
+ OpenStackInstance(instance_id).get_addresses())[0]
return ip
@staticmethod
def get_instance_id(name):
- instance = json.loads(misc.sh("openstack server show -f json " + name))
- return TeuthologyOpenStack.get_value(instance, 'id')
+ return OpenStackInstance(name)['id']
@staticmethod
def delete_floating_ip(instance_id):
"""
Return true if there exists an instance running the teuthology cluster.
"""
- if not self.exists(self.args.name):
+ instance = OpenStackInstance(self.args.name)
+ if not instance.exists():
return False
- instance_id = self.get_instance_id(self.args.name)
- ip = self.get_floating_ip_or_ip(instance_id)
+ ip = self.get_floating_ip_or_ip(instance['id'])
return self.cloud_init_wait(ip)
def teardown(self):
import logging
import os
import pytest
+import subprocess
import tempfile
from mock import patch
import teuthology
from teuthology import misc
-from teuthology.openstack import TeuthologyOpenStack, OpenStack
+from teuthology.openstack import TeuthologyOpenStack, OpenStack, OpenStackInstance
import scripts.openstack
+
+class TestOpenStackInstance(object):
+
+ teuthology_instance = '[{"Field": "OS-DCF:diskConfig", "Value": "MANUAL"}, {"Field": "OS-EXT-AZ:availability_zone", "Value": "nova"}, {"Field": "OS-EXT-STS:power_state", "Value": 1}, {"Field": "OS-EXT-STS:task_state", "Value": null}, {"Field": "OS-EXT-STS:vm_state", "Value": "active"}, {"Field": "OS-SRV-USG:launched_at", "Value": "2015-11-12T14:18:42.000000"}, {"Field": "OS-SRV-USG:terminated_at", "Value": null}, {"Field": "accessIPv4", "Value": ""}, {"Field": "accessIPv6", "Value": ""}, {"Field": "addresses", "Value": "Ext-Net=167.114.233.32"}, {"Field": "config_drive", "Value": ""}, {"Field": "created", "Value": "2015-11-12T14:18:22Z"}, {"Field": "flavor", "Value": "eg-30 (3c1d6170-0097-4b5c-a3b3-adff1b7a86e0)"}, {"Field": "hostId", "Value": "b482bcc97b6b2a5b3569dc349e2b262219676ddf47a4eaf72e415131"}, {"Field": "id", "Value": "f3ca32d7-212b-458b-a0d4-57d1085af953"}, {"Field": "image", "Value": "teuthology-ubuntu-14.04 (4300a7ca-4fbd-4b34-a8d5-5a4ebf204df5)"}, {"Field": "key_name", "Value": "myself"}, {"Field": "name", "Value": "teuthology"}, {"Field": "os-extended-volumes:volumes_attached", "Value": [{"id": "627e2631-fbb3-48cd-b801-d29cd2a76f74"}, {"id": "09837649-0881-4ee2-a560-adabefc28764"}, {"id": "44e5175b-6044-40be-885a-c9ddfb6f75bb"}]}, {"Field": "progress", "Value": 0}, {"Field": "project_id", "Value": "131b886b156a4f84b5f41baf2fbe646c"}, {"Field": "properties", "Value": ""}, {"Field": "security_groups", "Value": [{"name": "teuthology"}]}, {"Field": "status", "Value": "ACTIVE"}, {"Field": "updated", "Value": "2015-11-12T14:18:42Z"}, {"Field": "user_id", "Value": "291dde1633154837be2693c6ffa6315c"}]'
+
+ teuthology_instance_no_addresses = '[{"Field": "addresses", "Value": ""}, {"Field": "id", "Value": "f3ca32d7-212b-458b-a0d4-57d1085af953"}]'
+
+ def test_init(self):
+ with patch.multiple(
+ misc,
+ sh=lambda cmd: self.teuthology_instance,
+ ):
+ o = OpenStackInstance('NAME')
+ assert o['id'] == 'f3ca32d7-212b-458b-a0d4-57d1085af953'
+ o = OpenStackInstance('NAME', {"id": "OTHER"})
+ assert o['id'] == "OTHER"
+
+ def test_get_created(self):
+ with patch.multiple(
+ misc,
+ sh=lambda cmd: self.teuthology_instance,
+ ):
+ o = OpenStackInstance('NAME')
+ assert o.get_created() > 0
+
+ def test_exists(self):
+ with patch.multiple(
+ misc,
+ sh=lambda cmd: self.teuthology_instance,
+ ):
+ o = OpenStackInstance('NAME')
+ assert o.exists()
+ def sh_raises(cmd):
+ raise subprocess.CalledProcessError('FAIL', 'BAD')
+ with patch.multiple(
+ misc,
+ sh=sh_raises,
+ ):
+ o = OpenStackInstance('NAME')
+ assert not o.exists()
+
+ def test_volumes(self):
+ with patch.multiple(
+ misc,
+ sh=lambda cmd: self.teuthology_instance,
+ ):
+ o = OpenStackInstance('NAME')
+ assert len(o.get_volumes()) == 3
+
+ def test_get_addresses(self):
+ answers = [
+ self.teuthology_instance_no_addresses,
+ self.teuthology_instance,
+ ]
+ def sh(self):
+ return answers.pop(0)
+ with patch.multiple(
+ misc,
+ sh=sh,
+ ):
+ o = OpenStackInstance('NAME')
+ assert o.get_addresses() == 'Ext-Net=167.114.233.32'
+
+ def test_get_ip_neutron(self):
+ instance_id = '8e1fd70a-3065-46f8-9c30-84dc028c1834'
+ ip = '10.10.10.4'
+ def sh(cmd):
+ if 'neutron subnet-list' in cmd:
+ return """
+[
+ {
+ "ip_version": 6,
+ "id": "c45b9661-b2ba-4817-9e3a-f8f63bf32989"
+ },
+ {
+ "ip_version": 4,
+ "id": "e03a3dbc-afc8-4b52-952e-7bf755397b50"
+ }
+]
+ """
+ elif 'neutron port-list' in cmd:
+ return ("""
+[
+ {
+ "device_id": "915504ad-368b-4cce-be7c-4f8a83902e28",
+ "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"10.10.10.1\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc::1\\"}"
+ },
+ {
+ "device_id": "{instance_id}",
+ "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"{ip}\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc:f816:3eff:fe07:76c1\\"}"
+ },
+ {
+ "device_id": "17e4a968-4caa-4cee-8e4b-f950683a02bd",
+ "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"10.10.10.5\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc:f816:3eff:fe9c:37f0\\"}"
+ }
+]
+ """.replace('{instance_id}', instance_id).
+ replace('{ip}', ip))
+ else:
+ raise Exception("unexpected " + cmd)
+ with patch.multiple(
+ misc,
+ sh=sh,
+ ):
+ assert ip == OpenStackInstance(
+ instance_id,
+ { 'id': instance_id },
+ ).get_ip_neutron()
+
class TestOpenStack(object):
def test_interpret_hints(self):
else:
del os.environ['OS_AUTH_URL']
- def test_get_ip_neutron(self):
- instance_id = '8e1fd70a-3065-46f8-9c30-84dc028c1834'
- ip = '10.10.10.4'
- def sh(cmd):
- if 'neutron subnet-list' in cmd:
- return """
-[
- {
- "ip_version": 6,
- "id": "c45b9661-b2ba-4817-9e3a-f8f63bf32989"
- },
- {
- "ip_version": 4,
- "id": "e03a3dbc-afc8-4b52-952e-7bf755397b50"
- }
-]
- """
- elif 'neutron port-list' in cmd:
- return ("""
-[
- {
- "device_id": "915504ad-368b-4cce-be7c-4f8a83902e28",
- "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"10.10.10.1\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc::1\\"}"
- },
- {
- "device_id": "{instance_id}",
- "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"{ip}\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc:f816:3eff:fe07:76c1\\"}"
- },
- {
- "device_id": "17e4a968-4caa-4cee-8e4b-f950683a02bd",
- "fixed_ips": "{\\"subnet_id\\": \\"e03a3dbc-afc8-4b52-952e-7bf755397b50\\", \\"ip_address\\": \\"10.10.10.5\\"}\\n{\\"subnet_id\\": \\"c45b9661-b2ba-4817-9e3a-f8f63bf32989\\", \\"ip_address\\": \\"2607:f298:6050:9afc:f816:3eff:fe9c:37f0\\"}"
- }
-]
- """.replace('{instance_id}', instance_id).
- replace('{ip}', ip))
- else:
- raise Exception("unexpected " + cmd)
- with patch.multiple(
- misc,
- sh=sh,
- ):
- assert ip == OpenStack.get_ip_neutron(instance_id)
-
class TestTeuthologyOpenStack(object):
@classmethod
import random
import re
import subprocess
+import time
import tempfile
import yaml
-from .openstack import OpenStack
+from .openstack import OpenStack, OpenStackInstance
from .config import config
from .contextutil import safe_while
from .misc import decanonicalize_hostname, get_distro, get_distro_version
misc.sh("openstack server add volume " +
name + " " + volume_name)
- def list_volumes(self, name_or_id, server_info=None):
- """
- Return the uuid of the volumes attached to the name_or_id
- OpenStack instance.
-
- :param name_or_id: The name or ID of the server to query
- :param server_info: Optionally, use already-retrieved results of
- self.show()
- """
- if server_info is None:
- server_info = self.show(name_or_id)
- if not server_info:
- return []
- volumes = self.get_value(server_info,
- 'os-extended-volumes:volumes_attached')
- return [volume['id'] for volume in volumes ]
-
@staticmethod
def ip2name(prefix, ip):
"""
" --property ownedby=" + config.openstack['ip'] +
" --wait " +
" " + self.basename)
- all_instances = json.loads(misc.sh("openstack server list -f json --long"))
instances = filter(
lambda instance: self.property in instance['Properties'],
- all_instances)
+ self.list_instances())
+ instances = [OpenStackInstance(i['ID'], i) for i in instances]
fqdns = []
try:
network = config['openstack'].get('network', '')
for instance in instances:
- name = self.ip2name(self.basename, self.get_ip(instance['ID'], network))
+ name = self.ip2name(self.basename, instance.get_ip(network))
misc.sh("openstack server set " +
"--name " + name + " " +
instance['ID'])
fqdn = name + '.' + config.lab_domain
if not misc.ssh_keyscan_wait(fqdn):
raise ValueError('ssh_keyscan_wait failed for ' + fqdn)
- import time
time.sleep(15)
if not self.cloud_init_wait(fqdn):
raise ValueError('clound_init_wait failed for ' + fqdn)
return fqdns
def destroy(self, name_or_id):
- """
- Delete the name_or_id OpenStack instance.
- """
log.debug('ProvisionOpenStack:destroy ' + name_or_id)
- server_info = self.show(name_or_id)
- if not self.exists(name_or_id, server_info=server_info):
- return True
- volumes = self.list_volumes(name_or_id, server_info=server_info)
- server_id = self.get_value(server_info, 'ID')
- misc.sh("openstack server set --name REMOVE-ME-" + name_or_id +
- " " + server_id)
- misc.sh("openstack server delete --wait " + server_id + " || true")
- for volume in volumes:
- misc.sh("openstack volume set --name REMOVE-ME " + volume)
- misc.sh("openstack volume delete " + volume + " || true")
- return True
+ return OpenStackInstance(name_or_id).destroy()
def create_if_vm(ctx, machine_name, _downburst=None):