log = logging.getLogger(__name__)
+class NoFlavorException(Exception):
+ pass
+
def enforce_json_dictionary(something):
if type(something) is not types.DictType:
raise Exception(
class OpenStack(object):
- # wget -O debian-8.0.qcow2 http://cdimage.debian.org/cdimage/openstack/current/debian-8.1.0-openstack-amd64.qcow2
- # wget -O ubuntu-12.04.qcow2 https://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-amd64-disk1.img
- # wget -O ubuntu-12.04-i386.qcow2 https://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-i386-disk1.img
- # wget -O ubuntu-14.04.qcow2 https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
- # wget -O ubuntu-14.04-i386.qcow2 https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-i386-disk1.img
- # wget -O ubuntu-15.04.qcow2 https://cloud-images.ubuntu.com/vivid/current/vivid-server-cloudimg-arm64-disk1.img
- # wget -O ubuntu-15.04-i386.qcow2 https://cloud-images.ubuntu.com/vivid/current/vivid-server-cloudimg-i386-disk1.img
- # wget -O opensuse-13.2 http://download.opensuse.org/repositories/Cloud:/Images:/openSUSE_13.2/images/openSUSE-13.2-OpenStack-Guest.x86_64.qcow2
- # wget -O opensuse-42.1 http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.1/images/openSUSE-Leap-42.1-OpenStack.x86_64.qcow2
- # wget -O centos-7.0.qcow2 http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2
- # wget -O centos-6.6.qcow2 http://cloud.centos.org/centos/6/images/CentOS-6-x86_64-GenericCloud.qcow2
- # wget -O fedora-22.qcow2 https://download.fedoraproject.org/pub/fedora/linux/releases/22/Cloud/x86_64/Images/Fedora-Cloud-Base-22-20150521.x86_64.qcow2
- # wget -O fedora-21.qcow2 http://fedora.mirrors.ovh.net/linux/releases/21/Cloud/Images/x86_64/Fedora-Cloud-Base-20141203-21.x86_64.qcow2
- # wget -O fedora-20.qcow2 http://fedora.mirrors.ovh.net/linux/releases/20/Images/x86_64/Fedora-x86_64-20-20131211.1-sda.qcow2
+ # http://cdimage.debian.org/cdimage/openstack/current/
+ # https://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-amd64-disk1.img etc.
+ # http://download.opensuse.org/repositories/Cloud:/Images:/openSUSE_13.2/images/openSUSE-13.2-OpenStack-Guest.x86_64.qcow2
+ # http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 etc.
+ # http://cloud.centos.org/centos/6/images/CentOS-6-x86_64-GenericCloud.qcow2 etc.
+ # https://download.fedoraproject.org/pub/fedora/linux/releases/22/Cloud/x86_64/Images/Fedora-Cloud-Base-22-20150521.x86_64.qcow2
+ # http://fedora.mirrors.ovh.net/linux/releases/21/Cloud/Images/x86_64/Fedora-Cloud-Base-20141203-21.x86_64.qcow2
+ # http://fedora.mirrors.ovh.net/linux/releases/20/Images/x86_64/Fedora-x86_64-20-20131211.1-sda.qcow2
image2url = {
- 'centos-6.5': 'http://cloud.centos.org/centos/6/images/CentOS-6-x86_64-GenericCloud-1508.qcow2',
- 'centos-7.0': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1503.qcow2',
- 'centos-7.1': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1503.qcow2',
- 'centos-7.2': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1511.qcow2',
- 'opensuse-42.1': 'http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.1/images/openSUSE-Leap-42.1-OpenStack.x86_64.qcow2',
- 'ubuntu-12.04': 'https://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-amd64-disk1.img',
- 'ubuntu-14.04': 'https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img',
- 'debian-8.0': 'http://cdimage.debian.org/cdimage/openstack/current/debian-8.2.0-openstack-amd64.qcow2',
+ 'centos-6.5-x86_64': 'http://cloud.centos.org/centos/6/images/CentOS-6-x86_64-GenericCloud-1508.qcow2',
+ 'centos-7.0-x86_64': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1503.qcow2',
+ 'centos-7.1-x86_64': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1503.qcow2',
+ 'centos-7.2-x86_64': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1511.qcow2',
+ 'opensuse-42.1-x86_64': 'http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.1/images/openSUSE-Leap-42.1-OpenStack.x86_64.qcow2',
+ 'ubuntu-12.04-x86_64': 'https://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-amd64-disk1.img',
+ 'ubuntu-14.04-x86_64': 'https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img',
+ 'ubuntu-14.04-arm64': 'https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-arm64-disk1.img',
+ 'ubuntu-14.04-i686': 'https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-i386-disk1.img',
+ 'debian-8.0-x86_64': 'http://cdimage.debian.org/cdimage/openstack/current/debian-8.3.0-openstack-amd64.qcow2',
}
def __init__(self):
def set_provider(self):
if 'OS_AUTH_URL' not in os.environ:
raise Exception('no OS_AUTH_URL environment variable')
- providers = (('cloud.ovh.net', 'ovh'),
+ providers = (('runabove.io', 'runabove'),
+ ('cloud.ovh.net', 'ovh'),
('entercloudsuite.com', 'entercloudsuite'),
('rackspacecloud.com', 'rackspace'),
('dream.io', 'dreamhost'))
network))
return self.get_value(r, 'id')
- def type_version(self, os_type, os_version):
+ def type_version_arch(self, os_type, os_version, arch):
"""
Return the string used to differentiate os_type and os_version in names.
"""
- return os_type + '-' + os_version
+ return os_type + '-' + os_version + '-' + arch
def image_name(self, name):
"""
"""
return "teuthology-" + name
- def image_create(self, name):
+ def image_create(self, name, arch):
"""
- Upload an image into OpenStack with glance.
+ Upload an image into OpenStack
"""
misc.sh("wget -c -O " + name + ".qcow2 " + self.image2url[name])
if self.get_provider() == 'dreamhost':
else:
image = name + ".qcow2"
disk_format = 'qcow2'
- misc.sh("glance image-create --property ownedby=teuthology " +
+ if self.provider == 'runabove':
+ properties = [
+ "--property architecture_restrict=" + arch,
+ "--property architecture=" + arch
+ ]
+ elif self.get_provider() == 'cloudlab':
+ # if not, nova-compute fails on the compute node with
+ # Error: Cirrus VGA not available
+ properties = [
+ "--property hw_video_model=vga",
+ ]
+ else:
+ properties = []
+
+ misc.sh("openstack image create --property ownedby=teuthology " +
+ " ".join(properties) +
" --disk-format=" + disk_format + " --container-format=bare " +
- " --visibility private" +
- " --file " + image + " --name " + self.image_name(name))
+ " --private" +
+ " --file " + image + " " + self.image_name(name))
- def image(self, os_type, os_version):
+ def image(self, os_type, os_version, arch):
"""
Return the image name for the given os_type and os_version. If the image
does not exist it will be created.
"""
- name = self.type_version(os_type, os_version)
+ name = self.type_version_arch(os_type, os_version, arch)
if not self.image_exists(name):
- self.image_create(name)
+ self.image_create(name, arch)
return self.image_name(name)
- def flavor(self, hint, select):
+ def get_sorted_flavors(self, arch, select):
"""
Return the smallest flavor that satisfies the desired size.
"""
for flavor in flavors:
if select and not re.match(select, flavor['Name']):
continue
- if (flavor['RAM'] >= hint['ram'] and
- flavor['VCPUs'] >= hint['cpus'] and
- flavor['Disk'] >= hint['disk']):
- found.append(flavor)
- if not found:
- raise Exception("openstack flavor list: " + flavors_string +
- " does not contain a flavor in which" +
- " the desired " + str(hint) + " can fit")
+ found.append(flavor)
def sort_flavor(a, b):
return (a['VCPUs'] - b['VCPUs'] or
a['RAM'] - b['RAM'] or
a['Disk'] - b['Disk'])
- sorted_flavor = sorted(found, cmp=sort_flavor)
- log.debug("sorted flavor = " + str(sorted_flavor))
- return sorted_flavor[0]['Name']
+ sorted_flavors = sorted(found, cmp=sort_flavor)
+ log.debug("sorted flavors = " + str(sorted_flavors))
+ return sorted_flavors
+
+ def flavor(self, hint, arch, select):
+ """
+ Return the smallest flavor that satisfies the desired size.
+ """
+ flavors = self.get_sorted_flavors(arch, select)
+ for flavor in flavors:
+ if (flavor['RAM'] >= hint['ram'] and
+ flavor['VCPUs'] >= hint['cpus'] and
+ flavor['Disk'] >= hint['disk']):
+ return flavor['Name']
+ raise NoFlavorException("openstack flavor list: " + str(flavors) +
+ " does not contain a flavor in which" +
+ " the desired " + str(hint) + " can fit")
+
+ def flavor_range(self, min, good, arch, select):
+ """
+ Return the smallest flavor that satisfies the good hint.
+ If no such flavor, get the largest flavor smaller than good
+ and larger than min.
+ """
+ flavors = self.get_sorted_flavors(arch, select)
+ low_range = []
+ for flavor in flavors:
+ if (flavor['RAM'] >= good['ram'] and
+ flavor['VCPUs'] >= good['cpus'] and
+ flavor['Disk'] >= good['disk']):
+ return flavor['Name']
+ else:
+ low_range.append(flavor)
+ low_range.reverse()
+ for flavor in low_range:
+ if (flavor['RAM'] >= min['ram'] and
+ flavor['VCPUs'] >= min['cpus'] and
+ flavor['Disk'] >= min['disk']):
+ return flavor['Name']
+ raise NoFlavorException("openstack flavor list: " + str(flavors) +
+ " does not contain a flavor which" +
+ " is larger than " + str(min))
def interpret_hints(self, defaults, hints):
"""
def get_ip(self, instance_id, network):
return OpenStackInstance(instance_id).get_ip(network)
+ def get_available_arch(self):
+ if (self.provider == 'runabove' and
+ 'HZ1' in os.environ.get('OS_REGION_NAME', '')):
+ return ('aarch64',)
+ else:
+ return ('x86_64', 'i686')
+
+ def get_default_arch(self):
+ return self.get_available_archs()[0]
+
class TeuthologyOpenStack(OpenStack):
log.exception("flavor list")
raise Exception("verify openrc.sh has been sourced")
- def flavor(self):
+ def flavor(self, arch):
"""
Return an OpenStack flavor fit to run the teuthology cluster.
The RAM size depends on the maximum number of workers that
select = None
if self.get_provider() == 'ovh':
select = '^(vps|eg)-'
- return super(TeuthologyOpenStack, self).flavor(hint, select)
+ return super(TeuthologyOpenStack, self).flavor(hint, arch, select)
def net(self):
"""
security_group = ''
else:
security_group = " --security-group teuthology"
+ arch = self.get_default_arch()
self.run(
"server create " +
- " --image '" + self.image('ubuntu', '14.04') + "' " +
- " --flavor '" + self.flavor() + "' " +
+ " --image '" + self.image('ubuntu', '14.04', arch) + "' " +
+ " --flavor '" + self.flavor(arch) + "' " +
" " + self.net() +
" --key-name " + self.args.key_name +
" --user-data " + user_data +
from teuthology import misc
from teuthology.config import set_config_attr
from teuthology.openstack import TeuthologyOpenStack, OpenStack, OpenStackInstance
+from teuthology.openstack import NoFlavorException
import scripts.openstack
class TestOpenStack(object):
+ flavors = """[
+ {
+ "Name": "eg-60",
+ "RAM": 60000,
+ "Ephemeral": 0,
+ "VCPUs": 16,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "0297d7ac-fe6f-4ff1-b6e7-0b8b0908c94f"
+ },
+ {
+ "Name": "win-sp-60",
+ "RAM": 60000,
+ "Ephemeral": 0,
+ "VCPUs": 4,
+ "Is Public": true,
+ "Disk": 400,
+ "ID": "0417a0e6-f68a-4b8b-a642-ca5ecb9652f7"
+ },
+ {
+ "Name": "win-sp-240",
+ "RAM": 240000,
+ "Ephemeral": 0,
+ "VCPUs": 16,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "07885848-8831-486d-8525-91484c09cc7e"
+ },
+ {
+ "Name": "vps-ssd-1",
+ "RAM": 2000,
+ "Ephemeral": 0,
+ "VCPUs": 1,
+ "Is Public": true,
+ "Disk": 10,
+ "ID": "164fcc7e-7771-414f-a607-b388cb7b7aa0"
+ },
+ {
+ "Name": "eg-120",
+ "RAM": 120000,
+ "Ephemeral": 0,
+ "VCPUs": 32,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "1f1efedf-ec91-4a42-acd7-f5cf64b02d3c"
+ },
+ {
+ "Name": "win-eg-7",
+ "RAM": 7000,
+ "Ephemeral": 0,
+ "VCPUs": 2,
+ "Is Public": true,
+ "Disk": 200,
+ "ID": "377ded36-491f-4ad7-9eb4-876798b2aea9"
+ },
+ {
+ "Name": "eg-30",
+ "RAM": 30000,
+ "Ephemeral": 0,
+ "VCPUs": 8,
+ "Is Public": true,
+ "Disk": 800,
+ "ID": "3c1d6170-0097-4b5c-a3b3-adff1b7a86e0"
+ },
+ {
+ "Name": "eg-15",
+ "RAM": 15000,
+ "Ephemeral": 0,
+ "VCPUs": 4,
+ "Is Public": true,
+ "Disk": 400,
+ "ID": "675558ea-04fe-47a2-83de-40be9b2eacd4"
+ },
+ {
+ "Name": "win-eg-30",
+ "RAM": 30000,
+ "Ephemeral": 0,
+ "VCPUs": 8,
+ "Is Public": true,
+ "Disk": 800,
+ "ID": "6e12cae3-0492-483c-aa39-54a0dcaf86dd"
+ },
+ {
+ "Name": "vps-ssd-2",
+ "RAM": 4000,
+ "Ephemeral": 0,
+ "VCPUs": 1,
+ "Is Public": true,
+ "Disk": 20,
+ "ID": "7939cc5c-79b1-45c0-be2d-aa935d92faa1"
+ },
+ {
+ "Name": "sp-60",
+ "RAM": 60000,
+ "Ephemeral": 0,
+ "VCPUs": 4,
+ "Is Public": true,
+ "Disk": 400,
+ "ID": "80d8510a-79cc-4307-8db7-d1965c9e8ddb"
+ },
+ {
+ "Name": "win-sp-30",
+ "RAM": 30000,
+ "Ephemeral": 0,
+ "VCPUs": 2,
+ "Is Public": true,
+ "Disk": 200,
+ "ID": "8be9dc29-3eca-499b-ae2d-e3c99699131a"
+ },
+ {
+ "Name": "sp-120",
+ "RAM": 120000,
+ "Ephemeral": 0,
+ "VCPUs": 8,
+ "Is Public": true,
+ "Disk": 800,
+ "ID": "ac74cb45-d895-47dd-b9cf-c17778033d83"
+ },
+ {
+ "Name": "win-eg-15",
+ "RAM": 15000,
+ "Ephemeral": 0,
+ "VCPUs": 4,
+ "Is Public": true,
+ "Disk": 400,
+ "ID": "ae900175-72bd-4fbc-8ab2-4673b468aa5b"
+ },
+ {
+ "Name": "win-eg-120",
+ "RAM": 120000,
+ "Ephemeral": 0,
+ "VCPUs": 32,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "b798e44e-bf71-488c-9335-f20bf5976547"
+ },
+ {
+ "Name": "sp-30",
+ "RAM": 30000,
+ "Ephemeral": 0,
+ "VCPUs": 2,
+ "Is Public": true,
+ "Disk": 200,
+ "ID": "d1acf88d-6f55-4c5c-a914-4ecbdbd50d6b"
+ },
+ {
+ "Name": "win-eg-60",
+ "RAM": 60000,
+ "Ephemeral": 0,
+ "VCPUs": 16,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "def75cbd-a4b1-4f82-9152-90c65df9587b"
+ },
+ {
+ "Name": "vps-ssd-3",
+ "RAM": 8000,
+ "Ephemeral": 0,
+ "VCPUs": 2,
+ "Is Public": true,
+ "Disk": 40,
+ "ID": "e43d7458-6b82-4a78-a712-3a4dc6748cf4"
+ },
+ {
+ "Name": "sp-240",
+ "RAM": 240000,
+ "Ephemeral": 0,
+ "VCPUs": 16,
+ "Is Public": true,
+ "Disk": 1600,
+ "ID": "ed286e2c-769f-4c47-ac52-b8de7a4891f6"
+ },
+ {
+ "Name": "win-sp-120",
+ "RAM": 120000,
+ "Ephemeral": 0,
+ "VCPUs": 8,
+ "Is Public": true,
+ "Disk": 800,
+ "ID": "f247dc56-395b-49de-9a62-93ccc4fff4ed"
+ },
+ {
+ "Name": "eg-7",
+ "RAM": 7000,
+ "Ephemeral": 0,
+ "VCPUs": 2,
+ "Is Public": true,
+ "Disk": 200,
+ "ID": "fa3cc551-0358-4170-be64-56ea432b064c"
+ }
+ ]"""
+
+ @patch('teuthology.misc.sh')
+ def test_sorted_flavors(self, m_sh):
+ o = OpenStack()
+ select = '^(vps|eg)-'
+ m_sh.return_value = TestOpenStack.flavors
+ flavors = o.get_sorted_flavors('arch', select)
+ assert ['vps-ssd-1',
+ 'vps-ssd-2',
+ 'eg-7',
+ 'vps-ssd-3',
+ 'eg-15',
+ 'eg-30',
+ 'eg-60',
+ 'eg-120',
+ ] == [ f['Name'] for f in flavors ]
+ m_sh.assert_called_with("openstack flavor list -f json")
+
+ def test_flavor(self):
+ def get_sorted_flavors(self, arch, select):
+ return [
+ {
+ 'Name': 'too_small',
+ 'RAM': 2048,
+ 'Disk': 50,
+ 'VCPUs': 1,
+ },
+ ]
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+ with pytest.raises(NoFlavorException):
+ hint = { 'ram': 1000, 'disk': 40, 'cpus': 2 }
+ OpenStack().flavor(hint, 'arch', None)
+
+ flavor = 'good-flavor'
+ def get_sorted_flavors(self, arch, select):
+ return [
+ {
+ 'Name': flavor,
+ 'RAM': 2048,
+ 'Disk': 50,
+ 'VCPUs': 2,
+ },
+ ]
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+ hint = { 'ram': 1000, 'disk': 40, 'cpus': 2 }
+ assert flavor == OpenStack().flavor(hint, 'arch', None)
+
+ def test_flavor_range(self):
+ flavors = [
+ {
+ 'Name': 'too_small',
+ 'RAM': 2048,
+ 'Disk': 50,
+ 'VCPUs': 1,
+ },
+ ]
+ def get_sorted_flavors(self, arch, select):
+ return flavors
+
+ min = { 'ram': 1000, 'disk': 40, 'cpus': 2 }
+ good = { 'ram': 4000, 'disk': 40, 'cpus': 2 }
+
+ #
+ # there are no flavors in the required range
+ #
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+ with pytest.raises(NoFlavorException):
+ OpenStack().flavor_range(min, good, 'arch', None)
+
+ #
+ # there is one flavor in the required range
+ #
+ flavors.append({
+ 'Name': 'min',
+ 'RAM': 2048,
+ 'Disk': 40,
+ 'VCPUs': 2,
+ })
+
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+
+ assert 'min' == OpenStack().flavor_range(min, good, 'arch', None)
+
+ #
+ # out of the two flavors in the required range, get the bigger one
+ #
+ flavors.append({
+ 'Name': 'good',
+ 'RAM': 3000,
+ 'Disk': 40,
+ 'VCPUs': 2,
+ })
+
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+
+ assert 'good' == OpenStack().flavor_range(min, good, 'arch', None)
+
+ #
+ # there is one flavor bigger or equal to good, get this one
+ #
+ flavors.append({
+ 'Name': 'best',
+ 'RAM': 4000,
+ 'Disk': 40,
+ 'VCPUs': 2,
+ })
+
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+
+ assert 'best' == OpenStack().flavor_range(min, good, 'arch', None)
+
+ #
+ # there are two flavors bigger or equal to good, get the smallest one
+ #
+ flavors.append({
+ 'Name': 'too_big',
+ 'RAM': 30000,
+ 'Disk': 400,
+ 'VCPUs': 20,
+ })
+
+ with patch.multiple(
+ OpenStack,
+ get_sorted_flavors=get_sorted_flavors,
+ ):
+
+ assert 'best' == OpenStack().flavor_range(min, good, 'arch', None)
+
+
def test_interpret_hints(self):
defaults = {
'machine': {