blob: 395b9b1b88400a3c302af533d76604f07f9dc103 [file] [log] [blame]
Scott Bakerb63ea792016-08-11 10:24:48 -07001import os
2import base64
3import socket
4from django.db.models import F, Q
5from xos.config import Config
6from xos.settings import RESTAPI_HOSTNAME, RESTAPI_PORT
Scott Baker8b75e852016-08-16 15:04:59 -07007from synchronizers.openstack.openstacksyncstep import OpenStackSyncStep
Scott Bakerb63ea792016-08-11 10:24:48 -07008from core.models.instance import Instance
9from core.models.slice import Slice, SlicePrivilege, ControllerSlice
10from core.models.network import Network, NetworkSlice, ControllerNetwork
Sapan Bhatia259205e2017-01-24 19:32:59 +010011from synchronizers.base.ansible_helper import *
Scott Bakerb63ea792016-08-11 10:24:48 -070012from synchronizers.base.syncstep import *
13from xos.logger import observer_logger as logger
14
15def escape(s):
16 s = s.replace('\n',r'\n').replace('"',r'\"')
17 return s
18
19class SyncInstances(OpenStackSyncStep):
20 provides=[Instance]
21 requested_interval=0
22 observes=Instance
23 playbook='sync_instances.yaml'
24
25 def fetch_pending(self, deletion=False):
26 objs = super(SyncInstances, self).fetch_pending(deletion)
27 objs = [x for x in objs if x.isolation=="vm"]
28 return objs
29
30 def get_userdata(self, instance, pubkeys):
31 userdata = '#cloud-config\n\nopencloud:\n slicename: "%s"\n hostname: "%s"\n restapi_hostname: "%s"\n restapi_port: "%s"\n' % (instance.slice.name, instance.node.name, RESTAPI_HOSTNAME, str(RESTAPI_PORT))
32 userdata += 'ssh_authorized_keys:\n'
33 for key in pubkeys:
34 userdata += ' - %s\n' % key
35 return userdata
36
Andy Bavier4435bf02017-02-01 16:17:09 -050037 def get_nic_for_first_slot(self, nics):
38 # Try to find a NIC with "public" visibility
39 for nic in nics[:]:
40 network=nic.get("network", None)
41 if network:
42 tem = network.template
43 if (tem.visibility == "public"):
44 return nic
Scott Bakerb63ea792016-08-11 10:24:48 -070045
Andy Bavier4435bf02017-02-01 16:17:09 -050046 # Otherwise try to find a private network
Scott Bakerb63ea792016-08-11 10:24:48 -070047 for nic in nics[:]:
48 network=nic.get("network", None)
49 if network:
50 tem = network.template
51 if (tem.visibility == "private") and (tem.translation=="none") and ("management" not in tem.name):
Andy Bavier4435bf02017-02-01 16:17:09 -050052 return nic
53
54 raise Exception("Could not find a NIC for first slot")
55
56 def sort_nics(self, nics):
57 result = []
58
59 # Enforce VTN's network order requirement for vSG. The access network must be
60 # inserted into the first slot. The management network must be inserted
61 # into the second slot.
62 #
63 # Some VMs may connect to multiple networks that advertise gateways. In this case, the
64 # default gateway is enforced on eth0. So give priority to "public" networks when
65 # choosing a network for the first slot.
66
67 nic = self.get_nic_for_first_slot(nics)
68 result.append(nic)
69 nics.remove(nic)
Scott Bakerb63ea792016-08-11 10:24:48 -070070
71 # move the management network to the second spot
Scott Bakeread6af42016-10-11 16:40:40 -070072 for nic in nics[:]:
Scott Bakerb63ea792016-08-11 10:24:48 -070073 network=nic.get("network", None)
74 if network:
75 tem = network.template
76 if (tem.visibility == "private") and (tem.translation=="none") and ("management" in tem.name):
77#MCORD
78# if len(result)!=1:
79# raise Exception("Management network needs to be inserted in slot 1, but there are %d private nics" % len(result))
80 result.append(nic)
81 nics.remove(nic)
82
83 # add everything else. For VTN there probably shouldn't be any more.
84 result.extend(nics)
85
86 return result
87
88 def map_sync_inputs(self, instance):
Scott Bakere13170a2017-01-26 09:57:37 -080089
90 # sanity check - make sure model_policy for slice has run
91 if ((not instance.slice.policed) or (instance.slice.policed < instance.slice.updated)):
92 raise DeferredException("Instance %s waiting on Slice %s to execute model policies" % (instance, slice.name))
93
94 # sanity check - make sure model_policy for all slice networks have run
95 for network in instance.slice.ownedNetworks.all():
96 if ((not network.policed) or (network.policed < network.updated)):
97 raise DeferredException("Instance %s waiting on Network %s to execute model policies" % (instance, network.name))
98
Scott Bakerb63ea792016-08-11 10:24:48 -070099 inputs = {}
100 metadata_update = {}
101 if (instance.numberCores):
102 metadata_update["cpu_cores"] = str(instance.numberCores)
103
104 for tag in instance.slice.tags.all():
105 if tag.name.startswith("sysctl-"):
106 metadata_update[tag.name] = tag.value
107
108 slice_memberships = SlicePrivilege.objects.filter(slice=instance.slice)
109 pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
110 if instance.creator.public_key:
111 pubkeys.add(instance.creator.public_key)
112
113 if instance.slice.creator.public_key:
114 pubkeys.add(instance.slice.creator.public_key)
115
116 if instance.slice.service and instance.slice.service.public_key:
117 pubkeys.add(instance.slice.service.public_key)
118
119 nics=[]
120
121 # handle ports the were created by the user
122 port_ids=[]
123 for port in Port.objects.filter(instance=instance):
124 if not port.port_id:
125 raise DeferredException("Instance %s waiting on port %s" % (instance, port))
126 nics.append({"kind": "port", "value": port.port_id, "network": port.network})
127
128 # we want to exclude from 'nics' any network that already has a Port
129 existing_port_networks = [port.network for port in Port.objects.filter(instance=instance)]
130
131 networks = [ns.network for ns in NetworkSlice.objects.filter(slice=instance.slice) if ns.network not in existing_port_networks]
132 controller_networks = ControllerNetwork.objects.filter(network__in=networks,
133 controller=instance.node.site_deployment.controller)
134
Scott Baker6c69a122016-12-07 16:08:55 -0800135 for network in networks:
136 if not ControllerNetwork.objects.filter(network=network, controller=instance.node.site_deployment.controller).exists():
137 raise DeferredException("Instance %s Private Network %s lacks ControllerNetwork object" % (instance, network.name))
138
Scott Bakerb63ea792016-08-11 10:24:48 -0700139 #controller_networks = self.sort_controller_networks(controller_networks)
140 for controller_network in controller_networks:
141 # Lenient exception - causes slow backoff
Andy Bavier4435bf02017-02-01 16:17:09 -0500142 if controller_network.network.template.translation == 'none':
Scott Bakerb63ea792016-08-11 10:24:48 -0700143 if not controller_network.net_id:
144 raise DeferredException("Instance %s Private Network %s has no id; Try again later" % (instance, controller_network.network.name))
145 nics.append({"kind": "net", "value": controller_network.net_id, "network": controller_network.network})
146
147 # now include network template
148 network_templates = [network.template.shared_network_name for network in networks \
149 if network.template.shared_network_name]
150
151 #driver = self.driver.client_driver(caller=instance.creator, tenant=instance.slice.name, controller=instance.controllerNetwork)
152 driver = self.driver.admin_driver(tenant='admin', controller=instance.node.site_deployment.controller)
153 nets = driver.shell.neutron.list_networks()['networks']
154 for net in nets:
155 if net['name'] in network_templates:
156 nics.append({"kind": "net", "value": net['id'], "network": None})
157
158 if (not nics):
159 for net in nets:
160 if net['name']=='public':
161 nics.append({"kind": "net", "value": net['id'], "network": None})
162
163 nics = self.sort_nics(nics)
164
165 image_name = None
166 controller_images = instance.image.controllerimages.filter(controller=instance.node.site_deployment.controller)
167 if controller_images:
168 image_name = controller_images[0].image.name
169 logger.info("using image from ControllerImage object: " + str(image_name))
170
171 if image_name is None:
172 controller_driver = self.driver.admin_driver(controller=instance.node.site_deployment.controller)
173 images = controller_driver.shell.glanceclient.images.list()
174 for image in images:
175 if image.name == instance.image.name or not image_name:
176 image_name = image.name
177 logger.info("using image from glance: " + str(image_name))
178
179 try:
180 legacy = Config().observer_legacy
181 except:
182 legacy = False
183
184 if (legacy):
185 host_filter = instance.node.name.split('.',1)[0]
186 else:
187 host_filter = instance.node.name.strip()
188
189 availability_zone_filter = 'nova:%s'%host_filter
190 instance_name = '%s-%d'%(instance.slice.name,instance.id)
191 self.instance_name = instance_name
192
193 userData = self.get_userdata(instance, pubkeys)
194 if instance.userData:
195 userData += instance.userData
196
197 controller = instance.node.site_deployment.controller
198 fields = {'endpoint':controller.auth_url,
199 'endpoint_v3': controller.auth_url_v3,
200 'domain': controller.domain,
201 'admin_user': instance.creator.email,
202 'admin_password': instance.creator.remote_password,
203 'project_name': instance.slice.name,
204 'tenant': instance.slice.name,
205 'tenant_description': instance.slice.description,
206 'name':instance_name,
207 'ansible_tag':instance_name,
208 'availability_zone': availability_zone_filter,
209 'image_name':image_name,
210 'flavor_name':instance.flavor.name,
211 'nics':nics,
212 'meta':metadata_update,
213 'user_data':r'%s'%escape(userData)}
214 return fields
215
216
217 def map_sync_outputs(self, instance, res):
218 instance_id = res[0]['openstack']['OS-EXT-SRV-ATTR:instance_name']
219 instance_uuid = res[0]['id']
220
221 try:
222 hostname = res[0]['openstack']['OS-EXT-SRV-ATTR:hypervisor_hostname']
223 ip = socket.gethostbyname(hostname)
224 instance.ip = ip
225 except:
226 pass
227
228 instance.instance_id = instance_id
229 instance.instance_uuid = instance_uuid
230 instance.instance_name = self.instance_name
231 instance.save()
232
233
234 def map_delete_inputs(self, instance):
235 controller_register = json.loads(instance.node.site_deployment.controller.backend_register)
236
237 if (controller_register.get('disabled',False)):
238 raise InnocuousException('Controller %s is disabled'%instance.node.site_deployment.controller.name)
239
240 instance_name = '%s-%d'%(instance.slice.name,instance.id)
241 controller = instance.node.site_deployment.controller
242 input = {'endpoint':controller.auth_url,
243 'admin_user': instance.creator.email,
244 'admin_password': instance.creator.remote_password,
Andy Bavier9e65a352016-09-30 15:39:02 -0400245 'project_name': instance.slice.name,
Scott Bakerb63ea792016-08-11 10:24:48 -0700246 'tenant': instance.slice.name,
247 'tenant_description': instance.slice.description,
248 'name':instance_name,
249 'ansible_tag':instance_name,
250 'delete': True}
251 return input