import subprocess
import tempfile
import teuthology
+import time
import types
from subprocess import CalledProcessError
def set_info(self):
try:
self.info = json.loads(
- misc.sh("openstack -q server show -f json " + self.name_or_id))
+ OpenStack().run("server show -f json " + self.name_or_id))
enforce_json_dictionary(self.info)
except CalledProcessError:
self.info = None
self.get_addresses())[0]
def get_floating_ip(self):
- ips = json.loads(misc.sh("openstack -q ip floating list -f json"))
+ ips = json.loads(OpenStack().run("ip floating list -f json"))
for ip in ips:
if ip['Instance ID'] == self['id']:
return ip['IP']
if not self.exists():
return True
volumes = self.get_volumes()
- misc.sh("openstack -q server set --name REMOVE-ME-" + self.name_or_id +
- " " + self['id'])
- misc.sh("openstack -q server delete --wait " + self['id'] +
- " || true")
+ OpenStack().run("server set --name REMOVE-ME-" + self.name_or_id +
+ " " + self['id'])
+ OpenStack().run("server delete --wait " + self['id'] +
+ " || true")
for volume in volumes:
- misc.sh("openstack -q volume set --name REMOVE-ME " + volume + " || true")
- misc.sh("openstack -q volume delete " + volume + " || true")
+ OpenStack().run("volume set --name REMOVE-ME " + volume + " || true")
+ OpenStack().run("volume delete " + volume + " || true")
return True
self.up_string = "UNKNOWN"
self.teuthology_suite = 'teuthology-suite'
+ token = None
+ token_expires = None
+ token_cache_duration = 3600
+
+ def cache_token(self):
+ if self.provider != 'ovh':
+ return False
+ if (OpenStack.token is None and
+ os.environ.get('OS_AUTH_TYPE') == 'v2token' and
+ 'OS_TOKEN' in os.environ and
+ 'OS_TOKEN_EXPIRES' in os.environ):
+ log.debug("get token from the environment of the parent process")
+ OpenStack.token = os.environ['OS_TOKEN']
+ OpenStack.token_expires = int(os.environ['OS_TOKEN_EXPIRES'])
+ if (OpenStack.token_expires is not None and
+ OpenStack.token_expires < time.time()):
+ log.debug("token discarded because it has expired")
+ OpenStack.token = None
+ if OpenStack.token is None:
+ if os.environ.get('OS_AUTH_TYPE') == 'v2token':
+ del os.environ['OS_AUTH_TYPE']
+ OpenStack.token = misc.sh("openstack -q token issue -c id -f value").strip()
+ os.environ['OS_AUTH_TYPE'] = 'v2token'
+ os.environ['OS_TOKEN'] = OpenStack.token
+ OpenStack.token_expires = int(time.time() + OpenStack.token_cache_duration)
+ os.environ['OS_TOKEN_EXPIRES'] = str(OpenStack.token_expires)
+ log.info("caching OS_TOKEN and setting OS_AUTH_TYPE=v2token "
+ "during %s seconds" % OpenStack.token_cache_duration)
+ return True
+
+ def run(self, cmd, *args, **kwargs):
+ self.cache_token()
+ return misc.sh("openstack --quiet " + cmd, *args, **kwargs)
+
def set_provider(self):
if 'OS_AUTH_URL' not in os.environ:
raise Exception('no OS_AUTH_URL environment variable')
"""
Return true if the image exists in OpenStack.
"""
- found = misc.sh("openstack -q image list -f json --property name='" +
+ found = self.run("image list -f json --property name='" +
self.image_name(image) + "'")
return len(json.loads(found)) > 0
"""
Return the uuid of the network in OpenStack.
"""
- r = json.loads(misc.sh("openstack -q network show -f json " +
+ r = json.loads(self.run("network show -f json " +
network))
return self.get_value(r, 'id')
"""
Return the smallest flavor that satisfies the desired size.
"""
- flavors_string = misc.sh("openstack -q flavor list -f json")
+ flavors_string = self.run("flavor list -f json")
flavors = json.loads(flavors_string)
found = []
for flavor in flavors:
@staticmethod
def list_instances():
ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'"
- all = json.loads(misc.sh(
- "openstack -q server list -f json --long --name 'target'"))
+ all = json.loads(OpenStack().run(
+ "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 -q volume list -f json --long"))
+ all = json.loads(OpenStack().run("volume list -f json --long"))
def select(volume):
return (ownedby in volume['Properties'] and
volume['Display Name'].startswith('target'))
know already.
"""
try:
- misc.sh("openstack -q flavor list | tail -2")
+ self.run("flavor list | tail -2")
except subprocess.CalledProcessError:
- log.exception("openstack -q flavor list")
+ log.exception("flavor list")
raise Exception("verify openrc.sh has been sourced")
def flavor(self):
among instances created within the same tenant.
"""
try:
- misc.sh("openstack -q security group show teuthology")
+ self.run("security group show teuthology")
return
except subprocess.CalledProcessError:
pass
"""
Return a floating IP address not associated with an instance or None.
"""
- ips = json.loads(misc.sh("openstack -q ip floating list -f json"))
+ ips = json.loads(OpenStack().run("ip floating list -f json"))
for ip in ips:
if not ip['Instance ID']:
return ip['IP']
@staticmethod
def create_floating_ip():
- pools = json.loads(misc.sh("openstack -q ip floating pool list -f json"))
+ pools = json.loads(OpenStack().run("ip floating pool list -f json"))
if not pools:
return None
pool = pools[0]['Name']
try:
- ip = json.loads(misc.sh(
- "openstack -q ip floating create -f json '" + pool + "'"))
+ ip = json.loads(OpenStack().run(
+ "ip floating create -f json '" + pool + "'"))
return TeuthologyOpenStack.get_value(ip, 'ip')
except subprocess.CalledProcessError:
log.debug("create_floating_ip: not creating a floating ip")
if not ip:
ip = TeuthologyOpenStack.create_floating_ip()
if ip:
- misc.sh("openstack -q ip floating add " + ip + " " + name_or_id)
+ OpenStack().run("ip floating add " + ip + " " + name_or_id)
@staticmethod
def get_floating_ip_id(ip):
"""
Return the id of a floating IP
"""
- results = json.loads(misc.sh("openstack -q ip floating list -f json"))
+ results = json.loads(OpenStack().run("ip floating list -f json"))
for result in results:
if result['IP'] == ip:
return str(result['ID'])
ip = OpenStackInstance(instance_id).get_floating_ip()
if not ip:
return
- misc.sh("openstack -q ip floating remove " + ip + " " + instance_id)
+ OpenStack().run("ip floating remove " + ip + " " + instance_id)
ip_id = TeuthologyOpenStack.get_floating_ip_id(ip)
- misc.sh("openstack -q ip floating delete " + ip_id)
+ OpenStack().run("ip floating delete " + ip_id)
def create_cluster(self):
user_data = self.get_user_data()
security_group = ''
else:
security_group = " --security-group teuthology"
- misc.sh(
- "openstack server create " +
+ self.run(
+ "server create " +
" --image '" + self.image('ubuntu', '14.04') + "' " +
" --flavor '" + self.flavor() + "' " +
" " + self.net() +
self.ssh("sudo /etc/init.d/teuthology stop || true")
instance_id = self.get_instance_id(self.args.name)
self.delete_floating_ip(instance_id)
- misc.sh("openstack -q server delete packages-repository || true")
- misc.sh("openstack -q server delete --wait " + self.args.name)
+ self.run("server delete packages-repository || true")
+ self.run("server delete --wait " + self.args.name)
def main(ctx, argv):
return TeuthologyOpenStack(ctx, teuth_config, argv).main()
import pytest
import subprocess
import tempfile
+import time
from mock import patch
import teuthology
else:
del os.environ['OS_AUTH_URL']
+ @patch('teuthology.misc.sh')
+ def test_cache_token(self, m_sh):
+ token = 'TOKEN VALUE'
+ m_sh.return_value = token
+ OpenStack.token = None
+ o = OpenStack()
+ #
+ # Only for OVH
+ #
+ o.provider = 'something'
+ assert False == o.cache_token()
+ o.provider = 'ovh'
+ #
+ # Set the environment with the token
+ #
+ assert 'OS_AUTH_TYPE' not in os.environ
+ assert 'OS_TOKEN' not in os.environ
+ assert 'OS_TOKEN_EXPIRES' not in os.environ
+ assert True == o.cache_token()
+ m_sh.assert_called_with('openstack token issue -c id -f value')
+ assert 'v2token' == os.environ['OS_AUTH_TYPE']
+ assert token == os.environ['OS_TOKEN']
+ assert token == OpenStack.token
+ assert time.time() < int(os.environ['OS_TOKEN_EXPIRES'])
+ assert time.time() < OpenStack.token_expires
+ #
+ # Reset after it expires
+ #
+ token_expires = int(time.time()) - 2000
+ OpenStack.token_expires = token_expires
+ assert True == o.cache_token()
+ assert time.time() < int(os.environ['OS_TOKEN_EXPIRES'])
+ assert time.time() < OpenStack.token_expires
+ del os.environ['OS_AUTH_TYPE']
+ del os.environ['OS_TOKEN']
+ del os.environ['OS_TOKEN_EXPIRES']
+
+ @patch('teuthology.misc.sh')
+ def test_cache_token_from_environment(self, m_sh):
+ OpenStack.token = None
+ o = OpenStack()
+ o.provider = 'ovh'
+ token = 'TOKEN VALUE'
+ os.environ['OS_AUTH_TYPE'] = 'v2token'
+ os.environ['OS_TOKEN'] = token
+ token_expires = int(time.time()) + OpenStack.token_cache_duration
+ os.environ['OS_TOKEN_EXPIRES'] = str(token_expires)
+ assert True == o.cache_token()
+ assert token == OpenStack.token
+ assert token_expires == OpenStack.token_expires
+ m_sh.assert_not_called()
+ del os.environ['OS_AUTH_TYPE']
+ del os.environ['OS_TOKEN']
+ del os.environ['OS_TOKEN_EXPIRES']
+
+ @patch('teuthology.misc.sh')
+ def test_cache_token_expired_environment(self, m_sh):
+ token = 'TOKEN VALUE'
+ m_sh.return_value = token
+ OpenStack.token = None
+ o = OpenStack()
+ o.provider = 'ovh'
+ os.environ['OS_AUTH_TYPE'] = 'v2token'
+ os.environ['OS_TOKEN'] = token
+ token_expires = int(time.time()) - 2000
+ os.environ['OS_TOKEN_EXPIRES'] = str(token_expires)
+ assert True == o.cache_token()
+ m_sh.assert_called_with('openstack token issue -c id -f value')
+ assert 'v2token' == os.environ['OS_AUTH_TYPE']
+ assert token == os.environ['OS_TOKEN']
+ assert token == OpenStack.token
+ assert time.time() < int(os.environ['OS_TOKEN_EXPIRES'])
+ assert time.time() < OpenStack.token_expires
+ del os.environ['OS_AUTH_TYPE']
+ del os.environ['OS_TOKEN']
+ del os.environ['OS_TOKEN_EXPIRES']
+
class TestTeuthologyOpenStack(object):
@classmethod
ip = TeuthologyOpenStack.create_floating_ip()
if ip:
ip_id = TeuthologyOpenStack.get_floating_ip_id(ip)
- misc.sh("openstack -q ip floating delete " + ip_id)
+ OpenStack().run("ip floating delete " + ip_id)
self.can_create_floating_ips = True
else:
self.can_create_floating_ips = False
ip = TeuthologyOpenStack.get_unassociated_floating_ip()
assert expected == ip
ip_id = TeuthologyOpenStack.get_floating_ip_id(ip)
- misc.sh("openstack -q ip floating delete " + ip_id)
+ OpenStack().run("ip floating delete " + ip_id)
for i in range(volumes['count']):
volume_name = name + '-' + str(i)
try:
- misc.sh("openstack volume show -f json " +
- volume_name)
+ self.run("volume show -f json " + volume_name)
except subprocess.CalledProcessError as e:
if 'No volume with a name or ID' not in e.output:
raise e
- misc.sh("openstack volume create -f json " +
- config['openstack'].get('volume-create', '') + " " +
- " --property ownedby=" + config.openstack['ip'] +
- " --size " + str(volumes['size']) + " " +
- volume_name)
+ self.run("volume create -f json " +
+ config['openstack'].get('volume-create', '') + " " +
+ " --property ownedby=" + config.openstack['ip'] +
+ " --size " + str(volumes['size']) + " " +
+ volume_name)
with safe_while(sleep=2, tries=100,
action="volume " + volume_name) as proceed:
while proceed():
- r = misc.sh("openstack -q volume show -f json " +
- volume_name)
+ r = self.run("volume show -f json " + volume_name)
status = self.get_value(json.loads(r), 'status')
if status == 'available':
break
else:
log.info("volume " + volume_name +
" not available yet")
- misc.sh("openstack server add volume " +
- name + " " + volume_name)
+ self.run("server add volume " + name + " " + volume_name)
@staticmethod
def ip2name(prefix, ip):
for instance in instances:
ip = instance.get_ip(network)
name = self.ip2name(self.basename, ip)
- misc.sh("openstack server set " +
- "--name " + name + " " +
- instance['ID'])
+ self.run("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)