|  | import collections | 
|  | import logging | 
|  | import time | 
|  |  | 
|  | log = logging.getLogger(__name__) | 
|  |  | 
|  |  | 
|  | def __virtual__(): | 
|  | return 'ironicv1' if 'ironicv1.node_list' in __salt__ else False | 
|  |  | 
|  |  | 
|  | def _ironicv1_call(fname, *args, **kwargs): | 
|  | return __salt__['ironicv1.{}'.format(fname)](*args, **kwargs) | 
|  |  | 
|  |  | 
|  | def node_present(name, cloud_name, driver, **kwargs): | 
|  | resource = 'node' | 
|  | microversion = kwargs.pop('microversion', '1.16') | 
|  | try: | 
|  | method_name = '{}_get_details'.format(resource) | 
|  | exact_resource = _ironicv1_call( | 
|  | method_name, name, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | ) | 
|  | except Exception as e: | 
|  | if 'Not Found' in str(e): | 
|  | try: | 
|  | method_name = '{}_create'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, driver, name=name, cloud_name=cloud_name, | 
|  | microversion=microversion, | 
|  | **kwargs | 
|  | ) | 
|  | except Exception as e: | 
|  | log.exception('Ironic {0} create failed with {1}'. | 
|  | format('node', e)) | 
|  | return _failed('create', name, resource) | 
|  | return _succeeded('create', name, resource, resp) | 
|  | raise | 
|  |  | 
|  | to_change = [] | 
|  | for prop in kwargs: | 
|  | path = prop.replace('~', '~0').replace('/', '~1') | 
|  | if prop in exact_resource: | 
|  | if exact_resource[prop] != kwargs[prop]: | 
|  | to_change.append({ | 
|  | 'op': 'replace', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | else: | 
|  | to_change.append({ | 
|  | 'op': 'add', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | if to_change: | 
|  | try: | 
|  | method_name = '{}_update'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, name, properties=to_change, | 
|  | microversion=microversion, cloud_name=cloud_name, | 
|  | ) | 
|  | except Exception as e: | 
|  | log.exception( | 
|  | 'Ironic {0} update failed with {1}'.format(resource, e)) | 
|  | return _failed('update', name, resource) | 
|  | return _succeeded('update', name, resource, resp) | 
|  | return _succeeded('no_changes', name, resource) | 
|  |  | 
|  |  | 
|  | def node_absent(name, cloud_name, **kwargs): | 
|  | resource = 'node' | 
|  | microversion = kwargs.pop('microversion', '1.16') | 
|  | try: | 
|  | method_name = '{}_get_details'.format(resource) | 
|  | _ironicv1_call( | 
|  | method_name, name, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | ) | 
|  | except Exception as e: | 
|  | if 'Not Found' in str(e): | 
|  | return _succeeded('absent', name, resource) | 
|  | try: | 
|  | method_name = '{}_delete'.format(resource) | 
|  | _ironicv1_call( | 
|  | method_name, name, cloud_name=cloud_name, microversion=microversion | 
|  | ) | 
|  | except Exception as e: | 
|  | log.error('Ironic delete {0} failed with {1}'.format(resource, e)) | 
|  | return _failed('delete', name, resource) | 
|  | return _succeeded('delete', name, resource) | 
|  |  | 
|  |  | 
|  | def port_present(name, cloud_name, node, address, **kwargs): | 
|  | resource = 'port' | 
|  | microversion = kwargs.pop('microversion', '1.16') | 
|  | method_name = '{}_list'.format(resource) | 
|  | exact_resource = _ironicv1_call( | 
|  | method_name, node=node, address=address, | 
|  | cloud_name=cloud_name, microversion=microversion | 
|  | )['ports'] | 
|  | if len(exact_resource) == 0: | 
|  | try: | 
|  | node_uuid = _ironicv1_call( | 
|  | 'node_get_details', node, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | )['uuid'] | 
|  | except Exception as e: | 
|  | return _failed('create', node, "port's node") | 
|  | try: | 
|  | method_name = '{}_create'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, node_uuid, address, cloud_name=cloud_name, | 
|  | microversion=microversion, **kwargs) | 
|  | except Exception as e: | 
|  | log.exception('Ironic {0} create failed with {1}'. | 
|  | format('node', e)) | 
|  | return _failed('create', name, resource) | 
|  | return _succeeded('create', name, resource, resp) | 
|  | if len(exact_resource) == 1: | 
|  | exact_resource = exact_resource[0] | 
|  | to_change = [] | 
|  | for prop in kwargs: | 
|  | path = prop.replace('~', '~0').replace('/', '~1') | 
|  | if prop in exact_resource: | 
|  | if exact_resource[prop] != kwargs[prop]: | 
|  | to_change.append({ | 
|  | 'op': 'replace', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | else: | 
|  | to_change.append({ | 
|  | 'op': 'add', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | if to_change: | 
|  | try: | 
|  | method_name = '{}_update'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, exact_resource['uuid'], properties=to_change, | 
|  | microversion=microversion, cloud_name=cloud_name, | 
|  | ) | 
|  | except Exception as e: | 
|  | log.exception( | 
|  | 'Ironic {0} update failed with {1}'.format(resource, e)) | 
|  | return _failed('update', name, resource) | 
|  | return _succeeded('update', name, resource, resp) | 
|  | return _succeeded('no_changes', name, resource) | 
|  | else: | 
|  | return _failed('find', name, resource) | 
|  |  | 
|  |  | 
|  | def port_absent(name, cloud_name, node, address, **kwargs): | 
|  | resource = 'port' | 
|  | microversion = kwargs.pop('microversion', '1.16') | 
|  | method_name = '{}_list'.format(resource) | 
|  | exact_resource = _ironicv1_call( | 
|  | method_name, node=node, address=address, | 
|  | cloud_name=cloud_name, microversion=microversion | 
|  | )['ports'] | 
|  | if len(exact_resource) == 0: | 
|  | return _succeeded('absent', name, resource) | 
|  | elif len(exact_resource) == 1: | 
|  | port_id = exact_resource[0]['uuid'] | 
|  | try: | 
|  | method_name = '{}_delete'.format(resource) | 
|  | _ironicv1_call( | 
|  | method_name, port_id, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | ) | 
|  | except Exception as e: | 
|  | log.error('Ironic delete {0} failed with {1}'.format(resource, e)) | 
|  | return _failed('delete', name, resource) | 
|  | return _succeeded('delete', name, resource) | 
|  | else: | 
|  | return _failed('find', name, resource) | 
|  |  | 
|  |  | 
|  | def volume_connector_present(name, node, volume_type, cloud_name, | 
|  | **kwargs): | 
|  | """ | 
|  |  | 
|  | :param name: alias for connector_id because of how salt works | 
|  | :param node: node_ident | 
|  | :param volume_type: type of volume | 
|  | """ | 
|  | resource = 'volume_connector' | 
|  | microversion = kwargs.pop('microversion', '1.32') | 
|  | method_name = '{}_list'.format(resource) | 
|  | exact_resource = filter( | 
|  | lambda data: data['connector_id'] == name, | 
|  | _ironicv1_call(method_name, node=node, | 
|  | cloud_name=cloud_name, | 
|  | microversion=microversion)['connectors']) | 
|  | if len(exact_resource) == 0: | 
|  | try: | 
|  | method_name = 'node_get_details' | 
|  | node_uuid = _ironicv1_call( | 
|  | method_name, node, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | )['uuid'] | 
|  | except Exception as e: | 
|  | if 'Not Found' in str(e): | 
|  | return _failed('not_found', node, 'node') | 
|  | raise | 
|  | try: | 
|  | method_name = '{}_create'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, node_uuid, volume_type, name, | 
|  | cloud_name=cloud_name, microversion=microversion, **kwargs) | 
|  | except Exception as e: | 
|  | log.exception('Ironic {0} create failed with {1}'. | 
|  | format('node', e)) | 
|  | return _failed('create', name, resource) | 
|  | return _succeeded('create', name, resource, resp) | 
|  | elif len(exact_resource) == 1: | 
|  | exact_resource = exact_resource[0] | 
|  | to_change = [] | 
|  | for prop in kwargs: | 
|  | path = prop.replace('~', '~0').replace('/', '~1') | 
|  | if prop in exact_resource: | 
|  | if exact_resource[prop] != kwargs[prop]: | 
|  | to_change.append({ | 
|  | 'op': 'replace', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | else: | 
|  | to_change.append({ | 
|  | 'op': 'add', | 
|  | 'path': '/{}'.format(path), | 
|  | 'value': kwargs[prop], | 
|  | }) | 
|  | if to_change: | 
|  | try: | 
|  | method_name = '{}_update'.format(resource) | 
|  | resp = _ironicv1_call( | 
|  | method_name, exact_resource['uuid'], properties=to_change, | 
|  | microversion=microversion, cloud_name=cloud_name, | 
|  | ) | 
|  | except Exception as e: | 
|  | log.exception( | 
|  | 'Ironic {0} update failed with {1}'.format(resource, | 
|  | e)) | 
|  | return _failed('update', name, resource) | 
|  | return _succeeded('update', name, resource, resp) | 
|  | return _succeeded('no_changes', name, resource) | 
|  | else: | 
|  | return _failed('find', name, resource) | 
|  |  | 
|  |  | 
|  | def volume_connector_absent(name, cloud_name, node, **kwargs): | 
|  | """ | 
|  |  | 
|  | :param name: alias for connector_id because of how salt works | 
|  | :param node: node ident | 
|  | """ | 
|  | resource = 'volume_connector' | 
|  | microversion = kwargs.pop('microversion', '1.32') | 
|  | method_name = '{}_list'.format(resource) | 
|  | exact_resource = filter( | 
|  | lambda data: data['connector_id'] == name, | 
|  | _ironicv1_call(method_name, node=node, | 
|  | cloud_name=cloud_name, | 
|  | microversion=microversion)['connectors']) | 
|  | if len(exact_resource) == 0: | 
|  | return _succeeded('absent', name, resource) | 
|  | elif len(exact_resource) == 1: | 
|  | connector_uuid = exact_resource[0]['uuid'] | 
|  | try: | 
|  | method_name = '{}_delete'.format(resource) | 
|  | _ironicv1_call( | 
|  | method_name, connector_uuid, cloud_name=cloud_name, | 
|  | microversion=microversion | 
|  | ) | 
|  | except Exception as e: | 
|  | log.error('Ironic delete {0} failed with {1}'.format(resource, e)) | 
|  | return _failed('delete', name, resource) | 
|  | return _succeeded('delete', name, resource) | 
|  | else: | 
|  | return _failed('find', name, resource) | 
|  |  | 
|  |  | 
|  | def ensure_target_state(name, cloud_name, node_names=None, | 
|  | provision_state=None, pool_size=3, sleep_time=5, | 
|  | timeout=600, **kwargs): | 
|  | """ | 
|  | Ensures nodes are moved to target state. As node distinguisher might take | 
|  | either list of nodes specified in node names param or provision state. | 
|  | Is designed to move nodes from enroll to available state for now. | 
|  |  | 
|  | :param name: name of target state | 
|  | :param node_names: list of node names | 
|  | :param provision_state: current provision_state to filter nodes by. | 
|  | :param cloud_name: the mane of cloud in clouds.yml | 
|  | :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') | 
|  |  | 
|  | if node_names is None: | 
|  | nodes = _ironicv1_call('node_list', provision_state=provision_state, | 
|  | cloud_name=cloud_name, | 
|  | fields='name', | 
|  | microversion=microversion)['nodes'] | 
|  | node_names = [n['name'] for n in nodes] | 
|  |  | 
|  | 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': provision_state or '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', | 
|  | 'delete': '{0} {1} removed', | 
|  | 'update': '{0} {1} updated', | 
|  | 'no_changes': '{0} {1} is in desired state', | 
|  | 'absent': '{0} {1} not present' | 
|  | } | 
|  | changes_dict = { | 
|  | 'name': name, | 
|  | 'result': True, | 
|  | 'comment': msg_map[op].format(resource, name), | 
|  | 'changes': changes or {}, | 
|  | } | 
|  | return changes_dict | 
|  |  | 
|  |  | 
|  | def _failed(op, name, resource): | 
|  | msg_map = { | 
|  | 'create': '{0} {1} failed to create', | 
|  | 'delete': '{0} {1} failed to delete', | 
|  | 'update': '{0} {1} failed to update', | 
|  | 'find': '{0} {1} found multiple {0}', | 
|  | 'not found': '{0} {1} not found', | 
|  | } | 
|  | changes_dict = { | 
|  | 'name': name, | 
|  | 'result': False, | 
|  | 'comment': msg_map[op].format(resource, name), | 
|  | 'changes': {}, | 
|  | } | 
|  | return changes_dict |