Projet

Général

Profil

0002-environment-add-migrate_service-command-58908.patch

Emmanuel Cazenave, 13 décembre 2021 17:41

Télécharger (13,9 ko)

Voir les différences:

Subject: [PATCH 2/2] environment: add migrate_service command (#58908)

 hobo/environment/management/commands/cook.py  | 39 +--------
 .../management/commands/migrate_service.py    | 74 +++++++++++++++++
 hobo/environment/utils.py                     | 39 +++++++++
 tests/test_cook.py                            |  1 +
 tests_schemas/conftest.py                     |  1 +
 tests_schemas/test_hobo_deploy.py             |  2 +-
 tests_schemas/test_migrate_service.py         | 81 +++++++++++++++++++
 7 files changed, 200 insertions(+), 37 deletions(-)
 create mode 100644 hobo/environment/management/commands/migrate_service.py
 create mode 100644 tests_schemas/test_migrate_service.py
hobo/environment/management/commands/cook.py
20 20
import os
21 21
import string
22 22
import subprocess
23
import sys
24
import time
25 23

  
26 24
from django.contrib.auth.models import User
27 25
from django.contrib.contenttypes.models import ContentType
......
50 48
    Wcs,
51 49
    Welco,
52 50
)
51
from hobo.environment.utils import wait_operationals
53 52
from hobo.environment.validators import validate_service_url
54 53
from hobo.multitenant.middleware import TenantMiddleware
55 54
from hobo.profile.models import AttributeDefinition
......
79 78
        self.verbosity = kwargs.get('verbosity')
80 79
        self.timeout = kwargs.get('timeout')
81 80
        self.permissive = kwargs.get('permissive')
81
        self.terminal_width = 0
82 82
        if self.verbosity > 1:
83 83
            try:
84 84
                self.terminal_width = int(subprocess.check_output(['tput', 'cols']).strip())
......
119 119
        services = []
120 120
        for service_class in AVAILABLE_SERVICES:
121 121
            services.extend(service_class.objects.all())
122

  
123
        t0 = time.time()
124
        i = 0
125
        last_service_url = None
126
        last_notification = t0
127
        while len(services) > 0:
128
            if time.time() - last_notification > 15:
129
                last_notification = time.time()
130
                notify_agents(None)
131
            for service in services[:]:
132
                if service.last_operational_success_timestamp:
133
                    services.remove(service)
134
                    continue
135
                service.check_operational()
136
            if len(services) == 0:
137
                break
138
            if self.verbosity == 1:
139
                sys.stderr.write('.')
140
            elif self.verbosity > 1:
141
                if last_service_url != services[0].base_url:
142
                    last_service_url = services[0].base_url
143
                    i = 0
144
                elif i == (self.terminal_width - len(services[0].base_url) - 25):
145
                    i = 0
146
                i += 1
147
                sys.stderr.write('\rWaiting for %s ' % services[0].base_url)
148
                sys.stderr.write('%5ds ' % (timeout - (time.time() - t0)))
149
                sys.stderr.write('.' * i)
150
                sys.stderr.flush()
151
            time.sleep(0.5)
152
            if time.time() - t0 > timeout:
153
                if self.verbosity:
154
                    sys.stderr.write('\n')
155
                raise CommandError('timeout waiting for %s' % ', '.join([x.base_url for x in services]))
122
        wait_operationals(services, timeout, self.verbosity, self.terminal_width, notify_agents)
156 123

  
157 124
    def create_hobo(self, url, primary=False, title=None, slug=None, **kwargs):
158 125
        if connection.get_tenant().schema_name == 'public':
hobo/environment/management/commands/migrate_service.py
1
# hobo - portal to configure and deploy applications
2
# Copyright (C) 2021  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import subprocess
18

  
19
from django.core.management.base import BaseCommand, CommandError
20

  
21
from hobo.deploy.signals import notify_agents
22
from hobo.environment.utils import get_installed_services, wait_operationals
23

  
24

  
25
def normalize_url(url):
26
    if not url.endswith('/'):
27
        url += '/'
28
    return url
29

  
30

  
31
class Command(BaseCommand):
32
    verbosity = 1
33

  
34
    def add_arguments(self, parser):
