summaryrefslogtreecommitdiff
path: root/heat_integrationtests/functional/test_reload_on_sighup.py
blob: d6465811ad2ab1601ec59976237f13fef4caa53a (plain)
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
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import re
import subprocess
import time

import eventlet

from oslo_concurrency import processutils
from six.moves import configparser

from heat_integrationtests.functional import functional_base


class ReloadOnSighupTest(functional_base.FunctionalTestsBase):

    def setUp(self):
        self.config_file = "/etc/heat/heat.conf"
        super(ReloadOnSighupTest, self).setUp()

    def _is_mod_wsgi_daemon(self, service):
        process = ''.join(['wsgi:', service[:9]]).replace('_', '-')
        s = subprocess.Popen(["ps", "ax"], stdout=subprocess.PIPE)
        for x in s.stdout:
            if re.search(process, x):
                return True

    def _set_config_value(self, service, key, value):
        config = configparser.ConfigParser()

        # NOTE(prazumovsky): If there are several workers, there can be
        # situation, when one thread opens self.config_file for writing
        # (so config_file erases with opening), in that moment other thread
        # intercepts to this file and try to set config option value, i.e.
        # write to file, which is already erased by first thread, so,
        # NoSectionError raised. So, should wait until first thread writes to
        # config_file.
        retries_count = self.conf.sighup_config_edit_retries
        while True:
            config.read(self.config_file)
            try:
                config.set(service, key, value)
            except configparser.NoSectionError:
                if retries_count <= 0:
                    raise
                retries_count -= 1
                eventlet.sleep(1)
            else:
                break

        with open(self.config_file, 'wb') as f:
            config.write(f)

    def _get_config_value(self, service, key):
        config = configparser.ConfigParser()
        config.read(self.config_file)
        val = config.get(service, key)
        return val

    def _get_heat_api_pids(self, service):
        # get the pids of all heat-api processes
        if service == "heat_api":
            process = "heat-api|grep -Ev 'grep|cloudwatch|cfn'"
        else:
            process = "%s|grep -Ev 'grep'" % service.replace('_', '-')
        cmd = "ps -ef|grep %s|awk '{print $2}'" % process
        out, err = processutils.execute(cmd, shell=True)
        self.assertIsNotNone(out, "heat-api service not running. %s" % err)
        pids = filter(None, out.split('\n'))

        # get the parent pids of all heat-api processes
        cmd = "ps -ef|grep %s|awk '{print $3}'" % process
        out, _ = processutils.execute(cmd, shell=True)
        parent_pids = filter(None, out.split('\n'))

        heat_api_parent = list(set(pids) & set(parent_pids))[0]
        heat_api_children = list(set(pids) - set(parent_pids))

        return heat_api_parent, heat_api_children

    def _change_config(self, service, old_workers, new_workers):
        pre_reload_parent, pre_reload_children = self._get_heat_api_pids(
            service)
        self.assertEqual(old_workers, len(pre_reload_children))

        # change the config values
        self._set_config_value(service, 'workers', new_workers)
        cmd = "kill -HUP %s" % pre_reload_parent
        processutils.execute(cmd, shell=True)

        # wait till heat-api reloads
        start_time = time.time()
        while time.time() - start_time < self.conf.sighup_timeout:
            post_reload_parent, post_reload_children = self._get_heat_api_pids(
                service)
            intersect = set(post_reload_children) & set(pre_reload_children)
            if (new_workers == len(post_reload_children)
                and pre_reload_parent == post_reload_parent
                    and intersect == set()):
                break
            eventlet.sleep(1)
        self.assertEqual(pre_reload_parent, post_reload_parent)
        self.assertEqual(new_workers, len(post_reload_children))
        # test if all child processes are newly created
        self.assertEqual(set(post_reload_children) & set(pre_reload_children),
                         set())

    def _reload(self, service):
        old_workers = int(self._get_config_value(service, 'workers'))
        new_workers = old_workers + 1
        self.addCleanup(self._set_config_value, service, 'workers',
                        old_workers)

        self._change_config(service, old_workers, new_workers)
        # revert all the changes made
        self._change_config(service, new_workers, old_workers)

    def _reload_on_sighup(self, service):
        if not self._is_mod_wsgi_daemon(service):
            self._reload(service)
        else:
            self.skipTest('Skipping Test, Service running under httpd.')

    def test_api_reload_on_sighup(self):
        self._reload_on_sighup('heat_api')

    def test_api_cfn_reload_on_sighup(self):
        self._reload_on_sighup('heat_api_cfn')

    def test_api_cloudwatch_on_sighup(self):
        self._reload_on_sighup('heat_api_cloudwatch')