~johnsca/charms/trusty/loggregator-v1/trunk

« back to all changes in this revision

Viewing changes to hooks/cloudfoundry/reconciler.py

  • Committer: Cory Johns
  • Date: 2014-09-29 14:29:03 UTC
  • Revision ID: cory.johns@canonical.com-20140929142903-ddqik0vvusszptij
loggregator version 1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
"""
 
4
Run deployer in a loop, then remove any services not
 
5
in the expected state.
 
6
 
 
7
To experiment with this::
 
8
 
 
9
    #activate virtualenv
 
10
    . .tox/py27/bin/activate
 
11
    python cloudfoundry/reconciler.py \
 
12
        --logging=debug --repo=build --port=8888
 
13
 
 
14
    # then in another window you can do the follow to play
 
15
    # with the REST API
 
16
    cd tests
 
17
    ./test-server.sh
 
18
 
 
19
    This should push a bundle of expected state, HUP the
 
20
    server to force a run and then status the system.
 
21
    Debug output should show whats happening and
 
22
 
 
23
 
 
24
ISSUES:
 
25
    local charm version in charm url
 
26
        need to probe server still
 
27
    reconcile loop:
 
28
        should only trigger builds after push/reality change
 
29
        execute should happen by queueing the callback
 
30
        should listen directly on the websocket and schedule
 
31
            rebuilds on change
 
32
    no support for unit state currently (auto-retry/replace, etc)
 
33
    relation removal still needs work
 
34
"""
 
35
import json
 
36
import logging
 
37
import os
 
38
import signal
 
39
import time
 
40
 
 
41
import tornado.autoreload
 
42
import tornado.httpserver
 
43
import tornado.ioloop
 
44
import tornado.options
 
45
import tornado.process
 
46
import tornado.web
 
47
 
 
48
from tornado.options import define, options
 
49
from cloudfoundry import config
 
50
from cloudfoundry import model
 
51
from cloudfoundry import utils
 
52
 
 
53
application = None
 
54
env_name = None
 
55
server = None
 
56
db = None
 
57
 
 
58
 
 
59
def reconcile():
 
60
    # delta state real vs expected
 
61
    # build strategy
 
62
    # execute strategy inside lock
 
63
    if not db.strategy:
 
64
        reality = db.real
 
65
        db.build_strategy(reality)
 
66
    if db.strategy and application.config.runtime.modify:
 
67
        db.execute_strategy()
 
68
 
 
69
 
 
70
def sig_reconcile(sig, frame):
 
71
    logging.info("Forcing reconcile loop")
 
72
    tornado.ioloop.IOLoop.instance().add_callback(reconcile)
 
73
 
 
74
 
 
75
def sig_restart(sig, frame):
 
76
    logging.warning('Caught signal: %s', sig)
 
77
    tornado.ioloop.IOLoop.instance().add_callback(shutdown)
 
78
 
 
79
 
 
80
def shutdown():
 
81
    logging.info('Stopping http server')
 
82
    server.stop()
 
83
 
 
84
    logging.info('Will shutdown in %s seconds ...',
 
85
                 config.MAX_WAIT_SECONDS_BEFORE_SHUTDOWN)
 
86
    io_loop = tornado.ioloop.IOLoop.instance()
 
87
 
 
88
    deadline = time.time() + config.MAX_WAIT_SECONDS_BEFORE_SHUTDOWN
 
89
 
 
90
    def stop_loop():
 
91
        now = time.time()
 
92
        if now < deadline and (io_loop._callbacks or io_loop._timeouts):
 
93
            io_loop.add_timeout(now + 1, stop_loop)
 
94
        else:
 
95
            io_loop.stop()
 
96
            logging.info('Shutdown')
 
97
    stop_loop()
 
98
 
 
99
 
 
100
class StateHandler(tornado.web.RequestHandler):
 
101
    def get(self):
 
102
        self.write(json.dumps(db.expected, indent=2, default=utils.serializable))
 
103
 
 
104
    def post(self):
 
105
        db.expected = json.loads(self.request.body)
 
106
        tornado.ioloop.IOLoop.instance().add_callback(reconcile)
 
107
 
 
108
 
 
109
class StrategyHandler(tornado.web.RequestHandler):
 
110
    def get(self):
 
111
        self.write(utils.jsonify(db.strategy))
 
112
 
 
113
 
 
114
class ResetHandler(tornado.web.RequestHandler):
 
115
    def get(self):
 
116
        db.reset()
 
117
 
 
118
 
 
119
class ReconcilerWebApp(tornado.web.Application):
 
120
    @property
 
121
    def config(self):
 
122
        return self.settings['config']
 
123
 
 
124
 
 
125
def main():
 
126
    define('config', default='/etc/juju-deployer/server.conf', type=str)
 
127
 
 
128
    tornado.options.parse_command_line()
 
129
    config = utils.parse_config(options.config, {
 
130
        'server.address': '127.0.0.1',
 
131
        'server.port': 8888,
 
132
        'credentials.user': 'user-admin',
 
133
        'server.repository': 'build',
 
134
        'juju.environment': utils.current_env(),
 
135
        'runtime.observe': True,
 
136
        'runtime.modify': True
 
137
    })
 
138
 
 
139
    global application
 
140
    application = ReconcilerWebApp([
 
141
        (r"/api/v1/", StateHandler),
 
142
        (r"/api/v1/strategy", StrategyHandler),
 
143
        (r"/api/v1/reset", ResetHandler),
 
144
    ],
 
145
        autoreload=True,
 
146
        config=config,
 
147
        **config['server']
 
148
    )
 
149
 
 
150
    global server
 
151
    global db
 
152
 
 
153
    if not os.path.exists(config['server.repository']):
 
154
        os.makedirs(config['server.repository'])
 
155
 
 
156
    db = model.StateDatabase(config)
 
157
    server = tornado.httpserver.HTTPServer(application)
 
158
    server.listen(config['server.port'], config['server.address'])
 
159
 
 
160
    signal.signal(signal.SIGTERM, sig_restart)
 
161
    signal.signal(signal.SIGINT, sig_restart)
 
162
    signal.signal(signal.SIGHUP, sig_reconcile)
 
163
 
 
164
    # config file change should reload the application
 
165
    # its values are passed to Application init
 
166
    tornado.autoreload.watch(options.config)
 
167
    utils.record_pid()
 
168
    loop = tornado.ioloop.IOLoop.instance()
 
169
    # tornado.ioloop.PeriodicCallback(reconcile, 500, io_loop=loop).start()
 
170
    loop.start()
 
171
 
 
172
 
 
173
if __name__ == "__main__":
 
174
    main()