blob: a2109ad5ef620d39dd11003a2ccea83367ac5a8e [file] [log] [blame]
Sapan Bhatia037c9472016-01-14 11:44:43 -05001import hashlib
2import os
3import socket
4import sys
5import base64
6import time
7from django.db.models import F, Q
8from xos.config import Config
Sapan Bhatia77bb93a2016-01-14 17:05:48 -05009from synchronizers.base.syncstep import SyncStep
10from synchronizers.base.ansible import run_template_ssh
Sapan Bhatia037c9472016-01-14 11:44:43 -050011from core.models import Service, Slice, ControllerSlice, ControllerUser
Scott Bakerb1c0e722016-01-15 07:57:33 -080012from xos.logger import Logger, logging
Sapan Bhatia037c9472016-01-14 11:44:43 -050013
14logger = Logger(level=logging.INFO)
15
16class SyncInstanceUsingAnsible(SyncStep):
17 # All of the following should be defined for classes derived from this
Scott Baker4c052b22016-02-11 11:08:42 -080018 # base class. Examples below use VSGTenant.
Sapan Bhatia037c9472016-01-14 11:44:43 -050019
Scott Baker4c052b22016-02-11 11:08:42 -080020 # provides=[VSGTenant]
21 # observes=VSGTenant
Sapan Bhatia037c9472016-01-14 11:44:43 -050022 # requested_interval=0
23 # template_name = "sync_vcpetenant.yaml"
24 # service_key_name = "/opt/xos/observers/vcpe/vcpe_private_key"
25
26 def __init__(self, **args):
27 SyncStep.__init__(self, **args)
28
Scott Bakerdf540bc2016-02-10 12:41:10 -080029 def skip_ansible_fields(self, o):
30 # Return True if the instance processing and get_ansible_fields stuff
31 # should be skipped. This hook is primarily for the OnosApp
32 # sync step, so it can do its external REST API sync thing.
33 return False
34
Sapan Bhatia037c9472016-01-14 11:44:43 -050035 def defer_sync(self, o, reason):
36 logger.info("defer object %s due to %s" % (str(o), reason))
37 raise Exception("defer object %s due to %s" % (str(o), reason))
38
39 def get_extra_attributes(self, o):
40 # This is a place to include extra attributes that aren't part of the
41 # object itself.
42
43 return {}
44
45 def get_instance(self, o):
46 # We need to know what instance is associated with the object. Let's
47 # assume 'o' has a field called 'instance'. If the field is called
48 # something else, or if custom logic is needed, then override this
49 # method.
50
51 return o.instance
52
Scott Baker268e2aa2016-02-10 12:23:53 -080053 def get_external_sync(self, o):
54 hostname = getattr(o, "external_hostname", None)
55 container = getattr(o, "external_container", None)
56 if hostname and container:
57 return (hostname, container)
58 else:
59 return None
60
Sapan Bhatia037c9472016-01-14 11:44:43 -050061 def run_playbook(self, o, fields, template_name=None):
62 if not template_name:
63 template_name = self.template_name
64 tStart = time.time()
65 run_template_ssh(template_name, fields)
66 logger.info("playbook execution time %d" % int(time.time()-tStart))
67
68 def pre_sync_hook(self, o, fields):
69 pass
70
71 def post_sync_hook(self, o, fields):
72 pass
73
74 def sync_fields(self, o, fields):
75 self.run_playbook(o, fields)
76
77 def prepare_record(self, o):
78 pass
79
80 def get_node(self,o):
81 return o.node
82
83 def get_node_key(self, node):
84 return "/root/setup/node_key"
85
86 def get_ansible_fields(self, instance):
87 # return all of the fields that tell Ansible how to talk to the context
88 # that's setting up the container.
89
90 if (instance.isolation == "vm"):
91 # legacy where container was configured by sync_vcpetenant.py
92
93 fields = { "instance_name": instance.name,
94 "hostname": instance.node.name,
95 "instance_id": instance.instance_id,
96 "username": "ubuntu",
Scott Bakerac785432016-02-10 15:25:08 -080097 "ssh_ip": instance.get_ssh_ip(),
Sapan Bhatia037c9472016-01-14 11:44:43 -050098 }
99 key_name = self.service_key_name
100 elif (instance.isolation == "container"):
101 # container on bare metal
102 node = self.get_node(instance)
103 hostname = node.name
104 fields = { "hostname": hostname,
105 "baremetal_ssh": True,
106 "instance_name": "rootcontext",
107 "username": "root",
108 "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
Scott Bakerac785432016-02-10 15:25:08 -0800109 # ssh_ip is not used for container-on-metal
Sapan Bhatia037c9472016-01-14 11:44:43 -0500110 }
111 key_name = self.get_node_key(node)
112 else:
113 # container in a VM
114 if not instance.parent:
115 raise Exception("Container-in-VM has no parent")
116 if not instance.parent.instance_id:
117 raise Exception("Container-in-VM parent is not yet instantiated")
118 if not instance.parent.slice.service:
119 raise Exception("Container-in-VM parent has no service")
120 if not instance.parent.slice.service.private_key_fn:
121 raise Exception("Container-in-VM parent service has no private_key_fn")
122 fields = { "hostname": instance.parent.node.name,
123 "instance_name": instance.parent.name,
124 "instance_id": instance.parent.instance_id,
125 "username": "ubuntu",
Scott Bakerac785432016-02-10 15:25:08 -0800126 "ssh_ip": instance.parent.get_ssh_ip(),
Sapan Bhatia037c9472016-01-14 11:44:43 -0500127 "container_name": "%s-%s" % (instance.slice.name, str(instance.id))
128 }
129 key_name = instance.parent.slice.service.private_key_fn
130
131 if not os.path.exists(key_name):
132 raise Exception("Node key %s does not exist" % key_name)
133
134 key = file(key_name).read()
135
136 fields["private_key"] = key
137
138 # now the ceilometer stuff
139
140 cslice = ControllerSlice.objects.get(slice=instance.slice)
141 if not cslice:
142 raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
143
144 cuser = ControllerUser.objects.get(user=instance.creator)
145 if not cuser:
146 raise Exception("Controller user object for %s does not exist" % instance.creator)
147
148 fields.update({"keystone_tenant_id": cslice.tenant_id,
149 "keystone_user_id": cuser.kuser_id,
150 "rabbit_user": instance.controller.rabbit_user,
151 "rabbit_password": instance.controller.rabbit_password,
152 "rabbit_host": instance.controller.rabbit_host})
153
154 return fields
155
156 def sync_record(self, o):
157 logger.info("sync'ing object %s" % str(o))
158
159 self.prepare_record(o)
160
Scott Bakerdf540bc2016-02-10 12:41:10 -0800161 if self.skip_ansible_fields(o):
162 fields = {}
Sapan Bhatia037c9472016-01-14 11:44:43 -0500163 else:
Scott Bakerdf540bc2016-02-10 12:41:10 -0800164 if self.get_external_sync(o):
165 # sync to some external host
Sapan Bhatia037c9472016-01-14 11:44:43 -0500166
Scott Bakerdf540bc2016-02-10 12:41:10 -0800167 # UNTESTED
Sapan Bhatia037c9472016-01-14 11:44:43 -0500168
Scott Bakerdf540bc2016-02-10 12:41:10 -0800169 (hostname, container_name) = self.get_external_sync(o)
170 fields = { "hostname": hostname,
171 "baremetal_ssh": True,
172 "instance_name": "rootcontext",
173 "username": "root",
174 "container_name": container_name
175 }
176 key_name = self.get_node_key(node)
177 if not os.path.exists(key_name):
178 raise Exception("Node key %s does not exist" % key_name)
179
180 key = file(key_name).read()
181
182 fields["private_key"] = key
183 # TO DO: Ceilometer stuff
184 else:
185 instance = self.get_instance(o)
186 # sync to an XOS instance
187 if not instance:
188 self.defer_sync(o, "waiting on instance")
189 return
190
191 if not instance.instance_name:
192 self.defer_sync(o, "waiting on instance.instance_name")
193 return
194
195 fields = self.get_ansible_fields(instance)
Sapan Bhatia037c9472016-01-14 11:44:43 -0500196
Scott Baker268e2aa2016-02-10 12:23:53 -0800197 fields["ansible_tag"] = o.__class__.__name__ + "_" + str(o.id)
Sapan Bhatia037c9472016-01-14 11:44:43 -0500198
199 # If 'o' defines a 'sync_attributes' list, then we'll copy those
200 # attributes into the Ansible recipe's field list automatically.
201 if hasattr(o, "sync_attributes"):
202 for attribute_name in o.sync_attributes:
203 fields[attribute_name] = getattr(o, attribute_name)
204
205 fields.update(self.get_extra_attributes(o))
206
207 self.sync_fields(o, fields)
208
209 o.save()
210
211 def delete_record(self, o):
212 try:
213 controller = o.get_controller()
214 controller_register = json.loads(o.node.site_deployment.controller.backend_register)
215
216 if (controller_register.get('disabled',False)):
217 raise InnocuousException('Controller %s is disabled'%o.node.site_deployment.controller.name)
218 except AttributeError:
219 pass
220
221 instance = self.get_instance(o)
222 if isinstance(instance, basestring):
223 # sync to some external host
224
225 # XXX - this probably needs more work...
226
227 fields = { "hostname": instance,
228 "instance_id": "ubuntu", # this is the username to log into
229 "private_key": service.key,
230 }
231 else:
232 # sync to an XOS instance
233 fields = self.get_ansible_fields(instance)
234
235 fields["ansible_tag"] = o.__class__.__name__ + "_" + str(o.id)
236
237 # If 'o' defines a 'sync_attributes' list, then we'll copy those
238 # attributes into the Ansible recipe's field list automatically.
239 if hasattr(o, "sync_attributes"):
240 for attribute_name in o.sync_attributes:
241 fields[attribute_name] = getattr(o, attribute_name)
242
243 fields.update(self.map_delete_inputs(o))
244
245 fields['delete']=True
246 res = self.run_playbook(o,fields)
247 try:
248 self.map_delete_outputs(o,res)
249 except AttributeError:
250 pass