Skip to content

Commit 9f97acb

Browse files
author
v.shepard
committed
PBCKP-152 test multihost
1 parent 3d368cc commit 9f97acb

File tree

4 files changed

+148
-32
lines changed

4 files changed

+148
-32
lines changed

testgres/cache.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@
2121
execute_utility
2222

2323

24-
def cached_initdb(data_dir, logfile=None, hostname='localhost', ssh_key=None, params=None):
24+
def cached_initdb(data_dir, logfile=None, host='localhost', ssh_key=None, user='dev', params=None):
2525
"""
2626
Perform initdb or use cached node files.
2727
"""
2828

2929
def call_initdb(initdb_dir, log=None):
3030
try:
31-
_params = [get_bin_path("initdb"), "-D", initdb_dir, "-N"]
31+
_params = [get_bin_path("initdb", host, ssh_key), "-D", initdb_dir, "-N"]
3232

3333
# DDD return executions code
34-
execute_utility(_params + (params or []), log, hostname=hostname, ssh_key=ssh_key)
34+
execute_utility(_params + (params or []), log, host=host, ssh_key=ssh_key, user=user)
3535
except ExecUtilException as e:
3636
raise_from(InitNodeException("Failed to run initdb"), e)
3737

testgres/node.py

+12-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import subprocess
77
import time
88

9+
from os_ops import OsOperations
10+
911
try:
1012
from collections.abc import Iterable
1113
except ImportError:
@@ -110,7 +112,7 @@ def __repr__(self):
110112

111113
class PostgresNode(object):
112114
def __init__(self, name=None, port=None, base_dir=None,
113-
host='127.0.0.1', hostname='locahost', ssh_key=None):
115+
host='locahost', ssh_key=None, username='dev'):
114116
"""
115117
PostgresNode constructor.
116118
@@ -129,10 +131,10 @@ def __init__(self, name=None, port=None, base_dir=None,
129131

130132
# basic
131133
self.host = host
132-
self.hostname = hostname
133134
self.ssh_key = ssh_key
134135
self.name = name or generate_app_name()
135136
self.port = port or reserve_port()
137+
self.os_ops = OsOperations(host, ssh_key, username)
136138

137139
# defaults for __exit__()
138140
self.cleanup_on_good_exit = testgres_config.node_cleanup_on_good_exit
@@ -255,20 +257,14 @@ def base_dir(self):
255257
if not self._base_dir:
256258
self._base_dir = mkdtemp(prefix=TMP_NODE)
257259

258-
# NOTE: it's safe to create a new dir
259-
if not os.path.exists(self._base_dir):
260-
os.makedirs(self._base_dir)
260+
self.os_ops.makedirs(self._base_dir)
261261

262262
return self._base_dir
263263

264264
@property
265265
def logs_dir(self):
266266
path = os.path.join(self.base_dir, LOGS_DIR)
267-
268-
# NOTE: it's safe to create a new dir
269-
if not os.path.exists(path):
270-
os.makedirs(path)
271-
267+
self.os_ops.makedirs(path)
272268
return path
273269

274270
@property
@@ -420,7 +416,7 @@ def _collect_special_files(self):
420416

421417
return result
422418

423-
def init(self, initdb_params=None, hostname='localhost', ssh_key=None, **kwargs):
419+
def init(self, initdb_params=None, host='localhost', ssh_key=None, **kwargs):
424420
"""
425421
Perform initdb for this node.
426422
@@ -429,6 +425,8 @@ def init(self, initdb_params=None, hostname='localhost', ssh_key=None, **kwargs)
429425
fsync: should this node use fsync to keep data safe?
430426
unix_sockets: should we enable UNIX sockets?
431427
allow_streaming: should this node add a hba entry for replication?
428+
host: for remote connection
429+
ssh_key: for remote connection
432430
433431
Returns:
434432
This instance of :class:`.PostgresNode`
@@ -437,7 +435,7 @@ def init(self, initdb_params=None, hostname='localhost', ssh_key=None, **kwargs)
437435
# initialize this PostgreSQL node
438436
cached_initdb(data_dir=self.data_dir,
439437
logfile=self.utils_log_file,
440-
hostname=hostname,
438+
host=host,
441439
ssh_key=ssh_key,
442440
params=initdb_params)
443441

