Merge "Refactored packstack.installer.setup_sequences"

This commit is contained in:
Jenkins
2013-05-22 11:14:20 +00:00
committed by Gerrit Code Review
23 changed files with 274 additions and 216 deletions

View File

@@ -41,10 +41,5 @@ EXEC_CHKCONFIG = "chkconfig"
EXEC_SERVICE = "service"
EXEC_IP = "ip"
# text colors
NO_COLOR = "\033[0m"
COLORS = {'red': "\033[0;31m", 'green': "\033[92m", 'blue': "\033[94m",
'yellow': "\033[93m"}
# space len size for color print
SPACE_LEN = 70

View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
"""
Base class for steps & sequences
"""
import re
import sys
import logging
import traceback
from .. import utils
from ..exceptions import SequenceError
class Step(object):
"""
Wrapper for function representing single setup step.
"""
def __init__(self, name, function, title=None):
self.name = name
self.title = title or ('Step: %s' % name)
# process step function
if function and not callable(function):
raise SequenceError("Function object have to be callable. "
"Object %s is not callable." % function)
self.function = function
def run(self, config=None):
config = config or {}
# TO-DO: complete logger name when logging will be setup correctly
logger = logging.getLogger()
logger.debug('Running step %s.' % self.name)
sys.stdout.write('%s...' % self.title)
sys.stdout.flush()
# count space needed for title print
title = self.title
for color in utils.COLORS.itervalues():
title = re.sub(re.escape(color), '', title)
space = 70 - len(title)
# execute and report state
state_fmt = '[ %s ]\n'
try:
self.function(config)
except Exception, ex:
logger.debug(traceback.format_exc())
state = state_fmt % utils.color_text('ERROR', 'red')
sys.stdout.write(state.rjust(space))
sys.stdout.flush()
raise SequenceError(str(ex))
else:
state = state_fmt % utils.color_text('DONE', 'green')
sys.stdout.write(state.rjust(space))
sys.stdout.flush()
class Sequence(object):
"""
Wrapper for sequence of setup steps.
"""
def __init__(self, name, steps, title=None, condition=None,
cond_match=None):
self.name = name
self.title = title
self.condition = condition
self.cond_match = cond_match
# process sequence steps
self.steps = utils.SortedDict()
for step in steps:
name, func = step['name'], step['function']
self.steps[name] = Step(name, func, title=step.get('title'))
def validate_condition(self, config):
"""
Returns True if config option condition has value given
in cond_match. Otherwise returns False.
"""
if not self.condition:
return True
result = config.get(self.condition)
return result == self.cond_match
def run(self, config=None, step=None):
"""
Runs sequence of steps. Runs only specific step if step's name
is given via 'step' parameter.
"""
config = config or {}
if not self.validate_condition(config):
return
if step:
self.steps[step].run(config=config)
return
logger = logging.getLogger()
logger.debug('Running sequence %s.' % self.name)
if self.title:
sys.stdout.write('%s\n' % self.title)
sys.stdout.flush()
for step in self.steps.itervalues():
step.run(config=config)

View File

@@ -64,5 +64,11 @@ class ScriptRuntimeError(PackStackError):
"""
pass
class ExecuteRuntimeError(PackStackError):
"""Raised when utils.execute does not end successfully."""
class SequenceError(PackStackError):
"""Exception for errors during setup sequence run."""
pass

View File

@@ -3,7 +3,15 @@ Controller class is a SINGLETON which handles all groups, params, sequences,
steps and replaces the CONF dictionary.
"""
from setup_params import Group
from setup_sequences import Sequence
from .core.sequences import Sequence
def steps_new_format(steplist):
# we have to duplicate title to name parameter and also only sigle
# function is allowed in new step
return [{'name': i['title'], 'title': i['title'],
'function': i['functions'][0]} for i in steplist]
class Controller(object):
@@ -64,27 +72,32 @@ class Controller(object):
# Sequences and steps
def addSequence(self, desc, cond, cond_match, steps):
self.__SEQUENCES.append(Sequence(desc, cond, cond_match, steps))
self.__SEQUENCES.append(Sequence(desc, steps_new_format(steps),
condition=cond,
cond_match=cond_match))
def insertSequence(self, desc, cond, cond_match, steps, index=0):
self.__SEQUENCES.insert(index, Sequence(desc, cond, cond_match, steps))
self.__SEQUENCES.insert(index, Sequence(desc,
steps_new_format(steps),
condition=cond,
cond_match=cond_match))
def getAllSequences(self):
return self.__SEQUENCES
def runAllSequences(self):
for sequence in self.__SEQUENCES:
sequence.run()
sequence.run(self.CONF)
def getSequenceByDesc(self, desc):
for sequence in self.getAllSequences():
if sequence.getDescription() == desc:
if sequence.name == desc:
return sequence
return None
def __getSequenceIndexByDesc(self, desc):
for sequence in self.getAllSequences():
if sequence.getDescription() == desc:
if sequence.name == desc:
return self.__SEQUENCES.index(sequence)
return None
@@ -97,7 +110,10 @@ class Controller(object):
index = self.__getSequenceIndexByDesc(sequenceName)
if index == None:
index = len(self.getAllSequences())
self.__SEQUENCES.insert(index, Sequence(desc, cond, cond_match, steps))
self.__SEQUENCES.insert(index, Sequence(desc,
steps_new_format(steps),
condition=cond,
cond_match=cond_match))
# Groups and params
def addGroup(self, group, params):

