Repository: openshift/openshift-ansible

Issue:
OpenStack installs result in 'unicode' object has no attribute 'items'
#### Description
Running a multiple master install on OpenStack fails with an ansible error:  
```
Failure summary:


  1. Hosts:    app-node-0.scale-ci.example.com, app-node-1.scale-ci.example.com, infra-node-0.scale-ci.example.com, infra-node-1.scale-ci.example.com, infra-node-2.scale-ci.example.com, master-0.scale-ci.example.com, master-1.scale-ci.example.com, master-2.scale-ci.example.com
     Play:     Configure nodes
     Task:     openshift_node : file
     Message:  The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u"{{ openshift_config_base ~ '/openstack.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u"{{ openshift_config_base ~ '/gce.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u"{{ openshift_config_base ~ '/aws.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'

               The error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may
               be elsewhere in the file depending on the exact syntax problem.

               The offending line appears to be:


               - file:
                 ^ here
```

##### Version

* Your ansible version per `ansible --version`
```
"ansible 2.4.2.0", 
"  config file = /home/cloud-user/ansible.cfg", 
"  configured module search path = [u'/home/cloud-user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']", 
"  ansible python module location = /usr/lib/python2.7/site-packages/ansible", 
"  executable location = /usr/bin/ansible", 
"  python version = 2.7.5 (default, Dec  8 2017, 16:39:59) [GCC 4.8.5 20150623 (Red Hat 4.8.5-25)]
```

* The output of `git describe`: `openshift-ansible-3.9.0-0.10.0-50-gfbf32e8`

* The output of `rpm -q atomic-openshift-utils openshift-ansible`

Place the output between the code block below:

```
atomic-openshift-utils-3.9.0-0.10.0.git.0.33f648a.el7.noarch
openshift-ansible-3.9.0-0.10.0.git.0.33f648a.el7.noarch
```

##### Steps To Reproduce

I was following the OpenStack install instructions: 
https://github.com/openshift/openshift-ansible/tree/master/playbooks/openstack

Here is the command used to run the OpenShift install:  
`source /home/cloud-user/keystonerc; ansible-playbook -vvv --user openshift -i inventory openshift-ansible/playbooks/openstack/openshift-cluster/provision_install.yml -e openshift_repos_enable_testing=true 2>&1 > /home/cloud-user/openshift_install.log`

##### Expected Results
I expected the installer to complete without error

##### Observed Results
The installer fails with an object has no attribute "items" and I have seen this before on multiple installs. The playbook fails to find items for each host. So this task fails eight times.
```
...
TASK [openshift_node : file] ***************************************************
task path: /home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml:26
Thursday 21 December 2017  16:53:24 -0500 (0:00:00.282)       0:22:48.205 ***** 
fatal: [infra-node-0.scale-ci.example.com]: FAILED! => {
    "msg": "The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u\"{{ openshift_config_base ~ '/openstack.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u\"{{ openshift_config_base ~ '/gce.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u\"{{ openshift_config_base ~ '/aws.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'\n\nThe error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- file:\n  ^ here\n"
}
fatal: [app-node-1.scale-ci.example.com]: FAILED! => {
    "msg": "The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u\"{{ openshift_config_base ~ '/openstack.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u\"{{ openshift_config_base ~ '/gce.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u\"{{ openshift_config_base ~ '/aws.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'\n\nThe error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- file:\n  ^ here\n"
}
...
```

I have the logs and can attach if you wish, but this looks like a logic error in the Ansible.


##### Additional Information
I am trying to automate the install and can reproduce this error several times in a row. 


OpenStack installs result in 'unicode' object has no attribute 'items'
#### Description
Running a multiple master install on OpenStack fails with an ansible error:  
```
Failure summary:


  1. Hosts:    app-node-0.scale-ci.example.com, app-node-1.scale-ci.example.com, infra-node-0.scale-ci.example.com, infra-node-1.scale-ci.example.com, infra-node-2.scale-ci.example.com, master-0.scale-ci.example.com, master-1.scale-ci.example.com, master-2.scale-ci.example.com
     Play:     Configure nodes
     Task:     openshift_node : file
     Message:  The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u"{{ openshift_config_base ~ '/openstack.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u"{{ openshift_config_base ~ '/gce.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u"{{ openshift_config_base ~ '/aws.conf' }}"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'

               The error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may
               be elsewhere in the file depending on the exact syntax problem.

               The offending line appears to be:


               - file:
                 ^ here
```

