Skip to content

Commit 160aa2c

Browse files
author
James William Pye
committed
Initial work toward implementing tin and cluster.
1 parent 5b9a466 commit 160aa2c

8 files changed

Lines changed: 751 additions & 160 deletions

File tree

bin/pg_dotconf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env python
2+
import sys
3+
from postgresql.configfile import pg_dotconf
4+
sys.exit(pg_dotconf(sys.argv))

bin/pg_tin

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env python
2+
import sys
3+
from postgresql.tin import command
4+
sys.exit(command(sys.argv))

bin/pg_withcluster

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python
2+
"""
3+
Entry point for pg_withcluster.
4+
"""
5+
import tempfile
6+
import subprocess as sp
7+
from optparse import OptionParser
8+
from postgresql.cluster import Cluster
9+
10+
op = OptionParser(
11+
"%prog [-p] [--version] {[setting=value], ...} [pg_config] command",
12+
version = '1.0'
13+
)
14+
op.allow_interspersed_args = False
15+
op.add_option(
16+
'-p',
17+
dest = 'look_in_path',
18+
help = 'Look for "pg_config" in the PATH environment',
19+
action = 'store_true',
20+
default = False
21+
)
22+
co, ca = op.parse_args(args[1:])
23+
if not ca:
24+
sys.stderr.write("no command given to run in cluster context")
25+
sys.exit(1)
26+
27+
i = 0
28+
for x in ca:
29+
if '=' not in x:
30+
break
31+
i += 1
32+
settings = ca[0:i]
33+
command = ca[i:]
34+
d = tempfile.mkdtemp()
35+
try:
36+
c = Cluster.create(d)
37+
c.settings.update([
38+
x.split('=', 1) for x in settings
39+
])
40+
c.control.start()
41+
try:
42+
p = sp.Popen(
43+
command,
44+
env = {
45+
},
46+
)
47+
return p.wait()
48+
finally:
49+
c.control.stop()
50+
finally:
51+
c.drop()

