File: test_002_restarts.py

package info (click to toggle)
apache2 2.4.66-8
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 59,960 kB
  • sloc: ansic: 212,327; python: 13,830; perl: 11,307; sh: 7,266; php: 1,320; javascript: 1,314; awk: 749; makefile: 715; lex: 374; yacc: 161; xml: 2
file content (150 lines) | stat: -rw-r--r-- 5,768 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os
import re
import time
from datetime import datetime, timedelta
from threading import Thread

import pytest

from .env import CoreTestEnv
from pyhttpd.conf import HttpdConf


class Loader:

    def __init__(self, env, url: str, clients: int, req_per_client: int = 10):
        self.env = env
        self.url = url
        self.clients = clients
        self.req_per_client = req_per_client
        self.result = None
        self.total_request = 0
        self._thread = None

    def run(self):
        self.total_requests = self.clients * self.req_per_client
        conn_per_client = 5
        args = [self.env.h2load, f"--connect-to=localhost:{self.env.https_port}",
                "--h1",                                 # use only http/1.1
                "-n", str(self.total_requests),              # total # of requests to make
                "-c", str(conn_per_client * self.clients),   # total # of connections to make
                "-r", str(self.clients),                     # connections at a time
                "--rate-period", "2",                   # create conns every 2 sec
                self.url,
                ]
        self.result = self.env.run(args)

    def start(self):
        self._thread = Thread(target=self.run)
        self._thread.start()

    def join(self):
        self._thread.join()


class ChildDynamics:

    RE_DATE_TIME = re.compile(r'\[(?P<date_time>[^\]]+)\] .*')
    RE_TIME_FRAC = re.compile(r'(?P<dt>.* \d\d:\d\d:\d\d)(?P<frac>.(?P<micros>.\d+)) (?P<year>\d+)')
    RE_CHILD_CHANGE = re.compile(r'\[(?P<date_time>[^\]]+)\] '
                                 r'\[mpm_event:\w+\]'
                                 r' \[pid (?P<main_pid>\d+):tid \w+\] '
                                 r'.* Child (?P<child_no>\d+) (?P<action>\w+): '
                                 r'pid (?P<pid>\d+), gen (?P<generation>\d+), .*')

    def __init__(self, env: CoreTestEnv):
        self.env = env
        self.changes = list()
        self._start = None
        for l in open(env.httpd_error_log.path):
            m = self.RE_CHILD_CHANGE.match(l)
            if m:
                self.changes.append({
                    'pid': int(m.group('pid')),
                    'child_no': int(m.group('child_no')),
                    'gen': int(m.group('generation')),
                    'action': m.group('action'),
                    'rtime' : self._rtime(m.group('date_time'))
                })
                continue
            if self._start is None:
                m = self.RE_DATE_TIME.match(l)
                if m:
                    self._rtime(m.group('date_time'))

    def _rtime(self, s: str) -> timedelta:
        micros = 0
        m = self.RE_TIME_FRAC.match(s)
        if m:
            micros = int(m.group('micros'))
            s = f"{m.group('dt')} {m.group('year')}"
        d = datetime.strptime(s, '%a %b %d %H:%M:%S %Y') + timedelta(microseconds=micros)
        if self._start is None:
            self._start = d
        delta = d - self._start
        return f"{delta.seconds:+02d}.{delta.microseconds:06d}"



@pytest.mark.skipif(condition='STRESS_TEST' not in os.environ,
                    reason="STRESS_TEST not set in env")
@pytest.mark.skipif(condition=not CoreTestEnv().h2load_is_at_least('1.41.0'),
                    reason="h2load unavailable or misses --connect-to option")
class TestRestarts:

    def test_core_002_01(self, env):
        # Lets make a tight config that triggers dynamic child behaviour
        conf = HttpdConf(env, extras={
            'base': f"""
        StartServers            1
        ServerLimit             3
        ThreadLimit             4
        ThreadsPerChild         4
        MinSpareThreads         4
        MaxSpareThreads         6
        MaxRequestWorkers       12
        MaxConnectionsPerChild  0

        LogLevel mpm_event:trace6
                """,
        })
        conf.add_vhost_cgi()
        conf.install()

        # clear logs and start server, start load
        env.httpd_error_log.clear_log()
        assert env.apache_restart() == 0
        # we should see a single child started
        cd = ChildDynamics(env)
        assert len(cd.changes) == 1, f"{cd.changes}"
        assert cd.changes[0]['action'] == 'started'
        # This loader simulates 6 clients, each making 10 requests.
        # delay.py sleeps for 1sec, so this should run for about 10 seconds
        loader = Loader(env=env, url=env.mkurl("https", "cgi", "/delay.py"),
                        clients=6, req_per_client=10)
        loader.start()
        # Expect 2 more children to have been started after half time
        time.sleep(5)
        cd = ChildDynamics(env)
        assert len(cd.changes) == 3, f"{cd.changes}"
        assert len([x for x in cd.changes if x['action'] == 'started']) == 3, f"{cd.changes}"

        # Trigger a server reload
        assert env.apache_reload() == 0
        # a graceful reload lets ongoing requests continue, but
        # after a while all gen 0 children should have stopped
        time.sleep(3)  # FIXME: this pbly depends on the runtime a lot, do we have expectations?
        cd = ChildDynamics(env)
        gen0 = [x for x in cd.changes if x['gen'] == 0]
        assert len([x for x in gen0 if x['action'] == 'stopped']) == 3

        # wait for the loader to finish and stop the server
        loader.join()
        env.apache_stop()

        # Similar to before the reload, we expect 3 children to have
        # been started and stopped again on server stop
        cd = ChildDynamics(env)
        gen1 = [x for x in cd.changes if x['gen'] == 1]
        assert len([x for x in gen1 if x['action'] == 'started']) == 3
        assert len([x for x in gen1 if x['action'] == 'stopped']) == 3