source: trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/config.py @ 1197

Last change on this file since 1197 was 1197, checked in by broder, 16 years ago

Now that we're using Python 2.5, we can actually write with statements

File size: 3.7 KB
Line 
1from __future__ import with_statement
2
3import json
4from invirt.common import *
5from os import rename
6from os.path import getmtime
7from contextlib import closing
8
9default_src_path   = '/etc/invirt/master.yaml'
10default_cache_path = '/var/lib/invirt/cache.json'
11lock_path          = '/var/lib/invirt/cache.lock'
12
13def load(src_path = default_src_path,
14         cache_path = default_cache_path,
15         force_refresh = False):
16    """
17    Try loading the configuration from the faster-to-load JSON cache at
18    cache_path.  If it doesn't exist or is outdated, load the configuration
19    instead from the original YAML file at src_path and regenerate the cache.
20    I assume I have the permissions to write to the cache directory.
21    """
22
23    # Namespace container for state variables, so that they can be updated by
24    # closures.
25    ns = struct()
26
27    if force_refresh:
28        do_refresh = True
29    else:
30        src_mtime = getmtime(src_path)
31        try:            cache_mtime = getmtime(cache_path)
32        except OSError: do_refresh  = True
33        else:           do_refresh  = src_mtime + 1 >= cache_mtime
34
35        # We chose not to simply say
36        #
37        #   do_refresh = src_mtime >= cache_time
38        #
39        # because between the getmtime(src_path) and the time the cache is
40        # rewritten, the master configuration may have been updated, so future
41        # checks here would find a cache with a newer mtime than the master
42        # (and thus treat the cache as containing the latest version of the
43        # master).  The +1 means that for at least a full second following the
44        # update to the master, this function will refresh the cache, giving us
45        # 1 second to write the cache.  Note that if it takes longer than 1
46        # second to write the cache, then this situation could still arise.
47        #
48        # The getmtime calls should logically be part of the same transaction
49        # as the rest of this function (cache read + conditional cache
50        # refresh), but to wrap everything in an flock would cause the
51        # following cache read to be less streamlined.
52
53    if not do_refresh:
54        # Try reading from the cache first.  This must be transactionally
55        # isolated from concurrent writes to prevent reading an incomplete
56        # (changing) version of the data (but the transaction can share the
57        # lock with other concurrent reads).  This isolation is accomplished
58        # using an atomic filesystem rename in the refreshing stage.
59        try: 
60            with closing(file(cache_path)) as f:
61                ns.cfg = json.read(f.read())
62        except: do_refresh = True
63
64    if do_refresh:
65        # Atomically reload the source and regenerate the cache.  The read and
66        # write must be a single transaction, or a stale version may be
67        # written (if another read/write of a more recent configuration
68        # is interleaved).  The final atomic rename is to keep this
69        # transactionally isolated from the above cache read.  If we fail to
70        # acquire the lock, just try to load the master configuration.
71        import yaml
72        try:    loader = yaml.CSafeLoader
73        except: loader = yaml.SafeLoader
74        try:
75            with lock_file(lock_path):
76                with closing(file(src_path)) as f:
77                    ns.cfg = yaml.load(f, loader)
78                try: 
79                    with closing(file(cache_path + '.tmp', 'w')) as f:
80                        f.write(json.write(ns.cfg))
81                except: pass # silent failure
82                else: rename(cache_path + '.tmp', cache_path)
83        except IOError:
84            with closing(file(src_path)) as f:
85                ns.cfg = yaml.load(f, loader)
86    return ns.cfg
87
88dicts = load()
89structs = dicts2struct(dicts)
90
91# vim:et:sw=4:ts=4
Note: See TracBrowser for help on using the repository browser.