blob: 51499bd07c814e13c65a5fcb42ce833b79a2becc [file] [log] [blame]
import os
import base64
import jinja2
import string
import sys
import urllib2
import urlparse
import xmlrpclib
from xos.config import Config
from core.models import Service, ServiceController, ServiceControllerResource, XOS
from xos.logger import Logger, logging
logger = Logger(level=logging.INFO)
class XOSBuilder(object):
UI_KINDS=["models", "admin", "admin_template", "django_library", "rest_service", "rest_tenant", "tosca_custom_types", "tosca_resource","public_key"]
SYNC_CONTROLLER_KINDS=["synchronizer", "private_key", "public_key"]
SYNC_ALLCONTROLLER_KINDS=["models", "django_library"]
def __init__(self):
self.source_sync_image = "xosproject/xos-synchronizer-openstack"
self.build_dir = "/opt/xos/BUILD/"
# stuff that has to do with downloading
def get_dest_dir(self, scr):
xos_base = "opt/xos"
service_name = scr.service_controller.name
base_dirs = {"models": "%s/services/%s/" % (xos_base, service_name),
"admin": "%s/services/%s/" % (xos_base, service_name),
"admin_template": "%s/services/%s/templates/" % (xos_base, service_name),
"django_library": "%s/services/%s/" % (xos_base, service_name),
"synchronizer": "%s/synchronizers/%s/" % (xos_base, service_name),
"tosca_custom_types": "%s/tosca/custom_types/" % (xos_base),
"tosca_resource": "%s/tosca/resources/" % (xos_base),
"rest_service": "%s/api/service/" % (xos_base),
"rest_tenant": "%s/api/tenant/" % (xos_base),
"private_key": "%s/services/%s/keys/" % (xos_base, service_name),
"public_key": "%s/services/%s/keys/" % (xos_base, service_name)}
dest_dir = base_dirs[scr.kind]
if scr.subdirectory:
dest_dir = os.path.join(dest_dir, scr.subdirectory)
return dest_dir
def get_build_fn(self, scr):
dest_dir = self.get_dest_dir(scr)
dest_fn = os.path.split(urlparse.urlsplit(scr.full_url).path)[-1]
return os.path.join(dest_dir, dest_fn)
def get_download_fn(self, scr):
dest_fn = self.get_build_fn(scr)
return os.path.join(self.build_dir, dest_fn)
def read_manifest(self, scr, fn):
manifest = []
manifest_lines = file(fn).readlines()
manifest_lines = [x.strip() for x in manifest_lines]
manifest_lines = [x for x in manifest_lines if x]
for line in manifest_lines:
url_parts = urlparse.urlsplit(scr.full_url)
new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]),line)
url = urlparse.urlunsplit( (url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment) )
build_fn = os.path.join(self.get_dest_dir(scr), line)
download_fn = os.path.join(self.build_dir, build_fn)
manifest.append( (url, download_fn, build_fn) )
return manifest
def download_file(self, url, dest_fn):
logger.info("Download %s to %s" % (url, dest_fn))
if not os.path.exists(os.path.dirname(dest_fn)):
os.makedirs(os.path.dirname(dest_fn))
obj = urllib2.urlopen(url)
file(dest_fn,"w").write(obj.read())
# make python files executable
if dest_fn.endswith(".py"): # and contents.startswith("#!"):
os.chmod(dest_fn, 0755)
def download_resource(self, scr):
if scr.format == "manifest":
manifest_fn = self.get_download_fn(scr)
self.download_file(scr.full_url, manifest_fn)
manifest = self.read_manifest(scr, manifest_fn)
for (url, download_fn, build_fn) in manifest:
self.download_file(url, download_fn)
else:
self.download_file(scr.full_url, self.get_download_fn(scr))
# XXX docker creates a new container and commits it for every single COPY
# line in the dockerfile. This causes services with many files (for example,
# vsg) to take ~ 10-15 minutes to build the docker file. So instead we'll copy
# the whole build directory, and then run a script that copies the files
# we want.
# def get_docker_lines(self, scr):
# if scr.format == "manifest":
# manifest_fn = self.get_download_fn(scr)
# manifest = self.read_manifest(scr, manifest_fn)
# lines = []
# for (url, download_fn, build_fn) in manifest:
# script.append("mkdir -p
# #lines.append("COPY %s /%s" % (build_fn, build_fn))
# return lines
# else:
# build_fn = self.get_build_fn(scr)
# #return ["COPY %s /%s" % (build_fn, build_fn)]
# def get_controller_docker_lines(self, controller, kinds):
# need_service_init_py = False
# dockerfile=[]
# for scr in controller.service_controller_resources.all():
# if scr.kind in kinds:
# lines = self.get_docker_lines(scr)
# dockerfile = dockerfile + lines
# if scr.kind in ["admin", "models"]:
# need_service_init_py = True
#
# if need_service_init_py:
# file(os.path.join(self.build_dir, "opt/xos/empty__init__.py"),"w").write("")
# dockerfile.append("COPY opt/xos/empty__init__.py /opt/xos/services/%s/__init__.py" % controller.name)
#
# return dockerfile
def get_script_lines(self, scr):
if scr.format == "manifest":
manifest_fn = self.get_download_fn(scr)
manifest = self.read_manifest(scr, manifest_fn)
lines = []
for (url, download_fn, build_fn) in manifest:
lines.append("mkdir -p /%s" % os.path.dirname(build_fn))
lines.append("cp /build/%s /%s" % (build_fn, build_fn))
return lines
else:
build_fn = self.get_build_fn(scr)
return ["mkdir -p /%s" % os.path.dirname(build_fn),
"cp /build/%s /%s" % (build_fn, build_fn)]
def get_controller_script_lines(self, controller, kinds):
need_service_init_py = False
script=[]
for scr in controller.service_controller_resources.all():
if scr.kind in kinds:
lines = self.get_script_lines(scr)
script = script + lines
if scr.kind in ["admin", "models"]:
need_service_init_py = True
if need_service_init_py:
script.append("echo > /opt/xos/services/%s/__init__.py" % controller.name)
return script
def check_controller_unready(self, controller):
unready_resources=[]
for scr in controller.service_controller_resources.all():
if (not scr.backend_status) or (not scr.backend_status.startswith("1")):
unready_resources.append(scr)
return unready_resources
# stuff that has to do with building
def create_xos_app_data(self, name, script, app_list, migration_list):
if not os.path.exists(os.path.join(self.build_dir,"opt/xos/xos")):
os.makedirs(os.path.join(self.build_dir,"opt/xos/xos"))
if app_list:
script.append("mkdir -p /opt/xos/xos")
script.append("cp /build/opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
#dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_app_list") % name, "w").write("\n".join(app_list)+"\n")
if migration_list:
script.append("mkdir -p /opt/xos/xos")
script.append("cp /build/opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
#dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_migration_list") % name, "w").write("\n".join(migration_list)+"\n")
def create_ui_dockerfile(self):
xos = XOS.objects.all()[0]
dockerfile_fn = "Dockerfile.UI"
app_list = []
migration_list = []
dockerfile = ["FROM %s" % xos.source_ui_image]
script = []
for controller in ServiceController.objects.all():
if self.check_controller_unready(controller):
logger.warning("Controller %s has unready resources" % str(controller))
continue
#dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS)
script = script + self.get_controller_script_lines(controller, self.UI_KINDS)
if controller.service_controller_resources.filter(kind="models").exists():
app_list.append("services." + controller.name)
migration_list.append(controller.name)
self.create_xos_app_data("ui", script, app_list, migration_list)
file(os.path.join(self.build_dir, "install-xos.sh"), "w").write("\n".join(script)+"\n")
dockerfile.append("COPY . /build/")
dockerfile.append("RUN bash /build/install-xos.sh")
file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
return {"dockerfile_fn": dockerfile_fn,
"docker_image_name": "xosproject/xos-ui"}
def create_synchronizer_dockerfile(self, controller):
# bake in the synchronizer from this controller
sync_lines = self.get_controller_script_lines(controller, self.SYNC_CONTROLLER_KINDS)
if not sync_lines:
return []
dockerfile_fn = "Dockerfile.%s" % controller.name
dockerfile = ["FROM %s" % self.source_sync_image]
script = []
# Now bake in models from this controller as well as the others
# It's important to bake all services in, because some services'
# synchronizers may depend on models from another service.
app_list = []
for c in ServiceController.objects.all():
#dockerfile = dockerfile + self.get_controller_docker_lines(c, self.SYNC_ALLCONTROLLER_KINDS)
script = script + self.get_controller_script_lines(c, self.SYNC_ALLCONTROLLER_KINDS)
if controller.service_controller_resources.filter(kind="models").exists():
app_list.append("services." + c.name)
self.create_xos_app_data(controller.name, script, app_list, None)
script = script + sync_lines
file(os.path.join(self.build_dir, "install-%s.sh" % controller.name), "w").write("\n".join(script)+"\n")
dockerfile.append("COPY . /build/")
dockerfile.append("RUN bash /build/install-%s.sh" % controller.name)
file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
return {"dockerfile_fn": dockerfile_fn,
"docker_image_name": "xosproject/xos-synchronizer-%s" % controller.name}
def create_docker_compose(self):
xos = XOS.objects.all()[0]
volume_list = []
for volume in xos.volumes.all():
volume_list.append({"host_path": volume.host_path,
"container_path": volume.container_path,
"read_only": volume.read_only})
containers = {}
containers["xos_db"] = \
{"image": "xosproject/xos-postgres",
"expose": [5432]}
db_container_name = xos.docker_project_name + "_xos_db_1"
containers["xos_ui"] = \
{"image": "xosproject/xos-ui",
"command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
"ports": {"%d"%xos.ui_port : "%d"%xos.ui_port},
"links": ["xos_db"],
#"external_links": [db_container_name],
"volumes": volume_list}
# containers["xos_bootstrap_ui"] = {"image": "xosproject/xos",
# "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port,
# "ports": {"%d"%xos.bootstrap_ui_port : "%d"%xos.bootstrap_ui_port},
# #"external_links": [db_container_name],
# "links": ["xos_db"],
# "volumes": volume_list}
if not xos.frontend_only:
for c in ServiceController.objects.all():
if self.check_controller_unready(c):
logger.warning("Controller %s has unready resources" % str(c))
continue
if c.service_controller_resources.filter(kind="synchronizer").exists():
if c.synchronizer_run and c.synchronizer_config:
command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; python ./%s -C %s"' % (c.name, c.synchronizer_run, c.synchronizer_config)
else:
command = 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; bash ./run.sh"' % c.name
containers["xos_synchronizer_%s" % c.name] = \
{"image": "xosproject/xos-synchronizer-%s" % c.name,
"command": command,
#"external_links": [db_container_name],
"links": ["xos_db"],
"volumes": volume_list}
vars = { "containers": containers }
template_loader = jinja2.FileSystemLoader( "/opt/xos/synchronizers/onboarding/templates/" )
template_env = jinja2.Environment(loader=template_loader)
template = template_env.get_template("docker-compose.yml.j2")
buffer = template.render(vars)
if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"):
os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose")
file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer)
# def build_xos(self):
# dockerfiles=[]
# dockerfiles.append(self.create_ui_dockerfile())
#
# for controller in ServiceController.objects.all():
# dockerfiles.append(self.create_synchronizer_dockerfile(controller))