blob: 746cc7ffa546352de7ad266e850f21e1cfd91db1 [file] [log] [blame]
Scott Baker62c7eaf2018-05-22 15:59:26 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import base64
17import random
18import string
19
Scott Bakerc808c672019-02-04 11:38:20 -080020from xossynchronizer.modelaccessor import OpenStackServiceInstance, Node, NetworkSlice, Flavor
Scott Baker62c7eaf2018-05-22 15:59:26 -070021from newopenstacksyncstep import NewOpenStackSyncStep
22
23from xosconfig import Config
24from multistructlog import create_logger
25
26log = create_logger(Config().get('logging'))
27
28class SyncOpenStackServiceInstance(NewOpenStackSyncStep):
29 provides=[OpenStackServiceInstance]
30 requested_interval=0
31 observes=OpenStackServiceInstance
32
33 def get_connected_networks(self, instance):
34 xos_networks = [ns.network for ns in NetworkSlice.objects.filter(slice_id=instance.slice.id)]
35 return xos_networks
36
37 def get_user_data(self, instance):
38 pubkeys=[]
39
40 if instance.slice.creator and instance.slice.creator.public_key:
Scott Baker004e1f12018-06-26 16:47:35 -070041 pubkeys.append(instance.slice.creator.public_key)
Scott Baker62c7eaf2018-05-22 15:59:26 -070042
43 if instance.slice.service and instance.slice.service.public_key:
Scott Baker004e1f12018-06-26 16:47:35 -070044 pubkeys.append(instance.slice.service.public_key)
Scott Baker62c7eaf2018-05-22 15:59:26 -070045
46 userdata = '#cloud-config\n\n'
47# userdata += 'opencloud:\n slicename: "%s"\n hostname: "%s"\n restapi_hostname: "%s"\n restapi_port: "%s"\n' % (
48# instance.slice.name, instance.node.name, RESTAPI_HOSTNAME, str(RESTAPI_PORT))
49 userdata += 'ssh_authorized_keys:\n'
50 for key in pubkeys:
51 userdata += ' - %s\n' % key
52
53 log.info("generated userdata", userdata=userdata)
54
55 return userdata
56
Scott Bakera52f3532018-06-29 17:04:15 -070057 def get_network_for_first_slot(self, networks):
58 # Try to find a NIC with "public" visibility
59 for network in networks:
60 if (network.template.visibility == "public"):
61 return network
62
63 # Otherwise try to find a private network
64 for network in networks:
65 if (network.template.visibility == "private") and (network.template.translation == "none") and \
66 ("management" not in network.template.name):
67 return network
68
69 return None
70
71 def get_network_for_second_slot(self, networks):
72 for network in networks:
73 if (network.template.visibility == "private") and (network.template.translation == "none") and \
74 ("management" in network.template.name):
75 return network
76
77 return None
78
79 def sort_networks(self, networks):
80 """ Implement our historic idiosyncratic network selection process.
81
82 1) Try to put a public network in the first slot. If you don't find a public network, then put a private
83 non-management network in the first slot.
84
85 2) Try to put the management network in the second slot.
86
87 3) Add all remaining networks that were now selected for #1 or #2.
88 """
89
90 networks = networks[:]
91 result = []
92
93 first_network = self.get_network_for_first_slot(networks)
94 if (first_network):
95 result.append(first_network)
96 networks.remove(first_network)
97
98 second_network = self.get_network_for_second_slot(networks)
99 if (second_network):
100 result.append(second_network)
101 networks.remove(second_network)
102
103 result.extend(networks)
104
105 return result
106
Scott Baker62c7eaf2018-05-22 15:59:26 -0700107 def sync_record(self, instance):
108 slice = instance.slice
109 if not slice.trust_domain:
110 raise Exception("Instance's slice has no trust domain")
111
112 service = instance.slice.trust_domain.owner.leaf_model
113 #conn = self.connect_openstack_slice(slice)
114 conn = self.connect_openstack_admin(service)
115
116 os_domain = conn.identity.find_domain(slice.trust_domain.name)
117 os_project = conn.identity.find_project(slice.name, domain_id=os_domain.id)
118
119 os_instances = list(conn.compute.servers(name=instance.name, project_id=os_project.id))
120 if os_instances:
121 os_instance=os_instances[0]
122 log.info("Instance already exists in openstack", instance=instance)
123 else:
124 image_name = instance.image.name
125 image_id = conn.compute.find_image(image_name).id
126
127 if instance.flavor:
128 flavor_name = instance.flavor.name
129 else:
130 # pick a sensible default
131 flavor_name = "m1.small"
132 flavor_id = conn.compute.find_flavor(flavor_name).id
133
134 xos_networks = self.get_connected_networks(instance)
Scott Bakera52f3532018-06-29 17:04:15 -0700135 xos_networks = self.sort_networks(xos_networks)
Scott Baker62c7eaf2018-05-22 15:59:26 -0700136 networks = []
137 for xos_network in xos_networks:
138 networks.append({"uuid": conn.network.find_network(xos_network.name).id})
139
140 # TODO(smbaker): No ssh keys specified
141
Scott Baker14b74fd2018-06-11 17:35:58 -0700142 if not instance.node:
Scott Baker818c7bd2018-06-13 16:28:01 -0700143 availability_zone = "nova"
144 else:
145 availability_zone="nova:%s" % instance.node.name
Scott Baker62c7eaf2018-05-22 15:59:26 -0700146
147 log.info("Creating Instance", instance=instance, image_id=image_id, flavor_id=flavor_id,
148 availability_zone=availability_zone,
149 networks=networks)
150
151 if not instance.admin_password:
152 instance.admin_password = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8))
153 instance.save(update_fields=["admin_password"])
154
155 user_data = self.get_user_data(instance)
156
157 os_instance = conn.compute.create_server(name=instance.name,
158 image_id=image_id,
159 flavor_id=flavor_id,
160 project_domain_id=os_project.id,
161 availability_zone=availability_zone,
162 networks=networks,
163 config_drive=True,
164 user_data=base64.b64encode(user_data),
165 admin_password=instance.admin_password)
166
167 if os_instance.id != instance.backend_handle:
168 instance.backend_handle = os_instance.id
169 instance.save(update_fields=["backend_handle"])
170
171 def delete_record(self, instance):
172 slice = instance.slice
173 if not slice.trust_domain:
174 raise Exception("Instance's slice has no trust domain")
175
176 service = slice.trust_domain.owner.leaf_model
177 conn = self.connect_openstack_admin(service)
178
179 os_domain = conn.identity.find_domain(slice.trust_domain.name)
180 os_project = conn.identity.find_project(slice.name, domain_id=os_domain.id)
181
182 os_instances = list(conn.compute.servers(name=instance.name, project_id=os_project.id))
183 if (not os_instances):
184 log.info("Instance already does not exist in openstack", instance=instance)
185 else:
186 os_instance=os_instances[0]
187 log.info("Deleting Instance", instance=instance, os_id=os_instance.id)
188 conn.compute.delete_server(os_instance.id)