From c1a31ebbb3d44ac9aa3f4a0b98e00650d2259968 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 17 May 2018 12:42:08 +0200 Subject: [PATCH 1/3] ozwillo: keep deployment request state (#23885) Fields added to OzwilloInstance: * state * deploy_data * created * modified First migration initialize all instances with the state DEPLOYED but new instance will get the state NEW (change done in second migration). OzwilloInstance was registered in the admin for managing deployments. --- hobo/contrib/ozwillo/admin.py | 10 + hobo/contrib/ozwillo/management/__init__.py | 0 .../ozwillo/management/commands/__init__.py | 0 .../management/commands/ozwillo_worker.py | 68 +++++ .../migrations/0002_auto_20180517_1047.py | 52 ++++ hobo/contrib/ozwillo/models.py | 256 +++++++++++++++++- hobo/contrib/ozwillo/views.py | 218 ++------------- .../management/commands/runscript.py | 2 +- 8 files changed, 414 insertions(+), 192 deletions(-) create mode 100644 hobo/contrib/ozwillo/admin.py create mode 100644 hobo/contrib/ozwillo/management/__init__.py create mode 100644 hobo/contrib/ozwillo/management/commands/__init__.py create mode 100644 hobo/contrib/ozwillo/management/commands/ozwillo_worker.py create mode 100644 hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py diff --git a/hobo/contrib/ozwillo/admin.py b/hobo/contrib/ozwillo/admin.py new file mode 100644 index 0000000..dc9525d --- /dev/null +++ b/hobo/contrib/ozwillo/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import OzwilloInstance + + +class OzwilloInstanceAdmin(admin.ModelAdmin): + list_display = ['id', 'domain_slug', 'external_ozwillo_id', 'state', 'created', 'modified'] + list_filter = ['state', 'created', 'modified'] + +admin.site.register(OzwilloInstance, OzwilloInstanceAdmin) diff --git a/hobo/contrib/ozwillo/management/__init__.py b/hobo/contrib/ozwillo/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hobo/contrib/ozwillo/management/commands/__init__.py b/hobo/contrib/ozwillo/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hobo/contrib/ozwillo/management/commands/ozwillo_worker.py b/hobo/contrib/ozwillo/management/commands/ozwillo_worker.py new file mode 100644 index 0000000..53328b0 --- /dev/null +++ b/hobo/contrib/ozwillo/management/commands/ozwillo_worker.py @@ -0,0 +1,68 @@ +# Ozwillo plugin to deploy Publik +# Copyright (C) 2017 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging + +from hobo.contrib.ozwillo.models import OzwilloInstance + +from django.db.transaction import atomic +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('args', nargs='*') + + @atomic + def handle(self, *args, **options): + qs = OzwilloInstance.objects.select_for_update() + + # deployment + if args: + # allow deploying manually failed instances + to_deploy = qs.fiter( + domain_slug__in=args, + state__in=[OzwilloInstance.STATE_TO_DEPLOY, OzwilloInstance.STATE_DEPLOY_ERROR]) + else: + # deploy everything to be deployed + to_deploy = qs.filter( + state=OzwilloInstance.STATE_TO_DEPLOY) + for instance in to_deploy: + try: + with atomic(): + instance.deploy() + except Exception: + logger.exception(u'ozwillo: deploying %s from CRON failed', instance) + instance.deploy_error() + + # destruction + if args: + # allow deploying manually failed instances + to_destroy = qs.filter( + domain_slug__in=args, + state__in=[OzwilloInstance.STATE_TO_DESTROY, OzwilloInstance.STATE_DESTROY_ERROR]) + else: + # destroy everything to be destroyed + to_destroy = qs.filter(state=OzwilloInstance.STATE_TO_DESTROY) + for instance in to_destroy: + try: + with atomic(): + instance.destroy() + except Exception: + logger.exception(u'ozwillo: destroying %s from CRON failed', instance) + instance.destroy_error() + +logger = logging.getLogger(__name__) diff --git a/hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py b/hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py new file mode 100644 index 0000000..75d0d01 --- /dev/null +++ b/hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-05-17 10:47 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('ozwillo', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='ozwilloinstance', + name='created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='ozwilloinstance', + name='deploy_data', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='ozwilloinstance', + name='modified', + field=models.DateTimeField(auto_now=True, default=django.utils.timezone.now), + ), + migrations.AddField( + model_name='ozwilloinstance', + name='state', + field=models.CharField(choices=[(b'new', b'new'), (b'to_deploy', b'to_deploy'), (b'deployed', b'deployed'), (b'deploy_error', b'deploy_error'), (b'to_destroy', b'to_destroy'), (b'destroy_error', b'destroy_error'), (b'destroyed', b'destroyed')], default=b'deployed', max_length=16), + ), + migrations.AlterField( + model_name='ozwilloinstance', + name='external_ozwillo_id', + field=models.CharField(max_length=450, unique=True), + ), + migrations.AlterField( + model_name='ozwilloinstance', + name='modified', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='ozwilloinstance', + name='state', + field=models.CharField(choices=[(b'new', b'new'), (b'to_deploy', b'to_deploy'), (b'deployed', b'deployed'), (b'deploy_error', b'deploy_error'), (b'to_destroy', b'to_destroy'), (b'destroy_error', b'destroy_error'), (b'destroyed', b'destroyed')], default=b'new', max_length=16), + ), + ] diff --git a/hobo/contrib/ozwillo/models.py b/hobo/contrib/ozwillo/models.py index 0f3b4a2..0111034 100644 --- a/hobo/contrib/ozwillo/models.py +++ b/hobo/contrib/ozwillo/models.py @@ -14,13 +14,263 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging +import os +import subprocess +import tempfile +import json + +import requests + +from django.conf import settings from django.db import models +from django.db.transaction import atomic +from django.utils.text import slugify +from django.core.management import call_command class OzwilloInstance(models.Model): + STATE_NEW = 'new' + STATE_TO_DEPLOY = 'to_deploy' + STATE_DEPLOY_ERROR = 'deploy_error' + STATE_DEPLOYED = 'deployed' + STATE_TO_DESTROY = 'to_destroy' + STATE_DESTROY_ERROR = 'destroy_error' + STATE_DESTROYED = 'destroyed' + + STATES = [ + (STATE_NEW, 'new'), + (STATE_TO_DEPLOY, 'to_deploy'), + (STATE_DEPLOYED, 'deployed'), + (STATE_DEPLOY_ERROR, 'deploy_error'), + (STATE_TO_DESTROY, 'to_destroy'), + (STATE_DESTROY_ERROR, 'destroy_error'), + (STATE_DESTROYED, 'destroyed'), + ] + + state = models.CharField(max_length=16, default=STATE_NEW, choices=STATES) + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) domain_slug = models.CharField(max_length=250, unique=True) - external_ozwillo_id = models.CharField(max_length=450) + external_ozwillo_id = models.CharField(max_length=450, unique=True) + deploy_data = models.TextField(null=True) def __unicode__(self): - return 'external ozwillo id: %s, domain slug: %s' % (self.external_ozwillo_id, - self.domain_slug) + return self.domain_slug + + def __repr__(self): + return '' % ( + self.external_ozwillo_id, self.domain_slug, self.state) + + @property + def data(self): + return json.loads(self.deploy_data) if self.deploy_data else None + + def to_deploy(self): + assert self.state == self.STATE_NEW + try: + with atomic(): + # lock the new instance to prevent collision with the CRON job + OzwilloInstance.objects.select_for_update().filter(id=self.id) + # instance starts with the NEW state, to prevent the CRON from + # deploying instance juste created + # only instance in the state STATE_TO_DEPLOY are deployed. + self.state = self.STATE_TO_DEPLOY + self.save() + self.deploy() + except Exception: + # something failed, still make the instance to be deployed + # an reraise exception + self.state = self.STATE_TO_DEPLOY + self.save() + raise + + def to_destroy(self): + assert self.state == self.STATE_DEPLOYED + self.state = self.STATE_TO_DESTROY + self.save() + + def deploy_error(self): + assert self.state in [self.STATE_DEPLOY_ERROR, self.STATE_TO_DEPLOY] + if self.state == self.STATE_TO_DEPLOY: + self.state = self.STATE_DEPLOY_ERROR + self.save() + + def deploy(self): + logger.info(u'ozwillo: deploy start for %s', self) + data = self.data + if not data: + logger.warning(u'ozwillo: unable to deploy, no data') + return + + # Request parsing + client_id = data['client_id'] + client_secret = data['client_secret'] + instance_id = data['instance_id'] + instance_name = data['organization_name'] + instance_name = slugify(instance_name) + registration_uri = data['instance_registration_uri'] + user = data['user'] + + # Cook new platform + template_recipe = json.load(open('/etc/hobo/ozwillo/template_recipe.json', 'rb')) + var = template_recipe['variables'] + for key, value in var.items(): + var[key] = value.replace('instance_name', instance_name) + + template_recipe['variables'] = var + domain = var['combo'] + domain_agent = var['combo_agent'] + domain_passerelle = var['passerelle'] + logger.info(u'ozwillo: cooking %s', template_recipe) + + with tempfile.NamedTemporaryFile() as recipe_file: + json.dump(template_recipe, recipe_file) + recipe_file.flush() + call_command('cook', recipe_file.name, timeout=1000, verbosity=0) + + # Load user portal template + logger.info(u'ozwillo: loading combo template') + run_command([ + 'sudo', '-u', 'combo', + 'combo-manage', 'tenant_command', 'import_site', + '/etc/hobo/ozwillo/import-site-template.json', + '-d', domain + ]) + + # Load agent portal template + logger.info(u'ozwillo: loading combo agent template') + run_command([ + 'sudo', '-u', 'combo', + 'combo-manage', 'tenant_command', 'import_site', + '/etc/hobo/ozwillo/import-site-agents.json', + '-d', domain_agent + ]) + + # Configure OIDC Ozwillo authentication + logger.info(u'ozwillo: configuring OIDC ozwillo authentication') + domain_name = 'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN) + if run_command([ + 'sudo', '-u', 'authentic-multitenant', + 'authentic2-multitenant-manage', 'tenant_command', 'oidc-register-issuer', + '-d', domain_name, + '--scope', 'profile', + '--scope', 'email', + '--issuer', settings.OZWILLO_PLATEFORM, + '--client-id', client_id, + '--client-secret', client_secret, + '--claim-mapping', 'given_name first_name always_verified', + '--claim-mapping', 'family_name last_name always_verified', + '--ou-slug', 'default', + '--claim-mapping', 'email email required', + 'Ozwillo' + ]): + # creation of the admin user depends upon the creation of provider + logger.info(u'ozwillo: creating admin user') + create_user_script = os.path.dirname(__file__) + '/scripts/create_user_ozwillo.py' + run_command([ + 'sudo', '-u', 'authentic-multitenant', + 'authentic2-multitenant-manage', 'tenant_command', 'runscript', '-d', domain_name, + create_user_script, user['email_address'], user['id'], user['name'] + ]) + + # Load passerelle template + logger.info(u'ozwillo: loading passerelle template') + run_command([ + 'sudo', '-u', 'passerelle', + 'passerelle-manage', 'tenant_command', 'import_site', + '/etc/hobo/ozwillo/import-site-passerelle.json', + '--import-user', '-d', domain_passerelle + ]) + + # Sending done event to Ozwillo + services = { + 'services': [{ + 'local_id': 'publik', + 'name': 'Publik - %s' % (instance_name), + 'service_uri': 'https://connexion-%s.%s/accounts/oidc/login?iss=%s' + % (instance_name, settings.OZWILLO_ENV_DOMAIN, + settings.OZWILLO_PLATEFORM), + 'description': 'Gestion de la relation usagers', + 'tos_uri': 'https://publik.entrouvert.com/', + 'policy_uri': 'https://publik.entrouvert.com/', + 'icon': 'https://publik.entrouvert.com/static/img/logo-publik-64x64.png', + 'payment_option': 'FREE', + 'target_audience': ['PUBLIC_BODIES', + 'CITIZENS', + 'COMPANIES'], + 'contacts': ['https://publik.entrouvert.com/'], + 'redirect_uris': ['https://connexion-%s.%s/accounts/oidc/callback/' + % (instance_name, settings.OZWILLO_ENV_DOMAIN)], + }], + 'instance_id': instance_id, + 'destruction_uri': settings.OZWILLO_DESTRUCTION_URI, + 'destruction_secret': settings.OZWILLO_DESTRUCTION_SECRET, + 'needed_scopes': [] + } + logger.info(u'ozwillo: sending registration request, %r', services) + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + response = requests.post( + registration_uri, + data=json.dumps(services), + auth=(client_id, client_secret), + headers=headers) + logger.info(u'ozwillo: registration response, status=%s content=%r', + response.status_code, response.content) + self.state = self.STATE_DEPLOYED + self.save() + logger.info(u'ozwillo: deploy finished') + + def deploy_error(self): + assert self.state in [self.STATE_DESTROY_ERROR, self.STATE_TO_DESTROY] + if self.state == self.STATE_TO_DESTROY: + self.state = self.STATE_DESTROY_ERROR + self.save() + + def destroy(self): + logger.info(u'ozwillo: delete start for %s', self) + instance_slug = self.domain_slug + services = settings.OZWILLO_SERVICES + wcs = services['wcs-au-quotidien'] + + for s, infos in services.items(): + # to get the two combo instances which have same service + service_user = s.split('_')[0] + + tenant = '%s%s.%s' % (infos[0], instance_slug, settings.OZWILLO_ENV_DOMAIN) + + run_command([ + 'sudo', '-u', service_user, infos[1], + 'delete_tenant', '--force-drop', tenant + ]) + + tenant = '%s%s.%s' % (wcs[0], instance_slug, settings.OZWILLO_ENV_DOMAIN) + run_command([ + 'sudo', '-u', 'wcs', + wcs[1], '-f', '/etc/wcs/wcs-au-quotidien.cfg', + 'delete_tenant', '--force-drop', tenant + ]) + + self.delete() + logger.info(u'ozwillo: destroy thread finished') + + +def run_command(args): + try: + process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError as e: + logger.error('ozwillo: launching subprocess %s raised error %s', args, e) + return False + logger.info('ozwillo: launching subprocess with pid %s : %s', process.pid, args) + stdoutdata, stderrdata = process.communicate() + if process.returncode != 0: + logger.error('ozwillo: subprocess %s failed returncode=%s stdout=%r stderr=%r', + process.pid, process.returncode, stdoutdata, stderrdata) + return False + logger.info('ozwillo: subprocess terminated') + return True + + +logger = logging.getLogger(__name__) + + diff --git a/hobo/contrib/ozwillo/views.py b/hobo/contrib/ozwillo/views.py index 8106950..13a3814 100644 --- a/hobo/contrib/ozwillo/views.py +++ b/hobo/contrib/ozwillo/views.py @@ -14,44 +14,25 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import os import logging -import requests import json -import subprocess import hmac import threading -import tempfile from hashlib import sha1 from django.views.decorators.csrf import csrf_exempt from django.conf import settings from django.http import (HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotFound, HttpResponse) -from django.core.management import call_command from django.utils.text import slugify +from django.db import DatabaseError +from django.db.transaction import atomic from .models import OzwilloInstance logger = logging.getLogger(__name__) -def run_command(args): - try: - process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except OSError as e: - logger.error('ozwillo: launching subprocess %s raised error %s', args, e) - return False - logger.debug('ozwillo: launching subprocess with pid %s : %s', process.pid, args) - stdoutdata, stderrdata = process.communicate() - if process.returncode != 0: - logger.error('ozwillo: subprocess %s failed returncode=%s stdout=%r stderr=%r', - process.pid, process.returncode, stdoutdata, stderrdata) - return False - logger.debug('ozwillo: subprocess terminated') - return True - - def valid_signature_required(setting): '''Validate Ozwillo signatures''' signature_header_name = 'HTTP_X_HUB_SIGNATURE' @@ -97,7 +78,7 @@ def create_publik_instance(request): logger.warning(u'ozwillo: received non JSON request') return HttpResponseBadRequest('invalid JSON content') - logger.debug(u'ozwillo: create publik instance request, %r', data) + logger.info(u'ozwillo: create publik instance request, %r', data) if 'organization_name' not in data.keys(): logger.warning(u'ozwillo: missing organization_name') @@ -108,143 +89,34 @@ def create_publik_instance(request): logger.warning(u'ozwillo: instance %s already exists', org_name) return HttpResponseBadRequest('instance %s already exists' % org_name) - OzwilloInstance.objects.create(external_ozwillo_id=data['instance_id'], domain_slug=org_name) - + try: + instance = OzwilloInstance.objects.create( + external_ozwillo_id=data['instance_id'], + state=OzwilloInstance.NEW, + domain_slug=org_name, + # deploy_data is a TextField containing JSON + deploy_data=json.dumps(data, indent=4)) + except DatabaseError as e: + logger.warning(u'ozwillo: could not create instance_id %r org_name %r: %r', + data['instance_id'], org_name, e) + return HttpResponseBadRequest(u'cannot create the instance: %r', e) + + # immediate deploy in a thread def thread_function(data): try: - ozwillo_deploy_thread(data) + instance.to_deploy() except Exception: - logger.exception(u'ozwillo: error occured while deploying instance %s', - data['organization_name']) - + logger.exception(u'ozwillo: error occured duging initial deploy request %s', org_name) thread = threading.Thread(target=thread_function, args=(data,)) thread.start() return HttpResponse() -def ozwillo_deploy_thread(data): - logger.debug(u'ozwillo: deploy thread start') - # Request parsing - client_id = data['client_id'] - client_secret = data['client_secret'] - instance_id = data['instance_id'] - instance_name = data['organization_name'] - instance_name = slugify(instance_name) - registration_uri = data['instance_registration_uri'] - user = data['user'] - - # Cook new platform - template_recipe = json.load(open('/etc/hobo/ozwillo/template_recipe.json', 'rb')) - var = template_recipe['variables'] - for key, value in var.items(): - var[key] = value.replace('instance_name', instance_name) - - template_recipe['variables'] = var - domain = var['combo'] - domain_agent = var['combo_agent'] - domain_passerelle = var['passerelle'] - logger.debug(u'ozwillo: cooking %s', template_recipe) - - with tempfile.NamedTemporaryFile() as recipe_file: - json.dump(template_recipe, recipe_file) - recipe_file.flush() - call_command('cook', recipe_file.name, timeout=1000, verbosity=0) - - # Load user portal template - logger.debug(u'ozwillo: loading combo template') - run_command([ - 'sudo', '-u', 'combo', - 'combo-manage', 'tenant_command', 'import_site', - '/etc/hobo/ozwillo/import-site-template.json', - '-d', domain - ]) - - # Load agent portal template - logger.debug(u'ozwillo: loading combo agent template') - run_command([ - 'sudo', '-u', 'combo', - 'combo-manage', 'tenant_command', 'import_site', - '/etc/hobo/ozwillo/import-site-agents.json', - '-d', domain_agent - ]) - - # Configure OIDC Ozwillo authentication - logger.debug(u'ozwillo: configuring OIDC ozwillo authentication') - domain_name = 'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN) - if run_command([ - 'sudo', '-u', 'authentic-multitenant', - 'authentic2-multitenant-manage', 'tenant_command', 'oidc-register-issuer', - '-d', domain_name, - '--scope', 'profile', - '--scope', 'email', - '--issuer', settings.OZWILLO_PLATEFORM, - '--client-id', client_id, - '--client-secret', client_secret, - '--claim-mapping', 'given_name first_name always_verified', - '--claim-mapping', 'family_name last_name always_verified', - '--ou-slug', 'default', - '--claim-mapping', 'email email required', - 'Ozwillo' - ]): - # creation of the admin user depends upon the creation of provider - logger.debug(u'ozwillo: creating admin user') - create_user_script = os.path.dirname(__file__) + '/scripts/create_user_ozwillo.py' - run_command([ - 'sudo', '-u', 'authentic-multitenant', - 'authentic2-multitenant-manage', 'tenant_command', 'runscript', '-d', domain_name, - create_user_script, user['email_address'], user['id'], user['name'] - ]) - - # Load passerelle template - logger.debug(u'ozwillo: loading passerelle template') - run_command([ - 'sudo', '-u', 'passerelle', - 'passerelle-manage', 'tenant_command', 'import_site', - '/etc/hobo/ozwillo/import-site-passerelle.json', - '--import-user', '-d', domain_passerelle - ]) - - # Sending done event to Ozwillo - services = { - 'services': [{ - 'local_id': 'publik', - 'name': 'Publik - %s' % (instance_name), - 'service_uri': 'https://connexion-%s.%s/accounts/oidc/login?iss=%s' - % (instance_name, settings.OZWILLO_ENV_DOMAIN, - settings.OZWILLO_PLATEFORM), - 'description': 'Gestion de la relation usagers', - 'tos_uri': 'https://publik.entrouvert.com/', - 'policy_uri': 'https://publik.entrouvert.com/', - 'icon': 'https://publik.entrouvert.com/static/img/logo-publik-64x64.png', - 'payment_option': 'FREE', - 'target_audience': ['PUBLIC_BODIES', - 'CITIZENS', - 'COMPANIES'], - 'contacts': ['https://publik.entrouvert.com/'], - 'redirect_uris': ['https://connexion-%s.%s/accounts/oidc/callback/' - % (instance_name, settings.OZWILLO_ENV_DOMAIN)], - }], - 'instance_id': instance_id, - 'destruction_uri': settings.OZWILLO_DESTRUCTION_URI, - 'destruction_secret': settings.OZWILLO_DESTRUCTION_SECRET, - 'needed_scopes': [] - } - logger.debug(u'ozwillo: sending registration request, %r', services) - headers = {'Content-type': 'application/json', 'Accept': 'application/json'} - response = requests.post( - registration_uri, - data=json.dumps(services), - auth=(client_id, client_secret), - headers=headers) - logger.debug(u'ozwillo: registration response, status=%s content=%r', response.status_code, - response.content) - logger.debug(u'ozwillo: deploy thread finished') - - @csrf_exempt @is_ozwillo_enabled @valid_signature_required(setting='OZWILLO_DESTRUCTION_SECRET') +@atomic def delete_publik_instance(request): try: data = json.loads(request.body) @@ -252,49 +124,19 @@ def delete_publik_instance(request): logger.warning(u'ozwillo: received non JSON request') return HttpResponseBadRequest('invalid JSON content') - logger.debug(u'ozwillo: delete publik instance request, %r', data) + logger.info(u'ozwillo: delete publik instance request (%r)', data) try: - instance = OzwilloInstance.objects.get(external_ozwillo_id=data['instance_id']) + instance_id = data['instance_id'] + except Exception: + logger.warning(u'ozwillo: no instance id in destroy request (%r)', data) + return HttpResponseBadRequest('no instance id') + try: + instance = OzwilloInstance.objects.select_for_update().get( + external_ozwillo_id=instance_id, + # only deployed instances can be destroyed + state=OzwilloInstance.STATE_DEPLOYED) except OzwilloInstance.DoesNotExist: return HttpResponseBadRequest('no instance with id %s' % data['instance_id']) - - def thread_function(data): - try: - ozwillo_destroy_thread(instance) - logger.debug(u'ozwillo: instance %s destroyed', instance.domain_slug) - except Exception: - logger.exception('ozwillo: error occured while destroying instance %s', - instance.domain_slug) - thread = threading.Thread(target=ozwillo_destroy_thread, args=(instance,)) - thread.start() - + instance.to_destroy() return HttpResponse(status=200) - - -def ozwillo_destroy_thread(instance): - logger.debug(u'ozwillo: destroy thread start') - instance_slug = instance.domain_slug - services = settings.OZWILLO_SERVICES - wcs = services['wcs-au-quotidien'] - - for s, infos in services.items(): - # to get the two combo instances which have same service - service_user = s.split('_')[0] - - tenant = '%s%s.%s' % (infos[0], instance_slug, settings.OZWILLO_ENV_DOMAIN) - - run_command([ - 'sudo', '-u', service_user, infos[1], - 'delete_tenant', '--force-drop', tenant - ]) - - tenant = '%s%s.%s' % (wcs[0], instance_slug, settings.OZWILLO_ENV_DOMAIN) - run_command([ - 'sudo', '-u', 'wcs', - wcs[1], '-f', '/etc/wcs/wcs-au-quotidien.cfg', - 'delete_tenant', '--force-drop', tenant - ]) - - instance.delete() - logger.debug(u'ozwillo: destroy thread finished') diff --git a/hobo/multitenant/management/commands/runscript.py b/hobo/multitenant/management/commands/runscript.py index 2405e5d..fa20c86 100644 --- a/hobo/multitenant/management/commands/runscript.py +++ b/hobo/multitenant/management/commands/runscript.py @@ -24,7 +24,7 @@ from django.core.management.base import BaseCommand class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('args', nargs=argparse.REMAINDER) + parser.add_argument('args', nargs='*') def handle(self, *args, **options): fullpath = os.path.dirname(os.path.abspath(args[0])) -- 2.17.0