...
========= 1 passed in 204.35 seconds =========
+Defining instances flavor and volumes
+-------------------------------------
+
+Each target (i.e. a virtual machine or instance in the OpenStack
+parlance) created by the OpenStack backend are exactly the same. By
+default they have at least 8GB RAM, 20GB disk, 1 cpus and no disk
+attached. It is equivalent to having the following in the
+`~/.teuthology.yaml <https://github.com/ceph/teuthology/blob/master/docs/siteconfig.rst>`_ file::
+
+ openstack:
+ ...
+ machine:
+ disk: 20 # GB
+ ram: 8000 # MB
+ cpus: 1
+ volumes:
+ count: 0
+ size: 1 # GB
+
+If a job needs more RAM or disk etc. the following can be included in
+an existing facet (yaml file in the teuthology parlance)::
+
+ openstack:
+ - machine:
+ disk: 100 # GB
+ volumes:
+ count: 4
+ size: 10 # GB
+
+Teuthology interprets this as the minimimum requirements, on top of
+the defaults found in the ``~/.teuthology.yaml`` file and the job will
+be given instances with at least 100GB root disk, 8GB RAM, 1 cpus and
+four 10GB volumes attached. The highest value wins: if the job claims
+to need 4GB RAM and the defaults are 8GB RAM, the targets will all
+have 8GB RAM.
+
+Note the dash before the ``machine`` key: the ``openstack`` element is
+an array with one value. If the dash is missing, it is a dictionary instead.
+It matters because there can be multiple entries per job such as::
+
+ openstack:
+ - machine:
+ disk: 40 # GB
+ ram: 8000 # MB
+
+ openstack:
+ - machine:
+ ram: 32000 # MB
+
+ openstack:
+ - volumes: # attached to each instance
+ count: 3
+ size: 200 # GB
+
+When a job is composed with these, theuthology aggregates them as::
+
+ openstack:
+ - machine:
+ disk: 40 # GB
+ ram: 8000 # MB
+ - machine:
+ ram: 32000 # MB
+ - volumes: # attached to each instance
+ count: 3
+ size: 200 # GB
+
+i.e. all entries are grouped in a list in the same fashion ``tasks`` are.
+The resource requirement is the maximum of the resources found in each
+element (including the default values). In the example above it is equivalent to::
+
+ openstack:
+ machine:
+ disk: 40 # GB
+ ram: 32000 # MB
+ volumes: # attached to each instance
+ count: 3
+ size: 200 # GB
+
VIRTUAL MACHINE SUPPORT
=======================
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
+import copy
import json
import logging
import os
import subprocess
import tempfile
import teuthology
+import types
from teuthology.contextutil import safe_while
from teuthology.config import config as teuth_config
log.debug("sorted flavor = " + str(sorted_flavor))
return sorted_flavor[0]['Name']
+ def interpret_hints(self, defaults, hints):
+ """
+ Return a hint hash which is the interpretation of a list of hints
+ """
+ result = copy.deepcopy(defaults)
+ if not hints:
+ return result
+ if type(hints) is types.DictType:
+ hints = [hints]
+ # TODO: raise after converting all ceph-qa-suite to only use arrays (oct 2015)
+ # raise Exception("openstack: " + str(hints) + " must be an array, not a dict")
+ for hint in hints:
+ for resource in ('machine', 'volumes'):
+ if resource in hint:
+ new = hint[resource]
+ current = result[resource]
+ for key, value in hint[resource].iteritems():
+ current[key] = max(current[key], new[key])
+ return result
+
def cloud_init_wait(self, name_or_ip):
"""
Wait for cloud-init to complete on the name_or_ip OpenStack instance.
import teuthology
from teuthology import misc
-from teuthology.openstack import TeuthologyOpenStack
+from teuthology.openstack import TeuthologyOpenStack, OpenStack
import scripts.openstack
+class TestOpenStack(object):
+
+ def test_interpret_hints(self):
+ defaults = {
+ 'machine': {
+ 'ram': 0,
+ 'disk': 0,
+ 'cpus': 0,
+ },
+ 'volumes': {
+ 'count': 0,
+ 'size': 0,
+ },
+ }
+ expected_disk = 10 # first hint larger than the second
+ expected_ram = 20 # second hint larger than the first
+ expected_cpus = 0 # not set, hence zero by default
+ expected_count = 30 # second hint larger than the first
+ expected_size = 40 # does not exist in the first hint
+ hints = [
+ {
+ 'machine': {
+ 'ram': 2,
+ 'disk': expected_disk,
+ },
+ 'volumes': {
+ 'count': 9,
+ 'size': expected_size,
+ },
+ },
+ {
+ 'machine': {
+ 'ram': expected_ram,
+ 'disk': 3,
+ },
+ 'volumes': {
+ 'count': expected_count,
+ },
+ },
+ ]
+ hint = OpenStack().interpret_hints(defaults, hints)
+ assert hint == {
+ 'machine': {
+ 'ram': expected_ram,
+ 'disk': expected_disk,
+ 'cpus': expected_cpus,
+ },
+ 'volumes': {
+ 'count': expected_count,
+ 'size': expected_size,
+ }
+ }
+ assert defaults == OpenStack().interpret_hints(defaults, None)
+
+
class TestTeuthologyOpenStack(object):
@classmethod
lab_domain=config.lab_domain)
open(self.user_data, 'w').write(user_data)
- def attach_volumes(self, name, hint):
+ def attach_volumes(self, name, volumes):
"""
Create and attach volumes to the named OpenStack instance.
"""
- if hint:
- volumes = hint['volumes']
- else:
- volumes = config['openstack']['volumes']
for i in range(volumes['count']):
volume_name = name + '-' + str(i)
try:
described in resources_hint.
"""
log.debug('ProvisionOpenStack:create')
+ resources_hint = self.interpret_hints({
+ 'machine': config['openstack']['machine'],
+ 'volumes': config['openstack']['volumes'],
+ }, resources_hint)
self.init_user_data(os_type, os_version)
image = self.image(os_type, os_version)
if 'network' in config['openstack']:
net = "--nic net-id=" + str(self.net_id(config['openstack']['network']))
else:
net = ''
- if resources_hint:
- flavor_hint = resources_hint['machine']
- else:
- flavor_hint = config['openstack']['machine']
- flavor = self.flavor(flavor_hint,
+ flavor = self.flavor(resources_hint['machine'],
config['openstack'].get('flavor-select-regexp'))
misc.sh("openstack server create" +
" " + config['openstack'].get('server-create', '') +
time.sleep(15)
if not self.cloud_init_wait(fqdn):
raise ValueError('clound_init_wait failed for ' + fqdn)
- self.attach_volumes(name, resources_hint)
+ self.attach_volumes(name, resources_hint['volumes'])
fqdns.append(fqdn)
except Exception as e:
log.exception(str(e))