blob: 13e023c3ede722a5967559a34abd5d86c4e05ae9 [file] [log] [blame]
Scott Bakerb63ea792016-08-11 10:24:48 -07001import os
2import base64
3import socket
Scott Baker8b75e852016-08-16 15:04:59 -07004from synchronizers.openstack.openstacksyncstep import OpenStackSyncStep
Scott Bakeraf599eb2017-03-21 12:43:26 -07005from synchronizers.new_base.ansible_helper import *
6from synchronizers.new_base.syncstep import *
Scott Bakerb63ea792016-08-11 10:24:48 -07007from xos.logger import observer_logger as logger
Scott Bakeraf599eb2017-03-21 12:43:26 -07008from synchronizers.new_base.modelaccessor import *
Scott Bakerb63ea792016-08-11 10:24:48 -07009
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070010RESTAPI_HOSTNAME = socket.gethostname()
11RESTAPI_PORT = "8000"
12
Scott Baker8c542842017-03-22 15:45:44 -070013
Scott Bakerb63ea792016-08-11 10:24:48 -070014def escape(s):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070015 s = s.replace('\n', r'\n').replace('"', r'\"')
Scott Bakerb63ea792016-08-11 10:24:48 -070016 return s
17
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070018
Scott Bakerb63ea792016-08-11 10:24:48 -070019class SyncInstances(OpenStackSyncStep):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070020 provides = [Instance]
21 requested_interval = 0
22 observes = Instance
23 playbook = 'sync_instances.yaml'
Scott Bakerb63ea792016-08-11 10:24:48 -070024
25 def fetch_pending(self, deletion=False):
26 objs = super(SyncInstances, self).fetch_pending(deletion)
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070027 objs = [x for x in objs if x.isolation == "vm"]
Scott Bakerb63ea792016-08-11 10:24:48 -070028 return objs
29
30 def get_userdata(self, instance, pubkeys):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070031 userdata = '#cloud-config\n\nopencloud:\n slicename: "%s"\n hostname: "%s"\n restapi_hostname: "%s"\n restapi_port: "%s"\n' % (
32 instance.slice.name, instance.node.name, RESTAPI_HOSTNAME, str(RESTAPI_PORT))
Scott Bakerb63ea792016-08-11 10:24:48 -070033 userdata += 'ssh_authorized_keys:\n'
34 for key in pubkeys:
35 userdata += ' - %s\n' % key
36 return userdata
37
Andy Bavier4435bf02017-02-01 16:17:09 -050038 def get_nic_for_first_slot(self, nics):
39 # Try to find a NIC with "public" visibility
40 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070041 network = nic.get("network", None)
Andy Bavier4435bf02017-02-01 16:17:09 -050042 if network:
43 tem = network.template
44 if (tem.visibility == "public"):
45 return nic
Scott Bakerb63ea792016-08-11 10:24:48 -070046
Andy Bavier4435bf02017-02-01 16:17:09 -050047 # Otherwise try to find a private network
Scott Bakerb63ea792016-08-11 10:24:48 -070048 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070049 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070050 if network:
51 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070052 if (tem.visibility == "private") and (tem.translation == "none") and ("management" not in tem.name):
53 return nic
Andy Bavier4435bf02017-02-01 16:17:09 -050054
55 raise Exception("Could not find a NIC for first slot")
56
57 def sort_nics(self, nics):
58 result = []
59
60 # Enforce VTN's network order requirement for vSG. The access network must be
61 # inserted into the first slot. The management network must be inserted
62 # into the second slot.
63 #
64 # Some VMs may connect to multiple networks that advertise gateways. In this case, the
65 # default gateway is enforced on eth0. So give priority to "public" networks when
66 # choosing a network for the first slot.
67
68 nic = self.get_nic_for_first_slot(nics)
69 result.append(nic)
70 nics.remove(nic)
Scott Bakerb63ea792016-08-11 10:24:48 -070071
72 # move the management network to the second spot
Scott Bakeread6af42016-10-11 16:40:40 -070073 for nic in nics[:]:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070074 network = nic.get("network", None)
Scott Bakerb63ea792016-08-11 10:24:48 -070075 if network:
76 tem = network.template
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070077 if (tem.visibility == "private") and (tem.translation == "none") and ("management" in tem.name):
78 # MCORD
79 # if len(result)!=1:
80 # 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 -070081 result.append(nic)
82 nics.remove(nic)
83
84 # add everything else. For VTN there probably shouldn't be any more.
85 result.extend(nics)
86
87 return result
88
89 def map_sync_inputs(self, instance):
Scott Bakere13170a2017-01-26 09:57:37 -080090
91 # sanity check - make sure model_policy for slice has run
92 if ((not instance.slice.policed) or (instance.slice.policed < instance.slice.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070093 raise DeferredException(
94 "Instance %s waiting on Slice %s to execute model policies" % (instance, instance.slice.name))
Scott Bakere13170a2017-01-26 09:57:37 -080095
96 # sanity check - make sure model_policy for all slice networks have run
97 for network in instance.slice.ownedNetworks.all():
98 if ((not network.policed) or (network.policed < network.updated)):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -070099 raise DeferredException(
100 "Instance %s waiting on Network %s to execute model policies" % (instance, network.name))
Scott Bakere13170a2017-01-26 09:57:37 -0800101
Scott Bakerb63ea792016-08-11 10:24:48 -0700102 inputs = {}
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700103 metadata_update = {}
Scott Bakerb63ea792016-08-11 10:24:48 -0700104 if (instance.numberCores):
105 metadata_update["cpu_cores"] = str(instance.numberCores)
106
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700107 # not supported by API... assuming it's not used ... look into enabling later
108 # for tag in instance.slice.tags.all():
109 # if tag.name.startswith("sysctl-"):
110 # metadata_update[tag.name] = tag.value
Scott Bakerb63ea792016-08-11 10:24:48 -0700111
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700112 slice_memberships = SlicePrivilege.objects.filter(slice_id=instance.slice.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700113 pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
114 if instance.creator.public_key:
115 pubkeys.add(instance.creator.public_key)
116
117 if instance.slice.creator.public_key:
118 pubkeys.add(instance.slice.creator.public_key)
119
120 if instance.slice.service and instance.slice.service.public_key:
121 pubkeys.add(instance.slice.service.public_key)
122
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700123 nics = []
Scott Bakerb63ea792016-08-11 10:24:48 -0700124
125 # handle ports the were created by the user
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700126 port_ids = []
Scott Bakeraf599eb2017-03-21 12:43:26 -0700127 for port in Port.objects.filter(instance_id=instance.id):
Scott Bakerb63ea792016-08-11 10:24:48 -0700128 if not port.port_id:
129 raise DeferredException("Instance %s waiting on port %s" % (instance, port))
130 nics.append({"kind": "port", "value": port.port_id, "network": port.network})
131
132 # we want to exclude from 'nics' any network that already has a Port
Scott Bakeraf599eb2017-03-21 12:43:26 -0700133 existing_port_networks = [port.network for port in Port.objects.filter(instance_id=instance.id)]
134 existing_port_network_ids = [x.id for x in existing_port_networks]
Scott Bakerb63ea792016-08-11 10:24:48 -0700135
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700136 networks = [ns.network for ns in NetworkSlice.objects.filter(slice_id=instance.slice.id) if
137 ns.network.id not in existing_port_network_ids]
Scott Bakeraf599eb2017-03-21 12:43:26 -0700138 networks_ids = [x.id for x in networks]
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700139 controller_networks = ControllerNetwork.objects.filter(
140 controller_id=instance.node.site_deployment.controller.id)
Scott Bakeraf599eb2017-03-21 12:43:26 -0700141 controller_networks = [x for x in controller_networks if x.id in networks_ids]
142
Scott Baker6c69a122016-12-07 16:08:55 -0800143 for network in networks:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700144 if not ControllerNetwork.objects.filter(network_id=network.id,
145 controller_id=instance.node.site_deployment.controller.id).exists():
146 raise DeferredException(
147 "Instance %s Private Network %s lacks ControllerNetwork object" % (instance, network.name))
Scott Baker6c69a122016-12-07 16:08:55 -0800148
Scott Bakerb63ea792016-08-11 10:24:48 -0700149 for controller_network in controller_networks:
150 # Lenient exception - causes slow backoff
Andy Bavier4435bf02017-02-01 16:17:09 -0500151 if controller_network.network.template.translation == 'none':
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700152 if not controller_network.net_id:
153 raise DeferredException("Instance %s Private Network %s has no id; Try again later" % (
154 instance, controller_network.network.name))
155 nics.append({"kind": "net", "value": controller_network.net_id, "network": controller_network.network})
Scott Bakerb63ea792016-08-11 10:24:48 -0700156
157 # now include network template
158 network_templates = [network.template.shared_network_name for network in networks \
159 if network.template.shared_network_name]
160
Scott Bakerb63ea792016-08-11 10:24:48 -0700161 driver = self.driver.admin_driver(tenant='admin', controller=instance.node.site_deployment.controller)
162 nets = driver.shell.neutron.list_networks()['networks']
163 for net in nets:
164 if net['name'] in network_templates:
165 nics.append({"kind": "net", "value": net['id'], "network": None})
166
167 if (not nics):
168 for net in nets:
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700169 if net['name'] == 'public':
Scott Bakerb63ea792016-08-11 10:24:48 -0700170 nics.append({"kind": "net", "value": net['id'], "network": None})
171
172 nics = self.sort_nics(nics)
173
174 image_name = None
Scott Bakeraf599eb2017-03-21 12:43:26 -0700175 controller_images = instance.image.controllerimages.all()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700176 controller_images = [x for x in controller_images if
177 x.controller_id == instance.node.site_deployment.controller.id]
Scott Bakerb63ea792016-08-11 10:24:48 -0700178 if controller_images:
179 image_name = controller_images[0].image.name
180 logger.info("using image from ControllerImage object: " + str(image_name))
181
182 if image_name is None:
183 controller_driver = self.driver.admin_driver(controller=instance.node.site_deployment.controller)
184 images = controller_driver.shell.glanceclient.images.list()
185 for image in images:
186 if image.name == instance.image.name or not image_name:
187 image_name = image.name
188 logger.info("using image from glance: " + str(image_name))
189
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700190 host_filter = instance.node.name.strip()
Scott Bakerb63ea792016-08-11 10:24:48 -0700191
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700192 availability_zone_filter = 'nova:%s' % host_filter
193 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700194 self.instance_name = instance_name
195
196 userData = self.get_userdata(instance, pubkeys)
197 if instance.userData:
198 userData += instance.userData
199
Scott Baker75bae452017-03-27 20:10:58 -0700200 # make sure nics is pickle-able
201 sanitized_nics = [{"kind": nic["kind"], "value": nic["value"]} for nic in nics]
202
Scott Bakerb63ea792016-08-11 10:24:48 -0700203 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700204 fields = {'endpoint': controller.auth_url,
205 'endpoint_v3': controller.auth_url_v3,
206 'domain': controller.domain,
207 'admin_user': instance.creator.email,
208 'admin_password': instance.creator.remote_password,
209 'project_name': instance.slice.name,
210 'tenant': instance.slice.name,
211 'tenant_description': instance.slice.description,
212 'name': instance_name,
213 'ansible_tag': instance_name,
214 'availability_zone': availability_zone_filter,
215 'image_name': image_name,
216 'flavor_name': instance.flavor.name,
217 'nics': sanitized_nics,
218 'meta': metadata_update,
219 'user_data': r'%s' % escape(userData)}
Scott Bakerb63ea792016-08-11 10:24:48 -0700220 return fields
221
Scott Bakerb63ea792016-08-11 10:24:48 -0700222 def map_sync_outputs(self, instance, res):
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700223 instance_id = res[0]['openstack']['OS-EXT-SRV-ATTR:instance_name']
Scott Bakerb63ea792016-08-11 10:24:48 -0700224 instance_uuid = res[0]['id']
225
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700226 try:
Scott Bakerb63ea792016-08-11 10:24:48 -0700227 hostname = res[0]['openstack']['OS-EXT-SRV-ATTR:hypervisor_hostname']
228 ip = socket.gethostbyname(hostname)
229 instance.ip = ip
230 except:
231 pass
232
233 instance.instance_id = instance_id
234 instance.instance_uuid = instance_uuid
235 instance.instance_name = self.instance_name
236 instance.save()
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700237
Scott Bakerb63ea792016-08-11 10:24:48 -0700238 def map_delete_inputs(self, instance):
239 controller_register = json.loads(instance.node.site_deployment.controller.backend_register)
240
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700241 if (controller_register.get('disabled', False)):
242 raise InnocuousException('Controller %s is disabled' % instance.node.site_deployment.controller.name)
Scott Bakerb63ea792016-08-11 10:24:48 -0700243
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700244 instance_name = '%s-%d' % (instance.slice.name, instance.id)
Scott Bakerb63ea792016-08-11 10:24:48 -0700245 controller = instance.node.site_deployment.controller
Matteo Scandoloceccb1f2017-06-05 10:35:44 -0700246 input = {'endpoint': controller.auth_url,
247 'admin_user': instance.creator.email,
248 'admin_password': instance.creator.remote_password,
249 'project_name': instance.slice.name,
250 'tenant': instance.slice.name,
251 'tenant_description': instance.slice.description,
252 'name': instance_name,
253 'ansible_tag': instance_name,
254 'delete': True}
Scott Bakerb63ea792016-08-11 10:24:48 -0700255 return input