##### Version

* Your ansible version per `ansible --version`
```
"ansible 2.4.2.0", 
"  config file = /home/cloud-user/ansible.cfg", 
"  configured module search path = [u'/home/cloud-user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']", 
"  ansible python module location = /usr/lib/python2.7/site-packages/ansible", 
"  executable location = /usr/bin/ansible", 
"  python version = 2.7.5 (default, Dec  8 2017, 16:39:59) [GCC 4.8.5 20150623 (Red Hat 4.8.5-25)]
```

* The output of `git describe`: `openshift-ansible-3.9.0-0.10.0-50-gfbf32e8`

* The output of `rpm -q atomic-openshift-utils openshift-ansible`

Place the output between the code block below:

```
atomic-openshift-utils-3.9.0-0.10.0.git.0.33f648a.el7.noarch
openshift-ansible-3.9.0-0.10.0.git.0.33f648a.el7.noarch
```

##### Steps To Reproduce

I was following the OpenStack install instructions: 
https://github.com/openshift/openshift-ansible/tree/master/playbooks/openstack

Here is the command used to run the OpenShift install:  
`source /home/cloud-user/keystonerc; ansible-playbook -vvv --user openshift -i inventory openshift-ansible/playbooks/openstack/openshift-cluster/provision_install.yml -e openshift_repos_enable_testing=true 2>&1 > /home/cloud-user/openshift_install.log`

##### Expected Results
I expected the installer to complete without error

##### Observed Results
The installer fails with an object has no attribute "items" and I have seen this before on multiple installs. The playbook fails to find items for each host. So this task fails eight times.
```
...
TASK [openshift_node : file] ***************************************************
task path: /home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml:26
Thursday 21 December 2017  16:53:24 -0500 (0:00:00.282)       0:22:48.205 ***** 
fatal: [infra-node-0.scale-ci.example.com]: FAILED! => {
    "msg": "The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u\"{{ openshift_config_base ~ '/openstack.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u\"{{ openshift_config_base ~ '/gce.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u\"{{ openshift_config_base ~ '/aws.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'\n\nThe error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- file:\n  ^ here\n"
}
fatal: [app-node-1.scale-ci.example.com]: FAILED! => {
    "msg": "The conditional check '('config' in l2_openshift_node_kubelet_args) | bool' failed. The error was: {{ l_node_kubelet_args_default | combine(l_openshift_node_kubelet_args, recursive=True) }}: {{ openshift_node_kubelet_args_dict[openshift_cloudprovider_kind | default('undefined')] }}: {u'openstack': {u'cloud-config': [u\"{{ openshift_config_base ~ '/openstack.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'openstack']}, u'gce': {u'cloud-config': [u\"{{ openshift_config_base ~ '/gce.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'gce']}, u'aws': {u'cloud-config': [u\"{{ openshift_config_base ~ '/aws.conf' }}\"], u'node-labels': u'{{ l_node_kubelet_node_labels }}', u'cloud-provider': [u'aws']}, u'undefined': {u'node-labels': u'{{ l_node_kubelet_node_labels }}'}}: {{ openshift_node_labels | default({}) | lib_utils_oo_dict_to_keqv_list }}: 'unicode' object has no attribute 'items'\n\nThe error appears to have been in '/home/cloud-user/openshift-ansible/roles/openshift_node/tasks/config.yml': line 26, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- file:\n  ^ here\n"
}
...
```

I have the logs and can attach if you wish, but this looks like a logic error in the Ansible.


##### Additional Information
I am trying to automate the install and can reproduce this error several times in a row. 




Discussion:

Resolving patch:
diff --git a/playbooks/openstack/sample-inventory/inventory.py b/playbooks/openstack/sample-inventory/inventory.py
--- a/playbooks/openstack/sample-inventory/inventory.py
+++ b/playbooks/openstack/sample-inventory/inventory.py
@@ -9,6 +9,7 @@
 
 from __future__ import print_function
 
+from collections import Mapping
 import json
 
 import shade
@@ -94,6 +95,10 @@ def build_inventory():
         hostvars['openshift_public_hostname'] = server.name
 
         node_labels = server.metadata.get('node_labels')
+        # NOTE(shadower): the node_labels value must be a dict not string
+        if not isinstance(node_labels, Mapping):
+            node_labels = json.loads(node_labels)
+
         if node_labels:
             hostvars['openshift_node_labels'] = node_labels
 


Test patch: