blob: df0736dae388c323be9e74fd5c707ef8a223c0b8 [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']))
Jiri Broulikf1b3aa42017-01-26 17:08:44 +010089 existing_network = _neutron_module_call(
90 'list_networks', name=name, **connection_args)
91 network_arguments = _get_non_null_args(
92 name=name,
93 provider_network_type=provider_network_type,
94 provider_physical_network=provider_physical_network,
95 router_external=router_external,
96 admin_state_up=admin_state_up,
97 shared=shared,
Jiri Broulik5368cc52017-02-08 18:53:59 +010098 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +010099 provider_segmentation_id=provider_segmentation_id)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100100
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100101 if not existing_network:
102 network_arguments.update(connection_args)
103 _neutron_module_call('create_network', **network_arguments)
104 existing_network = _neutron_module_call(
105 'list_networks', name=name, **connection_args)
106 if existing_network:
107 return _created(name, 'network', existing_network[name])
108 return _update_failed(name, 'network')
Jiri Broulik5368cc52017-02-08 18:53:59 +0100109
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100110 LOG.info('CONNECTION STRINGS' + str(connection_args))
111 LOG.info('existing ' + str(existing_network))
112 LOG.info('new ' + str(network_arguments))
113 existing_network = dict((key.replace(':', '_', 1), value)
114 for key, value in
115 existing_network[name].iteritems())
116 # generate differential
117 diff = dict((key, value) for key, value in network_arguments.iteritems()
118 if existing_network.get(key, None) != value)
119 if diff:
120 # update the changes
121 network_arguments = diff.copy()
122 network_arguments.update(connection_args)
123 try:
124 LOG.debug('updating network {0} with changes {1}'.format(
125 name, str(diff)))
126 _neutron_module_call('update_network',
127 existing_network['id'],
128 **network_arguments)
129 changes_dict = _created(name, 'network', diff)
130 changes_dict['comment'] = '{1} {0} updated'.format(name, 'network')
131 return changes_dict
132 except:
133 LOG.exception('Could not update network {0}'.format(name))
134 return _update_failed(name, 'network')
135 return _no_change(name, 'network')
136
137
138@_test_call
139def network_absent(name, profile=None):
140 connection_args = _auth(profile)
141 existing_network = _neutron_module_call(
142 'list_networks', name=name, **connection_args)
143 if existing_network:
144 _neutron_module_call(
145 'delete_network', existing_network[name]['id'], **connection_args)
146 if _neutron_module_call('list_networks', name=name, **connection_args):
147 return _delete_failed(name, 'network')
148 return _deleted(name, 'network', existing_network[name])
149 return _absent(name, 'network')
150
151
152@_test_call
153def subnet_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100154 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100155 network=None,
156 cidr=None,
157 ip_version=4,
158 enable_dhcp=True,
159 allocation_pools=None,
160 gateway_ip=None,
161 dns_nameservers=None,
162 host_routes=None,
163 profile=None):
164 '''
165 Ensure that the neutron subnet is present with the specified properties.
166 name
167 The name of the subnet to manage
168 '''
169 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100170 tenant_name = tenant
171 try:
172 tenant_id = __salt__['keystone.tenant_get'](
173 name=tenant_name, **connection_args)[tenant_name]['id']
174 except:
175 tenant_id = None
176 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
177 connection_args['connection_user']))
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100178 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100179 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100180 subnet_arguments = _get_non_null_args(
181 name=name,
182 network=network,
183 cidr=cidr,
184 ip_version=ip_version,
185 enable_dhcp=enable_dhcp,
186 allocation_pools=allocation_pools,
187 gateway_ip=gateway_ip,
188 dns_nameservers=dns_nameservers,
189 host_routes=host_routes)
190 # replace network with network_id
191 if 'network' in subnet_arguments:
192 network = subnet_arguments.pop('network', None)
193 existing_network = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100194 'list_networks', tenant_id=tenant_id, name=network, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100195 if existing_network:
196 subnet_arguments['network_id'] = existing_network[network]['id']
197 if not existing_subnet:
198 subnet_arguments.update(connection_args)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100199 _neutron_module_call('create_subnet', tenant_id=tenant_id, **subnet_arguments)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100200 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100201 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100202 if existing_subnet:
203 return _created(name, 'subnet', existing_subnet[name])
204 return _update_failed(name, 'subnet')
205 # change from internal representation
206 existing_subnet = existing_subnet[name]
207 # create differential
208 LOG.error('existing ' + str(existing_subnet))
209 LOG.error('new ' + str(subnet_arguments))
210 diff = dict((key, value) for key, value in subnet_arguments.iteritems()
211 if existing_subnet.get(key, None) != value)
212 if diff:
213 # update the changes
214 subnet_arguments = diff.copy()
215 subnet_arguments.update(connection_args)
216 try:
217 LOG.debug('updating subnet {0} with changes {1}'.format(
218 name, str(diff)))
219 _neutron_module_call('update_subnet',
220 existing_subnet['id'],
221 **subnet_arguments)
222 changes_dict = _created(name, 'subnet', diff)
223 changes_dict['comment'] = '{1} {0} updated'.format(name, 'subnet')
224 return changes_dict
225 except:
226 LOG.exception('Could not update subnet {0}'.format(name))
227 return _update_failed(name, 'subnet')
228 return _no_change(name, 'subnet')
229
230
231@_test_call
232def subnet_absent(name, profile=None):
233 connection_args = _auth(profile)
234 existing_subnet = _neutron_module_call(
Jiri Broulik5368cc52017-02-08 18:53:59 +0100235 'list_subnets', tenant_id=tenant_id, name=name, **connection_args)
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100236 if existing_subnet:
237 _neutron_module_call(
238 'delete_subnet', existing_subnet[name]['id'], **connection_args)
239 if _neutron_module_call('list_subnets', name=name, **connection_args):
240 return _delete_failed(name, 'subnet')
241 return _deleted(name, 'subnet', existing_subnet[name])
242 return _absent(name, 'subnet')
243 return _absent(name, 'network')
244
245
246@_test_call
247def router_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100248 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100249 gateway_network=None,
250 interfaces=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100251 admin_state_up=True,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100252 profile=None):
253 '''
254 Ensure that the neutron router is present with the specified properties.
255 name
256 The name of the subnet to manage
257 gateway_network
258 The network that would be the router's default gateway
259 interfaces
260 list of subnets the router attaches to
261 '''
262 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100263 tenant_name = tenant
264 try:
265 tenant_id = __salt__['keystone.tenant_get'](
266 name=tenant_name, **connection_args)[tenant_name]['id']
267 except:
268 tenant_id = None
269 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
270 connection_args['connection_user']))
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100271 existing_router = _neutron_module_call(
272 'list_routers', name=name, **connection_args)
273 if not existing_router:
Jiri Broulik5368cc52017-02-08 18:53:59 +0100274 _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 +0100275 created_router = _neutron_module_call(
276 'list_routers', name=name, **connection_args)
277 if created_router:
278 router_id = created_router[name]['id']
279 network = _neutron_module_call(
280 'list_networks', name=gateway_network, **connection_args)
281 gateway_network_id = network[gateway_network]['id']
282 _neutron_module_call('router_gateway_set',
283 router_id=router_id,
284 external_gateway=gateway_network_id,
285 **connection_args)
286 for interface in interfaces:
287 subnet = _neutron_module_call(
288 'list_subnets', name=interface, **connection_args)
289 subnet_id = subnet[interface]['id']
290 _neutron_module_call('router_add_interface',
291 router_id=router_id,
292 subnet_id=subnet_id,
293 **connection_args)
294 return _created(name,
295 'router',
296 _neutron_module_call('list_routers',
297 name=name,
298 **connection_args))
299 return _create_failed(name, 'router')
300
301 router_id = existing_router[name]['id']
302 existing_router = existing_router[name]
303 diff = {}
Jiri Broulik5368cc52017-02-08 18:53:59 +0100304 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 +0100305 diff.update({'admin_state_up': admin_state_up})
306 if gateway_network:
307 network = _neutron_module_call(
308 'list_networks', name=gateway_network, **connection_args)
309 gateway_network_id = network[gateway_network]['id']
310 if not existing_router['external_gateway_info'] and not existing_router['external_gateway_info'] == None:
311 if existing_router['external_gateway_info']['network_id'] != gateway_network_id:
312 diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
Jiri Broulik5368cc52017-02-08 18:53:59 +0100313 elif not existing_router['external_gateway_info'] == None:
314 if not 'network_id' in existing_router['external_gateway_info'] or existing_router['external_gateway_info']['network_id'] != gateway_network_id:
315 diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100316 if diff:
317 # update the changes
318 router_args = diff.copy()
319 router_args.update(connection_args)
320 try:
321 _neutron_module_call('update_router', existing_router['id'], **router_args)
322 changes_dict = _created(name, 'router', diff)
323 changes_dict['comment'] = 'Router {0} updated'.format(name)
324 return changes_dict
325 except:
326 LOG.exception('Router {0} could not be updated'.format(name))
327 return _update_failed(name, 'router')
328 return _no_change(name, 'router')
329
Jiri Broulikde2e2902017-02-13 15:03:47 +0100330
331def floatingip_present(name=None,
332 tenant_name=None,
333 subnet=None,
334 tenant=None,
335 network=None,
336 port_id=None,
337 fip_exists=False,
338 profile=None):
339 '''
340 Ensure that the floating ip address is present for an instance
341 '''
342 instance_id = __salt__['novang.server_get'](name=name, tenant_name=tenant_name, profile=profile)
343 subnet_name = subnet
344 connection_args = _auth(profile)
345 existing_subnet = _neutron_module_call(
346 'list_subnets', name=subnet_name, **connection_args)
347 subnet_id = existing_subnet[subnet_name]['id']
348
349 ret = {}
350 existing_ports = _neutron_module_call(
351 'list_ports', **connection_args)
352 existing_floatingips = _neutron_module_call(
353 'list_floatingips', **connection_args)
354
355 tenant = __salt__['keystone.tenant_get'](name=tenant_name, profile=profile, **connection_args)
356 tenant_id = tenant[tenant_name]['id']
357 existing_network = _neutron_module_call(
358 'list_networks', name=network, **connection_args)
359 floating_network_id = existing_network[network]['id']
360
361 for key, value in existing_ports.iteritems():
362 try:
363 if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
364 port_id=value['id']
365 except:
366 pass
367 for key, value in existing_floatingips.iteritems():
368 try:
369 if value['floating_network_id'] == floating_network_id and value['port_id'] == port_id and value['tenant_id'] == tenant_id:
370 fip_exists = True
371 break
372 except:
373 pass
374
375 if fip_exists == False:
376 for key, value in existing_ports.iteritems():
377 try:
378 if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
379 ret = _neutron_module_call('create_floatingip', floating_network_id=floating_network_id, port_id=value['id'], tenant_id=tenant_id, **connection_args)
380 except:
381 pass
382 return _created('port', 'floatingip', ret)
383 else:
384 return _no_change('for instance {0}'.format(name), 'floatingip')
385
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100386def security_group_present(name=None,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100387 tenant=None,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100388 description=None,
389 rules=[],
390 profile=None):
391 '''
392 Ensure that the security group is present with the specified properties.
393 name
394 The name of the security group
395 description
396 The description of the security group
397 rules
398 list of rules to be added to the given security group
399 '''
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100400 # If the user is an admin, he's able to see the security groups from
401 # other tenants. In this case, we'll use the tenant id to get an existing
402 # security group.
403 connection_args = _auth(profile)
Jiri Broulik5368cc52017-02-08 18:53:59 +0100404 tenant_name = tenant
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100405 try:
406 tenant_id = __salt__['keystone.tenant_get'](
407 name=tenant_name, **connection_args)[tenant_name]['id']
408 except:
409 tenant_id = None
410 LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
411 connection_args['connection_user']))
412 if tenant_id:
413 security_group = _neutron_module_call(
414 'list_security_groups', name=name, tenant_id=tenant_id,
415 **connection_args)
416 else:
417 security_group = _neutron_module_call(
418 'list_security_groups', name=name, **connection_args)
419
420 if not security_group:
421 # Create the security group as it doesn't exist already.
422 security_group_id = _neutron_module_call('create_security_group',
423 name=name,
424 description=description,
Jiri Broulik5368cc52017-02-08 18:53:59 +0100425 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100426 **connection_args)
427 else:
428 security_group_id = security_group[name]['id']
429
430 # Set the missing rules attributes (in case the user didn't specify them
431 # in pillar) to some default values.
432 rules_attributes_defaults = {
433 'direction': 'ingress',
434 'ethertype': 'IPv4',
435 'protocol': 'TCP',
436 'port_range_min': None,
437 'port_range_max': None,
438 'remote_ip_prefix': None
439 }
440 for rule in rules:
441 for attribute in rules_attributes_defaults.keys():
442 if not rule.has_key(attribute):
443 rule[attribute] = rules_attributes_defaults[attribute]
444
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100445 # Remove all the duplicates rules given by the user in pillar.
446 unique_rules = []
447 for rule in rules:
448 if rule not in unique_rules:
449 unique_rules.append(rule)
450
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100451 # Get the existing security group rules.
452 existing_rules = _neutron_module_call(
453 'list_security_groups',
454 id=security_group_id,
455 **connection_args)[name]['security_group_rules']
456
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100457 new_rules = {}
458 for rule in unique_rules:
459 rule_found = False
460 for existing_rule in existing_rules:
461 attributes_match = True
462 # Compare the attributes of the existing security group rule with
463 # the attributes of the rule that we want to add.
464 for attribute in rules_attributes_defaults.keys():
465 existing_attribute = '' if not existing_rule[attribute] \
466 else str(existing_rule[attribute]).lower()
467 attribute = '' if not rule[attribute] \
468 else str(rule[attribute]).lower()
469 if existing_attribute != attribute:
470 attributes_match = False
471 break
472 if attributes_match:
473 rule_found = True
474 break
475 if rule_found:
476 # Skip adding the rule as it already exists.
477 continue
478 rule_index = len(new_rules) + 1
479 new_rules.update({'Rule {0}'.format(rule_index): rule})
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100480 _neutron_module_call('create_security_group_rule',
481 security_group_id=security_group_id,
482 direction=rule['direction'],
483 ethertype=rule['ethertype'],
484 protocol=rule['protocol'],
485 port_range_min=rule['port_range_min'],
486 port_range_max=rule['port_range_max'],
487 remote_ip_prefix=rule['remote_ip_prefix'],
Jiri Broulik5368cc52017-02-08 18:53:59 +0100488 tenant_id=tenant_id,
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100489 **connection_args)
490
Jiri Broulikf1b3aa42017-01-26 17:08:44 +0100491 if not security_group:
492 # The security group didn't exist. It was created and specified
493 # rules were added to it.
494 security_group = _neutron_module_call('list_security_groups',
495 id=security_group_id,
496 **connection_args)[name]
497 return _created(name, 'security_group', security_group)
498 if len(new_rules) == 0:
499 # Security group already exists and specified rules are already
500 # present.
501 return _no_change(name, 'security_group')
502 # Security group already exists, but the specified rules were added to it.
503 return _updated(name, 'security_group', {'New Rules': new_rules})
504
505def _created(name, resource, resource_definition):
506 changes_dict = {'name': name,
507 'changes': resource_definition,
508 'result': True,
509 'comment': '{0} {1} created'.format(resource, name)}
510 return changes_dict
511
512def _updated(name, resource, resource_definition):
513 changes_dict = {'name': name,
514 'changes': resource_definition,
515 'result': True,
516 'comment': '{0} {1} updated'.format(resource, name)}
517 return changes_dict
518
519def _no_change(name, resource, test=False):
520 changes_dict = {'name': name,
521 'changes': {},
522 'result': True}
523 if test:
524 changes_dict['comment'] = \
525 '{0} {1} will be {2}'.format(resource, name, test)
526 else:
527 changes_dict['comment'] = \
528 '{0} {1} is in correct state'.format(resource, name)
529 return changes_dict
530
531
532def _deleted(name, resource, resource_definition):
533 changes_dict = {'name': name,
534 'changes': {},
535 'comment': '{0} {1} removed'.format(resource, name),
536 'result': True}
537 return changes_dict
538
539
540def _absent(name, resource):
541 changes_dict = {'name': name,
542 'changes': {},
543 'comment': '{0} {1} not present'.format(resource, name),
544 'result': True}
545 return changes_dict
546
547
548def _delete_failed(name, resource):
549 changes_dict = {'name': name,
550 'changes': {},
551 'comment': '{0} {1} failed to delete'.format(resource,
552 name),
553 'result': False}
554 return changes_dict
555
556def _create_failed(name, resource):
557 changes_dict = {'name': name,
558 'changes': {},
559 'comment': '{0} {1} failed to create'.format(resource,
560 name),
561 'result': False}
562 return changes_dict
563
564def _update_failed(name, resource):
565 changes_dict = {'name': name,
566 'changes': {},
567 'comment': '{0} {1} failed to update'.format(resource,
568 name),
569 'result': False}
570 return changes_dict
571
572
573def _get_non_null_args(**kwargs):
574 '''
575 Return those kwargs which are not null
576 '''
577 return dict((key, value,) for key, value in kwargs.iteritems()
578 if value is not None)