View File

@@ -1,163 +0,0 @@
"""
Base class for steps & sequences
"""
import logging
import sys
import re
import string
import traceback
import basedefs
import output_messages
from . import utils
class Step(object):
def __init__(self, title=None, functions=[]):
self.__TITLE = None
self.__FUNCTIONS = []
if title:
if not isinstance(title, str):
raise TypeError("step's title should be of string type instead of %s" % type(title))
if not isinstance(functions, list):
raise TypeError("step's function should be of list type instead of %s" % type(functions))
for function in functions:
if not callable(function):
raise TypeError("All parameters which pass as functions should be callable. %s is not callable" % function)
self.setTitle(title)
for function in functions:
self.addFunction(function)
def setTitle(self, title):
self.__TITLE = title
def getTitle(self):
return self.__TITLE
def addFunction(self, function):
self.__FUNCTIONS.append(function)
def removeFunction(self, function):
self.__FUNCTIONS.remove(function)
def getFunctions(self):
return self.__FUNCTIONS
def run(self):
# keep relative space
# allow newline chars in title. This is useful for plugins
alignedTitle = self.getTitle()
if re.search('\n', alignedTitle):
alignedTitle = self.getTitle().split('\n')[-1]
for color in basedefs.COLORS:
if color in alignedTitle:
alignedTitle = string.replace(alignedTitle, color, '')
spaceLen = basedefs.SPACE_LEN - len(alignedTitle)
print "%s..."%(self.getTitle()),
sys.stdout.flush()
for function in self.getFunctions():
try:
logging.debug("running %s"%(function.func_name))
function()
except:
logging.debug(traceback.format_exc())
raise
print ("[ " + utils.color_text(output_messages.INFO_DONE, 'green') + " ]").rjust(spaceLen)
class Sequence(object):
"""
Gets 4 parameters:
description, condition's name/function, condition's expected result and steps
steps should be a list of dictionaries, example:
[ { 'title' : 'step1's title',
'functions' : [ func1, func2, func3 ] },
{ 'title' : 'step2's tittle',
'functions' : [ func4, func6 ] } ]
"""
def __init__(self, desc=None, cond=[], cond_match=[], steps=[]):
self.__DESCRIPTION = None
self.__CONDITION = None
self.__COND_MATCH = None
self.__STEPS = []
self.setDescription(desc)
self.setCondition(cond, cond_match)
for step in steps:
if not isinstance(step, dict):
raise TypeError("step should be of dictionary type instead of %s" % type(step))
self.addStep(step['title'], step['functions'])
def addStep(self, title, functions):
self.__STEPS.append(Step(title, functions))
def setDescription(self, desc):
self.__DESCRIPTION = desc
def getDescription(self):
return self.__DESCRIPTION
def getSteps(self):
return self.__STEPS
def getStepByTitle(self, stepTitle):
for step in self.__STEPS:
if step.getTitle == stepTitle:
return step
return None
def setCondition(self, cond, cond_match):
for item in [cond, cond_match]:
if not isinstance(item, list):
raise TypeError("supplied parameter should be of list type instead of %s" % type(item))
self.__CONDITION = cond
self.__COND_MATCH = cond_match
def __validateCondition(self):
"""
Both _CONDITION & _COND_MATCH are lists.
if any of them is a function that needs to be run, the first member
of the list is the function and the rest of the members in that list
are the params for the said function
i.e. self._CONDITION = [function, arg1, arg2]
will be executed as function(arg1, arg2)
if the first member of the list is not a function. we handle it
as anything else (i.e. string/bool etc)
"""
if len(self.__CONDITION) < 1 and len(self.__COND_MATCH) < 1:
return True
condResult = None
condMatchResult = None
if callable(self.__CONDITION[0]):
condResult = self.__CONDITION[0](*self.__CONDITION[1:])
else:
condResult = self.__CONDITION[0]
if callable(self.__COND_MATCH[0]):
condMatchResult = self.__COND_MATCH[0](*self.__COND_MATCH[1:])
else:
condMatchResult = self.__COND_MATCH[0]
if condResult == condMatchResult:
return True
return False
def removeStepByTitle(self, stepTitle):
self.__STEPS.remove(stepTitle)
def run(self):
if self.__validateCondition():
for step in self.__STEPS:
step.run()
def runStepByTitle(self, stepTitle):
step = self.getStepByTitle(stepTitle)
step.run()
def listStepsByTitle(self):
output = []
for step in self.__STEPS:
output.append(step.getTitle())
return output