@@ -470,6 +468,7 @@ def default_conf(self,
470468
hba_conf = os.path.join(self.data_dir, HBA_CONF_FILE)
471469

472470
# filter lines in hba file
471+
lines = self.os_ops.readlines(hba_conf)
473472
with io.open(hba_conf, "r+") as conf:
474473
# get rid of comments and blank lines
475474
lines = [
@@ -585,10 +584,7 @@ def append_conf(self, line='', filename=PG_CONF_FILE, **kwargs):
585584
lines.append('{} = {}'.format(option, value))
586585

587586
config_name = os.path.join(self.data_dir, filename)
588-
with io.open(config_name, 'a') as conf:
589-
for line in lines:
590-
conf.write(text_type(line))
591-
conf.write(text_type('\n'))
587+
self.os_ops.write(config_name, '\n'.join(lines))
592588

593589
return self
594590

testgres/os_ops.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import os
2+
import shutil
3+
import subprocess
4+
5+
import paramiko
6+
7+
8+
class OsOperations:
9+
10+
def __init__(self, host, ssh_key=None, username='dev'):
11+
self.host = host
12+
self.ssh_key = ssh_key
13+
self.username = username
14+
self.remote = self.host != 'localhost'
15+
self.ssh = None
16+
17+
if self.remote:
18+
self.ssh = self.connect()
19+
20+
def __del__(self):
21+
if self.ssh:
22+
self.ssh.close()
23+
24+
def connect(self):
25+
# Check that the ssh key file exists and is a file
26+
if not os.path.isfile(self.ssh_key):
27+
raise ValueError(f"{self.ssh_key} does not exist or is not a file")
28+
29+
# Load the private key with the correct key type
30+
with open(self.ssh_key, 'r') as f:
31+
key_data = f.read()
32+
if 'BEGIN OPENSSH PRIVATE KEY' in key_data:
33+
key = paramiko.Ed25519Key.from_private_key_file(self.ssh_key)
34+
else:
35+
key = paramiko.RSAKey.from_private_key_file(self.ssh_key)
36+
37+
# Now use the loaded key in your SSH client code
38+
ssh = paramiko.SSHClient()
39+
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
40+
ssh.connect(self.host, username=self.username, pkey=key)
41+
return ssh
42+
43+
def makedirs(self, path):
44+
if self.remote:
45+
# Remove the remote directory
46+
stdin, stdout, stderr = self.ssh.exec_command(f'rm -rf {path}')
47+
stdout.channel.recv_exit_status()
48+
49+
# Create the remote directory
50+
stdin, stdout, stderr = self.ssh.exec_command(f'mkdir -p {path}')
51+
stdout.channel.recv_exit_status()
52+
else:
53+
shutil.rmtree(path, ignore_errors=True)
54+
os.makedirs(path)
55+
56+
def write(self, filename, text):
57+
if self.remote:
58+
command = f"echo '{text}' >> {filename}"
59+
stdin, stdout, stderr = self.ssh.exec_command(command)
60+
stdout.channel.recv_exit_status()
61+
else:
62+
with open(filename, 'a') as file:
63+
file.write(text)
64+
65+
def read(self, filename):
66+
if self.remote:
67+
stdin, stdout, stderr = self.ssh.exec_command(f'cat {filename}')
68+
stdout.channel.recv_exit_status()
69+
output = stdout.read().decode().rstrip()
70+
else:
71+
with open(filename) as file:
72+
output = file.read()
73+
return output
74+
75+
def readlines(self, filename):
76+
if self.remote:
77+
stdin, stdout, stderr = self.ssh.exec_command(f'cat {filename}')
78+
stdout.channel.recv_exit_status()
79+
output = stdout.read().decode().rstrip()
80+
else:
81+
with open(filename) as file:
82+
output = file.read()
83+
return output
84+
85+
def get_name(self):
86+
if self.remote:
87+
stdin, stdout, stderr = self.ssh.exec_command('python -c "import os; print(os.name)"')
88+
stdout.channel.recv_exit_status()
89+
name = stdout.read().decode().strip()
90+
else:
91+
name = os.name
92+
return name
93+
94+
def kill(self, pid, signal):
95+
if self.remote:
96+
stdin, stdout, stderr = self.ssh.exec_command(f'kill -{signal} {pid}')
97+
stdout.channel.recv_exit_status()
98+
else:
99+
os.kill(pid, signal)
100+
101+
def environ(self, var_name):
102+
if self.remote:
103+
# Get env variable
104+
check_command = f"echo ${var_name}"
105+
stdin, stdout, stderr = self.ssh.exec_command(check_command)
106+
stdout.channel.recv_exit_status()
107+
var_val = stdout.read().strip().decode('utf-8')
108+
else:
109+
var_val = os.environ.get(var_name)
110+
return var_val
111+
112+
def execute(self, cmd):
113+
if self.remote:
114+
stdin, stdout, stderr = self.ssh.exec_command(cmd)
115+
stdout.channel.recv_exit_status()
116+
result = stdout.read().decode('utf-8')
117+
else:
118+
result = subprocess.check_output([cmd]).decode('utf-8')
119+
return result
120+

testgres/utils.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# Add remote host call
2020
from fabric import Connection
2121

22+
from os_ops import OsOperations
2223
from .config import testgres_config
2324
from .exceptions import ExecUtilException
2425

@@ -51,7 +52,7 @@ def release_port(port):
5152
bound_ports.discard(port)
5253

5354

54-
def execute_utility(args, logfile=None, hostname='localhost', ssh_key=None):
55+
def execute_utility(args, logfile=None, host='localhost', ssh_key=None, user='dev'):
5556
"""
5657
Execute utility wrapper (pg_ctl, pg_dump etc).
5758
@@ -60,20 +61,19 @@ def execute_utility(args, logfile=None, hostname='localhost', ssh_key=None):
6061
6162
Args:
6263
args: utility + arguments (list).
63-
host_params : dict {
64+
host: remote connection host
65+
ssh_key: remote connection ssh_key
66+
user: remote connection user
6467
logfile: path to file to store stdout and stderr.
6568
6669
Returns:
6770
stdout of executed utility.
6871
"""
69-
70-
71-
if hostname != 'localhost':
72+
if host != 'localhost':
7273
conn = Connection(
73-
host=hostname,
74-
user="dev",
74+
host=host,
75+
user=user,
7576
connect_kwargs={
76-
# XXX pass SSH key path
7777
"key_filename": ssh_key,
7878
},
7979
)
@@ -87,9 +87,8 @@ def execute_utility(args, logfile=None, hostname='localhost', ssh_key=None):
8787

8888
return result
8989

90-
9190
# run utility
92-
if os.name == 'nt':
91+
elif os.name == 'nt':
9392
# using output to a temporary file in Windows
9493
buf = tempfile.NamedTemporaryFile()
9594

@@ -146,24 +145,25 @@ def execute_utility(args, logfile=None, hostname='localhost', ssh_key=None):
146145
return out
147146

148147

149-
def get_bin_path(filename):
148+
def get_bin_path(filename, host='localhost', ssh_key=None):
150149
"""
151150
Return absolute path to an executable using PG_BIN or PG_CONFIG.
152151
This function does nothing if 'filename' is already absolute.
153152
"""
153+
os_ops = OsOperations(host, ssh_key)
154154

155155
# check if it's already absolute
156156
if os.path.isabs(filename):
157157
return filename
158158

159159
# try PG_CONFIG
160-
pg_config = os.environ.get("PG_CONFIG")
160+
pg_config = os_ops.environ("PG_CONFIG")
161161
if pg_config:
162162
bindir = get_pg_config()["BINDIR"]
163163
return os.path.join(bindir, filename)
164164

165165
# try PG_BIN
166-
pg_bin = os.environ.get("PG_BIN")
166+
pg_bin = os_ops.environ("PG_BIN")
167167
if pg_bin:
168168
return os.path.join(pg_bin, filename)
169169

0 commit comments

Comments
 (0)