Commit 898d7676 by Michael DeHaan

Adds the 'serial' keyword to a playbook which controls how many hosts can be…

Adds the 'serial' keyword to a playbook which controls how many hosts can be running through a playbook at a single time.
The default is 0, which means all hosts.  If set to 1, each host would run a playbook all the way through before moving
on the next host.  Fact gathering is still parallel, regardless of the serial setting.
parent e13c33bb
...@@ -40,6 +40,7 @@ Ansible Changes By Release ...@@ -40,6 +40,7 @@ Ansible Changes By Release
* add pattern= as a paramter to the service module * add pattern= as a paramter to the service module
* various fixes to mysql & postresql modules * various fixes to mysql & postresql modules
* adds 'delegate_to' for a task, which can be used to signal outage windows and load balancers on behalf of hosts * adds 'delegate_to' for a task, which can be used to signal outage windows and load balancers on behalf of hosts
* adds 'serial' to playbook, allowing you to specify how many hosts can be processing a playbook at one time (default 0=all)
0.6 "Cabo" -- August 6, 2012 0.6 "Cabo" -- August 6, 2012
......
...@@ -34,7 +34,7 @@ class Inventory(object): ...@@ -34,7 +34,7 @@ class Inventory(object):
Host inventory for ansible. Host inventory for ansible.
""" """
__slots__ = [ 'host_list', 'groups', '_restriction', '_subset', '_is_script', __slots__ = [ 'host_list', 'groups', '_restriction', '_also_restriction', '_subset', '_is_script',
'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache' ] 'parser', '_vars_per_host', '_vars_per_group', '_hosts_cache' ]
def __init__(self, host_list=C.DEFAULT_HOST_LIST): def __init__(self, host_list=C.DEFAULT_HOST_LIST):
...@@ -55,6 +55,7 @@ class Inventory(object): ...@@ -55,6 +55,7 @@ class Inventory(object):
# a list of host(names) to contain current inquiries to # a list of host(names) to contain current inquiries to
self._restriction = None self._restriction = None
self._also_restriction = None
self._subset = None self._subset = None
# whether the inventory file is a script # whether the inventory file is a script
...@@ -122,6 +123,8 @@ class Inventory(object): ...@@ -122,6 +123,8 @@ class Inventory(object):
# exclude hosts mentioned in any restriction (ex: failed hosts) # exclude hosts mentioned in any restriction (ex: failed hosts)
if self._restriction is not None: if self._restriction is not None:
hosts = [ h for h in hosts if h.name in self._restriction ] hosts = [ h for h in hosts if h.name in self._restriction ]
if self._also_restriction is not None:
hosts = [ h for h in hosts if h.name in self._also_restriction ]
return sorted(hosts, key=lambda x: x.name) return sorted(hosts, key=lambda x: x.name)
...@@ -281,6 +284,7 @@ class Inventory(object): ...@@ -281,6 +284,7 @@ class Inventory(object):
def list_groups(self): def list_groups(self):
return sorted([ g.name for g in self.groups ], key=lambda x: x.name) return sorted([ g.name for g in self.groups ], key=lambda x: x.name)
# TODO: remove this function
def get_restriction(self): def get_restriction(self):
return self._restriction return self._restriction
...@@ -294,6 +298,15 @@ class Inventory(object): ...@@ -294,6 +298,15 @@ class Inventory(object):
restriction = [ restriction ] restriction = [ restriction ]
self._restriction = restriction self._restriction = restriction
def also_restrict_to(self, restriction):
"""
Works like restict_to but offers an additional restriction. Playbooks use this
to implement serial behavior.
"""
if type(restriction) != list:
restriction = [ restriction ]
self._also_restriction = restriction
def subset(self, subset_pattern): def subset(self, subset_pattern):
""" """
Limits inventory results to a subset of inventory that matches a given Limits inventory results to a subset of inventory that matches a given
...@@ -308,8 +321,11 @@ class Inventory(object): ...@@ -308,8 +321,11 @@ class Inventory(object):
def lift_restriction(self): def lift_restriction(self):
""" Do not restrict list operations """ """ Do not restrict list operations """
self._restriction = None self._restriction = None
def lift_also_restriction(self):
""" Clears the also restriction """
self._also_restriction = None
def is_file(self): def is_file(self):
""" did inventory come from a file? """ """ did inventory come from a file? """
......
...@@ -150,8 +150,8 @@ class PlayBook(object): ...@@ -150,8 +150,8 @@ class PlayBook(object):
# loop through all patterns and run them # loop through all patterns and run them
self.callbacks.on_start() self.callbacks.on_start()
for play_ds in self.playbook: for play_ds in self.playbook:
self._run_play(Play(self,play_ds)) play = Play(self,play_ds)
self._run_play(play)
# summarize the results # summarize the results
results = {} results = {}
for host in self.stats.processed.keys(): for host in self.stats.processed.keys():
...@@ -301,22 +301,43 @@ class PlayBook(object): ...@@ -301,22 +301,43 @@ class PlayBook(object):
self._do_setup_step(play) self._do_setup_step(play)
# now with that data, handle contentional variable file imports! # now with that data, handle contentional variable file imports!
play.update_vars_files(self.inventory.list_hosts(play.hosts))
all_hosts = self.inventory.list_hosts(play.hosts)
for task in play.tasks(): play.update_vars_files(all_hosts)
# only run the task if the requested tags match
should_run = False serialized_batch = []
for x in self.only_tags: if play.serial <= 0:
for y in task.tags: serialized_batch = [all_hosts]
if (x==y): else:
should_run = True # do N forks all the way through before moving to next
break while len(all_hosts) > 0:
if should_run: play_hosts = []
self._run_task(play, task, False) for x in range(1, play.serial):
if len(all_hosts) > 0:
# run notify actions play_hosts.append(all_hosts.pop())
for handler in play.handlers(): serialized_batch.append(play_hosts)
if len(handler.notified_by) > 0:
self.inventory.restrict_to(handler.notified_by) for on_hosts in serialized_batch:
self._run_task(play, handler, True)
self.inventory.lift_restriction() self.inventory.also_restrict_to(on_hosts)
for task in play.tasks():
# only run the task if the requested tags match
should_run = False
for x in self.only_tags:
for y in task.tags:
if (x==y):
should_run = True
break
if should_run:
self._run_task(play, task, False)
# run notify actions
for handler in play.handlers():
if len(handler.notified_by) > 0:
self.inventory.restrict_to(handler.notified_by)
self._run_task(play, handler, True)
self.inventory.lift_restriction()
self.inventory.lift_also_restriction()
...@@ -29,7 +29,7 @@ class Play(object): ...@@ -29,7 +29,7 @@ class Play(object):
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files', 'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
'handlers', 'remote_user', 'remote_port', 'handlers', 'remote_user', 'remote_port',
'sudo', 'sudo_user', 'transport', 'playbook', 'sudo', 'sudo_user', 'transport', 'playbook',
'tags', 'gather_facts', '_ds', '_handlers', '_tasks' 'tags', 'gather_facts', 'serial', '_ds', '_handlers', '_tasks'
] ]
# to catch typos and so forth -- these are userland names # to catch typos and so forth -- these are userland names
...@@ -37,7 +37,7 @@ class Play(object): ...@@ -37,7 +37,7 @@ class Play(object):
VALID_KEYS = [ VALID_KEYS = [
'hosts', 'name', 'vars', 'vars_prompt', 'vars_files', 'hosts', 'name', 'vars', 'vars_prompt', 'vars_files',
'tasks', 'handlers', 'user', 'port', 'include', 'tasks', 'handlers', 'user', 'port', 'include',
'sudo', 'sudo_user', 'connection', 'tags', 'gather_facts' 'sudo', 'sudo_user', 'connection', 'tags', 'gather_facts', 'serial'
] ]
# ************************************************* # *************************************************
...@@ -75,6 +75,7 @@ class Play(object): ...@@ -75,6 +75,7 @@ class Play(object):
self.transport = ds.get('connection', self.playbook.transport) self.transport = ds.get('connection', self.playbook.transport)
self.tags = ds.get('tags', None) self.tags = ds.get('tags', None)
self.gather_facts = ds.get('gather_facts', True) self.gather_facts = ds.get('gather_facts', True)
self.serial = ds.get('serial', 0)
self._update_vars_files_for_host(None) self._update_vars_files_for_host(None)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment