Initial commit
This patch adds initial formula that allow to install Ironic api
and conductor.
Change-Id: I21fe4cd93454ed64277ba6756a591155d0052dc8
diff --git a/_states/ironicng.py b/_states/ironicng.py
new file mode 100644
index 0000000..9012c74
--- /dev/null
+++ b/_states/ironicng.py
@@ -0,0 +1,254 @@
+# -*- 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)