Repository: ansible/ansible

Issue:
vdirect_file module does not notify the user about invalid state of uploaded source
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
vdirect_file

##### ANSIBLE VERSION
```
ansible 2.4.3.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/usr/share/my_modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC 4.8.4]
```

##### CONFIGURATION

##### OS / ENVIRONMENT

##### SUMMARY
When invalid workflow template or configuration template is uploaded, success status is reported back instead of a failure that describes the invalid state of the uploaded source

##### STEPS TO REPRODUCE

update_canary.vm source should have parsing error, like unclosed foreach loop, for example.

ansible localhost --verbose -m vdirect_file -a "vdirect_ip=10.205.124.63 vdirect_user=vDirect vdirect_password=radware vdirect_validate_certs=No file_name=update_canary.vm"

##### EXPECTED RESULTS
```
localhost | FAILED! => {
    "changed": false,
    "msg": "Error parsing template: Encountered \"<EOF>\" at tmp-velocity4541967573016366038.vm[line 47, column 22]\nWas expecting one of:\n    \"(\" ...\n    <RPAREN> ...\n    <ESCAPE_DIRECTIVE> ...\n    <SET_DIRECTIVE> ...\n    \"##\" ...\n    \"\\\\\\\\\" ...\n    \"\\\\\" ...\n    <TEXT> ...\n    \"*#\" ...\n    \"*#\" ...\n    \"]]#\" ...\n    <STRING_LITERAL> ...\n    <END> ...\n    <IF_DIRECTIVE> ...\n    <INTEGER_LITERAL> ...\n    <FLOATING_POINT_LITERAL> ...\n    <WORD> ...\n    <BRACKETED_WORD> ...\n    <IDENTIFIER> ...\n    <DOT> ...\n    \"{\" ...\n    \"}\" ...\n    <EMPTY_INDEX> ...\n    '"
}
```
##### ACTUAL RESULTS
```
localhost | SUCCESS => {
    "changed": false,
    "result": "Configuration template created"
}
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/network/radware/vdirect_file.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/radware/vdirect_file.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Resolving patch:
diff --git a/lib/ansible/modules/network/radware/vdirect_file.py b/lib/ansible/modules/network/radware/vdirect_file.py
--- a/lib/ansible/modules/network/radware/vdirect_file.py
+++ b/lib/ansible/modules/network/radware/vdirect_file.py
@@ -175,6 +175,21 @@
 )
 
 
+class FileException(Exception):
+    def __init__(self, reason, details):
+        self.reason = reason
+        self.details = details
+
+    def __str__(self):
+        return 'Reason: {0}. Details:{1}.'.format(self.reason, self.details)
+
+
+class InvalidSourceException(FileException):
+    def __init__(self, message):
+        super(InvalidSourceException, self).__init__(
+            'Error parsing file', repr(message))
+
+
 class VdirectFile(object):
     def __init__(self, params):
         self.client = rest_client.RestClient(params['vdirect_ip'],
@@ -195,26 +210,31 @@ def upload(self, fqn):
             runnable_file = open(fqn, 'r')
             file_content = runnable_file.read()
 
+            result_to_return = CONFIGURATION_TEMPLATE_CREATED_SUCCESS
             result = template.create_from_source(file_content, template_name, fail_if_invalid=True)
             if result[rest_client.RESP_STATUS] == 409:
-                template.upload_source(file_content, template_name, fail_if_invalid=True)
-                result = CONFIGURATION_TEMPLATE_UPDATED_SUCCESS
-            else:
-                result = CONFIGURATION_TEMPLATE_CREATED_SUCCESS
+                result_to_return = CONFIGURATION_TEMPLATE_UPDATED_SUCCESS
+                result = template.upload_source(file_content, template_name, fail_if_invalid=True)
+
+            if result[rest_client.RESP_STATUS] == 400:
+                raise InvalidSourceException(str(result[rest_client.RESP_STR]))
         elif fqn.endswith(WORKFLOW_EXTENSION):
             workflow = rest_client.WorkflowTemplate(self.client)
 
             runnable_file = open(fqn, 'rb')
             file_content = runnable_file.read()
+
+            result_to_return = WORKFLOW_TEMPLATE_CREATED_SUCCESS
             result = workflow.create_template_from_archive(file_content, fail_if_invalid=True)
             if result[rest_client.RESP_STATUS] == 409:
-                workflow.update_archive(file_content, os.path.splitext(os.path.basename(fqn))[0])
-                result = WORKFLOW_TEMPLATE_UPDATED_SUCCESS
-            else:
-                result = WORKFLOW_TEMPLATE_CREATED_SUCCESS
+                result_to_return = WORKFLOW_TEMPLATE_UPDATED_SUCCESS
+                result = workflow.update_archive(file_content, os.path.splitext(os.path.basename(fqn))[0])
+
+            if result[rest_client.RESP_STATUS] == 400:
+                raise InvalidSourceException(str(result[rest_client.RESP_STR]))
         else:
-            result = WRONG_EXTENSION_ERROR
-        return result
+            result_to_return = WRONG_EXTENSION_ERROR
+        return result_to_return
 
 
 def main():


Test patch:
diff --git a/test/units/modules/network/radware/test_vdirect_file.py b/test/units/modules/network/radware/test_vdirect_file.py
--- a/test/units/modules/network/radware/test_vdirect_file.py
+++ b/test/units/modules/network/radware/test_vdirect_file.py
@@ -138,7 +138,7 @@ def test_missing_file(self, *args):
             except IOError:
                 assert True
 
-    def test_template_upload_create_success(self, *args):
+    def test_template_upload_create(self, *args):
         module_mock = MagicMock()
         with patch.dict('sys.modules', **{
             'vdirect_client': module_mock,
@@ -148,14 +148,22 @@ def test_template_upload_create_success(self, *args):
             vdirect_file.rest_client.RESP_STATUS = 0
             vdirect_file.rest_client.Template = Template
 
-            Template.set_create_from_source_result([400])
             file = vdirect_file.VdirectFile(NONE_PARAMS)
             path = os.path.dirname(os.path.abspath(__file__))
+
+            Template.set_create_from_source_result([201])
             result = file.upload(os.path.join(path, "ct.vm"))
             self.assertEqual(result, vdirect_file.CONFIGURATION_TEMPLATE_CREATED_SUCCESS,
                              'Unexpected result received:' + repr(result))
 
-    def test_template_upload_update_success(self, *args):
+            Template.set_create_from_source_result([400, "", "Parsing error", ""])
+            try:
+                result = file.upload(os.path.join(path, "ct.vm"))
+                self.fail("InvalidSourceException was not thrown")
+            except vdirect_file.InvalidSourceException:
+                assert True
+
+    def test_template_upload_update(self, *args):
         module_mock = MagicMock()
         with patch.dict('sys.modules', **{
             'vdirect_client': module_mock,
@@ -165,15 +173,23 @@ def test_template_upload_update_success(self, *args):
             vdirect_file.rest_client.RESP_STATUS = 0
             vdirect_file.rest_client.Template = Template
 
-            Template.set_create_from_source_result([409])
-            Template.set_upload_source_result([400])
             file = vdirect_file.VdirectFile(NONE_PARAMS)
             path = os.path.dirname(os.path.abspath(__file__))
+
+            Template.set_create_from_source_result([409])
+            Template.set_upload_source_result([201])
             result = file.upload(os.path.join(path, "ct.vm"))
             self.assertEqual(result, vdirect_file.CONFIGURATION_TEMPLATE_UPDATED_SUCCESS,
                              'Unexpected result received:' + repr(result))
 
-    def test_workflow_upload_create_success(self, *args):
+            Template.set_upload_source_result([400, "", "Parsing error", ""])
+            try:
+                result = file.upload(os.path.join(path, "ct.vm"))
+                self.fail("InvalidSourceException was not thrown")
+            except vdirect_file.InvalidSourceException:
+                assert True
+
+    def test_workflow_upload_create(self, *args):
         module_mock = MagicMock()
         with patch.dict('sys.modules', **{
             'vdirect_client': module_mock,
@@ -183,14 +199,22 @@ def test_workflow_upload_create_success(self, *args):
             vdirect_file.rest_client.RESP_STATUS = 0
             vdirect_file.rest_client.WorkflowTemplate = WorkflowTemplate
 
-            WorkflowTemplate.set_create_template_from_archive_result([400])
             file = vdirect_file.VdirectFile(NONE_PARAMS)
             path = os.path.dirname(os.path.abspath(__file__))
+
+            WorkflowTemplate.set_create_template_from_archive_result([201])
             result = file.upload(os.path.join(path, "wt.zip"))
             self.assertEqual(result, vdirect_file.WORKFLOW_TEMPLATE_CREATED_SUCCESS,
                              'Unexpected result received:' + repr(result))
 
-    def test_workflow_upload_update_success(self, *args):
+            WorkflowTemplate.set_create_template_from_archive_result([400, "", "Parsing error", ""])
+            try:
+                result = file.upload(os.path.join(path, "wt.zip"))
+                self.fail("InvalidSourceException was not thrown")
+            except vdirect_file.InvalidSourceException:
+                assert True
+
+    def test_workflow_upload_update(self, *args):
         module_mock = MagicMock()
         with patch.dict('sys.modules', **{
             'vdirect_client': module_mock,
@@ -200,10 +224,18 @@ def test_workflow_upload_update_success(self, *args):
             vdirect_file.rest_client.RESP_STATUS = 0
             vdirect_file.rest_client.WorkflowTemplate = WorkflowTemplate
 
-            WorkflowTemplate.set_create_template_from_archive_result([409])
-            WorkflowTemplate.set_update_archive_result([400])
             file = vdirect_file.VdirectFile(NONE_PARAMS)
             path = os.path.dirname(os.path.abspath(__file__))
+
+            WorkflowTemplate.set_create_template_from_archive_result([409])
+            WorkflowTemplate.set_update_archive_result([201])
             result = file.upload(os.path.join(path, "wt.zip"))
             self.assertEqual(result, vdirect_file.WORKFLOW_TEMPLATE_UPDATED_SUCCESS,
                              'Unexpected result received:' + repr(result))
+
+            WorkflowTemplate.set_update_archive_result([400, "", "Parsing error", ""])
+            try:
+                result = file.upload(os.path.join(path, "wt.zip"))
+                self.fail("InvalidSourceException was not thrown")
+            except vdirect_file.InvalidSourceException:
+                assert True