blob: 9d4deaa525dee86a90a8daa3843b158a2949a586 [file] [log] [blame]
Jiri Broulikf1b3aa42017-01-26 17:08:44 +01001# -*- coding: utf-8 -*-
2'''
3Management of Neutron resources
4===============================
5:depends: - neutronclient Python module
6:configuration: See :py:mod:`salt.modules.neutron` for setup instructions.
7.. code-block:: yaml
8 neutron network present:
9 neutron.network_present:
10 - name: Netone
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 neutron module is present in __salt__
22 '''
23 return 'neutronng' if 'neutron.list_networks' 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 _neutron_module_call(method, *args, **kwargs):
42 return __salt__['neutronng.{0}'.format(method)](*args, **kwargs)
43
44
45def _auth(profile=None):
46 '''
47 Set up neutron 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
56 kwargs = {
57 'connection_user': user,
58 'connection_password': password,
59 'connection_tenant': tenant,
60 'connection_auth_url': auth_url
61 }
62
63 return kwargs
64
65@_test_call
66def network_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +010067 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +010068 provider_network_type=None,
69 provider_physical_network=None,
70 router_external=None,
71 admin_state_up=None,
72 shared=None,
73 provider_segmentation_id=None,
74 profile=None):
75 '''
76 Ensure that the neutron network is present with the specified properties.
77 name
78 The name of the network to manage
79 '''
Jiri Broulik5368cc52017-02-08 18:53:59 +010080 tenant_name = tenant
Jiri Broulikf1b3aa42017-01-26 17:08:44 +010081 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +010082 try:
83 tenant_id = __salt__['keystone.tenant_get'](
84 name=tenant_name, **connection_args)[tenant_name]['id']
85 except:
86 tenant_id = None
87 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
88 connection_args['connection_user']))
Richard Felklb97cbe72017-02-28 22:37:59 +010089 existing_networks = _neutron_module_call(
90 'list_networks', **connection_args)
91 for network in existing_networks:
92 if network.get(name) == name:
93 existing_network = network
Jiri Broulikf1b3aa42017-01-26 17:08:44 +010094 network_arguments = _get_non_null_args(
95 name=name,
96 provider_network_type=provider_network_type,
97 provider_physical_network=provider_physical_network,
98 router_external=router_external,
99 admin_state_up=admin_state_up,
100 shared=shared,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100101 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100102 provider_segmentation_id=provider_segmentation_id)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100103
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100104 if not existing_network:
105 network_arguments.update(connection_args)
106 _neutron_module_call('create_network', **network_arguments)
Richard Felklb97cbe72017-02-28 22:37:59 +0100107 existing_networks = _neutron_module_call(
108 'list_networks', **connection_args)
109 for network in existing_networks:
110 if network.get(name) == name:
111 existing_network = network
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100112 if existing_network:
113 return _created(name, 'network', existing_network[name])
114 return _update_failed(name, 'network')
Jiri Broulik5368cc52017-02-08 18:53:59 +0100115
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100116 LOG.info('CONNECTION STRINGS' + str(connection_args))
117 LOG.info('existing ' + str(existing_network))
118 LOG.info('new ' + str(network_arguments))
119 existing_network = dict((key.replace(':', '_', 1), value)
120 for key, value in
121 existing_network[name].iteritems())
122 # generate differential
123 diff = dict((key, value) for key, value in network_arguments.iteritems()
124 if existing_network.get(key, None) != value)
125 if diff:
126 # update the changes
127 network_arguments = diff.copy()
128 network_arguments.update(connection_args)
129 try:
130 LOG.debug('updating network {0} with changes {1}'.format(
131 name, str(diff)))
132 _neutron_module_call('update_network',
133 existing_network['id'],
134 **network_arguments)
135 changes_dict = _created(name, 'network', diff)
136 changes_dict['comment'] = '{1} {0} updated'.format(name, 'network')
137 return changes_dict
138 except:
139 LOG.exception('Could not update network {0}'.format(name))
140 return _update_failed(name, 'network')
141 return _no_change(name, 'network')
142
143
144@_test_call
145def network_absent(name, profile=None):
146 connection_args = _auth(profile)
147 existing_network = _neutron_module_call(
148 'list_networks', name=name, **connection_args)
149 if existing_network:
150 _neutron_module_call(
151 'delete_network', existing_network[name]['id'], **connection_args)
152 if _neutron_module_call('list_networks', name=name, **connection_args):
153 return _delete_failed(name, 'network')
154 return _deleted(name, 'network', existing_network[name])
155 return _absent(name, 'network')
156
157
158@_test_call
159def subnet_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100160 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100161 network=None,
162 cidr=None,
163 ip_version=4,
164 enable_dhcp=True,
165 allocation_pools=None,
166 gateway_ip=None,
167 dns_nameservers=None,
168 host_routes=None,
169 profile=None):
170 '''
171 Ensure that the neutron subnet is present with the specified properties.
172 name
173 The name of the subnet to manage
174 '''
175 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100176 tenant_name = tenant
177 try:
178 tenant_id = __salt__['keystone.tenant_get'](
179 name=tenant_name, **connection_args)[tenant_name]['id']
180 except:
181 tenant_id = None
182 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
183 connection_args['connection_user']))
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100184 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100185 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100186 subnet_arguments = _get_non_null_args(
187 name=name,
188 network=network,
189 cidr=cidr,
190 ip_version=ip_version,
191 enable_dhcp=enable_dhcp,
192 allocation_pools=allocation_pools,
193 gateway_ip=gateway_ip,
194 dns_nameservers=dns_nameservers,
195 host_routes=host_routes)
196 # replace network with network_id
197 if 'network' in subnet_arguments:
198 network = subnet_arguments.pop('network', None)
199 existing_network = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100200 'list_networks', tenant_id=tenant_id, name=network, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100201 if existing_network:
202 subnet_arguments['network_id'] = existing_network[network]['id']
203 if not existing_subnet:
204 subnet_arguments.update(connection_args)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100205 _neutron_module_call('create_subnet', tenant_id=tenant_id, **subnet_arguments)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100206 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100207 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100208 if existing_subnet:
209 return _created(name, 'subnet', existing_subnet[name])
210 return _update_failed(name, 'subnet')
211 # change from internal representation
212 existing_subnet = existing_subnet[name]
213 # create differential
214 LOG.error('existing ' + str(existing_subnet))
215 LOG.error('new ' + str(subnet_arguments))
216 diff = dict((key, value) for key, value in subnet_arguments.iteritems()
217 if existing_subnet.get(key, None) != value)
218 if diff:
219 # update the changes
220 subnet_arguments = diff.copy()
221 subnet_arguments.update(connection_args)
222 try:
223 LOG.debug('updating subnet {0} with changes {1}'.format(
224 name, str(diff)))
225 _neutron_module_call('update_subnet',
226 existing_subnet['id'],
227 **subnet_arguments)
228 changes_dict = _created(name, 'subnet', diff)
229 changes_dict['comment'] = '{1} {0} updated'.format(name, 'subnet')
230 return changes_dict
231 except:
232 LOG.exception('Could not update subnet {0}'.format(name))
233 return _update_failed(name, 'subnet')
234 return _no_change(name, 'subnet')
235
236
237@_test_call
238def subnet_absent(name, profile=None):
239 connection_args = _auth(profile)
240 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100241 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100242 if existing_subnet:
243 _neutron_module_call(
244 'delete_subnet', existing_subnet[name]['id'], **connection_args)
245 if _neutron_module_call('list_subnets', name=name, **connection_args):
246 return _delete_failed(name, 'subnet')
247 return _deleted(name, 'subnet', existing_subnet[name])
248 return _absent(name, 'subnet')
249 return _absent(name, 'network')
250
251
252@_test_call
253def router_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100254 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100255 gateway_network=None,
256 interfaces=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100257 admin_state_up=True,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100258 profile=None):
259 '''
260 Ensure that the neutron router is present with the specified properties.
261 name
262 The name of the subnet to manage
263 gateway_network
264 The network that would be the router's default gateway
265 interfaces
266 list of subnets the router attaches to
267 '''
268 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100269 tenant_name = tenant
270 try:
271 tenant_id = __salt__['keystone.tenant_get'](
272 name=tenant_name, **connection_args)[tenant_name]['id']
273 except:
274 tenant_id = None
275 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
276 connection_args['connection_user']))
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100277 existing_router = _neutron_module_call(
278 'list_routers', name=name, **connection_args)
279 if not existing_router:
Jiri Broulik5368cc52017-02-08 18:53:59 +0100280 _neutron_module_call('create_router', name=name, tenant_id=tenant_id, admin_state_up=admin_state_up, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100281 created_router = _neutron_module_call(
282 'list_routers', name=name, **connection_args)
283 if created_router:
284 router_id = created_router[name]['id']
285 network = _neutron_module_call(
286 'list_networks', name=gateway_network, **connection_args)
287 gateway_network_id = network[gateway_network]['id']
288 _neutron_module_call('router_gateway_set',
289 router_id=router_id,
290 external_gateway=gateway_network_id,
291 **connection_args)
292 for interface in interfaces:
293 subnet = _neutron_module_call(
294 'list_subnets', name=interface, **connection_args)
295 subnet_id = subnet[interface]['id']
296 _neutron_module_call('router_add_interface',
297 router_id=router_id,
298 subnet_id=subnet_id,
299 **connection_args)
300 return _created(name,
301 'router',
302 _neutron_module_call('list_routers',
303 name=name,
304 **connection_args))
305 return _create_failed(name, 'router')
306
307 router_id = existing_router[name]['id']
308 existing_router = existing_router[name]
309 diff = {}
Jiri Broulik5368cc52017-02-08 18:53:59 +0100310 if ( admin_state_up == True or admin_state_up == False ) and existing_router['admin_state_up'] != admin_state_up:
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100311 diff.update({'admin_state_up': admin_state_up})
312 if gateway_network:
313 network = _neutron_module_call(
314 'list_networks', name=gateway_network, **connection_args)
315 gateway_network_id = network[gateway_network]['id']
316 if not existing_router['external_gateway_info'] and not existing_router['external_gateway_info'] == None:
317 if existing_router['external_gateway_info']['network_id'] != gateway_network_id:
318 diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
Jiri Broulik5368cc52017-02-08 18:53:59 +0100319 elif not existing_router['external_gateway_info'] == None:
320 if not 'network_id' in existing_router['external_gateway_info'] or existing_router['external_gateway_info']['network_id'] != gateway_network_id:
321 diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100322 if diff:
323 # update the changes
324 router_args = diff.copy()
325 router_args.update(connection_args)
326 try:
327 _neutron_module_call('update_router', existing_router['id'], **router_args)
328 changes_dict = _created(name, 'router', diff)
329 changes_dict['comment'] = 'Router {0} updated'.format(name)
330 return changes_dict
331 except:
332 LOG.exception('Router {0} could not be updated'.format(name))
333 return _update_failed(name, 'router')
334 return _no_change(name, 'router')
335
Jiri Broulikde2e2902017-02-13 15:03:47 +0100336
337def floatingip_present(name=None,
338 tenant_name=None,
339 subnet=None,
340 tenant=None,
341 network=None,
342 port_id=None,
343 fip_exists=False,
344 profile=None):
345 '''
346 Ensure that the floating ip address is present for an instance
347 '''
348 instance_id = __salt__['novang.server_get'](name=name, tenant_name=tenant_name, profile=profile)
349 subnet_name = subnet
350 connection_args = _auth(profile)
351 existing_subnet = _neutron_module_call(
352 'list_subnets', name=subnet_name, **connection_args)
353 subnet_id = existing_subnet[subnet_name]['id']
354
355 ret = {}
356 existing_ports = _neutron_module_call(
357 'list_ports', **connection_args)
358 existing_floatingips = _neutron_module_call(
359 'list_floatingips', **connection_args)
360
361 tenant = __salt__['keystone.tenant_get'](name=tenant_name, profile=profile, **connection_args)
362 tenant_id = tenant[tenant_name]['id']
363 existing_network = _neutron_module_call(
364 'list_networks', name=network, **connection_args)
365 floating_network_id = existing_network[network]['id']
366
367 for key, value in existing_ports.iteritems():
368 try:
369 if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
370 port_id=value['id']
371 except:
372 pass
373 for key, value in existing_floatingips.iteritems():
374 try:
375 if value['floating_network_id'] == floating_network_id and value['port_id'] == port_id and value['tenant_id'] == tenant_id:
376 fip_exists = True
377 break
378 except:
379 pass
380
381 if fip_exists == False:
382 for key, value in existing_ports.iteritems():
383 try:
384 if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
385 ret = _neutron_module_call('create_floatingip', floating_network_id=floating_network_id, port_id=value['id'], tenant_id=tenant_id, **connection_args)
386 except:
387 pass
388 return _created('port', 'floatingip', ret)
389 else:
390 return _no_change('for instance {0}'.format(name), 'floatingip')
391
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100392def security_group_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100393 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100394 description=None,
395 rules=[],
396 profile=None):
397 '''
398 Ensure that the security group is present with the specified properties.
399 name
400 The name of the security group
401 description
402 The description of the security group
403 rules
404 list of rules to be added to the given security group
405 '''
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100406 # If the user is an admin, he's able to see the security groups from
407 # other tenants. In this case, we'll use the tenant id to get an existing
408 # security group.
409 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100410 tenant_name = tenant
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100411 try:
412 tenant_id = __salt__['keystone.tenant_get'](
413 name=tenant_name, **connection_args)[tenant_name]['id']
414 except:
415 tenant_id = None
416 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
417 connection_args['connection_user']))
418 if tenant_id:
419 security_group = _neutron_module_call(
420 'list_security_groups', name=name, tenant_id=tenant_id,
421 **connection_args)
422 else:
423 security_group = _neutron_module_call(
424 'list_security_groups', name=name, **connection_args)
425
426 if not security_group:
427 # Create the security group as it doesn't exist already.
428 security_group_id = _neutron_module_call('create_security_group',
429 name=name,
430 description=description,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100431 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100432 **connection_args)
433 else:
434 security_group_id = security_group[name]['id']
435
436 # Set the missing rules attributes (in case the user didn't specify them
437 # in pillar) to some default values.
438 rules_attributes_defaults = {
439 'direction': 'ingress',
440 'ethertype': 'IPv4',
441 'protocol': 'TCP',
442 'port_range_min': None,
443 'port_range_max': None,
444 'remote_ip_prefix': None
445 }
446 for rule in rules:
447 for attribute in rules_attributes_defaults.keys():
448 if not rule.has_key(attribute):
449 rule[attribute] = rules_attributes_defaults[attribute]
450
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100451 # Remove all the duplicates rules given by the user in pillar.
452 unique_rules = []
453 for rule in rules:
454 if rule not in unique_rules:
455 unique_rules.append(rule)
456
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100457 # Get the existing security group rules.
458 existing_rules = _neutron_module_call(
459 'list_security_groups',
460 id=security_group_id,
461 **connection_args)[name]['security_group_rules']
462
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100463 new_rules = {}
464 for rule in unique_rules:
465 rule_found = False
466 for existing_rule in existing_rules:
467 attributes_match = True
468 # Compare the attributes of the existing security group rule with
469 # the attributes of the rule that we want to add.
470 for attribute in rules_attributes_defaults.keys():
471 existing_attribute = '' if not existing_rule[attribute] \
472 else str(existing_rule[attribute]).lower()
473 attribute = '' if not rule[attribute] \
474 else str(rule[attribute]).lower()
475 if existing_attribute != attribute:
476 attributes_match = False
477 break
478 if attributes_match:
479 rule_found = True
480 break
481 if rule_found:
482 # Skip adding the rule as it already exists.
483 continue
484 rule_index = len(new_rules) + 1
485 new_rules.update({'Rule {0}'.format(rule_index): rule})
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100486 _neutron_module_call('create_security_group_rule',
487 security_group_id=security_group_id,
488 direction=rule['direction'],
489 ethertype=rule['ethertype'],
490 protocol=rule['protocol'],
491 port_range_min=rule['port_range_min'],
492 port_range_max=rule['port_range_max'],
493 remote_ip_prefix=rule['remote_ip_prefix'],
Jiri Broulik5368cc52017-02-08 18:53:59 +0100494 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100495 **connection_args)
496
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100497 if not security_group:
498 # The security group didn't exist. It was created and specified
499 # rules were added to it.
500 security_group = _neutron_module_call('list_security_groups',
501 id=security_group_id,
502 **connection_args)[name]
503 return _created(name, 'security_group', security_group)
504 if len(new_rules) == 0:
505 # Security group already exists and specified rules are already
506 # present.
507 return _no_change(name, 'security_group')
508 # Security group already exists, but the specified rules were added to it.
509 return _updated(name, 'security_group', {'New Rules': new_rules})
510
511def _created(name, resource, resource_definition):
512 changes_dict = {'name': name,
513 'changes': resource_definition,
514 'result': True,
515 'comment': '{0} {1} created'.format(resource, name)}
516 return changes_dict
517
518def _updated(name, resource, resource_definition):
519 changes_dict = {'name': name,
520 'changes': resource_definition,
521 'result': True,
522 'comment': '{0} {1} updated'.format(resource, name)}
523 return changes_dict
524
525def _no_change(name, resource, test=False):
526 changes_dict = {'name': name,
527 'changes': {},
528 'result': True}
529 if test:
530 changes_dict['comment'] = \
531 '{0} {1} will be {2}'.format(resource, name, test)
532 else:
533 changes_dict['comment'] = \
534 '{0} {1} is in correct state'.format(resource, name)
535 return changes_dict
536
537
538def _deleted(name, resource, resource_definition):
539 changes_dict = {'name': name,
540 'changes': {},
541 'comment': '{0} {1} removed'.format(resource, name),
542 'result': True}
543 return changes_dict
544
545
546def _absent(name, resource):
547 changes_dict = {'name': name,
548 'changes': {},
549 'comment': '{0} {1} not present'.format(resource, name),
550 'result': True}
551 return changes_dict
552
553
554def _delete_failed(name, resource):
555 changes_dict = {'name': name,
556 'changes': {},
557 'comment': '{0} {1} failed to delete'.format(resource,
558 name),
559 'result': False}
560 return changes_dict
561
562def _create_failed(name, resource):
563 changes_dict = {'name': name,
564 'changes': {},
565 'comment': '{0} {1} failed to create'.format(resource,
566 name),
567 'result': False}
568 return changes_dict
569
570def _update_failed(name, resource):
571 changes_dict = {'name': name,
572 'changes': {},
573 'comment': '{0} {1} failed to update'.format(resource,
574 name),
575 'result': False}
576 return changes_dict
577
578
579def _get_non_null_args(**kwargs):
580 '''
581 Return those kwargs which are not null
582 '''
583 return dict((key, value,) for key, value in kwargs.iteritems()
584 if value is not None)