blob: ccbcb2c75c45cbb3d67138b1c94322d687440b74 [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 socket
Scott Bakerc808c672019-02-04 11:38:20 -080018from openstacksyncstep import OpenStackSyncStep
19from xossynchronizer.modelaccessor import *
20from xosconfig import Config
21from multistructlog import create_logger
22
23log = create_logger(Config().get('logging'))
Scott Bakerb63ea792016-08-11 10:24:48 -070024
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070025RESTAPI_HOSTNAME = socket.gethostname()
26RESTAPI_PORT = "8000"
27
Scott Baker8c542842017-03-22 15:45:44 -070028
Scott Bakerb63ea792016-08-11 10:24:48 -070029def escape(s):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070030 s = s.replace('\n', r'\n').replace('"', r'\"')
Scott Bakerb63ea792016-08-11 10:24:48 -070031 return s
32
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070033
Scott Bakerb63ea792016-08-11 10:24:48 -070034class SyncInstances(OpenStackSyncStep):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070035 provides = [Instance]
36 requested_interval = 0
37 observes = Instance
38 playbook = 'sync_instances.yaml'
Scott Bakerb63ea792016-08-11 10:24:48 -070039
40 def fetch_pending(self, deletion=False):
41 objs = super(SyncInstances, self).fetch_pending(deletion)
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070042 objs = [x for x in objs if x.isolation == "vm"]
Scott Bakerb63ea792016-08-11 10:24:48 -070043 return objs
44
45 def get_userdata(self, instance, pubkeys):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070046 userdata = '#cloud-config\n\nopencloud:\n slicename: "%s"\n hostname: "%s"\n restapi_hostname: "%s"\n restapi_port: "%s"\n' % (
47 instance.slice.name, instance.node.name, RESTAPI_HOSTNAME, str(RESTAPI_PORT))
Scott Bakerb63ea792016-08-11 10:24:48 -070048 userdata += 'ssh_authorized_keys:\n'
49 for key in pubkeys:
50 userdata += ' - %s\n' % key
51 return userdata
52
Andy Bavier4435bf02017-02-01 16:17:09 -050053 def get_nic_for_first_slot(self, nics):
54 # Try to find a NIC with "public" visibility
55 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070056 network = nic.get("network", None)
Andy Bavier4435bf02017-02-01 16:17:09 -050057 if network:
58 tem = network.template
59 if (tem.visibility == "public"):
60 return nic
Scott Bakerb63ea792016-08-11 10:24:48 -070061
Andy Bavier4435bf02017-02-01 16:17:09 -050062 # Otherwise try to find a private network
Scott Bakerb63ea792016-08-11 10:24:48 -070063 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070064 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070065 if network:
66 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070067 if (tem.visibility == "private") and (tem.translation == "none") and ("management" not in tem.name):
68 return nic
Andy Bavier4435bf02017-02-01 16:17:09 -050069
70 raise Exception("Could not find a NIC for first slot")
71
72 def sort_nics(self, nics):
73 result = []
74
75 # Enforce VTN's network order requirement for vSG. The access network must be
76 # inserted into the first slot. The management network must be inserted
77 # into the second slot.
78 #
79 # Some VMs may connect to multiple networks that advertise gateways. In this case, the
80 # default gateway is enforced on eth0. So give priority to "public" networks when
81 # choosing a network for the first slot.
82
83 nic = self.get_nic_for_first_slot(nics)
84 result.append(nic)
85 nics.remove(nic)
Scott Bakerb63ea792016-08-11 10:24:48 -070086
87 # move the management network to the second spot
Scott Bakeread6af42016-10-11 16:40:40 -070088 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070089 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070090 if network:
91 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070092 if (tem.visibility == "private") and (tem.translation == "none") and ("management" in tem.name):
93 # MCORD
94 # if len(result)!=1:
95 # 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 -070096 result.append(nic)
97 nics.remove(nic)
98
99 # add everything else. For VTN there probably shouldn't be any more.
100 result.extend(nics)
101
102 return result
103
104 def map_sync_inputs(self, instance):
Scott Bakere13170a2017-01-26 09:57:37 -0800105
106 # sanity check - make sure model_policy for slice has run
107 if ((not instance.slice.policed) or (instance.slice.policed < instance.slice.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700108 raise DeferredException(
109 "Instance %s waiting on Slice %s to execute model policies" % (instance, instance.slice.name))
Scott Bakere13170a2017-01-26 09:57:37 -0800110
111 # sanity check - make sure model_policy for all slice networks have run
112 for network in instance.slice.ownedNetworks.all():
113 if ((not network.policed) or (network.policed < network.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700114 raise DeferredException(
115 "Instance %s waiting on Network %s to execute model policies" % (instance, network.name))
Scott Bakere13170a2017-01-26 09:57:37 -0800116
Scott Bakerb63ea792016-08-11 10:24:48 -0700117 inputs = {}
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700118 metadata_update = {}
Scott Bakerb63ea792016-08-11 10:24:48 -0700119 if (instance.numberCores):
120 metadata_update["cpu_cores"] = str(instance.numberCores)
121
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700122 # not supported by API... assuming it's not used ... look into enabling later
123 # for tag in instance.slice.tags.all():
124 # if tag.name.startswith("sysctl-"):
125 # metadata_update[tag.name] = tag.value
Scott Bakerb63ea792016-08-11 10:24:48 -0700126
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700127 slice_memberships = SlicePrivilege.objects.filter(slice_id=instance.slice.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700128 pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
129 if instance.creator.public_key:
130 pubkeys.add(instance.creator.public_key)
131
132 if instance.slice.creator.public_key:
133 pubkeys.add(instance.slice.creator.public_key)
134
135 if instance.slice.service and instance.slice.service.public_key:
136 pubkeys.add(instance.slice.service.public_key)
137
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700138 nics = []
Scott Bakerb63ea792016-08-11 10:24:48 -0700139
140 # handle ports the were created by the user
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700141 port_ids = []
Scott Bakeraf599eb2017-03-21 12:43:26 -0700142 for port in Port.objects.filter(instance_id=instance.id):
Scott Bakerb63ea792016-08-11 10:24:48 -0700143 if not port.port_id:
144 raise DeferredException("Instance %s waiting on port %s" % (instance, port))
145 nics.append({"kind": "port", "value": port.port_id, "network": port.network})
146
147 # we want to exclude from 'nics' any network that already has a Port
Scott Bakeraf599eb2017-03-21 12:43:26 -0700148 existing_port_networks = [port.network for port in Port.objects.filter(instance_id=instance.id)]
149 existing_port_network_ids = [x.id for x in existing_port_networks]
Scott Bakerb63ea792016-08-11 10:24:48 -0700150
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700151 networks = [ns.network for ns in NetworkSlice.objects.filter(slice_id=instance.slice.id) if
152 ns.network.id not in existing_port_network_ids]
Scott Bakeraf599eb2017-03-21 12:43:26 -0700153 networks_ids = [x.id for x in networks]
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700154 controller_networks = ControllerNetwork.objects.filter(
155 controller_id=instance.node.site_deployment.controller.id)
Andy Bavierc8a596c2017-10-20 17:21:30 -0700156 controller_networks = [x for x in controller_networks if x.network_id in networks_ids]
Scott Bakeraf599eb2017-03-21 12:43:26 -0700157
Scott Baker6c69a122016-12-07 16:08:55 -0800158 for network in networks:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700159 if not ControllerNetwork.objects.filter(network_id=network.id,
160 controller_id=instance.node.site_deployment.controller.id).exists():
161 raise DeferredException(
162 "Instance %s Private Network %s lacks ControllerNetwork object" % (instance, network.name))
Scott Baker6c69a122016-12-07 16:08:55 -0800163
Scott Bakerb63ea792016-08-11 10:24:48 -0700164 for controller_network in controller_networks:
165 # Lenient exception - causes slow backoff
Andy Bavier4435bf02017-02-01 16:17:09 -0500166 if controller_network.network.template.translation == 'none':
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700167 if not controller_network.net_id:
168 raise DeferredException("Instance %s Private Network %s has no id; Try again later" % (
169 instance, controller_network.network.name))
170 nics.append({"kind": "net", "value": controller_network.net_id, "network": controller_network.network})
Scott Bakerb63ea792016-08-11 10:24:48 -0700171
172 # now include network template
173 network_templates = [network.template.shared_network_name for network in networks \
174 if network.template.shared_network_name]
175
Scott Bakerb63ea792016-08-11 10:24:48 -0700176 driver = self.driver.admin_driver(tenant='admin', controller=instance.node.site_deployment.controller)
177 nets = driver.shell.neutron.list_networks()['networks']
178 for net in nets:
179 if net['name'] in network_templates:
180 nics.append({"kind": "net", "value": net['id'], "network": None})
181
182 if (not nics):
183 for net in nets:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700184 if net['name'] == 'public':
Scott Bakerb63ea792016-08-11 10:24:48 -0700185 nics.append({"kind": "net", "value": net['id'], "network": None})
186
187 nics = self.sort_nics(nics)
188
189 image_name = None
Scott Bakeraf599eb2017-03-21 12:43:26 -0700190 controller_images = instance.image.controllerimages.all()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700191 controller_images = [x for x in controller_images if
192 x.controller_id == instance.node.site_deployment.controller.id]
Scott Bakerb63ea792016-08-11 10:24:48 -0700193 if controller_images:
194 image_name = controller_images[0].image.name
Scott Bakerc808c672019-02-04 11:38:20 -0800195 log.info("using image from ControllerImage object: " + str(image_name))
Scott Bakerb63ea792016-08-11 10:24:48 -0700196
197 if image_name is None:
198 controller_driver = self.driver.admin_driver(controller=instance.node.site_deployment.controller)
199 images = controller_driver.shell.glanceclient.images.list()
200 for image in images:
201 if image.name == instance.image.name or not image_name:
202 image_name = image.name
Scott Bakerc808c672019-02-04 11:38:20 -0800203 log.info("using image from glance: " + str(image_name))
Scott Bakerb63ea792016-08-11 10:24:48 -0700204
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700205 host_filter = instance.node.name.strip()
Scott Bakerb63ea792016-08-11 10:24:48 -0700206
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700207 availability_zone_filter = 'nova:%s' % host_filter
208 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700209 self.instance_name = instance_name
210
211 userData = self.get_userdata(instance, pubkeys)
212 if instance.userData:
213 userData += instance.userData
214
Scott Baker75bae452017-03-27 20:10:58 -0700215 # make sure nics is pickle-able
216 sanitized_nics = [{"kind": nic["kind"], "value": nic["value"]} for nic in nics]
217
Scott Bakerb63ea792016-08-11 10:24:48 -0700218 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700219 fields = {'endpoint': controller.auth_url,
220 'endpoint_v3': controller.auth_url_v3,
221 'domain': controller.domain,
222 'admin_user': instance.creator.email,
223 'admin_password': instance.creator.remote_password,
224 'project_name': instance.slice.name,
225 'tenant': instance.slice.name,
226 'tenant_description': instance.slice.description,
227 'name': instance_name,
228 'ansible_tag': instance_name,
229 'availability_zone': availability_zone_filter,
230 'image_name': image_name,
231 'flavor_name': instance.flavor.name,
232 'nics': sanitized_nics,
233 'meta': metadata_update,
234 'user_data': r'%s' % escape(userData)}
Scott Bakerb63ea792016-08-11 10:24:48 -0700235 return fields
236
Scott Bakerb63ea792016-08-11 10:24:48 -0700237 def map_sync_outputs(self, instance, res):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700238 instance_id = res[0]['openstack']['OS-EXT-SRV-ATTR:instance_name']
Scott Bakerb63ea792016-08-11 10:24:48 -0700239 instance_uuid = res[0]['id']
240
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700241 try:
Scott Bakerb63ea792016-08-11 10:24:48 -0700242 hostname = res[0]['openstack']['OS-EXT-SRV-ATTR:hypervisor_hostname']
243 ip = socket.gethostbyname(hostname)
244 instance.ip = ip
245 except:
246 pass
247
248 instance.instance_id = instance_id
249 instance.instance_uuid = instance_uuid
250 instance.instance_name = self.instance_name
251 instance.save()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700252
Scott Bakerb63ea792016-08-11 10:24:48 -0700253 def map_delete_inputs(self, instance):
254 controller_register = json.loads(instance.node.site_deployment.controller.backend_register)
255
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700256 if (controller_register.get('disabled', False)):
257 raise InnocuousException('Controller %s is disabled' % instance.node.site_deployment.controller.name)
Scott Bakerb63ea792016-08-11 10:24:48 -0700258
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700259 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700260 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700261 input = {'endpoint': controller.auth_url,
Andy Bavier66f9f342018-04-12 16:16:03 -0700262 'endpoint_v3': controller.auth_url_v3,
263 'domain': controller.domain,
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700264 'admin_user': instance.creator.email,
265 'admin_password': instance.creator.remote_password,
266 'project_name': instance.slice.name,
267 'tenant': instance.slice.name,
268 'tenant_description': instance.slice.description,
269 'name': instance_name,
270 'ansible_tag': instance_name,
271 'delete': True}
Scott Bakerb63ea792016-08-11 10:24:48 -0700272 return input