Add node deploy state

Change-Id: Iabe428bf35664dab6b56a00cb20691f7256ffea0
Related-Issue: PROD-26188 (PROD:26188)
(cherry picked from commit 2cd70515cd0966ab247966c3c68bd45ceb7c64c3)
diff --git a/_states/ironicv1.py b/_states/ironicv1.py
index 2505f68..53f7f36 100644
--- a/_states/ironicv1.py
+++ b/_states/ironicv1.py
@@ -1,4 +1,6 @@
+import collections
 import logging
+import time
 
 log = logging.getLogger(__name__)
 
@@ -282,6 +284,79 @@
         return _failed('find', name, resource)
 
 
+def enroll_to_state(name, node_names, cloud_name,
+                    pool_size=3, sleep_time=5, timeout=600, **kwargs):
+    """
+
+    :param name: name of target state
+    :param node_names: list of node names
+    :param cloud_name:
+    :param pool_size: max size of nodes to change state in one moment
+    :param sleep_time: time between checking states
+    :param timeout: global timeout
+    """
+    microversion = kwargs.pop('microversion', '1.32')
+    Transition = collections.namedtuple('Transition',
+                                        ('action', 'success', 'failures'))
+    transition_map = {
+        'enroll': Transition('manage', 'manageable', ('enroll',)),
+        'manageable': Transition('provide', 'available', ('clean failed',)),
+        'available': Transition('active', 'active', ('deploy failed',)),
+    }
+    nodes = [
+        {'name': node, 'status': 'new', 'result': None,
+         'current_state': 'enroll'}
+        for node in node_names
+    ]
+    counter = 0
+    while nodes and timeout > 0:
+        for node in nodes:
+            api_node = _ironicv1_call('node_get_details', node['name'],
+                                      cloud_name=cloud_name,
+                                      microversion=microversion)
+            if api_node['provision_state'] == name:
+                node['status'] = 'done'
+                node['result'] = 'success'
+                counter -= 1
+            elif (api_node['provision_state']
+                  == transition_map[node['current_state']].success):
+                new_state = transition_map[node['current_state']].success
+                _ironicv1_call('node_provision_state_change', node['name'],
+                               transition_map[new_state].action,
+                               cloud_name=cloud_name,
+                               microversion=microversion)
+                node['current_state'] = new_state
+            elif (node['status'] == 'processing'
+                  and not api_node['target_provision_state']
+                  and (api_node['provision_state']
+                       in transition_map[api_node['provision_state']].failures)
+                  ):
+                node['status'] = 'done'
+                node['result'] = 'failure'
+                counter -= 1
+            elif counter < pool_size:
+                if node['status'] == 'new':
+                    _ironicv1_call(
+                        'node_provision_state_change', node['name'],
+                        transition_map[node['current_state']].action,
+                        cloud_name=cloud_name, microversion=microversion)
+                    node['status'] = 'processing'
+                    counter += 1
+                else:
+                    continue
+            else:
+                break
+        nodes = filter(
+            lambda node: node['status'] in ['new', 'processing'], nodes)
+        time.sleep(sleep_time)
+        timeout -= sleep_time
+    return _succeeded('update', name, 'node_states',
+                      {'result': filter(
+                          lambda node: node['name'] in node_names,
+                          _ironicv1_call('node_list', cloud_name=cloud_name,
+                                         microversion=microversion)['nodes'])})
+
+
 def _succeeded(op, name, resource, changes=None):
     msg_map = {
         'create': '{0} {1} created',