Repository: ansible/ansible-modules-extras

Issue:
cs_staticnat: Creating a static nat fails
##### Issue Type:

Bug Report
##### Plugin Name:

module:cloud/cloudstack/cs_staticnat.py
module:cloud/cloudstack/cs_staticnat2.py
##### Ansible Version:

2.0.0.2
##### Ansible Configuration:
##### Environment:

Ubuntu 14.04
##### Summary:

When trying to assign static ip which was obtained for a vpc with `cs_staticnat` module an error occurs: 
##### Steps To Reproduce:
- Create a cloudstack instance
- Assign this instance to a network in a `vpc`
- Obtain a static ip address for this vpc

Run the following Playbook

```
    - cs_staticnat:
        ip_address: {{ obtained_address }}
        state: present
        vm: "{{ my_vm }}"
        zone: "{{ my_zone }}"
```
##### Expected Results:

SUCCESS
##### Actual Results:

> fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "CloudStackException: ('HTTP 431 response from CloudStack', <Response [431]>, {u'errorcode': 431, u'uuidList': [], u'cserrorcode': 4350, u'errortext': u'Unable to enable static nat for the ipAddress id=834 as ip is not associated with any network and no networkId is passed in'})"}



Discussion:
Thanks @profhase  for reporting

I am afraid, VPC is hardly tested in the cloudstack modules yet. I see in http://cloudstack.apache.org/api/apidocs-4.8/user/enableStaticNat.html that  the networkid must be given when using VPC, but this is not yet implemented. 

Since I am not a  VPC user, I am not sure how to implement it in a way that makes sense from a user perspective. May I ask you as a VPC user, would it makes sense to you if we have a `vpc` (vpc name) argument for `cs_staticnat` and we use the id of it to pass to `listNetworks` for get the `networkid`

``` yaml
 - cs_staticnat:
    ip_address: "{{ obtained_address }}"
    vm: "{{ my_vm }}"
    zone: "{{ my_zone }}"
    vpc: my_vpc
```

or would it make more sense to just pass the network name to find its id?

``` yaml
 - cs_staticnat:
    ip_address: "{{ obtained_address }}"
    vm: "{{ my_vm }}"
    zone: "{{ my_zone }}"
    network: my_vpc_network
```

What do you think?

Thanks @resmo for the fast answer.
I am not very experienced with cloudstack  but from my point of view I would prefer both parameters (`network` and `vpc`) which mutually exclude. The reason:
- In the two web interfaces I saw for cloudstack, one obtains an IP address either for a VPC or for a network which is not in a VPC 
- One can have the same network name in multiple VPCs . So for the second case the name of a network is not sufficient

Working on VPC support: ETA +14 days. 
In the meanwhile I will fix this "bug" with documentation that VPC is not fully supported yet.


Resolving patch:
diff --git a/cloud/cloudstack/cs_staticnat.py b/cloud/cloudstack/cs_staticnat.py
--- a/cloud/cloudstack/cs_staticnat.py
+++ b/cloud/cloudstack/cs_staticnat.py
@@ -42,6 +42,12 @@
       - VM guest NIC secondary IP address for the static NAT.
     required: false
     default: false
+  network:
+    description:
+      - Network the IP address is related to.
+    required: false
+    default: null
+    version_added: "2.2"
   state:
     description:
       - State of the static NAT.
@@ -198,6 +204,7 @@ def create_static_nat(self, ip_address):
         args['virtualmachineid'] = self.get_vm(key='id')
         args['ipaddressid'] = ip_address['id']
         args['vmguestip'] = self.get_vm_guest_ip()
+        args['networkid'] = self.get_network(key='id')
         if not self.module.check_mode:
             res = self.cs.enableStaticNat(**args)
             if 'errortext' in res:
@@ -223,7 +230,7 @@ def update_static_nat(self, ip_address):
                 res = self.cs.disableStaticNat(ipaddressid=ip_address['id'])
                 if 'errortext' in res:
                     self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
-                res = self._poll_job(res, 'staticnat')
+                self._poll_job(res, 'staticnat')
                 res = self.cs.enableStaticNat(**args)
                 if 'errortext' in res:
                     self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
@@ -253,17 +260,17 @@ def absent_static_nat(self):
                     self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
                 poll_async = self.module.params.get('poll_async')
                 if poll_async:
-                    res = self._poll_job(res, 'staticnat')
+                    self._poll_job(res, 'staticnat')
         return ip_address
 
 
-
 def main():
     argument_spec = cs_argument_spec()
     argument_spec.update(dict(
         ip_address = dict(required=True),
         vm = dict(default=None),
         vm_guest_ip = dict(default=None),
+        network = dict(default=None),
         state = dict(choices=['present', 'absent'], default='present'),
         zone = dict(default=None),
         domain = dict(default=None),


Test patch: