blob: 9012c74840702a6b22c0a1dfffd9dcde6ee7ca0d [file] [log] [blame]
Vasyl Saienko8403d172017-04-27 14:21:46 +03001# -*- coding: utf-8 -*-
2'''
3Management of Ironic resources
4===============================
5:depends: - ironicclient Python module
6:configuration: See :py:mod:`salt.modules.ironic` for setup instructions.
7.. code-block:: yaml
8 ironicng node present:
9 ironicng.node_present:
10 - name: node-1
11 - provider_physical_network: PHysnet1
12 - provider_network_type: vlan
13'''
14import logging
15from functools import wraps
16LOG = logging.getLogger(__name__)
17
18
19def __virtual__():
20 '''
21 Only load if ironic module is present in __salt__
22 '''
23 return 'ironicng' if 'ironicng.list_nodes' in __salt__ else False
24
25
26def _test_call(method):
27 (resource, functionality) = method.func_name.split('_')
28 if functionality == 'present':
29 functionality = 'updated'
30 else:
31 functionality = 'removed'
32
33 @wraps(method)
34 def check_for_testing(name, *args, **kwargs):
35 if __opts__.get('test', None):
36 return _no_change(name, resource, test=functionality)
37 return method(name, *args, **kwargs)
38 return check_for_testing
39
40
41def _ironic_module_call(method, *args, **kwargs):
42 return __salt__['ironicng.{0}'.format(method)](*args, **kwargs)
43
44def _auth(profile=None, endpoint_type=None,
45 ironic_api_version=None):
46 '''
47 Set up ironic credentials
48 '''
49 if profile:
50 credentials = __salt__['config.option'](profile)
51 user = credentials['keystone.user']
52 password = credentials['keystone.password']
53 tenant = credentials['keystone.tenant']
54 auth_url = credentials['keystone.auth_url']
55 kwargs = {
56 'connection_user': user,
57 'connection_password': password,
58 'connection_tenant': tenant,
59 'connection_auth_url': auth_url,
60 'connection_endpoint_type': endpoint_type,
61 'connection_ironic_api_version': ironic_api_version,
62 'profile': profile
63 }
64
65 return kwargs
66
67@_test_call
68def node_present(name,
69 driver,
70 driver_info=None,
71 properties=None,
72 extra=None,
73 console_enabled=None,
74 resource_class=None,
75 boot_interface=None,
76 console_interface=None,
77 deploy_interface=None,
78 inspect_interface=None,
79 management_interface=None,
80 network_interface=None,
81 power_interface=None,
82 raid_interface=None,
83 vendor_interface=None,
84 maintenance=None,
85 maintenance_reason=None,
86 ports=None,
87 profile=None,
88 endpoint_type=None,
89 ironic_api_version=None):
90 '''
91 Ensure that the ironic node is present with the specified properties.
92 '''
93 connection_args = _auth(profile, endpoint_type,
94 ironic_api_version=ironic_api_version)
95
96 existing_nodes = _ironic_module_call(
97 'list_nodes', detail=True, **connection_args)['nodes']
98 existing_nodes = [node for node in existing_nodes if node['name'] == name]
99
100 node_arguments = _get_non_null_args(
101 name=name,
102 driver=driver,
103 driver_info=driver_info,
104 properties=properties,
105 extra=extra,
106 console_enabled=console_enabled,
107 resource_class=resource_class,
108 boot_interface=boot_interface,
109 console_interface=console_interface,
110 deploy_interface=deploy_interface,
111 inspect_interface=inspect_interface,
112 management_interface=management_interface,
113 network_interface=network_interface,
114 power_interface=power_interface,
115 raid_interface=raid_interface,
116 vendor_interface=vendor_interface,
117 maintenance=maintenance,
118 maintenance_reason=maintenance_reason)
119
120 # In ironic node names are unique
121 if len(existing_nodes) == 0:
122 node_arguments.update(connection_args)
123 res = _ironic_module_call(
124 'create_node', **node_arguments)
125
126 if res.get('name') == name:
127 return _created(name, 'node', res)
128
129 elif len(existing_nodes) == 1:
130 # TODO(vsaienko) add update with deep comparison
131 return _no_change(name, 'node')
132 existing_node = existing_nodes[0]
133 return _create_failed(name, 'node')
134
135
136@_test_call
137def node_absent(name, uuid=None, profile=None, endpoint_type=None):
138 connection_args = _auth(profile, endpoint_type)
139 identifier = uuid or name
140 _ironic_module_call(
141 'delete_node', identifier, **connection_args)
142 return _absent(identifier, 'node')
143
144@_test_call
145def port_present(name,
146 address,
147 node_name=None,
148 node_uuid=None,
149 local_link_connection=None,
150 extra=None,
151 profile=None,
152 endpoint_type=None, ironic_api_version=None):
153 connection_args = _auth(profile, endpoint_type,
154 ironic_api_version=ironic_api_version)
155 identifier = node_uuid or node_name
156 existing_ports = _ironic_module_call('list_ports', detail=True,
157 address=address,
158 **connection_args)['ports']
159 # we filtered ports by address, so if port found only one item will
160 # exist since address is unique.
161 existing_port = existing_ports[0] if len(existing_ports) else {}
162
163 existing_node = _ironic_module_call('show_node', node_id=identifier,
164 **connection_args)
165
166 port_arguments = _get_non_null_args(
167 address=address,
168 node_uuid=existing_node['uuid'],
169 local_link_connection=local_link_connection,
170 extra=extra)
171
172 if not existing_port:
173 port_arguments.update(connection_args)
174 res = _ironic_module_call('create_port', **port_arguments)
175 return _created(address, 'port', res)
176 else:
177 # generate differential
178 # TODO(vsaienko) add update with deep comparison
179 return _no_change(address, 'port')
180
181def _created(name, resource, resource_definition):
182 changes_dict = {'name': name,
183 'changes': resource_definition,
184 'result': True,
185 'comment': '{0} {1} created'.format(resource, name)}
186 return changes_dict
187
188def _updated(name, resource, resource_definition):
189 changes_dict = {'name': name,
190 'changes': resource_definition,
191 'result': True,
192 'comment': '{0} {1} updated'.format(resource, name)}
193 return changes_dict
194
195def _no_change(name, resource, test=False):
196 changes_dict = {'name': name,
197 'changes': {},
198 'result': True}
199 if test:
200 changes_dict['comment'] = \
201 '{0} {1} will be {2}'.format(resource, name, test)
202 else:
203 changes_dict['comment'] = \
204 '{0} {1} is in correct state'.format(resource, name)
205 return changes_dict
206
207
208def _deleted(name, resource, resource_definition):
209 changes_dict = {'name': name,
210 'changes': {},
211 'comment': '{0} {1} removed'.format(resource, name),
212 'result': True}
213 return changes_dict
214
215
216def _absent(name, resource):
217 changes_dict = {'name': name,
218 'changes': {},
219 'comment': '{0} {1} not present'.format(resource, name),
220 'result': True}
221 return changes_dict
222
223
224def _delete_failed(name, resource):
225 changes_dict = {'name': name,
226 'changes': {},
227 'comment': '{0} {1} failed to delete'.format(resource,
228 name),
229 'result': False}
230 return changes_dict
231
232def _create_failed(name, resource):
233 changes_dict = {'name': name,
234 'changes': {},
235 'comment': '{0} {1} failed to create'.format(resource,
236 name),
237 'result': False}
238 return changes_dict
239
240def _update_failed(name, resource):
241 changes_dict = {'name': name,
242 'changes': {},
243 'comment': '{0} {1} failed to update'.format(resource,
244 name),
245 'result': False}
246 return changes_dict
247
248
249def _get_non_null_args(**kwargs):
250 '''
251 Return those kwargs which are not null
252 '''
253 return dict((key, value,) for key, value in kwargs.iteritems()
254 if value is not None)