| # -*- 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) |