CORD-3183 Fix synchronization of instances that use keys

Change-Id: I8910c560e762e011341595212b26d81c23734313
diff --git a/xos/examples/README.md b/xos/examples/README.md
new file mode 100644
index 0000000..c2a3e94
--- /dev/null
+++ b/xos/examples/README.md
@@ -0,0 +1,12 @@
+The Cirros examples require use of a password to login. The default password is "cubswin:)"
+
+The Ubuntu examples require use of an ssh key to login. Make sure to first set a public key for the
+user account that you're going to use to create the instance. For most demos, this will likely be
+the admin@opencord.org account. You can set the SSH key using the GUI, or you can set it by using
+xossh:
+
+```python
+u=User.objects.first()
+u.public_key = "the_contents_of_my_public_key"
+u.save()
+```
diff --git a/xos/examples/make_instance.yaml b/xos/examples/make_instance_cirros.yaml
similarity index 100%
rename from xos/examples/make_instance.yaml
rename to xos/examples/make_instance_cirros.yaml
diff --git a/xos/examples/make_instance_specific_node.yaml b/xos/examples/make_instance_cirros_specific_node.yaml
similarity index 100%
rename from xos/examples/make_instance_specific_node.yaml
rename to xos/examples/make_instance_cirros_specific_node.yaml
diff --git a/xos/examples/make_instance_ubuntu.yaml b/xos/examples/make_instance_ubuntu.yaml
new file mode 100644
index 0000000..9b16659
--- /dev/null
+++ b/xos/examples/make_instance_ubuntu.yaml
@@ -0,0 +1,128 @@
+---
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: >
+  Creates a TrustDomain, Principal, Slice, Image, and then brings up an openstack
+  VM attached to the management network.
+
+imports:
+  - custom_types/trustdomain.yaml
+  - custom_types/principal.yaml
+  - custom_types/image.yaml
+  - custom_types/flavor.yaml
+  - custom_types/network.yaml
+  - custom_types/networkslice.yaml
+  - custom_types/node.yaml
+  - custom_types/site.yaml
+  - custom_types/slice.yaml
+  - custom_types/openstackservice.yaml
+  - custom_types/openstackserviceinstance.yaml
+
+topology_template:
+  node_templates:
+    service#openstack:
+          type: tosca.nodes.OpenStackService
+          properties:
+            name: OpenStack
+            must-exist: true
+
+    mysite:
+      type: tosca.nodes.Site
+      properties:
+        name: mysite
+        must-exist: true
+
+    management_network:
+      type: tosca.nodes.Network
+      properties:
+        name: management
+        must-exist: true
+
+    m1.small:
+      type: tosca.nodes.Flavor
+      properties:
+        name: m1.small
+        must-exist: true
+
+    demo_trustdomain:
+      type: tosca.nodes.TrustDomain
+      properties:
+        name: "demo-trust"
+      requirements:
+        - owner:
+            node: service#openstack
+            relationship: tosca.relationships.BelongsToOne
+
+    demo_principal:
+      type: tosca.nodes.Principal
+      properties:
+        name: "demo-account"
+      requirements:
+        - trust_domain:
+            node: demo_trustdomain
+            relationship: tosca.relationships.BelongsToOne
+
+    image_ubuntu:
+      type: tosca.nodes.Image
+      properties:
+        name: "trusty-server-cloudimg"
+        container_format: "BARE"
+        disk_format: "QCOW2"
+        path: "http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img"
+
+    demo_slice:
+      type: tosca.nodes.Slice
+      properties:
+        name: "demo-slice"
+      requirements:
+        - site:
+            node: mysite
+            relationship: tosca.relationships.BelongsToOne
+        - trust_domain:
+            node: demo_trustdomain
+            relationship: tosca.relationships.BelongsToOne
+        - principal:
+            node: demo_principal
+            relationship: tosca.relationships.BelongsToOne
+
+    demo_slice_management_network:
+      type: tosca.nodes.NetworkSlice
+      requirements:
+        - network:
+            node: management_network
+            relationship: tosca.relationships.BelongsToOne
+        - slice:
+            node: demo_slice
+            relationship: tosca.relationships.BelongsToOne
+
+    demo_instance:
+      type: tosca.nodes.OpenStackServiceInstance
+      properties:
+        name: "demo-instance-ub"
+      requirements:
+        - slice:
+            node: demo_slice
+            relationship: tosca.relationships.BelongsToOne
+        - owner:
+            node: service#openstack
+            relationship: tosca.relationships.BelongsToOne
+        - image:
+            node: image_ubuntu
+            relationship: tosca.relationships.BelongsToOne
+        - flavor:
+            node: m1.small
+            relationship: tosca.relationships.BelongsToOne
diff --git a/xos/synchronizer/steps/sync_openstackserviceinstance.py b/xos/synchronizer/steps/sync_openstackserviceinstance.py
index 5e0b058..04f71b9 100644
--- a/xos/synchronizer/steps/sync_openstackserviceinstance.py
+++ b/xos/synchronizer/steps/sync_openstackserviceinstance.py
@@ -38,10 +38,10 @@
         pubkeys=[]
 
         if instance.slice.creator and instance.slice.creator.public_key:
