blob: d98343d5e0c245057a3d29b7ebf8fdb25079c0cb [file] [log] [blame]
Matteo Scandolof0441032017-08-08 13:05: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
16
Scott Bakerb63ea792016-08-11 10:24:48 -070017import os
18import base64
19import socket
Scott Baker8b75e852016-08-16 15:04:59 -070020from synchronizers.openstack.openstacksyncstep import OpenStackSyncStep
Scott Bakeraf599eb2017-03-21 12:43:26 -070021from synchronizers.new_base.ansible_helper import *
22from synchronizers.new_base.syncstep import *
Scott Bakerb63ea792016-08-11 10:24:48 -070023from xos.logger import observer_logger as logger
Scott Bakeraf599eb2017-03-21 12:43:26 -070024from synchronizers.new_base.modelaccessor import *
Scott Bakerb63ea792016-08-11 10:24:48 -070025
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070026RESTAPI_HOSTNAME = socket.gethostname()
27RESTAPI_PORT = "8000"
28
Scott Baker8c542842017-03-22 15:45:44 -070029
Scott Bakerb63ea792016-08-11 10:24:48 -070030def escape(s):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070031 s = s.replace('\n', r'\n').replace('"', r'\"')
Scott Bakerb63ea792016-08-11 10:24:48 -070032 return s
33
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070034
Scott Bakerb63ea792016-08-11 10:24:48 -070035class SyncInstances(OpenStackSyncStep):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070036 provides = [Instance]
37 requested_interval = 0
38 observes = Instance
39 playbook = 'sync_instances.yaml'
Scott Bakerb63ea792016-08-11 10:24:48 -070040
41 def fetch_pending(self, deletion=False):
42 objs = super(SyncInstances, self).fetch_pending(deletion)
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070043 objs = [x for x in objs if x.isolation == "vm"]
Scott Bakerb63ea792016-08-11 10:24:48 -070044 return objs
45
46 def get_userdata(self, instance, pubkeys):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070047 userdata = '#cloud-config\n\nopencloud:\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))
Scott Bakerb63ea792016-08-11 10:24:48 -070049 userdata += 'ssh_authorized_keys:\n'
50 for key in pubkeys:
51 userdata += ' - %s\n' % key
52 return userdata
53
Andy Bavier4435bf02017-02-01 16:17:09 -050054 def get_nic_for_first_slot(self, nics):
55 # Try to find a NIC with "public" visibility
56 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070057 network = nic.get("network", None)
Andy Bavier4435bf02017-02-01 16:17:09 -050058 if network:
59 tem = network.template
60 if (tem.visibility == "public"):
61 return nic
Scott Bakerb63ea792016-08-11 10:24:48 -070062
Andy Bavier4435bf02017-02-01 16:17:09 -050063 # Otherwise try to find a private network
Scott Bakerb63ea792016-08-11 10:24:48 -070064 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070065 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070066 if network:
67 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070068 if (tem.visibility == "private") and (tem.translation == "none") and ("management" not in tem.name):
69 return nic
Andy Bavier4435bf02017-02-01 16:17:09 -050070
71 raise Exception("Could not find a NIC for first slot")
72
73 def sort_nics(self, nics):
74 result = []
75
76 # Enforce VTN's network order requirement for vSG. The access network must be
77 # inserted into the first slot. The management network must be inserted
78 # into the second slot.
79 #
80 # Some VMs may connect to multiple networks that advertise gateways. In this case, the
81 # default gateway is enforced on eth0. So give priority to "public" networks when
82 # choosing a network for the first slot.
83
84 nic = self.get_nic_for_first_slot(nics)
85 result.append(nic)
86 nics.remove(nic)
Scott Bakerb63ea792016-08-11 10:24:48 -070087
88 # move the management network to the second spot
Scott Bakeread6af42016-10-11 16:40:40 -070089 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070090 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070091 if network:
92 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070093 if (tem.visibility == "private") and (tem.translation == "none") and ("management" in tem.name):
94 # MCORD
95 # if len(result)!=1:
96 # raise Exception("Management network needs to be inserted in slot 1, but there are %d private nics" % len(result))
Scott Bakerb63ea792016-08-11 10:24:48 -070097 result.append(nic)
98 nics.remove(nic)
99
100 # add everything else. For VTN there probably shouldn't be any more.
101 result.extend(nics)
102
103 return result
104
105 def map_sync_inputs(self, instance):
Scott Bakere13170a2017-01-26 09:57:37 -0800106
107 # sanity check - make sure model_policy for slice has run
108 if ((not instance.slice.policed) or (instance.slice.policed < instance.slice.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700109 raise DeferredException(
110 "Instance %s waiting on Slice %s to execute model policies" % (instance, instance.slice.name))
Scott Bakere13170a2017-01-26 09:57:37 -0800111
112 # sanity check - make sure model_policy for all slice networks have run
113 for network in instance.slice.ownedNetworks.all():
114 if ((not network.policed) or (network.policed < network.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700115 raise DeferredException(
116 "Instance %s waiting on Network %s to execute model policies" % (instance, network.name))
Scott Bakere13170a2017-01-26 09:57:37 -0800117
Scott Bakerb63ea792016-08-11 10:24:48 -0700118 inputs = {}
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700119 metadata_update = {}
Scott Bakerb63ea792016-08-11 10:24:48 -0700120 if (instance.numberCores):
121 metadata_update["cpu_cores"] = str(instance.numberCores)
122
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700123 # not supported by API... assuming it's not used ... look into enabling later
124 # for tag in instance.slice.tags.all():
125 # if tag.name.startswith("sysctl-"):
126 # metadata_update[tag.name] = tag.value
Scott Bakerb63ea792016-08-11 10:24:48 -0700127
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700128 slice_memberships = SlicePrivilege.objects.filter(slice_id=instance.slice.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700129 pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
130 if instance.creator.public_key:
131 pubkeys.add(instance.creator.public_key)
132
133 if instance.slice.creator.public_key:
134 pubkeys.add(instance.slice.creator.public_key)
135
136 if instance.slice.service and instance.slice.service.public_key:
137 pubkeys.add(instance.slice.service.public_key)
138
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700139 nics = []
Scott Bakerb63ea792016-08-11 10:24:48 -0700140
141 # handle ports the were created by the user
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700142 port_ids = []
Scott Bakeraf599eb2017-03-21 12:43:26 -0700143 for port in Port.objects.filter(instance_id=instance.id):
Scott Bakerb63ea792016-08-11 10:24:48 -0700144 if not port.port_id:
145 raise DeferredException("Instance %s waiting on port %s" % (instance, port))
146 nics.append({"kind": "port", "value": port.port_id, "network": port.network})
147
148 # we want to exclude from 'nics' any network that already has a Port
Scott Bakeraf599eb2017-03-21 12:43:26 -0700149 existing_port_networks = [port.network for port in Port.objects.filter(instance_id=instance.id)]
150 existing_port_network_ids = [x.id for x in existing_port_networks]
Scott Bakerb63ea792016-08-11 10:24:48 -0700151
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700152 networks = [ns.network for ns in NetworkSlice.objects.filter(slice_id=instance.slice.id) if
153 ns.network.id not in existing_port_network_ids]
Scott Bakeraf599eb2017-03-21 12:43:26 -0700154 networks_ids = [x.id for x in networks]
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700155 controller_networks = ControllerNetwork.objects.filter(
156 controller_id=instance.node.site_deployment.controller.id)
Andy Bavier68612552017-10-20 17:21:30 -0700157 controller_networks = [x for x in controller_networks if x.network_id in networks_ids]
Scott Bakeraf599eb2017-03-21 12:43:26 -0700158
Scott Baker6c69a122016-12-07 16:08:55 -0800159 for network in networks:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700160 if not ControllerNetwork.objects.filter(network_id=network.id,
161 controller_id=instance.node.site_deployment.controller.id).exists():
162 raise DeferredException(
163 "Instance %s Private Network %s lacks ControllerNetwork object" % (instance, network.name))
Scott Baker6c69a122016-12-07 16:08:55 -0800164
Scott Bakerb63ea792016-08-11 10:24:48 -0700165 for controller_network in controller_networks:
166 # Lenient exception - causes slow backoff
Andy Bavier4435bf02017-02-01 16:17:09 -0500167 if controller_network.network.template.translation == 'none':
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700168 if not controller_network.net_id:
169 raise DeferredException("Instance %s Private Network %s has no id; Try again later" % (
170 instance, controller_network.network.name))
171 nics.append({"kind": "net", "value": controller_network.net_id, "network": controller_network.network})
Scott Bakerb63ea792016-08-11 10:24:48 -0700172
173 # now include network template
174 network_templates = [network.template.shared_network_name for network in networks \
175 if network.template.shared_network_name]
176
Scott Bakerb63ea792016-08-11 10:24:48 -0700177 driver = self.driver.admin_driver(tenant='admin', controller=instance.node.site_deployment.controller)
178 nets = driver.shell.neutron.list_networks()['networks']
179 for net in nets:
180 if net['name'] in network_templates:
181 nics.append({"kind": "net", "value": net['id'], "network": None})
182
183 if (not nics):
184 for net in nets:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700185 if net['name'] == 'public':
Scott Bakerb63ea792016-08-11 10:24:48 -0700186 nics.append({"kind": "net", "value": net['id'], "network": None})
187
188 nics = self.sort_nics(nics)
189
190 image_name = None
Scott Bakeraf599eb2017-03-21 12:43:26 -0700191 controller_images = instance.image.controllerimages.all()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700192 controller_images = [x for x in controller_images if
193 x.controller_id == instance.node.site_deployment.controller.id]
Scott Bakerb63ea792016-08-11 10:24:48 -0700194 if controller_images:
195 image_name = controller_images[0].image.name
196 logger.info("using image from ControllerImage object: " + str(image_name))
197
198 if image_name is None:
199 controller_driver = self.driver.admin_driver(controller=instance.node.site_deployment.controller)
200 images = controller_driver.shell.glanceclient.images.list()
201 for image in images:
202 if image.name == instance.image.name or not image_name:
203 image_name = image.name
204 logger.info("using image from glance: " + str(image_name))
205
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700206 host_filter = instance.node.name.strip()
Scott Bakerb63ea792016-08-11 10:24:48 -0700207
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700208 availability_zone_filter = 'nova:%s' % host_filter
209 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700210 self.instance_name = instance_name
211
212 userData = self.get_userdata(instance, pubkeys)
213 if instance.userData:
214 userData += instance.userData
215
Scott Baker75bae452017-03-27 20:10:58 -0700216 # make sure nics is pickle-able
217 sanitized_nics = [{"kind": nic["kind"], "value": nic["value"]} for nic in nics]
218
Scott Bakerb63ea792016-08-11 10:24:48 -0700219 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700220 fields = {'endpoint': controller.auth_url,
221 'endpoint_v3': controller.auth_url_v3,
222 'domain': controller.domain,
223 'admin_user': instance.creator.email,
224 'admin_password': instance.creator.remote_password,
225 'project_name': instance.slice.name,
226 'tenant': instance.slice.name,
227 'tenant_description': instance.slice.description,
228 'name': instance_name,
229 'ansible_tag': instance_name,
230 'availability_zone': availability_zone_filter,
231 'image_name': image_name,
232 'flavor_name': instance.flavor.name,
233 'nics': sanitized_nics,
234 'meta': metadata_update,
235 'user_data': r'%s' % escape(userData)}
Scott Bakerb63ea792016-08-11 10:24:48 -0700236 return fields
237
Scott Bakerb63ea792016-08-11 10:24:48 -0700238 def map_sync_outputs(self, instance, res):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700239 instance_id = res[0]['openstack']['OS-EXT-SRV-ATTR:instance_name']
Scott Bakerb63ea792016-08-11 10:24:48 -0700240 instance_uuid = res[0]['id']
241
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700242 try:
Scott Bakerb63ea792016-08-11 10:24:48 -0700243 hostname = res[0]['openstack']['OS-EXT-SRV-ATTR:hypervisor_hostname']
244 ip = socket.gethostbyname(hostname)
245 instance.ip = ip
246 except:
247 pass
248
249 instance.instance_id = instance_id
250 instance.instance_uuid = instance_uuid
251 instance.instance_name = self.instance_name
252 instance.save()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700253
Scott Bakerb63ea792016-08-11 10:24:48 -0700254 def map_delete_inputs(self, instance):
255 controller_register = json.loads(instance.node.site_deployment.controller.backend_register)
256
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700257 if (controller_register.get('disabled', False)):
258 raise InnocuousException('Controller %s is disabled' % instance.node.site_deployment.controller.name)
Scott Bakerb63ea792016-08-11 10:24:48 -0700259
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700260 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700261 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700262 input = {'endpoint': controller.auth_url,
263 'admin_user': instance.creator.email,
264 'admin_password': instance.creator.remote_password,
265 'project_name': instance.slice.name,
266 'tenant': instance.slice.name,
267 'tenant_description': instance.slice.description,
268 'name': instance_name,
269 'ansible_tag': instance_name,
270 'delete': True}
Scott Bakerb63ea792016-08-11 10:24:48 -0700271 return input