File: clusterfixture.py

package info (click to toggle)
postgresfixture 0.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 224 kB
  • sloc: python: 1,094; makefile: 5
file content (145 lines) | stat: -rw-r--r-- 3,939 bytes parent folder | download | duplicates (4)
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
142
143
144
145
# Copyright 2012-2014 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Manage a PostgreSQL cluster."""

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

__metaclass__ = type
__all__ = [
    "ClusterFixture",
    ]

from errno import (
    EEXIST,
    ENOENT,
    ENOTEMPTY,
    )
from os import (
    getpid,
    listdir,
    makedirs,
    path,
    rmdir,
    unlink,
    )

from fixtures import Fixture
from postgresfixture.cluster import (
    Cluster,
    PG_VERSION_MAX,
    )


class ProcessSemaphore:
    """A sort-of-semaphore where it is considered locked if a directory cannot
    be removed.

    The locks are taken out one per-process, so this is a way of keeping a
    reference to a shared resource between processes.
    """

    def __init__(self, lockdir):
        super(ProcessSemaphore, self).__init__()
        self.lockdir = lockdir
        self.lockfile = path.join(
            self.lockdir, "%d" % getpid())

    def acquire(self):
        try:
            makedirs(self.lockdir)
        except OSError as error:
            if error.errno != EEXIST:
                raise
        open(self.lockfile, "w").close()

    def release(self):
        try:
            unlink(self.lockfile)
        except OSError as error:
            if error.errno != ENOENT:
                raise

    @property
    def locked(self):
        try:
            rmdir(self.lockdir)
        except OSError as error:
            if error.errno == ENOTEMPTY:
                return True
            elif error.errno == ENOENT:
                return False
            else:
                raise
        else:
            return False

    @property
    def locked_by(self):
        try:
            return [
                int(name) if name.isdigit() else name
                for name in listdir(self.lockdir)
                ]
        except OSError as error:
            if error.errno == ENOENT:
                return []
            else:
                raise


class ClusterFixture(Cluster, Fixture):
    """A fixture for a `Cluster`."""

    def __init__(self, datadir, preserve=False, version=PG_VERSION_MAX):
        """
        @param preserve: Leave the cluster and its databases behind, even if
            this fixture creates them.
        """
        super(ClusterFixture, self).__init__(datadir, version=version)
        self.preserve = preserve
        self.shares = ProcessSemaphore(
            path.join(self.datadir, "shares"))

    def setUp(self):
        super(ClusterFixture, self).setUp()
        # Only destroy the cluster if we create it...
        if not self.exists:
            # ... unless we've been asked to preserve it.
            if not self.preserve:
                self.addCleanup(self.destroy)
            self.create()
        self.addCleanup(self.stop)
        self.start()
        self.addCleanup(self.shares.release)
        self.shares.acquire()

    def createdb(self, name):
        """Create the named database if it does not exist already.

        Arranges to drop the named database during clean-up, unless `preserve`
        has been specified.
        """
        if name not in self.databases:
            super(ClusterFixture, self).createdb(name)
            if not self.preserve:
                self.addCleanup(self.dropdb, name)

    def dropdb(self, name):
        """Drop the named database if it exists."""
        if name in self.databases:
            super(ClusterFixture, self).dropdb(name)

    def stop(self):
        """Stop the cluster, but only if there are no other users."""
        if not self.shares.locked:
            super(ClusterFixture, self).stop()

    def destroy(self):
        """Destroy the cluster, but only if there are no other users."""
        if not self.shares.locked:
            super(ClusterFixture, self).destroy()