u'16.04(xenial)'],
u'sles': [u'11-sp2'],
u'debian': [u'6.0', u'7.0', u'8.0']}
- executable_cmd = provision.downburst_executable()
+ executable_cmd = provision.downburst.downburst_executable()
if not executable_cmd:
log.warn("Downburst not found!")
log.info('Using default values for supported os_type/os_version')
import subprocess
import time
import tempfile
-import yaml
from subprocess import CalledProcessError
from ..misc import decanonicalize_hostname, get_distro, get_distro_version
from ..lockstatus import get_status
+from .downburst import Downburst
-log = logging.getLogger(__name__)
-
-
-def downburst_executable():
- """
- First check for downburst in the user's path.
- Then check in ~/src, ~ubuntu/src, and ~teuthology/src.
- Return '' if no executable downburst is found.
- """
- if config.downburst:
- return config.downburst
- path = os.environ.get('PATH', None)
- if path:
- for p in os.environ.get('PATH', '').split(os.pathsep):
- pth = os.path.join(p, 'downburst')
- if os.access(pth, os.X_OK):
- return pth
- import pwd
- little_old_me = pwd.getpwuid(os.getuid()).pw_name
- for user in [little_old_me, 'ubuntu', 'teuthology']:
- pth = os.path.expanduser(
- "~%s/src/downburst/virtualenv/bin/downburst" % user)
- if os.access(pth, os.X_OK):
- return pth
- return ''
-
-
-class Downburst(object):
- """
- A class that provides methods for creating and destroying virtual machine
- instances using downburst: https://github.com/ceph/downburst
- """
- def __init__(self, name, os_type, os_version, status=None, user='ubuntu'):
- self.name = name
- self.os_type = os_type
- self.os_version = os_version
- self.status = status or get_status(self.name)
- self.config_path = None
- self.user_path = None
- self.user = user
- self.host = decanonicalize_hostname(self.status['vm_host']['name'])
- self.executable = downburst_executable()
-
- def create(self):
- """
- Launch a virtual machine instance.
-
- If creation fails because an instance with the specified name is
- already running, first destroy it, then try again. This process will
- repeat two more times, waiting 60s between tries, before giving up.
- """
- if not self.executable:
- log.error("No downburst executable found.")
- return False
- self.build_config()
- success = None
- with safe_while(sleep=60, tries=3,
- action="downburst create") as proceed:
- while proceed():
- (returncode, stdout, stderr) = self._run_create()
- if returncode == 0:
- log.info("Downburst created %s: %s" % (self.name,
- stdout.strip()))
- success = True
- break
- elif stderr:
- # If the guest already exists first destroy then re-create:
- if 'exists' in stderr:
- success = False
- log.info("Guest files exist. Re-creating guest: %s" %
- (self.name))
- self.destroy()
- else:
- success = False
- log.info("Downburst failed on %s: %s" % (
- self.name, stderr.strip()))
- break
- return success
-
- def _run_create(self):
- """
- Used by create(), this method is what actually calls downburst when
- creating a virtual machine instance.
- """
- if not self.config_path:
- raise ValueError("I need a config_path!")
- if not self.user_path:
- raise ValueError("I need a user_path!")
- shortname = decanonicalize_hostname(self.name)
- args = [
- self.executable,
- '-c', self.host,
- 'create',
- '--wait',
- '--meta-data=%s' % self.config_path,
- '--user-data=%s' % self.user_path,
- shortname,
- ]
- log.info("Provisioning a {distro} {distroversion} vps".format(
- distro=self.os_type,
- distroversion=self.os_version
- ))
- proc = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- out, err = proc.communicate()
- return (proc.returncode, out, err)
-
- def destroy(self):
- """
- Destroy (shutdown and delete) a virtual machine instance.
- """
- executable = self.executable
- if not executable:
- log.error("No downburst executable found.")
- return False
- shortname = decanonicalize_hostname(self.name)
- args = [executable, '-c', self.host, 'destroy', shortname]
- proc = subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,)
- out, err = proc.communicate()
- if err:
- log.error("Error destroying {machine}: {msg}".format(
- machine=self.name, msg=err))
- return False
- elif proc.returncode == 0:
- out_str = ': %s' % out if out else ''
- log.info("Destroyed %s%s" % (self.name, out_str))
- return True
- else:
- log.error("I don't know if the destroy of {node} succeded!".format(
- node=self.name))
- return False
-
- def build_config(self):
- """
- Assemble a configuration to pass to downburst, and write it to a file.
- """
- config_fd = tempfile.NamedTemporaryFile(delete=False)
-
- os_type = self.os_type.lower()
- mac_address = self.status['mac_address']
-
- file_info = {
- 'disk-size': '100G',
- 'ram': '3.8G',
- 'cpus': 1,
- 'networks': [
- {'source': 'front', 'mac': mac_address}],
- 'distro': os_type,
- 'distroversion': self.os_version,
- 'additional-disks': 3,
- 'additional-disks-size': '200G',
- 'arch': 'x86_64',
- }
- fqdn = self.name.split('@')[1]
- file_out = {
- 'downburst': file_info,
- 'local-hostname': fqdn,
- }
- yaml.safe_dump(file_out, config_fd)
- self.config_path = config_fd.name
-
- user_info = {
- 'user': self.user,
- # Remove the user's password so console logins are possible
- 'runcmd': [
- ['passwd', '-d', self.user],
- ]
- }
- # On CentOS/RHEL/Fedora, write the correct mac address
- if os_type in ['centos', 'rhel', 'fedora']:
- user_info['runcmd'].extend([
- ['sed', '-ie', 's/HWADDR=".*"/HWADDR="%s"/' % mac_address,
- '/etc/sysconfig/network-scripts/ifcfg-eth0'],
- ])
- # On Ubuntu, starting with 16.04, we need to install 'python' to get
- # python2.7, which ansible needs
- elif os_type == 'ubuntu':
- if not 'packages' in user_info:
- user_info['packages'] = list()
- user_info['packages'].extend([
- 'python',
- ])
- user_fd = tempfile.NamedTemporaryFile(delete=False)
- yaml.safe_dump(user_info, user_fd)
- self.user_path = user_fd.name
- return True
-
- def remove_config(self):
- """
- Remove the downburst configuration file created by build_config()
- """
- if self.config_path and os.path.exists(self.config_path):
- os.remove(self.config_path)
- self.config_path = None
- return True
- if self.user_path and os.path.exists(self.user_path):
- os.remove(self.user_path)
- self.user_path = None
- return True
- return False
-
- def __del__(self):
- self.remove_config()
+log = logging.getLogger(__name__)
class ProvisionOpenStack(OpenStack):
--- /dev/null
+import logging
+import os
+import subprocess
+import tempfile
+import yaml
+
+from ..config import config
+from ..contextutil import safe_while
+from ..misc import decanonicalize_hostname
+from ..lockstatus import get_status
+
+
+log = logging.getLogger(__name__)
+
+
+def downburst_executable():
+ """
+ First check for downburst in the user's path.
+ Then check in ~/src, ~ubuntu/src, and ~teuthology/src.
+ Return '' if no executable downburst is found.
+ """
+ if config.downburst:
+ return config.downburst
+ path = os.environ.get('PATH', None)
+ if path:
+ for p in os.environ.get('PATH', '').split(os.pathsep):
+ pth = os.path.join(p, 'downburst')
+ if os.access(pth, os.X_OK):
+ return pth
+ import pwd
+ little_old_me = pwd.getpwuid(os.getuid()).pw_name
+ for user in [little_old_me, 'ubuntu', 'teuthology']:
+ pth = os.path.expanduser(
+ "~%s/src/downburst/virtualenv/bin/downburst" % user)
+ if os.access(pth, os.X_OK):
+ return pth
+ return ''
+
+
+class Downburst(object):
+ """
+ A class that provides methods for creating and destroying virtual machine
+ instances using downburst: https://github.com/ceph/downburst
+ """
+ def __init__(self, name, os_type, os_version, status=None, user='ubuntu'):
+ self.name = name
+ self.os_type = os_type
+ self.os_version = os_version
+ self.status = status or get_status(self.name)
+ self.config_path = None
+ self.user_path = None
+ self.user = user
+ self.host = decanonicalize_hostname(self.status['vm_host']['name'])
+ self.executable = downburst_executable()
+
+ def create(self):
+ """
+ Launch a virtual machine instance.
+
+ If creation fails because an instance with the specified name is
+ already running, first destroy it, then try again. This process will
+ repeat two more times, waiting 60s between tries, before giving up.
+ """
+ if not self.executable:
+ log.error("No downburst executable found.")
+ return False
+ self.build_config()
+ success = None
+ with safe_while(sleep=60, tries=3,
+ action="downburst create") as proceed:
+ while proceed():
+ (returncode, stdout, stderr) = self._run_create()
+ if returncode == 0:
+ log.info("Downburst created %s: %s" % (self.name,
+ stdout.strip()))
+ success = True
+ break
+ elif stderr:
+ # If the guest already exists first destroy then re-create:
+ if 'exists' in stderr:
+ success = False
+ log.info("Guest files exist. Re-creating guest: %s" %
+ (self.name))
+ self.destroy()
+ else:
+ success = False
+ log.info("Downburst failed on %s: %s" % (
+ self.name, stderr.strip()))
+ break
+ return success
+
+ def _run_create(self):
+ """
+ Used by create(), this method is what actually calls downburst when
+ creating a virtual machine instance.
+ """
+ if not self.config_path:
+ raise ValueError("I need a config_path!")
+ if not self.user_path:
+ raise ValueError("I need a user_path!")
+ shortname = decanonicalize_hostname(self.name)
+
+ args = [
+ self.executable,
+ '-c', self.host,
+ 'create',
+ '--wait',
+ '--meta-data=%s' % self.config_path,
+ '--user-data=%s' % self.user_path,
+ shortname,
+ ]
+ log.info("Provisioning a {distro} {distroversion} vps".format(
+ distro=self.os_type,
+ distroversion=self.os_version
+ ))
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = proc.communicate()
+ return (proc.returncode, out, err)
+
+ def destroy(self):
+ """
+ Destroy (shutdown and delete) a virtual machine instance.
+ """
+ executable = self.executable
+ if not executable:
+ log.error("No downburst executable found.")
+ return False
+ shortname = decanonicalize_hostname(self.name)
+ args = [executable, '-c', self.host, 'destroy', shortname]
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,)
+ out, err = proc.communicate()
+ if err:
+ log.error("Error destroying {machine}: {msg}".format(
+ machine=self.name, msg=err))
+ return False
+ elif proc.returncode == 0:
+ out_str = ': %s' % out if out else ''
+ log.info("Destroyed %s%s" % (self.name, out_str))
+ return True
+ else:
+ log.error("I don't know if the destroy of {node} succeded!".format(
+ node=self.name))
+ return False
+
+ def build_config(self):
+ """
+ Assemble a configuration to pass to downburst, and write it to a file.
+ """
+ config_fd = tempfile.NamedTemporaryFile(delete=False)
+
+ os_type = self.os_type.lower()
+ mac_address = self.status['mac_address']
+
+ file_info = {
+ 'disk-size': '100G',
+ 'ram': '3.8G',
+ 'cpus': 1,
+ 'networks': [
+ {'source': 'front', 'mac': mac_address}],
+ 'distro': os_type,
+ 'distroversion': self.os_version,
+ 'additional-disks': 3,
+ 'additional-disks-size': '200G',
+ 'arch': 'x86_64',
+ }
+ fqdn = self.name.split('@')[1]
+ file_out = {
+ 'downburst': file_info,
+ 'local-hostname': fqdn,
+ }
+ yaml.safe_dump(file_out, config_fd)
+ self.config_path = config_fd.name
+
+ user_info = {
+ 'user': self.user,
+ # Remove the user's password so console logins are possible
+ 'runcmd': [
+ ['passwd', '-d', self.user],
+ ]
+ }
+ # On CentOS/RHEL/Fedora, write the correct mac address
+ if os_type in ['centos', 'rhel', 'fedora']:
+ user_info['runcmd'].extend([
+ ['sed', '-ie', 's/HWADDR=".*"/HWADDR="%s"/' % mac_address,
+ '/etc/sysconfig/network-scripts/ifcfg-eth0'],
+ ])
+ # On Ubuntu, starting with 16.04, we need to install 'python' to get
+ # python2.7, which ansible needs
+ elif os_type == 'ubuntu':
+ if 'packages' not in user_info:
+ user_info['packages'] = list()
+ user_info['packages'].extend([
+ 'python',
+ ])
+ user_fd = tempfile.NamedTemporaryFile(delete=False)
+ yaml.safe_dump(user_info, user_fd)
+ self.user_path = user_fd.name
+ return True
+
+ def remove_config(self):
+ """
+ Remove the downburst configuration file created by build_config()
+ """
+ if self.config_path and os.path.exists(self.config_path):
+ os.remove(self.config_path)
+ self.config_path = None
+ return True
+ if self.user_path and os.path.exists(self.user_path):
+ os.remove(self.user_path)
+ self.user_path = None
+ return True
+ return False
+
+ def __del__(self):
+ self.remove_config()
--- /dev/null
+from mock import Mock, MagicMock, patch
+
+from teuthology import provision
+
+
+class TestDownburst(object):
+ def setup(self):
+ self.ctx = Mock()
+ self.ctx.os_type = 'rhel'
+ self.ctx.os_version = '7.0'
+ self.ctx.config = dict()
+ self.name = 'vpm999'
+ self.status = dict(
+ vm_host=dict(name='host999'),
+ is_vm=True,
+ )
+
+ def test_create_if_vm_success(self):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ dbrst.executable = '/fake/path'
+ dbrst.build_config = MagicMock(name='build_config')
+ dbrst._run_create = MagicMock(name='_run_create')
+ dbrst._run_create.return_value = (0, '', '')
+ remove_config = MagicMock(name='remove_config')
+ dbrst.remove_config = remove_config
+
+ result = provision.create_if_vm(ctx, name, dbrst)
+ assert result is True
+
+ dbrst._run_create.assert_called_with()
+ dbrst.build_config.assert_called_with()
+ del dbrst
+ remove_config.assert_called_with()
+
+ def test_destroy_if_vm_success(self):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ dbrst.destroy = MagicMock(name='destroy')
+ dbrst.destroy.return_value = True
+
+ result = provision.destroy_if_vm(ctx, name, _downburst=dbrst)
+ assert result is True
+
+ dbrst.destroy.assert_called_with()
+
+ def test_destroy_if_vm_wrong_owner(self):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+ status['locked_by'] = 'user@a'
+
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ dbrst.destroy = MagicMock(name='destroy', side_effect=RuntimeError)
+
+ result = provision.destroy_if_vm(ctx, name, user='user@b',
+ _downburst=dbrst)
+ assert result is False
+
+ def test_destroy_if_vm_wrong_description(self):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+ status['description'] = 'desc_a'
+
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ dbrst.destroy = MagicMock(name='destroy')
+ dbrst.destroy = MagicMock(name='destroy', side_effect=RuntimeError)
+
+ result = provision.destroy_if_vm(ctx, name, description='desc_b',
+ _downburst=dbrst)
+ assert result is False
+
+ @patch('teuthology.provision.downburst.downburst_executable')
+ def test_create_fails_without_executable(self, m_exec):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+ m_exec.return_value = ''
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ result = dbrst.create()
+ assert result is False
+
+ @patch('teuthology.provision.downburst.downburst_executable')
+ def test_destroy_fails_without_executable(self, m_exec):
+ name = self.name
+ ctx = self.ctx
+ status = self.status
+ m_exec.return_value = ''
+ dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
+ result = dbrst.destroy()
+ assert result is False
+++ /dev/null
-from mock import Mock, MagicMock, patch
-
-from teuthology import provision
-
-
-class TestDownburst(object):
- def setup(self):
- self.ctx = Mock()
- self.ctx.os_type = 'rhel'
- self.ctx.os_version = '7.0'
- self.ctx.config = dict()
- self.name = 'vpm999'
- self.status = dict(
- vm_host=dict(name='host999'),
- is_vm=True,
- )
-
- def test_create_if_vm_success(self):
- name = self.name
- ctx = self.ctx
- status = self.status
-
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- dbrst.executable = '/fake/path'
- dbrst.build_config = MagicMock(name='build_config')
- dbrst._run_create = MagicMock(name='_run_create')
- dbrst._run_create.return_value = (0, '', '')
- remove_config = MagicMock(name='remove_config')
- dbrst.remove_config = remove_config
-
- result = provision.create_if_vm(ctx, name, dbrst)
- assert result is True
-
- dbrst._run_create.assert_called_with()
- dbrst.build_config.assert_called_with()
- del dbrst
- remove_config.assert_called_with()
-
- def test_destroy_if_vm_success(self):
- name = self.name
- ctx = self.ctx
- status = self.status
-
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- dbrst.destroy = MagicMock(name='destroy')
- dbrst.destroy.return_value = True
-
- result = provision.destroy_if_vm(ctx, name, _downburst=dbrst)
- assert result is True
-
- dbrst.destroy.assert_called_with()
-
- def test_destroy_if_vm_wrong_owner(self):
- name = self.name
- ctx = self.ctx
- status = self.status
- status['locked_by'] = 'user@a'
-
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- dbrst.destroy = MagicMock(name='destroy', side_effect=RuntimeError)
-
- result = provision.destroy_if_vm(ctx, name, user='user@b',
- _downburst=dbrst)
- assert result is False
-
- def test_destroy_if_vm_wrong_description(self):
- name = self.name
- ctx = self.ctx
- status = self.status
- status['description'] = 'desc_a'
-
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- dbrst.destroy = MagicMock(name='destroy')
- dbrst.destroy = MagicMock(name='destroy', side_effect=RuntimeError)
-
- result = provision.destroy_if_vm(ctx, name, description='desc_b',
- _downburst=dbrst)
- assert result is False
-
- @patch('teuthology.provision.downburst_executable')
- def test_create_fails_without_executable(self, m_exec):
- name = self.name
- ctx = self.ctx
- status = self.status
- m_exec.return_value = ''
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- result = dbrst.create()
- assert result is False
-
- @patch('teuthology.provision.downburst_executable')
- def test_destroy_fails_without_executable(self, m_exec):
- name = self.name
- ctx = self.ctx
- status = self.status
- m_exec.return_value = ''
- dbrst = provision.Downburst(name, ctx.os_type, ctx.os_version, status)
- result = dbrst.destroy()
- assert result is False
-from mock import patch
+from mock import patch, Mock
from .. import lock
-class Mock: pass
class TestVpsOsVersionParamCheck(object):
self.fake_ctx.os_type = 'ubuntu'
self.fake_ctx.os_version = 'precise'
with patch.multiple(
- lock.provision,
+ lock.provision.downburst,
downburst_executable=self.fake_downburst_executable,
):
check_value = lock.vps_version_or_type_valid(
self.fake_ctx.os_type = 'ubuntu'
self.fake_ctx.os_version = '12.04'
with patch.multiple(
- lock.provision,
+ lock.provision.downburst,
downburst_executable=self.fake_downburst_executable,
):
check_value = lock.vps_version_or_type_valid(
self.fake_ctx.os_type = '6.5'
self.fake_ctx.os_version = 'rhel'
with patch.multiple(
- lock.provision,
+ lock.provision.downburst,
downburst_executable=self.fake_downburst_executable,
):
check_value = lock.vps_version_or_type_valid(
self.fake_ctx.os_type = 'aardvark'
self.fake_ctx.os_version = '6.5'
with patch.multiple(
- lock.provision,
+ lock.provision.downburst,
downburst_executable=self.fake_downburst_executable,
):
check_value = lock.vps_version_or_type_valid(
self.fake_ctx.os_type = 'rhel'
self.fake_ctx.os_version = 'vampire_bat'
with patch.multiple(
- lock.provision,
+ lock.provision.downburst,
downburst_executable=self.fake_downburst_executable,
):
check_value = lock.vps_version_or_type_valid(