blob: 53f7f36b50ca763705c9f7a08daba0edb92292ec [file] [log] [blame]
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 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',
'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