Repository: ansible/ansible-modules-core

Issue:
KeyError: 'external_fixed_ips' crash on os_router second run
I have this playbook in Ansible 2.0 with:

```
- name: Create a router
  os_router:
    validate_certs: False
    state: present
    name: router
    admin_state_up: true
    interfaces:
      - external_subnet
    network: '{{ external_network }}'
```

It creates the router just fine. Problem is when I rerun the playbook. Then Ansible crashes like this:

```
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/home/kwach/.ansible/tmp/ansible-tmp-1455713963.72-212479757689390/os_router", line 2399, in <module>
    main()
  File "/home/kwach/.ansible/tmp/ansible-tmp-1455713963.72-212479757689390/os_router", line 324, in main
    if _needs_update(cloud, module, router, net, internal_ids):
  File "/home/kwach/.ansible/tmp/ansible-tmp-1455713963.72-212479757689390/os_router", line 203, in _needs_update
    for port in cloud.list_router_interfaces(router, 'internal'):
  File "/home/kwach/workspace/tieto-security-explorer/.virtualenvs/depl/local/lib/python2.7/site-packages/shade/openstackcloud.py", line 1724, in list_router_interfaces
    if router['external_gateway_info']
KeyError: 'external_fixed_ips'

fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "invocation": {"module_name": "os_router"}, "parsed": false}
```

I noticed there is a similar issue on the main repo [here](https://github.com/ansible/ansible/issues/13943).



Discussion:
cc @emonty @Shrews 

I am able to reproduce the same error.

Try as I might, I cannot reproduce this error. A little more information would be helpful:

1) What version of shade are you using?

2) Can you provide a complete playbook that I can run (one that creates the network and subnets you are using with the router) that reproduces this error?

Thanks.

Also, although I cannot reproduce this (I'd still like a playbook that does), are you able to test this change to shade that _should_ fix the exception?

https://review.openstack.org/289538

Shade version that I used is 1.40.

I'll try the fix and get back with the info.

Did you ever get a chance to test this? There have been releases of shade since I asked if you could test. Version 1.5.1 (current) or higher should have the potential fix for you.

Ugh, apologies, but that has NOT been released in shade yet. Misread the git log. The next version to be released will definitely have it.

Using Ansible 2.1.0 and shade 1.9.0 I still see this bug...
On the first run the task create the router and on the second run it fails with the output below. 
- name: Add subnet to router interface
  os_router:
    cloud: openstack
    name: router1
    interfaces: 
      - demo_subnet
    state: present

TASK [openstack_set_demo : Add subnet to router interface] *********************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: argument of type 'NoneType' is not iterable
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_DEeX3T/ansible_module_os_router.py\", line 362, in <module>\n    main()\n  File \"/tmp/ansible_DEeX3T/ansible_module_os_router.py\", line 323, in main\n    if _needs_update(cloud, module, router, net, internal_ids):\n  File \"/tmp/ansible_DEeX3T/ansible_module_os_router.py\", line 202, in _needs_update\n    for port in cloud.list_router_interfaces(router, 'internal'):\n  File \"/usr/lib/python2.7/site-packages/shade/openstackcloud.py\", line 2300, in list_router_interfaces\n    'external_fixed_ips' in router['external_gateway_info']):\nTypeError: argument of type 'NoneType' is not iterable\n", "module_stdout": "", "msg": "MODULE FAILURE", "parsed": false}

Hrm. Difficult to be certain, but I think I found a bug in update_router() where this could occur. If there was actually nothing to update, that call is returning None rather than a router dict. That is definitely a bug in shade, but that would also mean that our _needs_update() method in os_router.py probably has a bug as well.

@udis: Could you please try this change to shade to see if it corrects your current issue?

   https://review.openstack.org/337661

So, I think I've convinced myself to fix os_router rather than shade because others may be depending on the None return value from update_router(), and changing that breaks the API for them. Let me put up a PR for os_router.py instead...


Resolving patch:
diff --git a/cloud/openstack/os_router.py b/cloud/openstack/os_router.py
--- a/cloud/openstack/os_router.py
+++ b/cloud/openstack/os_router.py
@@ -360,18 +360,23 @@ def main():
             else:
                 if _needs_update(cloud, module, router, net, internal_ids):
                     kwargs = _build_kwargs(cloud, module, router, net)
-                    router = cloud.update_router(**kwargs)
+                    updated_router = cloud.update_router(**kwargs)
+
+                    # Protect against update_router() not actually
+                    # updating the router.
+                    if not updated_router:
+                        changed = False
 
                     # On a router update, if any internal interfaces were supplied,
                     # just detach all existing internal interfaces and attach the new.
-                    if internal_ids:
+                    elif internal_ids:
+                        router = updated_router
                         ports = cloud.list_router_interfaces(router, 'internal')
                         for port in ports:
                             cloud.remove_router_interface(router, port_id=port['id'])
                         for internal_subnet_id in internal_ids:
                             cloud.add_router_interface(router, subnet_id=internal_subnet_id)
-
-                    changed = True
+                        changed = True
 
             module.exit_json(changed=changed,
                              router=router,


Test patch: