Commit c74efadc authored by Jérome Perrin's avatar Jérome Perrin

stack/erp5: fix ZEO repozo backups not produced on python3

Products.TIDStorage was not ported to python3 and is not installed on
software-py3.cfg but the backup crontab expects tidstorage to be
present - as a result, it was silently failing to produce backups.

This brings minimal support to repozo backups on python3, without
Products.TIDStorage interraction and also extends software release test
to have a simple test checking that backups are produced and can be
restored.
parent 95b5d1a4
...@@ -51,6 +51,8 @@ setup(name=name, ...@@ -51,6 +51,8 @@ setup(name=name,
'cryptography', 'cryptography',
'pexpect', 'pexpect',
'pyOpenSSL', 'pyOpenSSL',
'ZEO',
'zodburi',
], ],
test_suite='test', test_suite='test',
) )
import contextlib
import subprocess
import json
import zodburi
from ZODB.DB import DB
from slapos.testing.utils import CrontabMixin
from . import ERP5InstanceTestCase, default, matrix, setUpModule, ERP5PY3
_ = setUpModule
class ZEOTestCase(ERP5InstanceTestCase):
__test_matrix__ = matrix((default,))
@classmethod
def getInstanceSoftwareType(cls) -> str:
return "zodb-zeo"
@classmethod
def _getInstanceParameterDict(cls) -> dict:
return {
"tcpv4-port": 8000,
"computer-memory-percent-threshold": 100,
"name": cls.__name__,
"monitor-passwd": "secret",
"zodb-dict": {"root": {}},
}
@classmethod
def getInstanceParameterDict(cls) -> dict:
return {"_": json.dumps(cls._getInstanceParameterDict())}
def setUp(self) -> None:
self.storage_dict = json.loads(
self.computer_partition.getConnectionParameterDict()["_"]
)["storage-dict"]
def db(self) -> contextlib.AbstractContextManager[DB]:
root = self.storage_dict["root"]
zeo_uri = f"zeo://{root['server']}?storage={root['storage']}"
storage_factory, dbkw = zodburi.resolve_uri(zeo_uri)
return contextlib.closing(DB(storage_factory(), **dbkw))
class TestRepozo(ZEOTestCase, CrontabMixin):
__partition_reference__ = "rpz"
def test_backup_and_restore(self) -> None:
def check_state():
(self.computer_partition_root_path / ".timestamp").unlink()
self.waitForInstance()
if ERP5PY3:
with self.db() as db:
with db.transaction() as cnx:
self.assertEqual(cnx.root.state, "before backup")
if ERP5PY3:
# as it is not possible to connect to a python2 ZEO server
# from a python3 client, we check more when the server is python3
with self.db() as db:
with db.transaction() as cnx:
cnx.root.state = "before backup"
check_state()
self._executeCrontabAtDate("tidstorage", "2000-01-01 UTC")
dat, fsz, index = sorted(
[
p.name
for p in (
self.computer_partition_root_path / "srv" / "backup" / "zodb" / "root"
).glob("*")
]
)
self.assertRegex(dat, r'2000-01-01-00-\d\d-\d\d.dat')
self.assertRegex(fsz, r'2000-01-01-00-\d\d-\d\d.fsz')
self.assertRegex(index, r'2000-01-01-00-\d\d-\d\d.index')
if ERP5PY3:
with self.db() as db:
with db.transaction() as cnx:
cnx.root.state = "after backup"
db.close()
restore_script = self.computer_partition_root_path / "srv" / "runner-import-restore"
self.assertTrue(restore_script.exists())
status, restore_output = subprocess.getstatusoutput(str(restore_script))
self.assertEqual(status, 1)
self.assertIn("Zeo is already running", restore_output)
with self.slap.instance_supervisor_rpc as supervisor:
supervisor.stopAllProcesses()
restore_output = subprocess.check_output(restore_script)
check_state()
...@@ -14,6 +14,7 @@ extends = ...@@ -14,6 +14,7 @@ extends =
../../component/python-pynacl/buildout.cfg ../../component/python-pynacl/buildout.cfg
../../component/python-backports-lzma/buildout.cfg ../../component/python-backports-lzma/buildout.cfg
../../component/selenium/buildout.cfg ../../component/selenium/buildout.cfg
../../component/ZODB/buildout.cfg
../../stack/slapos.cfg ../../stack/slapos.cfg
../../stack/nxdtest.cfg ../../stack/nxdtest.cfg
...@@ -356,6 +357,7 @@ setup = ${recurls-repository:location} ...@@ -356,6 +357,7 @@ setup = ${recurls-repository:location}
[python-interpreter] [python-interpreter]
eggs += eggs +=
${BTrees:egg}
${lxml-python:egg} ${lxml-python:egg}
${python-PyYAML:egg} ${python-PyYAML:egg}
${slapos.core-setup:egg} ${slapos.core-setup:egg}
...@@ -365,6 +367,7 @@ eggs += ...@@ -365,6 +367,7 @@ eggs +=
beautifulsoup4 beautifulsoup4
caucase caucase
erp5.util erp5.util
${persistent:egg}
${python-pynacl:egg} ${python-pynacl:egg}
${python-cryptography:egg} ${python-cryptography:egg}
${python-mysqlclient:egg} ${python-mysqlclient:egg}
...@@ -526,15 +529,23 @@ recurls = ...@@ -526,15 +529,23 @@ recurls =
slapos.core = slapos.core =
# Various needed versions # Various needed versions
Pillow = 10.2.0+SlapOSPatched001 BTrees = 6.1
forcediphttpsadapter = 1.0.1 forcediphttpsadapter = 1.0.1
image = 1.5.25 image = 1.5.25
mysqlclient = 2.1.1
paho-mqtt = 1.5.0
pcpp = 1.30
persistent = 6.1
Pillow = 10.2.0+SlapOSPatched001
plantuml = 0.3.0:whl plantuml = 0.3.0:whl
pypdf = 3.6.0:whl pypdf = 3.6.0:whl
pysftp = 0.2.9 pysftp = 0.2.9
requests-toolbelt = 0.8.0 requests-toolbelt = 0.8.0
testfixtures = 6.11.0 testfixtures = 6.11.0
mysqlclient = 2.1.1 transaction = 5.0
paho-mqtt = 1.5.0
pcpp = 1.30
xmltodict = 0.13.0 xmltodict = 0.13.0
ZEO = 6.0.0
ZODB = 6.0.0
zodbpickle = 4.1.1
zope.deferredimport = 5.0
zope.proxy = 6.1
...@@ -78,7 +78,7 @@ md5sum = 1333d2fc21f64da4010a4eafea59d141 ...@@ -78,7 +78,7 @@ md5sum = 1333d2fc21f64da4010a4eafea59d141
[template-zeo] [template-zeo]
filename = instance-zeo.cfg.in filename = instance-zeo.cfg.in
md5sum = 218ffa7beb27693670664f64b993ed44 md5sum = 33075c3ab06613874b928d90e0c0006a
[template-zeo-conf] [template-zeo-conf]
filename = zeo.conf.in filename = zeo.conf.in
......
...@@ -80,6 +80,44 @@ config-port = {{ "${" ~ zeo_section_name ~ ":port}" }} ...@@ -80,6 +80,44 @@ config-port = {{ "${" ~ zeo_section_name ~ ":port}" }}
{% set tidstorage_repozo_path = '' -%} {% set tidstorage_repozo_path = '' -%}
{% else -%} {% else -%}
[repozo-backup-script]
repozo-wrapper = ${buildout:bin-directory}/tidstorage-repozo
# BBB on python3 we don't use Products.TIDStorage but repozo directly.
[repozo-backup-script:python3]
recipe = slapos.recipe.template
inline =
#!/bin/sh
zodb_directory="${directory:zodb}"
zodb_backup_directory="{{ default_backup_path }}"
repozo="${tidstorage:repozo-binary}"
EXIT_CODE=0
{% for family, zodb in six.iteritems(zodb_dict) -%}
{% for name, zodb in zodb -%}
storage_name="{{ name }}"
zodb_path="$storage_name.fs"
[ ! -d "$zodb_backup_directory/$storage_name" ]] && mkdir "$zodb_backup_directory/$storage_name"
echo "Backing up $storage_name ..."
$repozo \
--backup \
--gzip \
--quick \
--repository="$zodb_backup_directory/$storage_name" \
--file="$zodb_directory/$zodb_path"
CURRENT_EXIT_CODE=$?
if [ ! "$CURRENT_EXIT_CODE"="0" ]; then
EXIT_CODE="$CURRENT_EXIT_CODE"
echo "$storage_name Backup restoration failed."
fi
{% endfor -%}
{% endfor -%}
exit $EXIT_CODE
repozo-wrapper = ${:output}
mode = 755
output = ${buildout:bin-directory}/repozo-backup
[tidstorage] [tidstorage]
recipe = slapos.cookbook:tidstorage recipe = slapos.cookbook:tidstorage
known-tid-storage-identifier-dict = {{ dumps(known_tid_storage_identifier_dict) }} known-tid-storage-identifier-dict = {{ dumps(known_tid_storage_identifier_dict) }}
...@@ -116,7 +154,7 @@ recipe = slapos.cookbook:cron.d ...@@ -116,7 +154,7 @@ recipe = slapos.cookbook:cron.d
cron-entries = ${cron:cron-entries} cron-entries = ${cron:cron-entries}
name = tidstorage name = tidstorage
time = {{ dumps(backup_periodicity) }} time = {{ dumps(backup_periodicity) }}
command = ${tidstorage:repozo-wrapper} command = ${repozo-backup-script:repozo-wrapper}
# Used for ERP5 resiliency or (more probably) # Used for ERP5 resiliency or (more probably)
# webrunner resiliency with erp5 inside. # webrunner resiliency with erp5 inside.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment