blob: bbe72942ff3c5f55202bef8bcf085aa2481d2601 [file] [log] [blame]
Scott Baker7581c252016-05-27 13:12:47 -07001import os
2import base64
Scott Bakeraab8a292016-06-03 16:32:45 -07003import jinja2
Scott Baker7581c252016-05-27 13:12:47 -07004import string
5import sys
Scott Bakerb0eb23e2016-06-01 16:08:04 -07006import urllib2
7import urlparse
Scott Baker7581c252016-05-27 13:12:47 -07008import xmlrpclib
9
Scott Baker7581c252016-05-27 13:12:47 -070010from xos.config import Config
Scott Bakeraab8a292016-06-03 16:32:45 -070011from core.models import Service, ServiceController, ServiceControllerResource, XOS
Scott Baker7581c252016-05-27 13:12:47 -070012from xos.logger import Logger, logging
13
Scott Baker2b3b1ad2016-08-02 22:29:18 -070014from django.utils import timezone
15
Scott Baker7581c252016-05-27 13:12:47 -070016logger = Logger(level=logging.INFO)
17
Scott Bakerfb4c98c2016-08-08 17:45:45 -070018def add_unique(list, item):
19 if not item in list:
20 list.append(item)
21
Scott Baker7581c252016-05-27 13:12:47 -070022class XOSBuilder(object):
Scott Bakerb44940a2016-06-14 09:12:17 -070023 UI_KINDS=["models", "admin", "admin_template", "django_library", "rest_service", "rest_tenant", "tosca_custom_types", "tosca_resource","public_key"]
Scott Baker18c89172016-06-02 16:40:25 -070024 SYNC_CONTROLLER_KINDS=["synchronizer", "private_key", "public_key"]
Scott Baker513ea452016-06-02 16:03:26 -070025 SYNC_ALLCONTROLLER_KINDS=["models", "django_library"]
Scott Bakerb0eb23e2016-06-01 16:08:04 -070026
Scott Baker7581c252016-05-27 13:12:47 -070027 def __init__(self):
Scott Bakerb0eb23e2016-06-01 16:08:04 -070028 self.source_sync_image = "xosproject/xos-synchronizer-openstack"
29 self.build_dir = "/opt/xos/BUILD/"
Scott Baker2b3b1ad2016-08-02 22:29:18 -070030 self.build_tainted = False
Scott Bakerb0eb23e2016-06-01 16:08:04 -070031
32 # stuff that has to do with downloading
33
Scott Bakerfb4c98c2016-08-08 17:45:45 -070034 def get_base_dest_dir(self, scr):
Scott Bakerb0eb23e2016-06-01 16:08:04 -070035 xos_base = "opt/xos"
36 service_name = scr.service_controller.name
37 base_dirs = {"models": "%s/services/%s/" % (xos_base, service_name),
38 "admin": "%s/services/%s/" % (xos_base, service_name),
Scott Bakere360ff82016-06-13 10:55:23 -070039 "admin_template": "%s/services/%s/templates/" % (xos_base, service_name),
Scott Bakerb0eb23e2016-06-01 16:08:04 -070040 "django_library": "%s/services/%s/" % (xos_base, service_name),
41 "synchronizer": "%s/synchronizers/%s/" % (xos_base, service_name),
42 "tosca_custom_types": "%s/tosca/custom_types/" % (xos_base),
Scott Baker18c89172016-06-02 16:40:25 -070043 "tosca_resource": "%s/tosca/resources/" % (xos_base),
Scott Baker16cfb9c2016-06-07 15:37:03 -070044 "rest_service": "%s/api/service/" % (xos_base),
Scott Baker7806f622016-06-07 17:45:04 -070045 "rest_tenant": "%s/api/tenant/" % (xos_base),
Scott Bakercfd53722016-06-13 19:02:43 -070046 "private_key": "%s/services/%s/keys/" % (xos_base, service_name),
Scott Baker18c89172016-06-02 16:40:25 -070047 "public_key": "%s/services/%s/keys/" % (xos_base, service_name)}
Scott Baker260a21c2016-06-13 10:42:49 -070048 dest_dir = base_dirs[scr.kind]
49
Scott Bakerfb4c98c2016-08-08 17:45:45 -070050 return dest_dir
51
52 def get_dest_dir(self, scr):
53 dest_dir = self.get_base_dest_dir(scr)
54
Scott Baker260a21c2016-06-13 10:42:49 -070055 if scr.subdirectory:
56 dest_dir = os.path.join(dest_dir, scr.subdirectory)
57
58 return dest_dir
Scott Bakerb0eb23e2016-06-01 16:08:04 -070059
60 def get_build_fn(self, scr):
61 dest_dir = self.get_dest_dir(scr)
62 dest_fn = os.path.split(urlparse.urlsplit(scr.full_url).path)[-1]
63 return os.path.join(dest_dir, dest_fn)
64
65 def get_download_fn(self, scr):
66 dest_fn = self.get_build_fn(scr)
67 return os.path.join(self.build_dir, dest_fn)
68
69 def read_manifest(self, scr, fn):
70 manifest = []
71 manifest_lines = file(fn).readlines()
72 manifest_lines = [x.strip() for x in manifest_lines]
Scott Bakerc2a2fe92016-06-02 13:19:10 -070073 manifest_lines = [x for x in manifest_lines if x]
Scott Bakerb0eb23e2016-06-01 16:08:04 -070074 for line in manifest_lines:
Scott Bakerc2a2fe92016-06-02 13:19:10 -070075 url_parts = urlparse.urlsplit(scr.full_url)
76 new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]),line)
77 url = urlparse.urlunsplit( (url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment) )
Scott Bakerb0eb23e2016-06-01 16:08:04 -070078
79 build_fn = os.path.join(self.get_dest_dir(scr), line)
Scott Bakerc2a2fe92016-06-02 13:19:10 -070080 download_fn = os.path.join(self.build_dir, build_fn)
Scott Bakerb0eb23e2016-06-01 16:08:04 -070081
82 manifest.append( (url, download_fn, build_fn) )
83 return manifest
84
85 def download_file(self, url, dest_fn):
86 logger.info("Download %s to %s" % (url, dest_fn))
87 if not os.path.exists(os.path.dirname(dest_fn)):
88 os.makedirs(os.path.dirname(dest_fn))
89 obj = urllib2.urlopen(url)
90 file(dest_fn,"w").write(obj.read())
91
Scott Bakerc2a2fe92016-06-02 13:19:10 -070092 # make python files executable
93 if dest_fn.endswith(".py"): # and contents.startswith("#!"):
94 os.chmod(dest_fn, 0755)
95
Scott Bakerb0eb23e2016-06-01 16:08:04 -070096 def download_resource(self, scr):
97 if scr.format == "manifest":
98 manifest_fn = self.get_download_fn(scr)
99 self.download_file(scr.full_url, manifest_fn)
Scott Bakerc2a2fe92016-06-02 13:19:10 -0700100 manifest = self.read_manifest(scr, manifest_fn)
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700101 for (url, download_fn, build_fn) in manifest:
102 self.download_file(url, download_fn)
103 else:
104 self.download_file(scr.full_url, self.get_download_fn(scr))
105
Scott Bakercf3586f2016-06-14 00:22:44 -0700106# XXX docker creates a new container and commits it for every single COPY
107# line in the dockerfile. This causes services with many files (for example,
108# vsg) to take ~ 10-15 minutes to build the docker file. So instead we'll copy
109# the whole build directory, and then run a script that copies the files
110# we want.
111
112# def get_docker_lines(self, scr):
113# if scr.format == "manifest":
114# manifest_fn = self.get_download_fn(scr)
115# manifest = self.read_manifest(scr, manifest_fn)
116# lines = []
117# for (url, download_fn, build_fn) in manifest:
118# script.append("mkdir -p
119# #lines.append("COPY %s /%s" % (build_fn, build_fn))
120# return lines
121# else:
122# build_fn = self.get_build_fn(scr)
123# #return ["COPY %s /%s" % (build_fn, build_fn)]
124
125# def get_controller_docker_lines(self, controller, kinds):
126# need_service_init_py = False
127# dockerfile=[]
128# for scr in controller.service_controller_resources.all():
129# if scr.kind in kinds:
130# lines = self.get_docker_lines(scr)
131# dockerfile = dockerfile + lines
132# if scr.kind in ["admin", "models"]:
133# need_service_init_py = True
134#
135# if need_service_init_py:
136# file(os.path.join(self.build_dir, "opt/xos/empty__init__.py"),"w").write("")
137# dockerfile.append("COPY opt/xos/empty__init__.py /opt/xos/services/%s/__init__.py" % controller.name)
138#
139# return dockerfile
140
141 def get_script_lines(self, scr):
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700142 if scr.format == "manifest":
143 manifest_fn = self.get_download_fn(scr)
144 manifest = self.read_manifest(scr, manifest_fn)
145 lines = []
146 for (url, download_fn, build_fn) in manifest:
Scott Bakercf3586f2016-06-14 00:22:44 -0700147 lines.append("mkdir -p /%s" % os.path.dirname(build_fn))
148 lines.append("cp /build/%s /%s" % (build_fn, build_fn))
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700149 return lines
150 else:
151 build_fn = self.get_build_fn(scr)
Scott Bakercf3586f2016-06-14 00:22:44 -0700152 return ["mkdir -p /%s" % os.path.dirname(build_fn),
153 "cp /build/%s /%s" % (build_fn, build_fn)]
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700154
Scott Bakercf3586f2016-06-14 00:22:44 -0700155 def get_controller_script_lines(self, controller, kinds):
Scott Baker7806f622016-06-07 17:45:04 -0700156 need_service_init_py = False
Scott Bakercf3586f2016-06-14 00:22:44 -0700157 script=[]
Scott Bakerfb4c98c2016-08-08 17:45:45 -0700158 inits=[]
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700159 for scr in list(controller.service_controller_resources.all()):
160 if not (scr.kind in kinds):
161 continue
162
163 # Check and see if the resource we're trying to install has
164 # disappeared. This may happen if the onboarding synchronizer
165 # container has been destroyed and restarted. In this case, flag
166 # the resource for re-download, and set the build_tainted bit
167 # so we can throw an exception after we've evaluated all
168 # resources.
169
170 download_fn = self.get_download_fn(scr)
171 if not os.path.exists(download_fn):
172 logger.info("File %s is missing; dirtying the resource" % download_fn)
173 scr.backend_status = "2 - download_fn is missing"
174 scr.updated = timezone.now()
175 scr.save(update_fields=['backend_status', 'updated'])
176 self.build_tainted = True
177 continue
178
179 lines = self.get_script_lines(scr)
180 script = script + lines
181
Scott Bakerfb4c98c2016-08-08 17:45:45 -0700182 # compute the set of __init__.py files that we will need
183 if scr.kind in ["admin", "models", "rest_service", "rest_tenant"]:
184 dir = self.get_base_dest_dir(scr)
185 add_unique(inits, dir)
Scott Baker7806f622016-06-07 17:45:04 -0700186
Scott Bakerfb4c98c2016-08-08 17:45:45 -0700187 if scr.subdirectory:
188 for part in scr.subdirectory.split("/"):
189 dir = os.path.join(dir, part)
190 add_unique(inits, dir)
191
192 for init in inits:
193 script.append("echo > %s" % os.path.join("/",init,"__init__.py"))
Scott Baker7806f622016-06-07 17:45:04 -0700194
Scott Bakercf3586f2016-06-14 00:22:44 -0700195 return script
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700196
Scott Baker7806f622016-06-07 17:45:04 -0700197 def check_controller_unready(self, controller):
198 unready_resources=[]
199 for scr in controller.service_controller_resources.all():
200 if (not scr.backend_status) or (not scr.backend_status.startswith("1")):
201 unready_resources.append(scr)
202
203 return unready_resources
204
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700205 # stuff that has to do with building
206
Scott Bakercf3586f2016-06-14 00:22:44 -0700207 def create_xos_app_data(self, name, script, app_list, migration_list):
Scott Bakerae196c32016-06-01 23:29:22 -0700208 if not os.path.exists(os.path.join(self.build_dir,"opt/xos/xos")):
209 os.makedirs(os.path.join(self.build_dir,"opt/xos/xos"))
210
211 if app_list:
Scott Bakercf3586f2016-06-14 00:22:44 -0700212 script.append("mkdir -p /opt/xos/xos")
213 script.append("cp /build/opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
214 #dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
Scott Baker513ea452016-06-02 16:03:26 -0700215 file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_app_list") % name, "w").write("\n".join(app_list)+"\n")
Scott Bakerae196c32016-06-01 23:29:22 -0700216
217 if migration_list:
Scott Bakercf3586f2016-06-14 00:22:44 -0700218 script.append("mkdir -p /opt/xos/xos")
219 script.append("cp /build/opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
220 #dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
Scott Baker513ea452016-06-02 16:03:26 -0700221 file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_migration_list") % name, "w").write("\n".join(migration_list)+"\n")
Scott Bakerae196c32016-06-01 23:29:22 -0700222
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700223 def create_ui_dockerfile(self):
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700224 self.build_tainted = False
Scott Bakerd2ba7492016-06-15 14:33:34 -0700225 xos = XOS.objects.all()[0]
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700226 dockerfile_fn = "Dockerfile.UI"
227
Scott Bakerae196c32016-06-01 23:29:22 -0700228 app_list = []
229 migration_list = []
230
Scott Bakerd2ba7492016-06-15 14:33:34 -0700231 dockerfile = ["FROM %s" % xos.source_ui_image]
Scott Bakercf3586f2016-06-14 00:22:44 -0700232 script = []
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700233 for controller in ServiceController.objects.all():
Scott Baker7806f622016-06-07 17:45:04 -0700234 if self.check_controller_unready(controller):
235 logger.warning("Controller %s has unready resources" % str(controller))
236 continue
237
Scott Bakercf3586f2016-06-14 00:22:44 -0700238 #dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS)
239 script = script + self.get_controller_script_lines(controller, self.UI_KINDS)
Scott Bakerae196c32016-06-01 23:29:22 -0700240 if controller.service_controller_resources.filter(kind="models").exists():
241 app_list.append("services." + controller.name)
242 migration_list.append(controller.name)
243
Scott Bakercf3586f2016-06-14 00:22:44 -0700244 self.create_xos_app_data("ui", script, app_list, migration_list)
245
246 file(os.path.join(self.build_dir, "install-xos.sh"), "w").write("\n".join(script)+"\n")
247 dockerfile.append("COPY . /build/")
248 dockerfile.append("RUN bash /build/install-xos.sh")
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700249
250 file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
251
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700252 if self.build_tainted:
253 raise Exception("Build was tainted due to errors")
254
Scott Baker8d252b62016-06-01 23:08:04 -0700255 return {"dockerfile_fn": dockerfile_fn,
256 "docker_image_name": "xosproject/xos-ui"}
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700257
258 def create_synchronizer_dockerfile(self, controller):
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700259 self.build_tainted = False
260
Scott Baker513ea452016-06-02 16:03:26 -0700261 # bake in the synchronizer from this controller
Scott Bakercf3586f2016-06-14 00:22:44 -0700262 sync_lines = self.get_controller_script_lines(controller, self.SYNC_CONTROLLER_KINDS)
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700263
264 if self.build_tainted:
265 raise Exception("Build was tainted due to errors")
266
267 # If there's no sync_lines for this ServiceController, then it must not
268 # have a synchronizer.
Scott Baker513ea452016-06-02 16:03:26 -0700269 if not sync_lines:
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700270 return None
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700271
272 dockerfile_fn = "Dockerfile.%s" % controller.name
273 dockerfile = ["FROM %s" % self.source_sync_image]
Scott Bakercf3586f2016-06-14 00:22:44 -0700274 script = []
Scott Baker513ea452016-06-02 16:03:26 -0700275
276 # Now bake in models from this controller as well as the others
277 # It's important to bake all services in, because some services'
278 # synchronizers may depend on models from another service.
279 app_list = []
280 for c in ServiceController.objects.all():
Scott Bakercf3586f2016-06-14 00:22:44 -0700281 #dockerfile = dockerfile + self.get_controller_docker_lines(c, self.SYNC_ALLCONTROLLER_KINDS)
282 script = script + self.get_controller_script_lines(c, self.SYNC_ALLCONTROLLER_KINDS)
Scott Baker513ea452016-06-02 16:03:26 -0700283 if controller.service_controller_resources.filter(kind="models").exists():
Scott Bakercf3586f2016-06-14 00:22:44 -0700284 app_list.append("services." + c.name)
Scott Baker513ea452016-06-02 16:03:26 -0700285
Scott Bakercf3586f2016-06-14 00:22:44 -0700286 self.create_xos_app_data(controller.name, script, app_list, None)
Scott Baker513ea452016-06-02 16:03:26 -0700287
Scott Bakercf3586f2016-06-14 00:22:44 -0700288 script = script + sync_lines
289
290 file(os.path.join(self.build_dir, "install-%s.sh" % controller.name), "w").write("\n".join(script)+"\n")
291 dockerfile.append("COPY . /build/")
292 dockerfile.append("RUN bash /build/install-%s.sh" % controller.name)
293
Scott Bakerc2a2fe92016-06-02 13:19:10 -0700294 file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700295
Scott Baker2b3b1ad2016-08-02 22:29:18 -0700296 if self.build_tainted:
297 raise Exception("Build was tainted due to errors")
298
Scott Baker8d252b62016-06-01 23:08:04 -0700299 return {"dockerfile_fn": dockerfile_fn,
300 "docker_image_name": "xosproject/xos-synchronizer-%s" % controller.name}
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700301
Scott Bakeraab8a292016-06-03 16:32:45 -0700302 def create_docker_compose(self):
303 xos = XOS.objects.all()[0]
304
Scott Baker29b677b2016-06-07 10:20:00 -0700305 volume_list = []
306 for volume in xos.volumes.all():
307 volume_list.append({"host_path": volume.host_path,
308 "container_path": volume.container_path,
309 "read_only": volume.read_only})
Scott Bakeraab8a292016-06-03 16:32:45 -0700310
311 containers = {}
312
Scott Bakerf68418f2016-06-16 15:30:09 -0700313# containers["xos_db"] = \
314# {"image": "xosproject/xos-postgres",
315# "expose": [5432]}
Scott Bakeraab8a292016-06-03 16:32:45 -0700316
317 containers["xos_ui"] = \
318 {"image": "xosproject/xos-ui",
319 "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
320 "ports": {"%d"%xos.ui_port : "%d"%xos.ui_port},
Scott Bakerf68418f2016-06-16 15:30:09 -0700321 #"links": ["xos_db"],
Scott Baker0c0139a2016-06-22 14:54:23 -0700322 "external_links": ["%s:%s" % (xos.db_container_name, "xos_db")],
Scott Bakeraab8a292016-06-03 16:32:45 -0700323 "volumes": volume_list}
324
Scott Baker584cd892016-06-09 16:10:31 -0700325# containers["xos_bootstrap_ui"] = {"image": "xosproject/xos",
326# "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port,
327# "ports": {"%d"%xos.bootstrap_ui_port : "%d"%xos.bootstrap_ui_port},
Scott Baker0c0139a2016-06-22 14:54:23 -0700328# #"external_links": ["%s:%s" % (xos.db_container_name, "xos_db")],
Scott Baker584cd892016-06-09 16:10:31 -0700329# "links": ["xos_db"],
330# "volumes": volume_list}
Scott Bakerbe41a122016-06-06 10:40:40 -0700331
Scott Baker057a6a12016-06-13 15:45:55 -0700332 if not xos.frontend_only:
333 for c in ServiceController.objects.all():
334 if self.check_controller_unready(c):
335 logger.warning("Controller %s has unready resources" % str(c))
336 continue
Scott Baker7806f622016-06-07 17:45:04 -0700337
Scott Baker07615902016-06-13 21:41:52 -0700338 if c.service_controller_resources.filter(kind="synchronizer").exists():
339 if c.synchronizer_run and c.synchronizer_config:
Scott Bakercf3586f2016-06-14 00:22:44 -0700340 command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; python ./%s -C %s"' % (c.name, c.synchronizer_run, c.synchronizer_config)
Scott Baker07615902016-06-13 21:41:52 -0700341 else:
342 command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; bash ./run.sh"' % c.name
343
344 containers["xos_synchronizer_%s" % c.name] = \
345 {"image": "xosproject/xos-synchronizer-%s" % c.name,
346 "command": command,
Scott Baker0c0139a2016-06-22 14:54:23 -0700347 "external_links": ["%s:%s" % (xos.db_container_name, "xos_db")],
Scott Bakerf68418f2016-06-16 15:30:09 -0700348 #"links": ["xos_db"],
Scott Baker07615902016-06-13 21:41:52 -0700349 "volumes": volume_list}
Scott Bakeraab8a292016-06-03 16:32:45 -0700350
351 vars = { "containers": containers }
352
353 template_loader = jinja2.FileSystemLoader( "/opt/xos/synchronizers/onboarding/templates/" )
354 template_env = jinja2.Environment(loader=template_loader)
355 template = template_env.get_template("docker-compose.yml.j2")
356 buffer = template.render(vars)
357
358 if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"):
359 os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose")
360 file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer)
361
Scott Baker8d252b62016-06-01 23:08:04 -0700362# def build_xos(self):
363# dockerfiles=[]
364# dockerfiles.append(self.create_ui_dockerfile())
365#
366# for controller in ServiceController.objects.all():
367# dockerfiles.append(self.create_synchronizer_dockerfile(controller))
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700368
369
370