35
        parser.add_argument('src_url', type=str)
36
        parser.add_argument('target_url', type=str)
37
        parser.add_argument(
38
            '--timeout',
39
            type=int,
40
            action='store',
41
            default=120,
42
            help='set the timeout for the wait_operationals method',
43
        )
44

  
45
    def handle(self, src_url, target_url, *args, **kwargs):
46
        timeout = kwargs.get('timeout')
47
        verbosity = kwargs.get('verbosity')
48
        terminal_width = 0
49
        if self.verbosity > 1:
50
            try:
51
                terminal_width = int(subprocess.check_output(['tput', 'cols']).strip())
52
            except OSError:
53
                terminal_width = 80
54

  
55
        src_url, target_url = normalize_url(src_url), normalize_url(target_url)
56
        target_service = None
57

  
58
        for service in get_installed_services():
59
            if service.get_base_url_path() == src_url:
60
                target_service = service
61
                break
62
        if target_service is None:
63
            raise CommandError('No service matches %s' % src_url)
64
        if target_service.Extra.service_id in ('authentic', 'hobo', 'wcs'):
65
            raise CommandError("%s service type is not supported" % target_service.Extra.service_id)
66

  
67
        target_service.change_base_url(target_url)
68
        target_service.last_operational_check_timestamp = None
69
        target_service.last_operational_success_timestamp = None
70
        target_service.save()
71
        notify_agents(None)
72
        wait_operationals([target_service], timeout, verbosity, terminal_width, notify_agents)
73
        if self.verbosity:
74
            print('Service migrated successfully, check it out : %s' % target_service.get_base_url_path())
hobo/environment/utils.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import hashlib
18
import sys
19
import time
18 20

  
19 21
from django.conf import settings
22
from django.core.management.base import CommandError
20 23
from django.db import connection, transaction
21 24
from django.urls import reverse
22 25
from django.utils.encoding import force_text
......
185 188
            for key, value in fields.items():
186 189
                setattr(obj, key, value)
187 190
            obj.save()
191

  
192

  
193
def wait_operationals(services, timeout, verbosity, terminal_width, notify_agents_func):
194
    t0 = time.time()
195
    i = 0
196
    last_service_url = None
197
    last_notification = t0
198
    while len(services) > 0:
199
        if time.time() - last_notification > 15:
200
            last_notification = time.time()
201
            notify_agents_func(None)
202
        for service in services[:]:
203
            if service.last_operational_success_timestamp:
204
                services.remove(service)
205
                continue
206
            service.check_operational()
207
        if len(services) == 0:
208
            break
209
        if verbosity == 1:
210
            sys.stderr.write('.')
211
        elif verbosity > 1:
212
            if last_service_url != services[0].base_url:
213
                last_service_url = services[0].base_url
214
                i = 0
215
            elif i == (terminal_width - len(services[0].base_url) - 25):
216
                i = 0
217
            i += 1
218
            sys.stderr.write('\rWaiting for %s ' % services[0].base_url)
219
            sys.stderr.write('%5ds ' % (timeout - (time.time() - t0)))
220
            sys.stderr.write('.' * i)
221
            sys.stderr.flush()
222
        time.sleep(0.5)
223
        if time.time() - t0 > timeout:
224
            if verbosity:
225
                sys.stderr.write('\n')
226
            raise CommandError('timeout waiting for %s' % ', '.join([x.base_url for x in services]))
tests/test_cook.py
179 179
    # already operational
180 180
    obj1.last_operational_success_timestamp = 'some date'
181 181
    obj2.last_operational_success_timestamp = 'some date'
182
    command.terminal_width = 80
182 183
    command.wait_operationals(2)
183 184
    assert True
184 185

  
tests_schemas/conftest.py
8 8
from tenant_schemas.utils import tenant_context
9 9

  
10 10
from hobo.environment.management.commands.cook import Command
11
from hobo.environment.management.commands.migrate_service import Command as MigrateServiceCommand
11 12
from hobo.multitenant.middleware import TenantMiddleware
12 13

  
13 14

  
tests_schemas/test_hobo_deploy.py
54 54
    assert_deployed(domain)