-            pubkeys.add(instance.slice.creator.public_key)
+            pubkeys.append(instance.slice.creator.public_key)
 
         if instance.slice.service and instance.slice.service.public_key:
-            pubkeys.add(instance.slice.service.public_key)
+            pubkeys.append(instance.slice.service.public_key)
 
         userdata = '#cloud-config\n\n'
 #        userdata += 'opencloud:\n   slicename: "%s"\n   hostname: "%s"\n   restapi_hostname: "%s"\n   restapi_port: "%s"\n' % (
diff --git a/xos/synchronizer/tests/test_sync_openstackserviceinstance.py b/xos/synchronizer/tests/test_sync_openstackserviceinstance.py
index d4fb9b5..8f0ad3a 100644
--- a/xos/synchronizer/tests/test_sync_openstackserviceinstance.py
+++ b/xos/synchronizer/tests/test_sync_openstackserviceinstance.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json
+import base64
 import os
 import sys
 import unittest
@@ -146,6 +146,46 @@
                                                               user_data=ANY)
             self.assertEqual(xos_instance.backend_handle, "1234")
 
+    def test_sync_record_create_noexist_with_keys(self):
+        fakeconn = MagicMock()
+        with patch.object(self.step_class, "connect_openstack_admin") as fake_connect_openstack_admin:
+            fake_connect_openstack_admin.return_value = fakeconn
+
+            xos_instance = OpenStackServiceInstance(name="test-instance", slice=self.slice, image=self.image,
+                                                    node=self.node, flavor=self.flavor)
+
+            admin_user = User(email="test_user@test.com", public_key="key1")
+            self.slice.creator = admin_user
+
+            owning_service = Service(name="test_service", public_key="key2")
+            self.slice.service = owning_service
+
+            step = self.step_class()
+            fakeconn.compute.servers.return_value = []
+            fakeconn.identity.find_project.return_value = MagicMock(id=self.slice.backend_handle)
+            fakeconn.identity.find_domain.return_value = MagicMock(id=self.trust_domain.backend_handle)
+            fakeconn.compute.find_image.return_value = MagicMock(id=self.image.backend_handle)
+            fakeconn.compute.find_flavor.return_value = MagicMock(id=self.flavor.backend_handle)
+
+            os_instance = MagicMock()
+            os_instance.id = "1234"
+            fakeconn.compute.create_server.return_value = os_instance
+
+            step.sync_record(xos_instance)
+
+            expected_userdata = base64.b64encode('#cloud-config\n\nssh_authorized_keys:\n  - key1\n  - key2\n')
+
+            fakeconn.compute.create_server.assert_called_with(admin_password=ANY,
+                                                              availability_zone="nova:test-node",
+                                                              config_drive=True,
+                                                              flavor_id=self.flavor.backend_handle,
+                                                              image_id=self.image.backend_handle,
+                                                              name=xos_instance.name,
+                                                              networks=[],
+                                                              project_domain_id=self.slice.backend_handle,
+                                                              user_data=expected_userdata)
+            self.assertEqual(xos_instance.backend_handle, "1234")
+
     def test_sync_record_create_exists(self):
         fakeconn = MagicMock()
         with patch.object(self.step_class, "connect_openstack_admin") as fake_connect_openstack_admin: