summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLogan V <logan2211@gmail.com>2019-03-16 12:41:18 -0500
committerLogan V <logan2211@gmail.com>2019-03-16 12:45:27 -0500
commit0cec8f1551a812aa3fe979e6105ab39c4803e8d2 (patch)
tree78bdd6df29216829fee2217436bbfa8703bed226
parent9493f4f845ace66060219de2dc6c0e2b93007c5a (diff)
Remove vendored os_router ansible module
The module was vendored for Pike because the ansible 2.3 module had a bug which broke idempotency and would constantly attach/deattach the router each playbook run, breaking the network. Since OSA is updated to queens, the vendored module is no longer needed as ansible 2.4 ships with the fixed module. Change-Id: I8594dda75900af43842a044de2055e22ef0d8283
Notes
Notes (review): Code-Review+2: Logan V <logan2211@gmail.com> Workflow+1: Logan V <logan2211@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Sat, 16 Mar 2019 17:47:19 +0000 Reviewed-on: https://review.openstack.org/643706 Project: openstack/limestone-ci-cloud Branch: refs/heads/master
-rw-r--r--network_bootstrap/bootstrap-neutron.yml4
-rw-r--r--network_bootstrap/library/os_router.py424
2 files changed, 0 insertions, 428 deletions
diff --git a/network_bootstrap/bootstrap-neutron.yml b/network_bootstrap/bootstrap-neutron.yml
index 9c804bd..f18798c 100644
--- a/network_bootstrap/bootstrap-neutron.yml
+++ b/network_bootstrap/bootstrap-neutron.yml
@@ -80,10 +80,6 @@
80 ipv6_address_mode: dhcpv6-stateless 80 ipv6_address_mode: dhcpv6-stateless
81 ipv6_ra_mode: dhcpv6-stateless 81 ipv6_ra_mode: dhcpv6-stateless
82 82
83 # NOTE(logan): uses vendored os_router module from stable-2.4
84 # since the os_router module in 2.3 (pike) has broken idempotency
85 # and causes needless interface attach/deattaches.
86 # remove vendored module in queens
87 - name: Create flat uplink router 83 - name: Create flat uplink router
88 os_router: 84 os_router:
89 cloud: default 85 cloud: default
diff --git a/network_bootstrap/library/os_router.py b/network_bootstrap/library/os_router.py
deleted file mode 100644
index 031866f..0000000
--- a/network_bootstrap/library/os_router.py
+++ /dev/null
@@ -1,424 +0,0 @@
1#!/usr/bin/python
2#
3# This module is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This software is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this software. If not, see <http://www.gnu.org/licenses/>.
15
16ANSIBLE_METADATA = {'metadata_version': '1.1',
17 'status': ['preview'],
18 'supported_by': 'community'}
19
20
21DOCUMENTATION = '''
22---
23module: os_router
24short_description: Create or delete routers from OpenStack
25extends_documentation_fragment: openstack
26version_added: "2.0"
27author: "David Shrewsbury (@Shrews)"
28description:
29 - Create or Delete routers from OpenStack. Although Neutron allows
30 routers to share the same name, this module enforces name uniqueness
31 to be more user friendly.
32options:
33 state:
34 description:
35 - Indicate desired state of the resource
36 choices: ['present', 'absent']
37 default: present
38 name:
39 description:
40 - Name to be give to the router
41 required: true
42 admin_state_up:
43 description:
44 - Desired admin state of the created or existing router.
45 required: false
46 default: true
47 enable_snat:
48 description:
49 - Enable Source NAT (SNAT) attribute.
50 required: false
51 default: true
52 network:
53 description:
54 - Unique name or ID of the external gateway network.
55 - required I(interfaces) or I(enable_snat) are provided.
56 required: false
57 default: None
58 project:
59 description:
60 - Unique name or ID of the project.
61 required: false
62 default: None
63 version_added: "2.2"
64 external_fixed_ips:
65 description:
66 - The IP address parameters for the external gateway network. Each
67 is a dictionary with the subnet name or ID (subnet) and the IP
68 address to assign on the subnet (ip). If no IP is specified,
69 one is automatically assigned from that subnet.
70 required: false
71 default: None
72 interfaces:
73 description:
74 - List of subnets to attach to the router internal interface.
75 required: false
76 default: None
77 availability_zone:
78 description:
79 - Ignored. Present for backwards compatibility
80 required: false
81requirements: ["shade"]
82'''
83
84EXAMPLES = '''
85# Create a simple router, not attached to a gateway or subnets.
86- os_router:
87 cloud: mycloud
88 state: present
89 name: simple_router
90
91# Create a simple router, not attached to a gateway or subnets for a given project.
92- os_router:
93 cloud: mycloud
94 state: present
95 name: simple_router
96 project: myproj
97
98# Creates a router attached to ext_network1 on an IPv4 subnet and one
99# internal subnet interface.
100- os_router:
101 cloud: mycloud
102 state: present
103 name: router1
104 network: ext_network1
105 external_fixed_ips:
106 - subnet: public-subnet
107 ip: 172.24.4.2
108 interfaces:
109 - private-subnet
110
111# Update existing router1 external gateway to include the IPv6 subnet.
112# Note that since 'interfaces' is not provided, any existing internal
113# interfaces on an existing router will be left intact.
114- os_router:
115 cloud: mycloud
116 state: present
117 name: router1
118 network: ext_network1
119 external_fixed_ips:
120 - subnet: public-subnet
121 ip: 172.24.4.2
122 - subnet: ipv6-public-subnet
123 ip: 2001:db8::3
124
125# Delete router1
126- os_router:
127 cloud: mycloud
128 state: absent
129 name: router1
130'''
131
132RETURN = '''
133router:
134 description: Dictionary describing the router.
135 returned: On success when I(state) is 'present'
136 type: complex
137 contains:
138 id:
139 description: Router ID.
140 type: string
141 sample: "474acfe5-be34-494c-b339-50f06aa143e4"
142 name:
143 description: Router name.
144 type: string
145 sample: "router1"
146 admin_state_up:
147 description: Administrative state of the router.
148 type: boolean
149 sample: true
150 status:
151 description: The router status.
152 type: string
153 sample: "ACTIVE"
154 tenant_id:
155 description: The tenant ID.
156 type: string
157 sample: "861174b82b43463c9edc5202aadc60ef"
158 external_gateway_info:
159 description: The external gateway parameters.
160 type: dictionary
161 sample: {
162 "enable_snat": true,
163 "external_fixed_ips": [
164 {
165 "ip_address": "10.6.6.99",
166 "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81"
167 }
168 ]
169 }
170 routes:
171 description: The extra routes configuration for L3 router.
172 type: list
173'''
174
175try:
176 import shade
177 HAS_SHADE = True
178except ImportError:
179 HAS_SHADE = False
180
181from distutils.version import StrictVersion
182
183ROUTER_INTERFACE_OWNERS = set([
184 'network:router_interface',
185 'network:router_interface_distributed',
186 'network:ha_router_replicated_interface'
187])
188
189
190def _router_internal_interfaces(cloud, router):
191 for port in cloud.list_router_interfaces(router, 'internal'):
192 if port['device_owner'] in ROUTER_INTERFACE_OWNERS:
193 yield port
194
195
196def _needs_update(cloud, module, router, network, internal_subnet_ids):
197 """Decide if the given router needs an update.
198 """
199 if router['admin_state_up'] != module.params['admin_state_up']:
200 return True
201 if router['external_gateway_info']:
202 if router['external_gateway_info'].get('enable_snat', True) != module.params['enable_snat']:
203 return True
204 if network:
205 if not router['external_gateway_info']:
206 return True
207 elif router['external_gateway_info']['network_id'] != network['id']:
208 return True
209
210 # check external interfaces
211 if module.params['external_fixed_ips']:
212 for new_iface in module.params['external_fixed_ips']:
213 subnet = cloud.get_subnet(new_iface['subnet'])
214 exists = False
215
216 # compare the requested interface with existing, looking for an existing match
217 for existing_iface in router['external_gateway_info']['external_fixed_ips']:
218 if existing_iface['subnet_id'] == subnet['id']:
219 if 'ip' in new_iface:
220 if existing_iface['ip_address'] == new_iface['ip']:
221 # both subnet id and ip address match
222 exists = True
223 break
224 else:
225 # only the subnet was given, so ip doesn't matter
226 exists = True
227 break
228
229 # this interface isn't present on the existing router
230 if not exists:
231 return True
232
233 # check internal interfaces
234 if module.params['interfaces']:
235 existing_subnet_ids = []
236 for port in _router_internal_interfaces(cloud, router):
237 if 'fixed_ips' in port:
238 for fixed_ip in port['fixed_ips']:
239 existing_subnet_ids.append(fixed_ip['subnet_id'])
240
241 if set(internal_subnet_ids) != set(existing_subnet_ids):
242 return True
243
244 return False
245
246
247def _system_state_change(cloud, module, router, network, internal_ids):
248 """Check if the system state would be changed."""
249 state = module.params['state']
250 if state == 'absent' and router:
251 return True
252 if state == 'present':
253 if not router:
254 return True
255 return _needs_update(cloud, module, router, network, internal_ids)
256 return False
257
258
259def _build_kwargs(cloud, module, router, network):
260 kwargs = {
261 'admin_state_up': module.params['admin_state_up'],
262 }
263
264 if router:
265 kwargs['name_or_id'] = router['id']
266 else:
267 kwargs['name'] = module.params['name']
268
269 if network:
270 kwargs['ext_gateway_net_id'] = network['id']
271 # can't send enable_snat unless we have a network
272 kwargs['enable_snat'] = module.params['enable_snat']
273
274 if module.params['external_fixed_ips']:
275 kwargs['ext_fixed_ips'] = []
276 for iface in module.params['external_fixed_ips']:
277 subnet = cloud.get_subnet(iface['subnet'])
278 d = {'subnet_id': subnet['id']}
279 if 'ip' in iface:
280 d['ip_address'] = iface['ip']
281 kwargs['ext_fixed_ips'].append(d)
282
283 return kwargs
284
285
286def _validate_subnets(module, cloud):
287 external_subnet_ids = []
288 internal_subnet_ids = []
289 if module.params['external_fixed_ips']:
290 for iface in module.params['external_fixed_ips']:
291 subnet = cloud.get_subnet(iface['subnet'])
292 if not subnet:
293 module.fail_json(msg='subnet %s not found' % iface['subnet'])
294 external_subnet_ids.append(subnet['id'])
295
296 if module.params['interfaces']:
297 for iface in module.params['interfaces']:
298 subnet = cloud.get_subnet(iface)
299 if not subnet:
300 module.fail_json(msg='subnet %s not found' % iface)
301 internal_subnet_ids.append(subnet['id'])
302
303 return external_subnet_ids, internal_subnet_ids
304
305
306def main():
307 argument_spec = openstack_full_argument_spec(
308 state=dict(default='present', choices=['absent', 'present']),
309 name=dict(required=True),
310 admin_state_up=dict(type='bool', default=True),
311 enable_snat=dict(type='bool', default=True),
312 network=dict(default=None),
313 interfaces=dict(type='list', default=None),
314 external_fixed_ips=dict(type='list', default=None),
315 project=dict(default=None)
316 )
317
318 module_kwargs = openstack_module_kwargs()
319 module = AnsibleModule(argument_spec,
320 supports_check_mode=True,
321 **module_kwargs)
322
323 if not HAS_SHADE:
324 module.fail_json(msg='shade is required for this module')
325
326 if (module.params['project'] and
327 StrictVersion(shade.__version__) <= StrictVersion('1.9.0')):
328 module.fail_json(msg="To utilize project, the installed version of"
329 "the shade library MUST be > 1.9.0")
330
331 state = module.params['state']
332 name = module.params['name']
333 network = module.params['network']
334 project = module.params['project']
335
336 if module.params['external_fixed_ips'] and not network:
337 module.fail_json(msg='network is required when supplying external_fixed_ips')
338
339 try:
340 cloud = shade.openstack_cloud(**module.params)
341 if project is not None:
342 proj = cloud.get_project(project)
343 if proj is None:
344 module.fail_json(msg='Project %s could not be found' % project)
345 project_id = proj['id']
346 filters = {'tenant_id': project_id}
347 else:
348 project_id = None
349 filters = None
350
351 router = cloud.get_router(name, filters=filters)
352 net = None
353 if network:
354 net = cloud.get_network(network)
355 if not net:
356 module.fail_json(msg='network %s not found' % network)
357
358 # Validate and cache the subnet IDs so we can avoid duplicate checks
359 # and expensive API calls.
360 external_ids, internal_ids = _validate_subnets(module, cloud)
361
362 if module.check_mode:
363 module.exit_json(
364 changed=_system_state_change(cloud, module, router, net, internal_ids)
365 )
366
367 if state == 'present':
368 changed = False
369
370 if not router:
371 kwargs = _build_kwargs(cloud, module, router, net)
372 if project_id:
373 kwargs['project_id'] = project_id
374 router = cloud.create_router(**kwargs)
375 for internal_subnet_id in internal_ids:
376 cloud.add_router_interface(router, subnet_id=internal_subnet_id)
377 changed = True
378 else:
379 if _needs_update(cloud, module, router, net, internal_ids):
380 kwargs = _build_kwargs(cloud, module, router, net)
381 updated_router = cloud.update_router(**kwargs)
382
383 # Protect against update_router() not actually
384 # updating the router.
385 if not updated_router:
386 changed = False
387
388 # On a router update, if any internal interfaces were supplied,
389 # just detach all existing internal interfaces and attach the new.
390 elif internal_ids:
391 router = updated_router
392 ports = _router_internal_interfaces(cloud, router)
393 for port in ports:
394 cloud.remove_router_interface(router, port_id=port['id'])
395 for internal_subnet_id in internal_ids:
396 cloud.add_router_interface(router, subnet_id=internal_subnet_id)
397 changed = True
398
399 module.exit_json(changed=changed,
400 router=router,
401 id=router['id'])
402
403 elif state == 'absent':
404 if not router:
405 module.exit_json(changed=False)
406 else:
407 # We need to detach all internal interfaces on a router before
408 # we will be allowed to delete it.
409 ports = _router_internal_interfaces(cloud, router)
410 router_id = router['id']
411 for port in ports:
412 cloud.remove_router_interface(router, port_id=port['id'])
413 cloud.delete_router(router_id)
414 module.exit_json(changed=True)
415
416 except shade.OpenStackCloudException as e:
417 module.fail_json(msg=str(e))
418
419
420# this is magic, see lib/ansible/module_common.py
421from ansible.module_utils.basic import *
422from ansible.module_utils.openstack import *
423if __name__ == '__main__':
424 main()