55 55

  
56 56

  
57
def test_deploy_specifics_on_hobo_agent(db):
57
def test_deploy_specifics_on_hobo_agent(transactional_db):
58 58
    """overloaded case for hobo:
59 59
    $ hobo-manage hobo-deploy env.json  # simulate this bash query
60 60
    """
tests_schemas/test_migrate_service.py
1
import os
2

  
3
import pytest
4
from django.core.management import call_command, load_command_class
5
from django.core.management.base import CommandError
6
from mock import Mock
7
from tenant_schemas.utils import tenant_context
8

  
9
from hobo.environment.models import Chrono
10
from hobo.environment.utils import get_installed_services
11
from hobo.multitenant.middleware import TenantMiddleware
12

  
13

  
14
def assert_deployed(domain):
15
    tenant = TenantMiddleware.get_tenant_by_hostname(domain)
16
    tenant_hobo_json = os.path.join(tenant.get_directory(), 'hobo.json')
17
    assert os.path.exists(tenant_hobo_json)
18

  
19

  
20
@pytest.fixture()
21
def hobo_tenant(db):
22
    name = 'hobo.dev.publik.love'
23
    yield call_command('create_hobo_tenant', name)
24
    call_command('delete_tenant', name)
25

  
26

  
27
def test_unknown_service(hobo_tenant):
28
    command = load_command_class('hobo.agent.hobo', 'hobo_deploy')
29
    domain = 'hobo.dev.publik.love'
30

  
31
    command.handle('https://%s/' % domain, 'tests_schemas/env.json')
32
    assert_deployed(domain)
33

  
34
    tenant = TenantMiddleware.get_tenant_by_hostname('hobo.dev.publik.love')
35
    with tenant_context(tenant):
36
        assert get_installed_services()
37
        with pytest.raises(CommandError) as e_info:
38
            call_command(
39
                'migrate_service', 'https://unkown.dev.publik.love/', 'https://new-chrono.dev.publik.love/'
40
            )
41
        assert 'No service matches https://unkown.dev.publik.love/' in str(e_info.value)
42

  
43

  
44
def test_unsupported_service(hobo_tenant):
45
    command = load_command_class('hobo.agent.hobo', 'hobo_deploy')
46
    domain = 'hobo.dev.publik.love'
47
    command.handle('https://%s/' % domain, 'tests_schemas/env.json')
48
    assert_deployed(domain)
49

  
50
    tenant = TenantMiddleware.get_tenant_by_hostname('hobo.dev.publik.love')
51
    with tenant_context(tenant):
52
        assert get_installed_services()
53
        with pytest.raises(CommandError) as e_info:
54
            call_command(
55
                'migrate_service', 'https://wcs.dev.publik.love/', 'https://new-wcs.dev.publik.love/'
56
            )
57
        assert 'wcs service type is not supported' in str(e_info.value)
58

  
59

  
60
def test_change_base_url(hobo_tenant, monkeypatch):
61
    deploy_command = load_command_class('hobo.agent.hobo', 'hobo_deploy')
62
    domain = 'hobo.dev.publik.love'
63
    deploy_command.handle('https://%s/' % domain, 'tests_schemas/env.json')
64
    assert_deployed(domain)
65

  
66
    tenant = TenantMiddleware.get_tenant_by_hostname(domain)
67
    with tenant_context(tenant):
68
        assert Chrono.objects.count() == 1
69
        chrono_service = Chrono.objects.first()
70
        assert chrono_service.get_base_url_path() == 'https://chrono.dev.publik.love/'
71
        assert get_installed_services()
72
        monkeypatch.setattr('hobo.environment.management.commands.migrate_service.notify_agents', Mock())
73
        monkeypatch.setattr('hobo.environment.management.commands.migrate_service.wait_operationals', Mock())
74
        call_command(
75
            'migrate_service', 'https://chrono.dev.publik.love/', 'https://new-chrono.dev.publik.love/'
76
        )
77
        assert Chrono.objects.count() == 1
78
        chrono_service = Chrono.objects.first()
79
        assert chrono_service.get_base_url_path() == 'https://new-chrono.dev.publik.love/'
80
        assert len(chrono_service.legacy_urls) == 1
81
        assert chrono_service.legacy_urls[0]['base_url'] == 'https://chrono.dev.publik.love/'
0
-