blob: 85e9c3c8f6fca05dd7e699b39f41be5e592c5f17 [file] [log] [blame]
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +03001import logging
Vasyl Saienkoba420732018-09-07 10:19:32 +00002import random
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +03003
4log = logging.getLogger(__name__)
5
6
7def __virtual__():
8 return 'neutronv2' if 'neutronv2.subnet_list' in __salt__ else False
9
10
11def _neutronv2_call(fname, *args, **kwargs):
Denis V. Meltsaykin984f6162020-04-14 19:31:19 +020012 if __opts__.get('test') and not any(x for x in ['_get_', '_list'] if x in fname):
13 return {'changes': 'neutronv2 state {} to be called with {} {}'.format(
14 fname, args, kwargs), 'result': None}
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030015 return __salt__['neutronv2.{}'.format(fname)](*args, **kwargs)
16
17
Ann Taraday8204f722018-12-12 16:38:57 +040018def _try_get_resource(resource, name, cloud_name):
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030019 try:
20 method_name = '{}_get_details'.format(resource)
21 exact_resource = _neutronv2_call(
Oleksiy Petrenko5bfb8bc2018-08-23 15:08:17 +030022 method_name, name, cloud_name=cloud_name
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030023 )[resource]
24 except Exception as e:
25 if 'ResourceNotFound' in repr(e):
Ann Taraday8204f722018-12-12 16:38:57 +040026 return None
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030027 else:
28 raise
Ann Taraday8204f722018-12-12 16:38:57 +040029 return exact_resource
30
31def _resource_present(resource, resource_name, changeable_params, cloud_name,
32 **kwargs):
33 exact_resource = None
34 try:
35 exact_resource = _try_get_resource(resource, resource_name, cloud_name)
36 if exact_resource is None:
37 # in case of rename - check if resource was already renamed
38 if kwargs.get('name') is not None and kwargs.get(
39 'name') != resource_name:
40 exact_resource = _try_get_resource(resource,
41 kwargs.get('name'),
42 cloud_name)
43 except Exception as e:
44 if 'MultipleResourcesFound' in repr(e):
45 return _failed('find', resource_name, resource)
46 else:
47 raise
48 if exact_resource is None:
49 try:
50 method_name = '{}_create'.format(resource)
51 exact_resource_name = kwargs.pop('name', resource_name)
52 resp = _neutronv2_call(
53 method_name, name=exact_resource_name,
54 cloud_name=cloud_name, **kwargs)
55 except Exception as e:
56 log.exception('Neutron {0} create failed with {1}'.
57 format(resource, e))
58 return _failed('create', exact_resource_name, resource)
59 return _succeeded('create', exact_resource_name, resource, resp)
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030060
61 to_update = {}
62 for key in kwargs:
63 if key in changeable_params and (key not in exact_resource
64 or kwargs[key] != exact_resource[key]):
65 to_update[key] = kwargs[key]
Ann Taraday8204f722018-12-12 16:38:57 +040066 if to_update:
67 try:
68 method_name = '{}_update'.format(resource)
69 resp = _neutronv2_call(
70 method_name, resource_name, cloud_name=cloud_name, **to_update
71 )
72 except Exception as e:
73 log.exception('Neutron {0} update failed with {1}'.format(resource, e))
74 return _failed('update', resource_name, resource)
75 return _succeeded('update', resource_name, resource, resp)
76 else:
77 return _succeeded('no_changes', resource_name, resource)
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030078
79
80def _resource_absent(resource, name, cloud_name):
81 try:
82 method_name = '{}_get_details'.format(resource)
83 _neutronv2_call(
Oleksiy Petrenko5bfb8bc2018-08-23 15:08:17 +030084 method_name, name, cloud_name=cloud_name
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030085 )[resource]
86 except Exception as e:
87 if 'ResourceNotFound' in repr(e):
88 return _succeeded('absent', name, resource)
89 if 'MultipleResourcesFound' in repr(e):
90 return _failed('find', name, resource)
91 try:
92 method_name = '{}_delete'.format(resource)
93 _neutronv2_call(
Oleksiy Petrenko5bfb8bc2018-08-23 15:08:17 +030094 method_name, name, cloud_name=cloud_name
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +030095 )
96 except Exception as e:
97 log.error('Neutron delete {0} failed with {1}'.format(resource, e))
98 return _failed('delete', name, resource)
99 return _succeeded('delete', name, resource)
100
101
102def network_present(name, cloud_name, **kwargs):
103 changeable = (
104 'admin_state_up', 'dns_domain', 'mtu', 'port_security_enabled',
105 'provider:network_type', 'provider:physical_network',
106 'provider:segmentation_id', 'qos_policy_id', 'router:external',
107 'segments', 'shared', 'description', 'is_default'
108 )
109
110 return _resource_present('network', name, changeable, cloud_name, **kwargs)
111
112
113def network_absent(name, cloud_name):
114 return _resource_absent('network', name, cloud_name)
115
116
117def subnet_present(name, cloud_name, network_id, ip_version, cidr, **kwargs):
118 kwargs.update({'network_id': network_id,
119 'ip_version': ip_version,
120 'cidr': cidr})
121 changeable = (
122 'name', 'enable_dhcp', 'dns_nameservers', 'allocation_pools',
123 'host_routes', 'gateway_ip', 'description', 'service_types',
124 )
125
126 return _resource_present('subnet', name, changeable, cloud_name, **kwargs)
127
128
129def subnet_absent(name, cloud_name):
130 return _resource_absent('subnet', name, cloud_name)
131
132
133def subnetpool_present(name, cloud_name, prefixes, **kwargs):
134 kwargs.update({'prefixes': prefixes})
135 changeable = (
136 'default_quota', 'min_prefixlen', 'address_scope_id',
137 'default_prefixlen', 'description'
138 )
139
140 return _resource_present('subnetpool', name, changeable, cloud_name, **kwargs)
141
142
143def subnetpool_absent(name, cloud_name):
144 return _resource_absent('subnetpool', name, cloud_name)
145
146
Oleksiy Petrenko5bfb8bc2018-08-23 15:08:17 +0300147def agent_present(name, agent_type, cloud_name, **kwargs):
148 """
149 :param name: agent host name
150 :param agent_type: type of the agent. i.e. 'L3 agent' or 'DHCP agent'
151 :param kwargs:
152 :param description: agent description
153 :param admin_state_up: administrative state of the agent
154 """
155 agents = _neutronv2_call(
156 'agent_list', host=name, agent_type=agent_type,
157 cloud_name=cloud_name)['agents']
158 # Make sure we have one and only one such agent
159 if len(agents) == 1:
160 agent = agents[0]
161 to_update = {}
162 for key in kwargs:
163 if kwargs[key] != agent[key]:
164 to_update[key] = kwargs[key]
165 if to_update:
166 try:
167 _neutronv2_call('agent_update', agent_id=agent['id'],
168 cloud_name=cloud_name, **kwargs)
169 except Exception:
170 return _failed('update', name, 'agent')
171 return _succeeded('update', name, 'agent')
172 return _succeeded('no_changes', name, 'agent')
173 else:
174 return _failed('find', name, 'agent')
175
176
Vasyl Saienko2893de32018-08-15 13:39:17 +0000177def agents_disabled(name, cloud_name, **kwargs):
178 """
179 :param name: agent host name
180 :param kwargs:
181 :param description: agent description
182 :param admin_state_up: administrative state of the agent
183 """
184 agents = _neutronv2_call(
185 'agent_list', host=name, cloud_name=cloud_name)['agents']
186
187 changes = {}
188 for agent in agents:
189 if agent['admin_state_up'] == True:
190 try:
191 changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
192 cloud_name=cloud_name, admin_state_up=False)
193 except Exception:
194 return _failed('update', name, 'agent')
195 return _succeeded('update', name, 'agent',changes)
196
197
198def agents_enabled(name, cloud_name, **kwargs):
199 """
200 :param name: agent host name
201 :param kwargs:
202 :param description: agent description
203 :param admin_state_up: administrative state of the agent
204 """
205 agents = _neutronv2_call(
206 'agent_list', host=name, cloud_name=cloud_name)['agents']
207
208 changes = {}
209 for agent in agents:
210 if agent['admin_state_up'] == False:
211 try:
212 changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
213 cloud_name=cloud_name, admin_state_up=True)
214 except Exception:
215 return _failed('update', name, 'agent')
216
217 return _succeeded('update', name, 'agent', changes)
218
219
Vasyl Saienkoba420732018-09-07 10:19:32 +0000220def l3_resources_moved(name, cloud_name, target=None):
221 """
222 Ensure l3 resources are moved to target/other nodes
223 Move non-HA (legacy and DVR) routers.
224
225 :param name: agent host to remove routers from
226 :param target: target host to move routers to
227 :param cloud_name: name of cloud from os client config
228 """
229
230 all_agents = _neutronv2_call(
231 'agent_list', agent_type='L3 agent', cloud_name=cloud_name)['agents']
232
233 current_agent_id = [x['id'] for x in all_agents if x['host'] == name][0]
234
235 if target is not None:
236 target_agents = [x['id'] for x in all_agents if x['host'] == target]
237 else:
238 target_agents = [x['id'] for x in all_agents
239 if x['host'] != name and x['alive'] and x['admin_state_up']]
240
241 if len(target_agents) == 0:
242 log.error("No candidate agents to move routers.")
243 return _failed('resources_moved', name, 'L3 agent')
244
245 routers_on_agent = _neutronv2_call(
246 'l3_agent_router_list', current_agent_id, cloud_name=cloud_name)['routers']
247
248 routers_on_agent = [x for x in routers_on_agent if x['ha'] == False]
249
250 try:
251 for router in routers_on_agent:
252 _neutronv2_call(
253 'l3_agent_router_remove', router_id=router['id'],
254 agent_id=current_agent_id, cloud_name=cloud_name)
255 _neutronv2_call(
256 'l3_agent_router_schedule', router_id=router['id'],
257 agent_id=random.choice(target_agents),
258 cloud_name=cloud_name)
259 except Exception as e:
260 log.exception("Failed to move router from {0}: {1}".format(name, e))
261 return _failed('resources_moved', name, 'L3 agent')
262
263 return _succeeded('resources_moved', name, 'L3 agent')
264
265
Ann Taraday8204f722018-12-12 16:38:57 +0400266def port_present(port_name, cloud_name, **kwargs):
267 changeable = (
268 'name', 'description', 'device_id', 'qos_policy',
269 'allowed_address_pair', 'fixed_ip',
270 'device_owner', 'admin_state_up', 'security_group', 'extra_dhcp_opt',
271 )
272
273 return _resource_present('port', port_name, changeable,
274 cloud_name, **kwargs)
275
276
Vyacheslav Struk3f529d42019-06-13 13:37:25 +0300277def rbac_get_rule_id(cloud_name, **kwargs):
278 existing_rules = _neutronv2_call('rbac_policies_list',
279 cloud_name=cloud_name)
280
281 match_condition_fields = ['action',
282 'target_tenant',
283 'object_id',
284 ]
285
286 for rule in existing_rules['rbac_policies']:
287 match = True
288 for field in match_condition_fields:
289 if rule[field] != kwargs[field]:
290 match = False
291 break
292 if match: return rule['id']
293
294
295def rbac_present(name, cloud_name, **kwargs):
296 resource = 'rbac_policies'
297 # Resolve network name to UID if needed
298 kwargs['object_id'] = __salt__['neutronv2.network_get_details'] \
299 (network_id=kwargs['object_id'],cloud_name=cloud_name)['network']['id']
300
301 if rbac_get_rule_id(cloud_name, **kwargs):
302 return _succeeded('no_changes', name, resource)
303
304 r = _neutronv2_call('{}_create'.format(resource),
305 cloud_name=cloud_name,
306 **kwargs)
307 if r:
308 return _succeeded('create', name, resource, changes=r)
309 else:
310 return _failed('create', name, kwargs)
311
312def rbac_absent(name, cloud_name, **kwargs):
313 resource = 'rbac_policies'
314 # Resolve network name to UID if needed
315 kwargs['object_id'] = __salt__['neutronv2.network_get_details'] \
316 (network_id=kwargs['object_id'],cloud_name=cloud_name)['network']['id']
317
318 rule_id = rbac_get_rule_id(cloud_name, **kwargs)
319
320 if rule_id:
321 r = _neutronv2_call('{}_delete'.format(resource),
322 cloud_name=cloud_name,
323 id=rule_id)
324 return _succeeded('delete', name, resource, changes=r)
325
326 return _succeeded('no_changes', name, resource)
327
328
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +0300329def _succeeded(op, name, resource, changes=None):
330 msg_map = {
331 'create': '{0} {1} created',
332 'delete': '{0} {1} removed',
333 'update': '{0} {1} updated',
334 'no_changes': '{0} {1} is in desired state',
Vasyl Saienkoba420732018-09-07 10:19:32 +0000335 'absent': '{0} {1} not present',
336 'resources_moved': '{1} resources were moved from {0}',
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +0300337 }
338 changes_dict = {
339 'name': name,
340 'result': True,
341 'comment': msg_map[op].format(resource, name),
342 'changes': changes or {},
343 }
344 return changes_dict
345
346
347def _failed(op, name, resource):
348 msg_map = {
349 'create': '{0} {1} failed to create',
350 'delete': '{0} {1} failed to delete',
351 'update': '{0} {1} failed to update',
Vasyl Saienkoba420732018-09-07 10:19:32 +0000352 'find': '{0} {1} found multiple {0}',
353 'resources_moved': 'failed to move {1} from {0}',
Oleksiy Petrenkocaad2032018-04-20 14:42:46 +0300354 }
355 changes_dict = {
356 'name': name,
357 'result': False,
358 'comment': msg_map[op].format(resource, name),
359 'changes': {},
360 }
361 return changes_dict