View File

@@ -4,11 +4,11 @@ from .datastructures import SortedDict
from .decorators import retry
from .network import get_localhost_ip, host2ip, force_ip, device_from_ip
from .shell import ScriptRunner, execute
from .strings import color_text, mask_string
from .strings import COLORS, color_text, mask_string
__all__ = ('SortedDict',
'retry',
'ScriptRunner', 'execute',
'get_localhost_ip', 'host2ip', 'force_ip', 'device_from_ip',
'color_text', 'mask_string')
'COLORS', 'color_text', 'mask_string')

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
from .. import basedefs
STR_MASK = '*' * 8
COLORS = {'nocolor': "\033[0m", 'red': "\033[0;31m",
'green': "\033[92m", 'blue': "\033[94m",
'yellow': "\033[93m"}
def color_text(text, color):
@@ -11,7 +12,7 @@ def color_text(text, color):
Returns given text string with appropriate color tag. Allowed values
for color parameter are 'red', 'blue', 'green' and 'yellow'.
"""
return '%s%s%s' % (basedefs.COLORS[color], text, basedefs.NO_COLOR)
return '%s%s%s' % (COLORS[color], text, COLORS['nocolor'])
def mask_string(unmasked, mask_list=None, replace_list=None):

View File

@@ -136,12 +136,12 @@ def initSequences(controller):
]
controller.addSequence("Installing OpenStack Cinder", [], [], cinder_steps)
def install_cinder_deps():
def install_cinder_deps(config):
server = utils.ScriptRunner(controller.CONF['CONFIG_CINDER_HOST'])
server.append("rpm -q %(package)s || yum install -y %(package)s" % {'package': "lvm2"})
server.execute()
def check_cinder_vg():
def check_cinder_vg(config):
cinders_volume = 'cinder-volumes'
@@ -211,13 +211,13 @@ def check_cinder_vg():
raise exceptions.MissingRequirements(err)
def create_keystone_manifest():
def create_keystone_manifest(config):
manifestfile = "%s_keystone.pp" % controller.CONF['CONFIG_KEYSTONE_HOST']
manifestdata = getManifestTemplate("keystone_cinder.pp")
appendManifestFile(manifestfile, manifestdata)
def create_manifest():
def create_manifest(config):
manifestfile = "%s_cinder.pp" % controller.CONF['CONFIG_CINDER_HOST']
manifestdata = getManifestTemplate("cinder.pp")
appendManifestFile(manifestfile, manifestdata)

View File

@@ -109,7 +109,7 @@ def initSequences(controller):
controller.addSequence("Installing OpenStack Horizon", [], [], steps)
def createmanifest():
def createmanifest(config):
controller.CONF["CONFIG_HORIZON_SECRET_KEY"] = uuid.uuid4().hex
horizon_host = controller.CONF['CONFIG_HORIZON_HOST']
manifestfile = "%s_horizon.pp" % horizon_host

View File

@@ -87,12 +87,12 @@ def initSequences(controller):
]
controller.addSequence("Installing OpenStack Glance", [], [], glancesteps)
def createkeystonemanifest():
def createkeystonemanifest(config):
manifestfile = "%s_keystone.pp" % controller.CONF['CONFIG_KEYSTONE_HOST']
manifestdata = getManifestTemplate("keystone_glance.pp")
appendManifestFile(manifestfile, manifestdata)
def createmanifest():
def createmanifest(config):
manifestfile = "%s_glance.pp" % controller.CONF['CONFIG_GLANCE_HOST']
manifestdata = getManifestTemplate("glance.pp")
appendManifestFile(manifestfile, manifestdata)

View File

@@ -92,7 +92,7 @@ def initSequences(controller):
]
controller.addSequence("Installing OpenStack Keystone", [], [], keystonesteps)
def createmanifest():
def createmanifest(config):
manifestfile = "%s_keystone.pp"%controller.CONF['CONFIG_KEYSTONE_HOST']
manifestdata = getManifestTemplate("keystone.pp")
appendManifestFile(manifestfile, manifestdata)

View File

@@ -81,7 +81,7 @@ def initSequences(controller):
controller.addSequence("Installing MySQL", [], [], mysqlsteps)
def createmanifest():
def createmanifest(config):
host = controller.CONF['CONFIG_MYSQL_HOST']
manifestfile = "%s_mysql.pp" % host
manifestdata = [getManifestTemplate("mysql.pp")]

View File

@@ -99,7 +99,7 @@ def nagios_host(hostname, **kwargs):
return "%s}\n" % out
def createmanifest():
def createmanifest(config):
manifest_entries = ''
# I should be adding service entries with nagios_service but it appears to be broken
# http://projects.puppetlabs.com/issues/3420
@@ -167,7 +167,7 @@ def createmanifest():
manifestdata = getManifestTemplate("nagios_server.pp")
appendManifestFile(manifestfile, manifestdata)
def createnrpemanifests():
def createnrpemanifests(config):
for hostname in gethostlist(controller.CONF):
controller.CONF['CONFIG_NRPE_HOST'] = hostname
manifestfile = "%s_nagios_nrpe.pp" % hostname

View File

@@ -272,25 +272,25 @@ def initSequences(controller):
controller.addSequence("Installing OpenStack Nova API", [], [], novaapisteps)
def createapimanifest():
def createapimanifest(config):
manifestfile = "%s_api_nova.pp"%controller.CONF['CONFIG_NOVA_API_HOST']
manifestdata = getManifestTemplate("nova_api.pp")
appendManifestFile(manifestfile, manifestdata, 'novaapi')
def createkeystonemanifest():
def createkeystonemanifest(config):
manifestfile = "%s_keystone.pp"%controller.CONF['CONFIG_KEYSTONE_HOST']
manifestdata = getManifestTemplate("keystone_nova.pp")
appendManifestFile(manifestfile, manifestdata)
def createcertmanifest():
def createcertmanifest(config):
manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_CERT_HOST']
manifestdata = getManifestTemplate("nova_cert.pp")
appendManifestFile(manifestfile, manifestdata)
def createconductormanifest():
def createconductormanifest(config):
manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_CONDUCTOR_HOST']
manifestdata = getManifestTemplate("nova_conductor.pp")
appendManifestFile(manifestfile, manifestdata)
@@ -328,7 +328,7 @@ def bring_up_ifcfg(host, device):
raise ScriptRuntimeError(msg)
def createcomputemanifest():
def createcomputemanifest(config):
for host in controller.CONF["CONFIG_NOVA_COMPUTE_HOSTS"].split(","):
host = host.strip()
controller.CONF["CONFIG_NOVA_COMPUTE_HOST"] = host
@@ -348,7 +348,7 @@ def createcomputemanifest():
appendManifestFile(manifestfile, manifestdata + "\n" + nova_config_options.getManifestEntry())
def createnetworkmanifest():
def createnetworkmanifest(config):
host = controller.CONF['CONFIG_NOVA_NETWORK_HOST']
for i in ('CONFIG_NOVA_NETWORK_PRIVIF', 'CONFIG_NOVA_NETWORK_PUBIF'):
check_ifcfg(host, controller.CONF[i])
@@ -373,19 +373,19 @@ def createnetworkmanifest():
appendManifestFile(manifestfile, manifestdata)
def createschedmanifest():
def createschedmanifest(config):
manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_SCHED_HOST']
manifestdata = getManifestTemplate("nova_sched.pp")
appendManifestFile(manifestfile, manifestdata)
def createvncproxymanifest():
def createvncproxymanifest(config):
manifestfile = "%s_nova.pp"%controller.CONF['CONFIG_NOVA_VNCPROXY_HOST']
manifestdata = getManifestTemplate("nova_vncproxy.pp")
appendManifestFile(manifestfile, manifestdata)
def createcommonmanifest():
def createcommonmanifest(config):
for manifestfile, marker in manifestfiles.getFiles():
if manifestfile.endswith("_nova.pp"):
data = getManifestTemplate("nova_common.pp")

View File

@@ -57,7 +57,7 @@ def initSequences(controller):
]
controller.addSequence("Installing OpenStack Client", [], [], osclientsteps)
def createmanifest():
def createmanifest(config):
client_host = controller.CONF['CONFIG_OSCLIENT_HOST'].strip()
manifestfile = "%s_osclient.pp" % client_host
manifestdata = getManifestTemplate("openstack_client.pp")

View File

@@ -42,7 +42,7 @@ def initSequences(controller):
]
controller.addSequence("Running post install scripts", [], [], osclientsteps)
def createmanifest():
def createmanifest(config):
for hostname in gethostlist(controller.CONF):
manifestfile = "%s_postscript.pp" % hostname
manifestdata = getManifestTemplate("postscript.pp")

View File

@@ -146,13 +146,13 @@ def initSequences(controller):
'instances might be problem for '
'some OpenStack components.')
def createmanifest():
def createmanifest(config):
for hostname in gethostlist(controller.CONF):
manifestfile = "%s_prescript.pp" % hostname
manifestdata = getManifestTemplate("prescript.pp")
appendManifestFile(manifestfile, manifestdata)
def create_ntp_manifest():
def create_ntp_manifest(config):
servers = ''
for srv in controller.CONF['CONFIG_NTP_SERVERS'].split(','):
srv = srv.strip()

View File

@@ -59,13 +59,13 @@ def initSequences(controller):
controller.addSequence("Puppet", [], [], puppetsteps)
def runCleanup():
def runCleanup(config):
localserver = utils.ScriptRunner()
localserver.append("rm -rf %s/*pp" % basedefs.PUPPET_MANIFEST_DIR)
localserver.execute()
def installdeps():
def installdeps(config):
for hostname in gethostlist(controller.CONF):
server = utils.ScriptRunner(hostname)
for package in ("puppet", "openssh-clients", "tar"):
@@ -73,7 +73,7 @@ def installdeps():
server.execute()
def copyPuppetModules():
def copyPuppetModules(config):
os_modules = ' '.join(('apache', 'cinder', 'concat',
'create_resources', 'firewall',
'glance', 'horizon', 'inifile',
@@ -165,7 +165,7 @@ def waitforpuppet(currently_running):
print ("[ " + utils.color_text(output_messages.INFO_DONE, 'green') + " ]")
def applyPuppetManifest():
def applyPuppetManifest(config):
print
currently_running = []
lastmarker = None

View File

@@ -57,7 +57,7 @@ def initSequences(controller):
]
controller.addSequence("Installing QPID", [], [], qpidsteps)
def createmanifest():
def createmanifest(config):
manifestfile = "%s_qpid.pp"%controller.CONF['CONFIG_QPID_HOST']
manifestdata = getManifestTemplate("qpid.pp")
appendManifestFile(manifestfile, manifestdata, 'pre')

View File

@@ -373,7 +373,7 @@ def initSequences(controller):
controller.addSequence("Preparing servers", [], [], preparesteps)
def serverprep():
def serverprep(config):
config = controller.CONF
rh_username = None

View File

@@ -61,7 +61,7 @@ def initSequences(controller):
controller.addSequence("Setting up ssh keys", [], [], puppetsteps)
def installKeys():
def installKeys(config):
with open(controller.CONF["CONFIG_SSH_KEY"]) as fp:
sshkeydata = fp.read().strip()
for hostname in gethostlist(controller.CONF):

View File

@@ -130,7 +130,7 @@ def initSequences(controller):
controller.addSequence("Installing OpenStack Swift", [], [], steps)
def createkeystonemanifest():
def createkeystonemanifest(config):
manifestfile = "%s_keystone.pp"%controller.CONF['CONFIG_KEYSTONE_HOST']
controller.CONF['CONFIG_SWIFT_PROXY'] = controller.CONF['CONFIG_SWIFT_PROXY_HOSTS'].split(',')[0]
manifestdata = getManifestTemplate("keystone_swift.pp")
@@ -158,7 +158,7 @@ def parse_devices(config_swift_storage_hosts):
# The ring file should be built and distributed befor the storage services
# come up. Specifically the replicator crashes if the ring isn't present
def createbuildermanifest():
def createbuildermanifest(config):
# TODO : put this on the proxy server, will need to change this later
controller.CONF['CONFIG_SWIFT_BUILDER_HOST'] = controller.CONF['CONFIG_SWIFT_PROXY_HOSTS'].split(',')[0]
manifestfile = "%s_ring_swift.pp"%controller.CONF['CONFIG_SWIFT_BUILDER_HOST']
@@ -178,7 +178,7 @@ def createbuildermanifest():
appendManifestFile(manifestfile, manifestdata, 'swiftbuilder')
def createproxymanifest():
def createproxymanifest(config):
manifestfile = "%s_swift.pp"%controller.CONF['CONFIG_SWIFT_PROXY_HOSTS']
manifestdata = getManifestTemplate("swift_proxy.pp")
# If the proxy server is also a storage server then swift::ringsync will be included for the storage server
@@ -212,7 +212,7 @@ def check_device(host, device):
return False
def createstoragemanifest():
def createstoragemanifest(config):
# this need to happen once per storage host
for host in set([device['host'] for device in devices]):
@@ -238,7 +238,7 @@ def createstoragemanifest():
appendManifestFile(manifestfile, manifestdata)
def createcommonmanifest():
def createcommonmanifest(config):
for manifestfile, marker in manifestfiles.getFiles():
if manifestfile.endswith("_swift.pp"):
data = getManifestTemplate("swift_common.pp")

View File

@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013, Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import StringIO
from unittest import TestCase
from packstack.installer import utils
from packstack.installer.core.sequences import *
from ..test_base import PackstackTestCaseMixin
class StepTestCase(PackstackTestCaseMixin, TestCase):
def setUp(self):
super(StepTestCase, self).setUp()
self._stdout = sys.stdout
sys.stdout = StringIO.StringIO()
def tearDown(self):
super(StepTestCase, self).tearDown()
sys.stdout = self._stdout
def test_run(self):
"""
Test packstack.instaler.core.sequences.Step run.
"""
def func(config):
if 'test' not in config:
raise AssertionError('Missing config value.')
step = Step('test', func, title='Running test')
step.run(config={'test': 'test'})
contents = sys.stdout.getvalue()
state = '[ %s ]\n' % utils.color_text('DONE', 'green')
if not contents.startswith('Running test') or \
not contents.endswith(state):
raise AssertionError('Step run test failed: %s' % contents)
class SequenceTestCase(PackstackTestCaseMixin, TestCase):
def setUp(self):
super(SequenceTestCase, self).setUp()
self._stdout = sys.stdout
sys.stdout = StringIO.StringIO()
self.steps = [{'name': '1', 'function': lambda x: True,
'title': 'Step 1'},
{'name': '2', 'function': lambda x: True,
'title': 'Step 2'},
{'name': '3', 'function': lambda x: True,
'title': 'Step 3'}]
self.seq = Sequence('test', self.steps, condition='test',
cond_match='test')
def tearDown(self):
super(SequenceTestCase, self).tearDown()
sys.stdout = self._stdout
def test_run(self):
"""
Test packstack.instaler.core.sequences.Sequence run.
"""
self.seq.run()
contents = sys.stdout.getvalue()
self.assertEqual(contents, '')
self.seq.run(config={'test': 'test'}, step='2')
contents = sys.stdout.getvalue()
assert contents.startswith('Step 2')
output = []
state_fmt = '[ %s ]\n'
self.steps.insert(0, {'title': 'Step 2'})
for i in self.steps:
space = 70 - len(i['title'])
title = '[ %s ]\n' % utils.color_text('DONE', 'green')
output.append('%s...%s' % (i['title'], title.rjust(space)))
self.seq.run(config={'test': 'test'})
contents = sys.stdout.getvalue()
self.assertEqual(contents, ''.join(output))