api_token: your_api_token
user_token: your_user_token
machine_types: ['mira', 'smithi']
+
+ # FOG provisioner is default and switching to Pelgas
+ # should be made explicitly
+ pelagos:
+ endpoint: http://head.ses.suse.de:5000/
+ machine_types: ['type1', 'type2', 'type3']
ctx.machine_type, ctx.os_type, ctx.os_version):
log.error('Invalid os-type or version detected -- lock failed')
return 1
- reimage_types = teuthology.provision.fog.get_types()
+ reimage_types = teuthology.provision.get_reimage_types()
reimage_machines = list()
updatekeys_machines = list()
+ machine_types = dict()
for machine in machines:
resp = ops.lock_one(machine, user, ctx.desc)
if resp.ok:
machine_status = resp.json()
machine_type = machine_status['machine_type']
+ machine_types[machine] = machine_type
if not resp.ok:
ret = 1
if not ctx.f:
with teuthology.parallel.parallel() as p:
ops.update_nodes(reimage_machines, True)
for machine in reimage_machines:
- p.spawn(teuthology.provision.reimage, ctx, machine)
+ p.spawn(teuthology.provision.reimage, ctx, machine, machine_types[machine])
for machine in updatekeys_machines:
ops.do_update_keys([machine])
ops.update_nodes(reimage_machines + machines_to_update)
# Only query for os_type/os_version if non-vps and non-libcloud, since
# in that case we just create them.
vm_types = ['vps'] + teuthology.provision.cloud.get_types()
- reimage_types = teuthology.provision.fog.get_types()
+ reimage_types = teuthology.provision.get_reimage_types()
if machine_type not in vm_types + reimage_types:
if os_type:
data['os_type'] = os_type
update_nodes(reimaged, True)
with teuthology.parallel.parallel() as p:
for machine in machines:
- p.spawn(teuthology.provision.reimage, ctx, machine)
+ p.spawn(teuthology.provision.reimage, ctx,
+ machine, machine_type)
reimaged[machine] = machines[machine]
reimaged = do_update_keys(reimaged.keys())[1]
update_nodes(reimaged)
remote = Remote(host)
remote.console.power_off()
return
+ elif status['machine_type'] in provision.pelagos.get_types():
+ provision.pelagos.park_node(host)
+ return
+
if (not ctx.noipmi and 'ipmi_user' in config and
'vpm' not in shortname):
try:
from teuthology.provision import downburst
from teuthology.provision import fog
from teuthology.provision import openstack
+from teuthology.provision import pelagos
import os
-
log = logging.getLogger(__name__)
return os.path.join(ctx.config['archive_path'],
shortname + '.downburst.log')
+def get_reimage_types():
+ return pelagos.get_types() + fog.get_types()
-def reimage(ctx, machine_name):
+def reimage(ctx, machine_name, machine_type):
os_type = get_distro(ctx)
os_version = get_distro_version(ctx)
- fog_obj = fog.FOG(machine_name, os_type, os_version)
- return fog_obj.create()
+
+ pelagos_types = pelagos.get_types()
+ fog_types = fog.get_types()
+ if machine_type in pelagos_types and machine_type in fog_types:
+ raise Exception('machine_type can be used with one provisioner only')
+ elif machine_type in pelagos_types:
+ obj = pelagos.Pelagos(machine_name, os_type, os_version)
+ elif machine_type in fog_types:
+ obj = fog.FOG(machine_name, os_type, os_version)
+ else:
+ raise Exception("The machine_type '%s' is not known to any "
+ "of configured provisioners" % machine_type)
+ return obj.create()
def create_if_vm(ctx, machine_name, _downburst=None):
--- /dev/null
+
+import logging
+import requests
+import re
+import time
+
+from teuthology.config import config
+from teuthology.contextutil import safe_while
+from teuthology.util.compat import HTTPError
+
+log = logging.getLogger(__name__)
+config_section = 'pelagos'
+
+# Provisioner configuration section description see in
+# docs/siteconfig.rst
+
+def enabled(warn=False):
+ """
+ Check for required Pelagos settings
+
+ :param warn: Whether or not to log a message containing unset parameters
+ :returns: True if they are present; False if they are not
+ """
+ conf = config.get(config_section, dict())
+ params = ['endpoint', 'machine_types']
+ unset = [_ for _ in params if not conf.get(_)]
+ if unset and warn:
+ log.warn(
+ "Pelagos is disabled; set the following config options to enable: %s",
+ ' '.join(unset),
+ )
+ return (unset == [])
+
+
+def get_types():
+ """
+ Fetch and parse config.pelagos['machine_types']
+
+ :returns: The list of Pelagos-configured machine types. An empty list if Pelagos is
+ not configured.
+ """
+ if not enabled():
+ return []
+ conf = config.get(config_section, dict())
+ types = conf.get('machine_types', '')
+ if not isinstance(types, list):
+ types = [_ for _ in types.split(',') if _]
+ return [_ for _ in types if _]
+
+def park_node(name):
+ p = Pelagos(name, "maintenance_image")
+ p.create()
+
+
+class Pelagos(object):
+
+ def __init__(self, name, os_type, os_version=""):
+ #for service should be a hostname, not a user@host
+ split_uri = re.search(r'(\w*)@(.+)', name)
+ if split_uri is not None:
+ self.name = split_uri.groups()[1]
+ else:
+ self.name = name
+
+ self.os_type = os_type
+ self.os_version = os_version
+ if os_version:
+ self.os_name = os_type + "-" + os_version
+ else:
+ self.os_name = os_type
+ self.log = log.getChild(self.name)
+
+ def create(self):
+ """
+ Initiate deployment via REST requests and wait until completion
+
+ """
+ if not enabled():
+ raise RuntimeError("Pelagos is not configured!")
+ location = None
+ try:
+ response = self.do_request('node/provision',
+ data={'os': self.os_name,
+ 'node': self.name},
+ method='POST')
+ location = response.headers.get('Location')
+ self.log.info("Waiting for deploy to finish")
+ self.log.info("Observe location: '%s'", location)
+ time.sleep(2)
+ with safe_while(sleep=15, tries=60) as proceed:
+ while proceed():
+ if not self.is_task_active(location):
+ break
+ except Exception as e:
+ if location:
+ self.cancel_deploy_task(location)
+ else:
+ self.log.error("No task started")
+ raise e
+ self.log.info("Deploy completed")
+ if self.task_status_response.status_code != 200:
+ raise Exception("Provisioning failed")
+ return self.task_status_response
+
+ def cancel_deploy_task(self, task_id):
+ # TODO implement it
+ return
+
+ def is_task_active(self, task_url):
+ try:
+ status_response = self.do_request('', url=task_url, verify=False)
+ except HTTPError as err:
+ self.log.error("Task fail reason: '%s'", err.reason)
+ if err.status_code == 404:
+ self.log.error(err.reason)
+ self.task_status_response = 'failed'
+ return False
+ else:
+ raise HTTPError(err.code, err.reason)
+ self.log.info("Response code '%s'", str(status_response.status_code))
+ self.task_status_response = status_response
+ if status_response.status_code == 202:
+ self.log.info("Status response: '%s'", status_response.headers['status'])
+ if status_response.headers['status'] == 'not completed':
+ return True
+ return False
+
+ def do_request(self, url_suffix, url="" , data=None, method='GET', verify=True):
+ """
+ A convenience method to submit a request to the Pelagos server
+ :param url_suffix: The portion of the URL to append to the endpoint,
+ e.g. '/system/info'
+ :param data: Optional JSON data to submit with the request
+ :param method: The HTTP method to use for the request (default: 'GET')
+ :param verify: Whether or not to raise an exception if the request is
+ unsuccessful (default: True)
+ :returns: A requests.models.Response object
+ """
+ prepared_url = config.pelagos['endpoint'] + url_suffix
+ if url != '':
+ prepared_url = url
+ self.log.info("Connect to: '%s'", prepared_url)
+ if data is not None:
+ self.log.info("Send data: '%s'", str(data))
+ req = requests.Request(
+ method,
+ prepared_url,
+ data=data
+ )
+ prepared = req.prepare()
+ resp = requests.Session().send(prepared)
+ self.log.debug("do_request code %s text %s", resp.status_code, resp.text)
+ if not resp.ok and resp.text:
+ self.log.error("%s: %s", resp.status_code, resp.text)
+ if verify:
+ resp.raise_for_status()
+ return resp
+
+ def destroy(self):
+ """A no-op; we just leave idle nodes as-is"""
+ pass
+
--- /dev/null
+from copy import deepcopy
+from pytest import raises
+from teuthology.config import config
+
+import teuthology.provision
+
+test_config = dict(
+ pelagos=dict(
+ endpoint='http://pelagos.example:5000/',
+ machine_types='ptype1,ptype2,common_type',
+ ),
+ fog=dict(
+ endpoint='http://fog.example.com/fog',
+ api_token='API_TOKEN',
+ user_token='USER_TOKEN',
+ machine_types='ftype1,ftype2,common_type',
+ )
+)
+
+class TestInitProvision(object):
+
+ def setup(self):
+ config.load(deepcopy(test_config))
+
+ def test_get_reimage_types(self):
+ reimage_types = teuthology.provision.get_reimage_types()
+ assert reimage_types == ["ptype1", "ptype2", "common_type",
+ "ftype1", "ftype2", "common_type"]
+
+ def test_reimage(self):
+ class context:
+ pass
+ ctx = context()
+ ctx.os_type = 'sle'
+ ctx.os_version = '15.1'
+ with raises(Exception) as e_info:
+ teuthology.provision.reimage(ctx, 'f.q.d.n.org', 'not-defined-type')
+ e_str = str(e_info)
+ print("Caught exception: " + e_str)
+ assert e_str.find("configured\sprovisioners") == -1
+
+ with raises(Exception) as e_info:
+ teuthology.provision.reimage(ctx, 'f.q.d.n.org', 'common_type')
+ e_str = str(e_info)
+ print("Caught exception: " + e_str)
+ assert e_str.find("used\swith\sone\sprovisioner\sonly") == -1
--- /dev/null
+from copy import deepcopy
+from pytest import raises
+from teuthology.config import config
+from teuthology.provision import pelagos
+
+import teuthology.provision
+
+
+test_config = dict(
+ pelagos=dict(
+ endpoint='http://pelagos.example:5000/',
+ machine_types='ptype1,ptype2',
+ ),
+)
+
+class TestPelagos(object):
+
+ def setup(self):
+ config.load(deepcopy(test_config))
+
+ def teardown(self):
+ pass
+
+ def test_get_types(self):
+ #klass = pelagos.Pelagos
+ types = pelagos.get_types()
+ assert types == ["ptype1", "ptype2"]
+
+ def test_disabled(self):
+ config.pelagos['endpoint'] = None
+ enabled = pelagos.enabled()
+ assert enabled == False
+
+ def test_pelagos(self):
+ class context:
+ pass
+
+ ctx = context()
+ ctx.os_type ='sle'
+ ctx.os_version = '15.1'
+ with raises(Exception) as e_info:
+ teuthology.provision.reimage(ctx, 'f.q.d.n.org', 'ptype1')
+ e_str = str(e_info)
+ print("Caught exception: " + e_str)
+ assert e_str.find("Name\sor\sservice\snot\sknown") == -1
+