blob: d6465811ad2ab1601ec59976237f13fef4caa53a [file] [log] [blame]
tyagi2cbc0a82015-05-21 02:53:14 -07001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
rabi2bde2782017-01-10 15:57:35 +053013import re
14import subprocess
Oleksii Chuprykov4be023a2015-07-08 07:17:08 -040015import time
16
tyagi2cbc0a82015-05-21 02:53:14 -070017import eventlet
18
19from oslo_concurrency import processutils
20from six.moves import configparser
21
Rabi Mishra477efc92015-07-31 13:01:45 +053022from heat_integrationtests.functional import functional_base
tyagi2cbc0a82015-05-21 02:53:14 -070023
24
Rabi Mishra477efc92015-07-31 13:01:45 +053025class ReloadOnSighupTest(functional_base.FunctionalTestsBase):
tyagi2cbc0a82015-05-21 02:53:14 -070026
27 def setUp(self):
28 self.config_file = "/etc/heat/heat.conf"
29 super(ReloadOnSighupTest, self).setUp()
30
rabi2bde2782017-01-10 15:57:35 +053031 def _is_mod_wsgi_daemon(self, service):
32 process = ''.join(['wsgi:', service[:9]]).replace('_', '-')
33 s = subprocess.Popen(["ps", "ax"], stdout=subprocess.PIPE)
34 for x in s.stdout:
35 if re.search(process, x):
36 return True
37
tyagi2cbc0a82015-05-21 02:53:14 -070038 def _set_config_value(self, service, key, value):
39 config = configparser.ConfigParser()
Peter Razumovsky483e64b2016-03-04 17:04:28 +030040
41 # NOTE(prazumovsky): If there are several workers, there can be
42 # situation, when one thread opens self.config_file for writing
43 # (so config_file erases with opening), in that moment other thread
44 # intercepts to this file and try to set config option value, i.e.
45 # write to file, which is already erased by first thread, so,
46 # NoSectionError raised. So, should wait until first thread writes to
47 # config_file.
48 retries_count = self.conf.sighup_config_edit_retries
49 while True:
50 config.read(self.config_file)
51 try:
52 config.set(service, key, value)
53 except configparser.NoSectionError:
54 if retries_count <= 0:
55 raise
56 retries_count -= 1
57 eventlet.sleep(1)
58 else:
59 break
60
tyagi2cbc0a82015-05-21 02:53:14 -070061 with open(self.config_file, 'wb') as f:
62 config.write(f)
63
64 def _get_config_value(self, service, key):
65 config = configparser.ConfigParser()
66 config.read(self.config_file)
67 val = config.get(service, key)
68 return val
69
70 def _get_heat_api_pids(self, service):
71 # get the pids of all heat-api processes
72 if service == "heat_api":
73 process = "heat-api|grep -Ev 'grep|cloudwatch|cfn'"
74 else:
75 process = "%s|grep -Ev 'grep'" % service.replace('_', '-')
76 cmd = "ps -ef|grep %s|awk '{print $2}'" % process
77 out, err = processutils.execute(cmd, shell=True)
78 self.assertIsNotNone(out, "heat-api service not running. %s" % err)
79 pids = filter(None, out.split('\n'))
80
81 # get the parent pids of all heat-api processes
82 cmd = "ps -ef|grep %s|awk '{print $3}'" % process
83 out, _ = processutils.execute(cmd, shell=True)
84 parent_pids = filter(None, out.split('\n'))
85
86 heat_api_parent = list(set(pids) & set(parent_pids))[0]
87 heat_api_children = list(set(pids) - set(parent_pids))
88
89 return heat_api_parent, heat_api_children
90
91 def _change_config(self, service, old_workers, new_workers):
92 pre_reload_parent, pre_reload_children = self._get_heat_api_pids(
93 service)
94 self.assertEqual(old_workers, len(pre_reload_children))
95
96 # change the config values
97 self._set_config_value(service, 'workers', new_workers)
98 cmd = "kill -HUP %s" % pre_reload_parent
99 processutils.execute(cmd, shell=True)
tyagi2cbc0a82015-05-21 02:53:14 -0700100
Oleksii Chuprykov4be023a2015-07-08 07:17:08 -0400101 # wait till heat-api reloads
102 start_time = time.time()
103 while time.time() - start_time < self.conf.sighup_timeout:
104 post_reload_parent, post_reload_children = self._get_heat_api_pids(
105 service)
106 intersect = set(post_reload_children) & set(pre_reload_children)
107 if (new_workers == len(post_reload_children)
108 and pre_reload_parent == post_reload_parent
109 and intersect == set()):
110 break
111 eventlet.sleep(1)
tyagi2cbc0a82015-05-21 02:53:14 -0700112 self.assertEqual(pre_reload_parent, post_reload_parent)
113 self.assertEqual(new_workers, len(post_reload_children))
114 # test if all child processes are newly created
115 self.assertEqual(set(post_reload_children) & set(pre_reload_children),
116 set())
117
118 def _reload(self, service):
119 old_workers = int(self._get_config_value(service, 'workers'))
120 new_workers = old_workers + 1
121 self.addCleanup(self._set_config_value, service, 'workers',
122 old_workers)
123
124 self._change_config(service, old_workers, new_workers)
125 # revert all the changes made
126 self._change_config(service, new_workers, old_workers)
127
rabi2bde2782017-01-10 15:57:35 +0530128 def _reload_on_sighup(self, service):
129 if not self._is_mod_wsgi_daemon(service):
130 self._reload(service)
131 else:
132 self.skipTest('Skipping Test, Service running under httpd.')
133
tyagi2cbc0a82015-05-21 02:53:14 -0700134 def test_api_reload_on_sighup(self):
rabi2bde2782017-01-10 15:57:35 +0530135 self._reload_on_sighup('heat_api')
tyagi2cbc0a82015-05-21 02:53:14 -0700136
137 def test_api_cfn_reload_on_sighup(self):
rabi2bde2782017-01-10 15:57:35 +0530138 self._reload_on_sighup('heat_api_cfn')
tyagi2cbc0a82015-05-21 02:53:14 -0700139
140 def test_api_cloudwatch_on_sighup(self):
rabi2bde2782017-01-10 15:57:35 +0530141 self._reload_on_sighup('heat_api_cloudwatch')