blob: 9012c74840702a6b22c0a1dfffd9dcde6ee7ca0d [file] [log] [blame]
# -*- coding: utf-8 -*-
'''
Management of Ironic resources
===============================
:depends: - ironicclient Python module
:configuration: See :py:mod:`salt.modules.ironic` for setup instructions.
.. code-block:: yaml
ironicng node present:
ironicng.node_present:
- name: node-1
- provider_physical_network: PHysnet1
- provider_network_type: vlan
'''
import logging
from functools import wraps
LOG = logging.getLogger(__name__)
def __virtual__():
'''
Only load if ironic module is present in __salt__
'''
return 'ironicng' if 'ironicng.list_nodes' in __salt__ else False
def _test_call(method):
(resource, functionality) = method.func_name.split('_')
if functionality == 'present':
functionality = 'updated'
else:
functionality = 'removed'
@wraps(method)
def check_for_testing(name, *args, **kwargs):
if __opts__.get('test', None):
return _no_change(name, resource, test=functionality)
return method(name, *args, **kwargs)
return check_for_testing
def _ironic_module_call(method, *args, **kwargs):
return __salt__['ironicng.{0}'.format(method)](*args, **kwargs)
def _auth(profile=None, endpoint_type=None,
ironic_api_version=None):
'''
Set up ironic credentials
'''
if profile:
credentials = __salt__['config.option'](profile)
user = credentials['keystone.user']
password = credentials['keystone.password']
tenant = credentials['keystone.tenant']
auth_url = credentials['keystone.auth_url']
kwargs = {
'connection_user': user,
'connection_password': password,
'connection_tenant': tenant,
'connection_auth_url': auth_url,
'connection_endpoint_type': endpoint_type,
'connection_ironic_api_version': ironic_api_version,
'profile': profile
}
return kwargs
@_test_call
def node_present(name,
driver,
driver_info=None,
properties=None,
extra=None,
console_enabled=None,
resource_class=None,
boot_interface=None,
console_interface=None,
deploy_interface=None,
inspect_interface=None,
management_interface=None,
network_interface=None,
power_interface=None,
raid_interface=None,
vendor_interface=None,
maintenance=None,
maintenance_reason=None,
ports=None,
profile=None,
endpoint_type=None,
ironic_api_version=None):
'''
Ensure that the ironic node is present with the specified properties.
'''
connection_args = _auth(profile, endpoint_type,
ironic_api_version=ironic_api_version)
existing_nodes = _ironic_module_call(
'list_nodes', detail=True, **connection_args)['nodes']
existing_nodes = [node for node in existing_nodes if node['name'] == name]
node_arguments = _get_non_null_args(
name=name,
driver=driver,
driver_info=driver_info,
properties=properties,
extra=extra,
console_enabled=console_enabled,
resource_class=resource_class,
boot_interface=boot_interface,
console_interface=console_interface,
deploy_interface=deploy_interface,
inspect_interface=inspect_interface,
management_interface=management_interface,
network_interface=network_interface,
power_interface=power_interface,
raid_interface=raid_interface,
vendor_interface=vendor_interface,
maintenance=maintenance,
maintenance_reason=maintenance_reason)
# In ironic node names are unique
if len(existing_nodes) == 0:
node_arguments.update(connection_args)
res = _ironic_module_call(
'create_node', **node_arguments)
if res.get('name') == name:
return _created(name, 'node', res)
elif len(existing_nodes) == 1:
# TODO(vsaienko) add update with deep comparison
return _no_change(name, 'node')
existing_node = existing_nodes[0]
return _create_failed(name, 'node')
@_test_call
def node_absent(name, uuid=None, profile=None, endpoint_type=None):
connection_args = _auth(profile, endpoint_type)
identifier = uuid or name
_ironic_module_call(
'delete_node', identifier, **connection_args)
return _absent(identifier, 'node')
@_test_call
def port_present(name,
address,
node_name=None,
node_uuid=None,
local_link_connection=None,
extra=None,
profile=None,
endpoint_type=None, ironic_api_version=None):
connection_args = _auth(profile, endpoint_type,
ironic_api_version=ironic_api_version)
identifier = node_uuid or node_name
existing_ports = _ironic_module_call('list_ports', detail=True,
address=address,
**connection_args)['ports']
# we filtered ports by address, so if port found only one item will
# exist since address is unique.
existing_port = existing_ports[0] if len(existing_ports) else {}
existing_node = _ironic_module_call('show_node', node_id=identifier,
**connection_args)
port_arguments = _get_non_null_args(
address=address,
node_uuid=existing_node['uuid'],
local_link_connection=local_link_connection,
extra=extra)
if not existing_port:
port_arguments.update(connection_args)
res = _ironic_module_call('create_port', **port_arguments)
return _created(address, 'port', res)
else:
# generate differential
# TODO(vsaienko) add update with deep comparison
return _no_change(address, 'port')
def _created(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': resource_definition,
'result': True,
'comment': '{0} {1} created'.format(resource, name)}
return changes_dict
def _updated(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': resource_definition,
'result': True,
'comment': '{0} {1} updated'.format(resource, name)}
return changes_dict
def _no_change(name, resource, test=False):
changes_dict = {'name': name,
'changes': {},
'result': True}
if test:
changes_dict['comment'] = \
'{0} {1} will be {2}'.format(resource, name, test)
else:
changes_dict['comment'] = \
'{0} {1} is in correct state'.format(resource, name)
return changes_dict
def _deleted(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} removed'.format(resource, name),
'result': True}
return changes_dict
def _absent(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} not present'.format(resource, name),
'result': True}
return changes_dict
def _delete_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to delete'.format(resource,
name),
'result': False}
return changes_dict
def _create_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to create'.format(resource,
name),
'result': False}
return changes_dict
def _update_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to update'.format(resource,
name),
'result': False}
return changes_dict
def _get_non_null_args(**kwargs):
'''
Return those kwargs which are not null
'''
return dict((key, value,) for key, value in kwargs.iteritems()
if value is not None)