postgresql/api.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,78 @@ def settings(self):
753753
bound to.
754754
"""
755755

756+
class Cluster(metaclass = ABCMeta):
757+
"""
758+
Interface to a PostgreSQL cluster--a data directory. An implementation of
759+
this provides a means to control a server.
760+
"""
761+
@classmethod
762+
def create(cls,
763+
path : "where to create the cluster",
764+
initdb : "path to the initdb to use",
765+
):
766+
"""
767+
Create a cluster using the specified initdb at the given path and return a
768+
`Cluster` instance to that cluster.
769+
"""
770+
raise NotImplementedError("classmethod 'Cluster.create' not implemented")
771+
772+
@abstractmethod
773+
def start(self):
774+
"""
775+
Start the cluster.
776+
"""
777+
778+
@abstractmethod
779+
def stop(self):
780+
"""
781+
Signal the server to shutdown.
782+
"""
783+
784+
@abstractmethod
785+
def restart(self):
786+
"""
787+
Restart the cluster; effectively stop() and start().
788+
"""
789+
790+
@abstractmethod
791+
def kill(self):
792+
"""
793+
Kill the server.
794+
"""
795+
796+
@abstractmethod
797+
def drop(self):
798+
"""
799+
Kill the server and completely remove the data directory.
800+
"""
801+
802+
@abstractproperty
803+
def pid(self):
804+
"""
805+
The process id of the running daemon.
806+
807+
This must be extracted from the run-info from the cluster directory.
808+
"""
809+
810+
@abstractproperty
811+
def settings(self):
812+
"""
813+
A `Settings` interface to the postgresql.conf file associated with the
814+
cluster.
815+
"""
816+
817+
def __enter__(self):
818+
if not self.running():
819+
self.start()
820+
821+
def __context__(self):
822+
return self
823+
824+
def __exit__(self, exc, val, tb):
825+
self.stop()
826+
return exc is None
827+
756828
if __name__ == '__main__':
757829
help('postgresql.api')
758830

postgresql/cluster.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
##
2+
# copyright 2008, pg/python project.
3+
# http://python.projects.postgresql.org
4+
##
5+
"""
6+
Create and interface with PostgreSQL clusters.
7+
"""
8+
import sys
9+
import os
10+
import subprocess as sp
11+
import warnings
12+
from . import api as pg_api
13+
from . import configfile
14+
15+
DEFAULT_CONFIG_FILENAME = 'postgresql.conf'
16+
DEFAULT_HBA_FILENAME = 'pg_hba.conf'
17+
18+
initdb_option_map = {
19+
'encoding' : '-E',
20+
'locale' : '--locale',
21+
'collate' : '--lc-collate',
22+
'ctype' : '--lc-ctype',
23+
'monetary' : '--lc-monetary',
24+
'numeric' : '--lc-numeric',
25+
'time' : '--lc-time',
26+
'authentication' : '-A',
27+
'superusername' : '-U',
28+
'superuserpass' : '--pwfile',
29+
'verbose' : '-d',
30+
'sources' : '-L',
31+
}
32+
33+
class Cluster(pg_api.Cluster):
34+
"""
35+
"""
36+
@property
37+
def settings(self):
38+
if not hasattr(self, '_settings'):
39+
self._settings = configfile.ConfigFile(self.pgsql_dot_conf)
40+
return self._settings
41+
42+
@classmethod
43+
def create(cls,
44+
path : "path to give to initdb",
45+
initdb = 'initdb',
46+
**kw
47+
):
48+
"""
49+
Create the cluster at the given `datadir` using the
50+
provided keyword parameters as options to the command.
51+
52+
`command_option_map` provides the mapping of keyword arguments
53+
to command options.
54+
"""
55+
# Transform keyword options into command options for the executable.
56+
opts = []
57+
for x in kw:
58+
if x in ('nowait', 'logfile', 'extra_arguments'):
59+
continue
60+
if x not in self.command_option_map:
61+
raise TypeError("got an unexpected keyword argument %r" %(x,))
62+
opts.append(self.command_option_map[x])
63+
opts.append(kw[x])
64+
nowait = kw.get('nowait', False)
65+
logfile = kw.get('logfile', sp.PIPE)
66+
extra_args = kw.get('extra_arguments', ())
67+
68+
cmd = (self._path, '-D', datadir) + tuple(opts) + extra_args
69+
p = sp.Popen(
70+
cmd,
71+
close_fds = True,
72+
stdin = sp.PIPE,
73+
stdout = logfile,
74+
stderr = sp.PIPE
75+
)
76+
p.stdin.close()
77+
78+
if nowait:
79+
return p
80+
81+
try:
82+
rc = p.wait()
83+
except KeyboardInterrupt:
84+
os.kill(p.pid, signal.SIGINT)
85+
raise
86+
87+
if rc != 0:
88+
raise InitDBError(cmd, rc, p.stderr.read())
89+
90+
def __init__(self, path, postgres_path = 'postgres'):
91+
"""
92+
"""
93+
if not os.path.isdir(path):
94+
raise ValueError("cluster at %s does not exist" %(path,))
95+
self.path = path
96+
97+
def __repr__(self):
98+
return "%s.%s(%r, %r)" %(
99+
type(self).__module__,
100+
type(self).__name__,
101+
self.path,
102+
self.postgres_path,
103+
)
104+
105+
def drop(self):
106+
'Stop the cluster and delete it from the filesystem'
107+
if self.running():
108+
self.stop()
109+
# Really, using rm -rf would be the best, but use this for portability.
110+
for root, dirs, files in os.walk(self.control.data, topdown = False):
111+
for name in files:
112+
os.remove(os.path.join(root, name))
113+
for name in dirs:
114+
os.rmdir(os.path.join(root, name))
115+
os.rmdir(self.control.data)
116+
117+
def start(self):
118+
"""
119+
Start the cluster
120+
"""
121+
##
122+
# vim: ts=3:sw=3:noet:

0 commit comments

Comments
 (0)