~ubuntu-branches/ubuntu/trusty/cinder/trusty

« back to all changes in this revision

Viewing changes to cinder/service.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Yolanda Robla Mota, James Page, Chuck Short
  • Date: 2013-02-22 10:45:17 UTC
  • mfrom: (1.1.11)
  • Revision ID: package-import@ubuntu.com-20130222104517-ng3r6ace9vi4m869
Tags: 2013.1.g3-0ubuntu1
[ Yolanda Robla Mota ]
* d/control: Add BD on python-hp3parclient.
* d/patches: Drop patches related to disabling hp3parclient.

[ James Page ]
* d/control: Add Suggests: python-hp3parclient for python-cinder.
* d/control: Add BD on python-oslo-config.
* d/*: Wrapped and sorted.

[ Chuck Short ]
* New upstream release.
* debian/rules, debian/cinder-volumes.install: 
  - Fail if binaries are missing and install missing binaries.
* debian/patches/fix-ubuntu-tests.patch: Fix failing tests.
* debian/control: Add python-rtslib and python-mock.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
"""Generic Node base class for all workers that run on hosts."""
21
21
 
 
22
import errno
22
23
import inspect
23
24
import os
24
25
import random
25
26
import signal
 
27
import sys
 
28
import time
26
29
 
27
30
import eventlet
28
31
import greenlet
 
32
from oslo.config import cfg
29
33
 
30
34
from cinder import context
31
35
from cinder import db
32
36
from cinder import exception
33
37
from cinder import flags
34
 
from cinder.openstack.common import cfg
35
38
from cinder.openstack.common import importutils
36
39
from cinder.openstack.common import log as logging
37
40
from cinder.openstack.common import rpc
39
42
from cinder import version
40
43
from cinder import wsgi
41
44
 
42
 
 
43
45
LOG = logging.getLogger(__name__)
44
46
 
45
47
service_opts = [
65
67
FLAGS.register_opts(service_opts)
66
68
 
67
69
 
 
70
class SignalExit(SystemExit):
 
71
    def __init__(self, signo, exccode=1):
 
72
        super(SignalExit, self).__init__(exccode)
 
73
        self.signo = signo
 
74
 
 
75
 
68
76
class Launcher(object):
69
77
    """Launch one or more services and wait for them to complete."""
70
78
 
128
136
                pass
129
137
 
130
138
 
 
139
class ServerWrapper(object):
 
140
    def __init__(self, server, workers):
 
141
        self.server = server
 
142
        self.workers = workers
 
143
        self.children = set()
 
144
        self.forktimes = []
 
145
 
 
146
 
 
147
class ProcessLauncher(object):
 
148
    def __init__(self):
 
149
        self.children = {}
 
150
        self.sigcaught = None
 
151
        self.running = True
 
152
        rfd, self.writepipe = os.pipe()
 
153
        self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r')
 
154
 
 
155
        signal.signal(signal.SIGTERM, self._handle_signal)
 
156
        signal.signal(signal.SIGINT, self._handle_signal)
 
157
 
 
158
    def _handle_signal(self, signo, frame):
 
159
        self.sigcaught = signo
 
160
        self.running = False
 
161
 
 
162
        # Allow the process to be killed again and die from natural causes
 
163
        signal.signal(signal.SIGTERM, signal.SIG_DFL)
 
164
        signal.signal(signal.SIGINT, signal.SIG_DFL)
 
165
 
 
166
    def _pipe_watcher(self):
 
167
        # This will block until the write end is closed when the parent
 
168
        # dies unexpectedly
 
169
        self.readpipe.read()
 
170
 
 
171
        LOG.info(_('Parent process has died unexpectedly, exiting'))
 
172
 
 
173
        sys.exit(1)
 
174
 
 
175
    def _child_process(self, server):
 
176
        # Setup child signal handlers differently
 
177
        def _sigterm(*args):
 
178
            signal.signal(signal.SIGTERM, signal.SIG_DFL)
 
179
            raise SignalExit(signal.SIGTERM)
 
180
 
 
181
        signal.signal(signal.SIGTERM, _sigterm)
 
182
        # Block SIGINT and let the parent send us a SIGTERM
 
183
        # signal.signal(signal.SIGINT, signal.SIG_IGN)
 
184
        # This differs from the behavior in nova in that we dont ignore this
 
185
        # It allows the non-wsgi services to be terminated properly
 
186
        signal.signal(signal.SIGINT, _sigterm)
 
187
 
 
188
        # Reopen the eventlet hub to make sure we don't share an epoll
 
189
        # fd with parent and/or siblings, which would be bad
 
190
        eventlet.hubs.use_hub()
 
191
 
 
192
        # Close write to ensure only parent has it open
 
193
        os.close(self.writepipe)
 
194
        # Create greenthread to watch for parent to close pipe
 
195
        eventlet.spawn(self._pipe_watcher)
 
196
 
 
197
        # Reseed random number generator
 
198
        random.seed()
 
199
 
 
200
        launcher = Launcher()
 
201
        launcher.run_server(server)
 
202
 
 
203
    def _start_child(self, wrap):
 
204
        if len(wrap.forktimes) > wrap.workers:
 
205
            # Limit ourselves to one process a second (over the period of
 
206
            # number of workers * 1 second). This will allow workers to
 
207
            # start up quickly but ensure we don't fork off children that
 
208
            # die instantly too quickly.
 
209
            if time.time() - wrap.forktimes[0] < wrap.workers:
 
210
                LOG.info(_('Forking too fast, sleeping'))
 
211
                time.sleep(1)
 
212
 
 
213
            wrap.forktimes.pop(0)
 
214
 
 
215
        wrap.forktimes.append(time.time())
 
216
 
 
217
        pid = os.fork()
 
218
        if pid == 0:
 
219
            # NOTE(johannes): All exceptions are caught to ensure this
 
220
            # doesn't fallback into the loop spawning children. It would
 
221
            # be bad for a child to spawn more children.
 
222
            status = 0
 
223
            try:
 
224
                self._child_process(wrap.server)
 
225
            except SignalExit as exc:
 
226
                signame = {signal.SIGTERM: 'SIGTERM',
 
227
                           signal.SIGINT: 'SIGINT'}[exc.signo]
 
228
                LOG.info(_('Caught %s, exiting'), signame)
 
229
                status = exc.code
 
230
            except SystemExit as exc:
 
231
                status = exc.code
 
232
            except BaseException:
 
233
                LOG.exception(_('Unhandled exception'))
 
234
                status = 2
 
235
            finally:
 
236
                wrap.server.stop()
 
237
 
 
238
            os._exit(status)
 
239
 
 
240
        LOG.info(_('Started child %d'), pid)
 
241
 
 
242
        wrap.children.add(pid)
 
243
        self.children[pid] = wrap
 
244
 
 
245
        return pid
 
246
 
 
247
    def launch_server(self, server, workers=1):
 
248
        wrap = ServerWrapper(server, workers)
 
249
 
 
250
        LOG.info(_('Starting %d workers'), wrap.workers)
 
251
        while self.running and len(wrap.children) < wrap.workers:
 
252
            self._start_child(wrap)
 
253
 
 
254
    def _wait_child(self):
 
255
        try:
 
256
            # Don't block if no child processes have exited
 
257
            pid, status = os.waitpid(0, os.WNOHANG)
 
258
            if not pid:
 
259
                return None
 
260
        except OSError as exc:
 
261
            if exc.errno not in (errno.EINTR, errno.ECHILD):
 
262
                raise
 
263
            return None
 
264
 
 
265
        if os.WIFSIGNALED(status):
 
266
            sig = os.WTERMSIG(status)
 
267
            LOG.info(_('Child %(pid)d killed by signal %(sig)d'), locals())
 
268
        else:
 
269
            code = os.WEXITSTATUS(status)
 
270
            LOG.info(_('Child %(pid)d exited with status %(code)d'), locals())
 
271
 
 
272
        if pid not in self.children:
 
273
            LOG.warning(_('pid %d not in child list'), pid)
 
274
            return None
 
275
 
 
276
        wrap = self.children.pop(pid)
 
277
        wrap.children.remove(pid)
 
278
        return wrap
 
279
 
 
280
    def wait(self):
 
281
        """Loop waiting on children to die and respawning as necessary."""
 
282
        while self.running:
 
283
            wrap = self._wait_child()
 
284
            if not wrap:
 
285
                # Yield to other threads if no children have exited
 
286
                # Sleep for a short time to avoid excessive CPU usage
 
287
                # (see bug #1095346)
 
288
                eventlet.greenthread.sleep(.01)
 
289
                continue
 
290
 
 
291
            while self.running and len(wrap.children) < wrap.workers:
 
292
                self._start_child(wrap)
 
293
 
 
294
        if self.sigcaught:
 
295
            signame = {signal.SIGTERM: 'SIGTERM',
 
296
                       signal.SIGINT: 'SIGINT'}[self.sigcaught]
 
297
            LOG.info(_('Caught %s, stopping children'), signame)
 
298
 
 
299
        for pid in self.children:
 
300
            try:
 
301
                os.kill(pid, signal.SIGTERM)
 
302
            except OSError as exc:
 
303
                if exc.errno != errno.ESRCH:
 
304
                    raise
 
305
 
 
306
        # Wait for children to die
 
307
        if self.children:
 
308
            LOG.info(_('Waiting on %d children to exit'), len(self.children))
 
309
            while self.children:
 
310
                self._wait_child()
 
311
 
 
312
 
131
313
class Service(object):
132
314
    """Service object for binaries running on hosts.
133
315
 
137
319
 
138
320
    def __init__(self, host, binary, topic, manager, report_interval=None,
139
321
                 periodic_interval=None, periodic_fuzzy_delay=None,
140
 
                 *args, **kwargs):
 
322
                 service_name=None, *args, **kwargs):
141
323
        self.host = host
142
324
        self.binary = binary
143
325
        self.topic = topic
144
326
        self.manager_class_name = manager
145
327
        manager_class = importutils.import_class(self.manager_class_name)
146
 
        self.manager = manager_class(host=self.host, *args, **kwargs)
 
328
        self.manager = manager_class(host=self.host,
 
329
                                     service_name=service_name,
 
330
                                     *args, **kwargs)
147
331
        self.report_interval = report_interval
148
332
        self.periodic_interval = periodic_interval
149
333
        self.periodic_fuzzy_delay = periodic_fuzzy_delay
152
336
        self.timers = []
153
337
 
154
338
    def start(self):
155
 
        vcs_string = version.version_string_with_vcs()
156
 
        LOG.audit(_('Starting %(topic)s node (version %(vcs_string)s)'),
157
 
                  {'topic': self.topic, 'vcs_string': vcs_string})
 
339
        version_string = version.version_info
 
340
        LOG.audit(_('Starting %(topic)s node (version %(version_string)s)'),
 
341
                  {'topic': self.topic, 'version_string': version_string})
158
342
        self.manager.init_host()
159
343
        self.model_disconnected = False
160
344
        ctxt = context.get_admin_context()
217
401
    @classmethod
218
402
    def create(cls, host=None, binary=None, topic=None, manager=None,
219
403
               report_interval=None, periodic_interval=None,
220
 
               periodic_fuzzy_delay=None):
 
404
               periodic_fuzzy_delay=None, service_name=None):
221
405
        """Instantiates class and passes back application object.
222
406
 
223
407
        :param host: defaults to FLAGS.host
247
431
        service_obj = cls(host, binary, topic, manager,
248
432
                          report_interval=report_interval,
249
433
                          periodic_interval=periodic_interval,
250
 
                          periodic_fuzzy_delay=periodic_fuzzy_delay)
 
434
                          periodic_fuzzy_delay=periodic_fuzzy_delay,
 
435
                          service_name=service_name)
251
436
 
252
437
        return service_obj
253
438
 
351
536
 
352
537
        """
353
538
        fl = '%s_manager' % self.name
354
 
        if not fl in FLAGS:
 
539
        if fl not in FLAGS:
355
540
            return None
356
541
 
357
542
        manager_class_name = FLAGS.get(fl, None)