Commit 38aabd7a authored by Jérome Perrin's avatar Jérome Perrin

Update Release Candidate

parents 1eb6911c daadef35
......@@ -48,6 +48,7 @@ recipe = zc.recipe.egg:develop
setup = ${ZEO4-repository:location}
egg = ZEO
egg-versions =
ZEO = 4.3.1
[ZEO4-repository]
recipe = slapos.recipe.build:gitclone
......
......@@ -24,6 +24,7 @@ extends =
../icu/buildout.cfg
../openssl/buildout.cfg
../libnsl/buildout.cfg
../libsodium/buildout.cfg
../sqlite3/buildout.cfg
../oniguruma/buildout.cfg
../xz-utils/buildout.cfg
......@@ -78,9 +79,12 @@ configure-options =
--enable-mbstring
--enable-pcntl
--enable-session
--enable-sysvsem
--with-apxs2=${apache:location}/bin/apxs
--with-bz2=${bzip2:location}
--with-curl
--with-freetype
--with-jpeg
--with-gettext=${gettext:location}
--with-imap-ssl
--with-imap=${cclient:location}
......@@ -88,6 +92,7 @@ configure-options =
--with-mysqli=mysqlnd
--with-openssl=${openssl:location}
--with-pdo-mysql=mysqlnd
--with-sodium=${libsodium:location}
--with-zip
--with-zlib
......@@ -95,7 +100,7 @@ configure-options =
# It will create a pear/temp directory under the SR instead of a shared /tmp/pear/temp.
# XXX we could mkdir tmp there
environment =
PKG_CONFIG_PATH=${libxml2:location}/lib/pkgconfig:${openssl:location}/lib/pkgconfig:${libzip:location}/lib/pkgconfig:${sqlite3:location}/lib/pkgconfig:${curl:location}/lib/pkgconfig:${icu:location}/lib/pkgconfig:${oniguruma:location}/lib/pkgconfig:${argon2:location}/lib/pkgconfig:${zlib:location}/lib/pkgconfig:${mariadb:location}/lib/pkgconfig:${libjpeg:location}/lib/pkgconfig:${libpng:location}/lib/pkgconfig:${freetype:location}/lib/pkgconfig:${libiconv:location}/lib/pkgconfig:${libzip:location}/lib/pkgconfig
PKG_CONFIG_PATH=${libxml2:location}/lib/pkgconfig:${openssl:location}/lib/pkgconfig:${libzip:location}/lib/pkgconfig:${sqlite3:location}/lib/pkgconfig:${curl:location}/lib/pkgconfig:${icu:location}/lib/pkgconfig:${oniguruma:location}/lib/pkgconfig:${argon2:location}/lib/pkgconfig:${zlib:location}/lib/pkgconfig:${mariadb:location}/lib/pkgconfig:${libjpeg:location}/lib/pkgconfig:${libpng:location}/lib/pkgconfig:${freetype:location}/lib/pkgconfig:${libiconv:location}/lib/pkgconfig:${libzip:location}/lib/pkgconfig:${libsodium:location}/lib/pkgconfig
PATH=${pkgconfig:location}/bin:${bzip2:location}/bin:${libxml2:location}/bin:${xz-utils:location}/bin:%(PATH)s
CPPFLAGS=-I${libzip:location}/include
LDFLAGS=-L${bzip2:location}/lib -Wl,-rpath -Wl,${bzip2:location}/lib -Wl,-rpath -Wl,${curl:location}/lib -L${libtool:location}/lib -Wl,-rpath -Wl,${libtool:location}/lib -L${mariadb:location}/lib -Wl,-rpath -Wl,${mariadb:location}/lib -L${zlib:location}/lib -Wl,-rpath -Wl,${zlib:location}/lib -L${libzip:location}/lib -Wl,-rpath -Wl,${libzip:location}/lib -L${argon2:location}/lib/x86_64-linux-gnu -Wl,-rpath -Wl,${argon2:location}/lib/x86_64-linux-gnu -Wl,-rpath -Wl,${zstd:location}/lib -L${libnsl:location}/lib -Wl,-rpath -Wl,${libnsl:location}/lib -L${sqlite3:location}/lib -Wl,-rpath -Wl,${sqlite3:location}/lib
......
......@@ -5,21 +5,24 @@
[buildout]
extends =
../chromium/buildout.cfg
../nss/buildout.cfg
../glib/buildout.cfg
../nspr/buildout.cfg
../nss/buildout.cfg
../pcre2/buildout.cfg
../xorg/buildout.cfg
parts =
chromedriver-wrapper
[chromedriver-wrapper-120]
<= chromedriver-wrapper
part = chromedriver-120
[chromedriver-wrapper-91]
<= chromedriver-wrapper
part = chromedriver-91
[chromedriver-wrapper-2.41]
<= chromedriver-wrapper
part = chromedriver-2.41
[chromedriver-wrapper]
# generate a wrapper named ${:wrapper-name} setting $LD_LIBRARY_PATH
......@@ -42,30 +45,36 @@ install =
[chromedriver]
<= chromedriver-91
<= chromedriver-120
[chromedriver-2.41]
[chromedriver-120]
<= chromedriver-download
version = 2.41
# Supports Chrome v67-69
md5sum-x86_64 = fbd8b9561575054e0e7e9cc53b680a70
version = 120.0.6099.109
revision-x86_64 = 1217362
generation-x86_64 = 1698717838856458
md5sum-x86_64 = 5cb8d386f01052cfc58c80ec63477db0
[chromedriver-91]
<= chromedriver-download
url = https://chromedriver.storage.googleapis.com/${:version}/chromedriver_linux64.zip
version = 91.0.4472.101
# Supports Chrome v91
md5sum-x86_64 = cc43ba0babbfff7f22b48165ec8e8c81
[chromedriver-download]
# Installs chromedriver ${version}.
# This chromedriver is not usable directly, it needs a wrapper.
recipe = slapos.recipe.build:download-unpacked
url = https://chromedriver.storage.googleapis.com/${:version}/chromedriver_${:_url}.zip
library =
${nss:location}/lib
${nspr:location}/lib
${glib:location}/lib
${libX11:location}/lib
${libXau:location}/lib
${libxcb:location}/lib
${libXdmcp:location}/lib
${nspr:location}/lib
${nss:location}/lib
${pcre2:location}/lib
[chromedriver-download:getattr(sys,'_multiarch',None)=='x86_64-linux-gnu']
_url = linux64
url = https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${:revision-x86_64}%2Fchromedriver_linux64.zip?generation=${:generation-x86_64}&alt=media
md5sum = ${:md5sum-x86_64}
......@@ -65,36 +65,35 @@ install =
))
os.fchmod(f.fileno(), 0o755)
[chromium-wrapper-120]
<= chromium-wrapper
part = chromium-120
[chromium-wrapper-91]
<= chromium-wrapper
part = chromium-91
[chromium-wrapper-69]
<= chromium-wrapper
part = chromium-69
[chromium]
<= chromium-120
[chromium-120]
<= chromium-download
version = 120.0.6099.109
revision-x86_64 = 1217362
md5sum-x86_64 = 86719e40f3d33f1b421d073bb4a71f41
generation-x86_64 = 1698717835110888
[chromium]
<= chromium-91
[chromium-91]
<= chromium-download
version = 91.0.4472.114
revision_x86-64 = 870763
revision-x86_64 = 870763
md5sum-x86_64 = 74eab41580469c2b8117cf396db823cb
generation-x86_64 = 1617926496067901
[chromium-69]
<= chromium-download
version = 69.0.3497.0
revision_x86-64 = 576753
md5sum-x86_64 = 08ac27fd40ace4ca8dfbd1db403deccb
generation-x86_64 = 1532051976706023
[chromium-download]
# macro to download a binary build of chromium and generate a
# wrapper as chrome-slapos in the part directory
......@@ -148,5 +147,5 @@ path =
${fontconfig:location}/bin
[chromium-download:getattr(sys,'_multiarch',None)=='x86_64-linux-gnu']
url = https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${:revision_x86-64}%2Fchrome-linux.zip?generation=${:generation-x86_64}&alt=media
url = https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${:revision-x86_64}%2Fchrome-linux.zip?generation=${:generation-x86_64}&alt=media
md5sum = ${:md5sum-x86_64}
From a02c80e17f794dc5eaea1c4edd3a2a3277a13638 Mon Sep 17 00:00:00 2001
From: Kazuhiko SHIOZAKI <kazuhiko@nexedi.com>
Date: Tue, 18 Jul 2023 10:26:54 +0200
Subject: [PATCH 1/4] Cast int to float in compare methods.
---
src/DateTime/DateTime.py | 30 ++++++++++-------------------
src/DateTime/tests/test_datetime.py | 17 ++++++++++++++++
2 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/DateTime/DateTime.py b/src/DateTime/DateTime.py
index 2d2d97f..c141306 100644
--- a/src/DateTime/DateTime.py
+++ b/src/DateTime/DateTime.py
@@ -1256,12 +1256,10 @@ class DateTime(object):
"""
if t is None:
t = 0
- if isinstance(t, float):
+ if isinstance(t, (float, int)):
return self._micros > long(t * 1000000)
- try:
+ else:
return self._micros > t._micros
- except AttributeError:
- return self._micros > t
__gt__ = greaterThan
@@ -1279,12 +1277,10 @@ class DateTime(object):
"""
if t is None:
t = 0
- if isinstance(t, float):
+ if isinstance(t, (float, int)):
return self._micros >= long(t * 1000000)
- try:
+ else:
return self._micros >= t._micros
- except AttributeError:
- return self._micros >= t
__ge__ = greaterThanEqualTo
@@ -1301,12 +1297,10 @@ class DateTime(object):
"""
if t is None:
t = 0
- if isinstance(t, float):
+ if isinstance(t, (float, int)):
return self._micros == long(t * 1000000)
- try:
+ else:
return self._micros == t._micros
- except AttributeError:
- return self._micros == t
def notEqualTo(self, t):
"""Compare this DateTime object to another DateTime object
@@ -1348,12 +1342,10 @@ class DateTime(object):
"""
if t is None:
t = 0
- if isinstance(t, float):
+ if isinstance(t, (float, int)):
return self._micros < long(t * 1000000)
- try:
+ else:
return self._micros < t._micros
- except AttributeError:
- return self._micros < t
__lt__ = lessThan
@@ -1370,12 +1362,10 @@ class DateTime(object):
"""
if t is None:
t = 0
- if isinstance(t, float):
+ if isinstance(t, (float, int)):
return self._micros <= long(t * 1000000)
- try:
+ else:
return self._micros <= t._micros
- except AttributeError:
- return self._micros <= t
__le__ = lessThanEqualTo
diff --git a/src/DateTime/tests/test_datetime.py b/src/DateTime/tests/test_datetime.py
index 249e79a..e6b3f93 100644
--- a/src/DateTime/tests/test_datetime.py
+++ b/src/DateTime/tests/test_datetime.py
@@ -228,6 +228,23 @@ class DateTimeTests(unittest.TestCase):
self.assertTrue(dt.lessThanEqualTo(dt1))
self.assertTrue(dt.notEqualTo(dt1))
self.assertFalse(dt.equalTo(dt1))
+ # Compare a date to float
+ dt = DateTime(1.0)
+ self.assertFalse(dt.greaterThan(1.0))
+ self.assertTrue(dt.greaterThanEqualTo(1.0))
+ self.assertFalse(dt.lessThan(1.0))
+ self.assertTrue(dt.lessThanEqualTo(1.0))
+ self.assertFalse(dt.notEqualTo(1.0))
+ self.assertTrue(dt.equalTo(1.0))
+ # Compare a date to int
+ dt = DateTime(1)
+ self.assertEqual(dt, DateTime(1.0))
+ self.assertFalse(dt.greaterThan(1))
+ self.assertTrue(dt.greaterThanEqualTo(1))
+ self.assertFalse(dt.lessThan(1))
+ self.assertTrue(dt.lessThanEqualTo(1))
+ self.assertFalse(dt.notEqualTo(1))
+ self.assertTrue(dt.equalTo(1))
def test_compare_methods_none(self):
# Compare a date to None
--
2.40.1
From 892e66132025ab8c213e4be57dd10f0f8eec7e60 Mon Sep 17 00:00:00 2001
From: Kazuhiko SHIOZAKI <kazuhiko@nexedi.com>
Date: Fri, 14 Jul 2023 21:10:06 +0200
Subject: [PATCH 2/4] Fix compare methods between DateTime(0) and None (fix
#52).
This is a fixup commit of 'further py3 work' that changed the behaviour of compare methods between DateTime(0) and None.
Now None is less than any DateTime instance including DateTime(0), just same as DateTime 2.
---
src/DateTime/DateTime.py | 10 +++++-----
src/DateTime/tests/test_datetime.py | 14 +++++++-------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/DateTime/DateTime.py b/src/DateTime/DateTime.py
index c141306..caf67e1 100644
--- a/src/DateTime/DateTime.py
+++ b/src/DateTime/DateTime.py
@@ -1255,7 +1255,7 @@ class DateTime(object):
long integer microseconds.
"""
if t is None:
- t = 0
+ return True
if isinstance(t, (float, int)):
return self._micros > long(t * 1000000)
else:
@@ -1276,7 +1276,7 @@ class DateTime(object):
long integer microseconds.
"""
if t is None:
- t = 0
+ return True
if isinstance(t, (float, int)):
return self._micros >= long(t * 1000000)
else:
@@ -1296,7 +1296,7 @@ class DateTime(object):
long integer microseconds.
"""
if t is None:
- t = 0
+ return False
if isinstance(t, (float, int)):
return self._micros == long(t * 1000000)
else:
@@ -1341,7 +1341,7 @@ class DateTime(object):
long integer microseconds.
"""
if t is None:
- t = 0
+ return False
if isinstance(t, (float, int)):
return self._micros < long(t * 1000000)
else:
@@ -1361,7 +1361,7 @@ class DateTime(object):
long integer microseconds.
"""
if t is None:
- t = 0
+ return False
if isinstance(t, (float, int)):
return self._micros <= long(t * 1000000)
else:
diff --git a/src/DateTime/tests/test_datetime.py b/src/DateTime/tests/test_datetime.py
index e6b3f93..1dd6c32 100644
--- a/src/DateTime/tests/test_datetime.py
+++ b/src/DateTime/tests/test_datetime.py
@@ -248,13 +248,13 @@ class DateTimeTests(unittest.TestCase):
def test_compare_methods_none(self):
# Compare a date to None
- dt = DateTime('1997/1/1')
- self.assertTrue(dt.greaterThan(None))
- self.assertTrue(dt.greaterThanEqualTo(None))
- self.assertFalse(dt.lessThan(None))
- self.assertFalse(dt.lessThanEqualTo(None))
- self.assertTrue(dt.notEqualTo(None))
- self.assertFalse(dt.equalTo(None))
+ for dt in (DateTime('1997/1/1'), DateTime(0)):
+ self.assertTrue(dt.greaterThan(None))
+ self.assertTrue(dt.greaterThanEqualTo(None))
+ self.assertFalse(dt.lessThan(None))
+ self.assertFalse(dt.lessThanEqualTo(None))
+ self.assertTrue(dt.notEqualTo(None))
+ self.assertFalse(dt.equalTo(None))
def test_pickle(self):
dt = DateTime()
--
2.40.1
From 4a9798072c87d2fe53b2e1e15b004ff982f9686a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Thu, 30 Nov 2023 06:19:54 +0100
Subject: [PATCH 3/4] Make it possible to pickle datetimes returned by
asdatetime
Fixes #58
---
src/DateTime/pytz_support.py | 5 +++++
src/DateTime/tests/test_datetime.py | 7 +++++++
2 files changed, 12 insertions(+)
diff --git a/src/DateTime/pytz_support.py b/src/DateTime/pytz_support.py
index 9ebf3db..e0746ea 100644
--- a/src/DateTime/pytz_support.py
+++ b/src/DateTime/pytz_support.py
@@ -199,9 +199,14 @@ for hour in range(0, 13):
_old_zmap['+%s00' % fhour] = 'GMT+%i' % hour
+def _p(zone):
+ return _numeric_timezones[zone]
+
+
def _static_timezone_factory(data):
zone = data[0]
cls = type(zone, (StaticTzInfo,), dict(
+ __reduce__=lambda _: (_p, (zone, )),
zone=zone,
_utcoffset=memorized_timedelta(data[5][0][0]),
_tzname=data[6][:-1])) # strip the trailing null
diff --git a/src/DateTime/tests/test_datetime.py b/src/DateTime/tests/test_datetime.py
index 1dd6c32..b9eeea9 100644
--- a/src/DateTime/tests/test_datetime.py
+++ b/src/DateTime/tests/test_datetime.py
@@ -270,6 +270,13 @@ class DateTimeTests(unittest.TestCase):
for key in DateTime.__slots__:
self.assertEqual(getattr(dt, key), getattr(new, key))
+ def test_pickle_asdatetime_with_tz(self):
+ dt = DateTime('2002/5/2 8:00am GMT+8')
+ data = pickle.dumps(dt.asdatetime(), 1)
+ new = DateTime(pickle.loads(data))
+ for key in DateTime.__slots__:
+ self.assertEqual(getattr(dt, key), getattr(new, key))
+
def test_pickle_with_numerical_tz(self):
for dt_str in ('2007/01/02 12:34:56.789 +0300',
'2007/01/02 12:34:56.789 +0430',
--
2.40.1
From 6ac321746ab86374871623ddaf414b7948325d22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Sun, 3 Dec 2023 15:57:01 +0100
Subject: [PATCH 4/4] Repair equality comparison between DateTime instances and
other types
Fixes #60
---
src/DateTime/DateTime.py | 20 +++++++++++++++-----
src/DateTime/tests/test_datetime.py | 17 +++++++++++++++++
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/src/DateTime/DateTime.py b/src/DateTime/DateTime.py
index caf67e1..84570b9 100644
--- a/src/DateTime/DateTime.py
+++ b/src/DateTime/DateTime.py
@@ -1258,8 +1258,10 @@ class DateTime(object):
return True
if isinstance(t, (float, int)):
return self._micros > long(t * 1000000)
- else:
+ try:
return self._micros > t._micros
+ except AttributeError:
+ return self._micros > t
__gt__ = greaterThan
@@ -1279,8 +1281,10 @@ class DateTime(object):
return True
if isinstance(t, (float, int)):
return self._micros >= long(t * 1000000)
- else:
+ try:
return self._micros >= t._micros
+ except AttributeError:
+ return self._micros >= t
__ge__ = greaterThanEqualTo
@@ -1299,8 +1303,10 @@ class DateTime(object):
return False
if isinstance(t, (float, int)):
return self._micros == long(t * 1000000)
- else:
+ try:
return self._micros == t._micros
+ except AttributeError:
+ return self._micros == t
def notEqualTo(self, t):
"""Compare this DateTime object to another DateTime object
@@ -1344,8 +1350,10 @@ class DateTime(object):
return False
if isinstance(t, (float, int)):
return self._micros < long(t * 1000000)
- else:
+ try:
return self._micros < t._micros
+ except AttributeError:
+ return self._micros < t
__lt__ = lessThan
@@ -1364,8 +1372,10 @@ class DateTime(object):
return False
if isinstance(t, (float, int)):
return self._micros <= long(t * 1000000)
- else:
+ try:
return self._micros <= t._micros
+ except AttributeError:
+ return self._micros <= t
__le__ = lessThanEqualTo
diff --git a/src/DateTime/tests/test_datetime.py b/src/DateTime/tests/test_datetime.py
index b9eeea9..970a072 100644
--- a/src/DateTime/tests/test_datetime.py
+++ b/src/DateTime/tests/test_datetime.py
@@ -230,6 +230,8 @@ class DateTimeTests(unittest.TestCase):
self.assertFalse(dt.equalTo(dt1))
# Compare a date to float
dt = DateTime(1.0)
+ self.assertTrue(dt == DateTime(1.0)) # testing __eq__
+ self.assertFalse(dt != DateTime(1.0)) # testing __ne__
self.assertFalse(dt.greaterThan(1.0))
self.assertTrue(dt.greaterThanEqualTo(1.0))
self.assertFalse(dt.lessThan(1.0))
@@ -239,12 +241,27 @@ class DateTimeTests(unittest.TestCase):
# Compare a date to int
dt = DateTime(1)
self.assertEqual(dt, DateTime(1.0))
+ self.assertTrue(dt == DateTime(1)) # testing __eq__
+ self.assertFalse(dt != DateTime(1)) # testing __ne__
self.assertFalse(dt.greaterThan(1))
self.assertTrue(dt.greaterThanEqualTo(1))
self.assertFalse(dt.lessThan(1))
self.assertTrue(dt.lessThanEqualTo(1))
self.assertFalse(dt.notEqualTo(1))
self.assertTrue(dt.equalTo(1))
+ # Compare a date to string; there is no implicit type conversion
+ # but behavior if consistent as when comparing, for example, an int
+ # and a string.
+ dt = DateTime("2023")
+ self.assertFalse(dt == "2023") # testing __eq__
+ self.assertTrue(dt != "2023") # testing __ne__
+ if sys.version_info > (3, ):
+ self.assertRaises(TypeError, dt.greaterThan, "2023")
+ self.assertRaises(TypeError, dt.greaterThanEqualTo, "2023")
+ self.assertRaises(TypeError, dt.lessThan, "2023")
+ self.assertRaises(TypeError, dt.lessThanEqualTo, "2023")
+ self.assertTrue(dt.notEqualTo("2023"))
+ self.assertFalse(dt.equalTo("2023"))
def test_compare_methods_none(self):
# Compare a date to None
--
2.40.1
......@@ -61,17 +61,9 @@ install =
<= firefox-wrapper
part = firefox-115
[firefox-wrapper-78]
[firefox-wrapper-102]
<= firefox-wrapper
part = firefox-78
[firefox-wrapper-68]
<= firefox-wrapper
part = firefox-68
[firefox-wrapper-60]
<= firefox-wrapper
part = firefox-60
part = firefox-102
[firefox-default-fonts-conf]
recipe = slapos.recipe.template:jinja2
......@@ -103,23 +95,12 @@ version = 115.3.1esr
i686-md5sum = f0df1b5cce1edd65addc823da02f9488
x86_64-md5sum = 910c0786459cf1e4dc214e6402d0633e
[firefox-78]
[firefox-102]
<= firefox-download
version = 78.1.0esr
i686-md5sum = 09595a1b9a99d17a618a51bc1f971e5e
x86_64-md5sum = 06f4d488721ce7229d9a86cb4c6786f3
version = 102.15.1esr
i686-md5sum = 418b51b3553e98070998fcdbc344487d
x86_64-md5sum = ff477480d34e44fbd0040c32ed905aaf
[firefox-68]
<= firefox-download
version = 68.0.2esr
i686-md5sum = eaa9e0246eb2a31ccf55c100dc2edd5a
x86_64-md5sum = d22dc17ce0949cdff78009afca6f2043
[firefox-60]
<= firefox-download
version = 60.0.2esr
i686-md5sum = ce7c80716036dfb5c2fb1ca2538556ff
x86_64-md5sum = 6fe25d9a3fcc82670320242c9047d1da
[firefox-download]
recipe = slapos.recipe.build
......@@ -199,18 +180,6 @@ version = 0.33.0
i686-md5sum = c4a9e6c92dc493f25c8d390f1c6fb11c
x86_64-md5sum = 563c82cfbb21478450e1c828e3730b10
[geckodriver-0.24.0]
<= geckodriver-base
version = 0.24.0
i686-md5sum = b88eee754f6c90b01f760f7a453dda95
x86_64-md5sum = 7552b85e43973c84763e212af7cca566
[geckodriver-0.22.0]
<= geckodriver-base
version = 0.22.0
i686-md5sum = 6de7544753fda56fbaa8382dcac99aaa
x86_64-md5sum = 81746200ce5841e00cabf3b8ea7db542
[geckodriver-base]
# Installs geckodriver ${version}
recipe = slapos.recipe.build
......
......@@ -3,10 +3,11 @@ extends =
../freetype/buildout.cfg
../libxml2/buildout.cfg
../pkgconfig/buildout.cfg
../python3/buildout.cfg
../bzip2/buildout.cfg
../zlib/buildout.cfg
../bzip2/buildout.cfg
../gperf/buildout.cfg
../xz-utils/buildout.cfg
buildout.hash.cfg
parts =
......@@ -15,8 +16,8 @@ parts =
[fontconfig]
recipe = slapos.recipe.cmmi
shared = true
url = http://fontconfig.org/release/fontconfig-2.12.6.tar.bz2
md5sum = 733f5e2371ca77b69707bd7b30cc2163
url = https://www.freedesktop.org/software/fontconfig/release/fontconfig-2.14.2.tar.xz
md5sum = 95261910ea727b5dd116b06fbfd84b1f
pkg_config_depends = ${freetype:pkg_config_depends}:${freetype:location}/lib/pkgconfig:${libxml2:location}/lib/pkgconfig
configure-options =
--disable-static
......@@ -24,7 +25,8 @@ configure-options =
--enable-libxml2
--with-add-fonts=no
environment =
PATH=${pkgconfig:location}/bin:${gperf:location}/bin:%(PATH)s
PATH=${python3:location}/bin:${pkgconfig:location}/bin:${gperf:location}/bin:${xz-utils:location}/bin:%(PATH)s
PYTHON=${python3:location}/bin/python3
PKG_CONFIG_PATH=${:pkg_config_depends}
CPPFLAGS=-I${zlib:location}/include -I${bzip2:location}/include
LDFLAGS=-L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${bzip2:location}/lib -Wl,-rpath=${bzip2:location}/lib
......
......@@ -13,8 +13,8 @@ parts = gdb
[gdb]
recipe = slapos.recipe.cmmi
shared = true
url = http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.xz
md5sum = db95524e554870209ab7d9f8fd8dc557
url = https://ftp.gnu.org/gnu/gdb/gdb-14.1.tar.xz
md5sum = 4a084d03915b271f67e9b8ea2ab24972
location = @@LOCATION@@
# gdb refuses to build in-tree -> build it inside build/
pre-configure =
......
......@@ -30,24 +30,28 @@ environment =
PATH=${swig:location}/bin:${patch:location}/bin:%(PATH)s
GOROOT_FINAL=${:location}
${:environment-extra}
patch-options = -p1
[golang-common-pre-1.21]
<= golang-common
# TestChown currently fails in a user-namespace
# https://github.com/golang/go/issues/42525
# the patches apply to go >= 1.12
patch-options = -p1
patches =
# the patches apply to 1.21 > go >= 1.12
# (in go 1.21 we can't apply it, due to code changes,
# in go > 1.21 it's hopefully fixed with
# https://github.com/golang/go/commit/9f03e8367d85d75675b2f2e90873e3293799d8aa)
patches +=
${:_profile_base_location_}/skip-chown-tests.patch#d4e3c8ef83788fb2a5d80dd75034786f
[golang-common-pre-1.19]
<= golang-common
<= golang-common-pre-1.21
# TestSCMCredentials fails in a user-namespace if golang version < 1.19
# https://github.com/golang/go/issues/42525
patches +=
${:_profile_base_location_}/fix-TestSCMCredentials.patch#1d8dbc97cd579e03fafd8627d48f1c59
[golang14]
<= golang-common-pre-1.19
# https://golang.org/doc/install/source#bootstrapFromSource
......@@ -117,13 +121,28 @@ environment-extra =
GOROOT_BOOTSTRAP=${golang14:location}
[golang1.20]
<= golang-common
<= golang-common-pre-1.21
url = https://go.dev/dl/go1.20.6.src.tar.gz
md5sum = 1dc2d18790cfaede7df1e73a1eff8b7b
# go1.20 requires go1.17.13 to bootstrap (see https://go.dev/doc/go1.20#bootstrap)
environment-extra =
GOROOT_BOOTSTRAP=${golang1.17:location}
[golang1.21]
<= golang-common
url = https://go.dev/dl/go1.21.5.src.tar.gz
md5sum = 99385ded31906f1554c27015bbbee52d
# go1.21 requires go1.17.13 to bootstrap (see https://go.dev/blog/rebuild)
environment-extra =
GOROOT_BOOTSTRAP=${golang1.17:location}
patches +=
# TestChown fix, old fix doesn't work due to code change,
# for golang > 1.21 this patch is hopefully already included with
# https://github.com/golang/go/commit/9f03e8367d85d75675b2f2e90873e3293799d8aa
${:_profile_base_location_}/os-skip-Chown-tests-for-auxiliary-groups-that-fail-d.patch#81b7f75786d9024049c26d1663b79ba4
${:_profile_base_location_}/skip-unshare-mount-test.patch#325446d5135452e8685e95ab99c13a51
# ---- infrastructure to build Go workspaces / projects ----
# gowork is the top-level section that defines Go workspace.
......@@ -183,7 +202,7 @@ bin = ${gowork.dir:bin}
depends = ${gowork.goinstall:recipe}
# go version used for the workspace (possible to override in applications)
golang = ${golang1.20:location}
golang = ${golang1.21:location}
# no special build flags by default
buildflags =
......
From 9f03e8367d85d75675b2f2e90873e3293799d8aa Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills" <bcmills@google.com>
Date: Tue, 15 Aug 2023 18:01:16 -0400
Subject: [PATCH] os: skip Chown tests for auxiliary groups that fail due to
permission errors
This addresses the failure mode described in
https://git.alpinelinux.org/aports/commit/community/go/tests-filter-overflow-gid.patch?id=9851dde0f5d2a5a50f7f3b5323d1b2ff22e1d028,
but without special-casing an implementation-specific group ID.
For #62053.
Change-Id: I70b1046837b8146889fff7085497213349cd2bf0
Reviewed-on: https://go-review.googlesource.com/c/go/+/520055
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
---
src/os/os_unix_test.go | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/os/os_unix_test.go b/src/os/os_unix_test.go
index 9041b25471..e4271ff905 100644
--- a/src/os/os_unix_test.go
+++ b/src/os/os_unix_test.go
@@ -75,6 +75,12 @@ func TestChown(t *testing.T) {
t.Log("groups: ", groups)
for _, g := range groups {
if err = Chown(f.Name(), -1, g); err != nil {
+ if testenv.SyscallIsNotSupported(err) {
+ t.Logf("chown %s -1 %d: %s (error ignored)", f.Name(), g, err)
+ // Since the Chown call failed, the file should be unmodified.
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ continue
+ }
t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err)
}
checkUidGid(t, f.Name(), int(sys.Uid), g)
@@ -123,6 +129,12 @@ func TestFileChown(t *testing.T) {
t.Log("groups: ", groups)
for _, g := range groups {
if err = f.Chown(-1, g); err != nil {
+ if testenv.SyscallIsNotSupported(err) {
+ t.Logf("chown %s -1 %d: %s (error ignored)", f.Name(), g, err)
+ // Since the Chown call failed, the file should be unmodified.
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ continue
+ }
t.Fatalf("fchown %s -1 %d: %s", f.Name(), g, err)
}
checkUidGid(t, f.Name(), int(sys.Uid), g)
@@ -181,12 +193,22 @@ func TestLchown(t *testing.T) {
t.Log("groups: ", groups)
for _, g := range groups {
if err = Lchown(linkname, -1, g); err != nil {
+ if testenv.SyscallIsNotSupported(err) {
+ t.Logf("lchown %s -1 %d: %s (error ignored)", f.Name(), g, err)
+ // Since the Lchown call failed, the file should be unmodified.
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ continue
+ }
t.Fatalf("lchown %s -1 %d: %s", linkname, g, err)
}
checkUidGid(t, linkname, int(sys.Uid), g)
// Check that link target's gid is unchanged.
checkUidGid(t, f.Name(), int(sys.Uid), int(sys.Gid))
+
+ if err = Lchown(linkname, -1, gid); err != nil {
+ t.Fatalf("lchown %s -1 %d: %s", f.Name(), gid, err)
+ }
}
}
--
2.36.0
https://github.com/golang/go/commit/092671423cd95eaa6df93eb29442fef41504d097 breaks
TestUnshareMountNameSpace test on SlapOS.
---
diff --git a/src/syscall/exec_linux_test.go b/src/syscall/exec_linux_test.go
index f4ff7bf81b..bc8bdb0a35 100644
--- a/src/syscall/exec_linux_test.go
+++ b/src/syscall/exec_linux_test.go
@@ -206,6 +206,7 @@ func TestGroupCleanupUserNamespace(t *testing.T) {
// Test for https://go.dev/issue/19661: unshare fails because systemd
// has forced / to be shared
func TestUnshareMountNameSpace(t *testing.T) {
+ t.Skip("skipping: not supported in SlapOS")
testenv.MustHaveExec(t)
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
......@@ -13,8 +13,8 @@ parts = haproxy
[haproxy]
recipe = slapos.recipe.cmmi
shared = true
url = https://www.haproxy.org/download/2.6/src/haproxy-2.6.15.tar.gz
md5sum = ecac9724e3ca6368624fb2fab29dd366
url = https://www.haproxy.org/download/2.6/src/haproxy-2.6.16.tar.gz
md5sum = b01e605cdaf2742fcedf214a61e187b4
configure-command = true
# for Linux kernel 2.6.28 and above, we use "linux-glibc" as the TARGET,
# otherwise use "generic".
......@@ -47,6 +47,7 @@ make-options =
${:QUIC}
USE_PCRE=1
USE_ZLIB=1
USE_PROMEX=1
ZLIB_INC=${zlib:location}/include
ZLIB_LIB=${zlib:location}/lib
ADDLIB="${:SSL_ADDLIB} -Wl,-rpath=${pcre:location}/lib -Wl,-rpath=${zlib:location}/lib"
......
......@@ -5,7 +5,7 @@ parts =
[libsodium]
recipe = slapos.recipe.cmmi
shared = true
url = https://download.libsodium.org/libsodium/releases/old/unsupported/libsodium-1.0.8.tar.gz
md5sum = 0a66b86fd3aab3fe4c858edcd2772760
url = https://download.libsodium.org/libsodium/releases/old/libsodium-1.0.17.tar.gz
md5sum = 0f71e2680187a1558b5461e6879342c5
configure-options =
--disable-static
......@@ -72,14 +72,6 @@ md5sum = 28bf6a4d98b238403fa58a0805f4a979
PATH = ${pkgconfig:location}/bin:${python2.7:location}/bin:%(PATH)s
configure-command = ./configure
[nodejs-8.9.4]
<= nodejs-base
version = v8.9.4
md5sum = 4ddc1daff327d7e6f63da57fdfc24f55
openssl-location = ${openssl-1.0:location}
PATH = ${pkgconfig:location}/bin:${python2.7:location}/bin:%(PATH)s
configure-command = ./configure
[nodejs-8.12.0]
<= nodejs-base
version = v8.12.0
......
[buildout]
extends =
extends =
../icu/buildout.cfg
../openssl/buildout.cfg
../pkgconfig/buildout.cfg
../readline/buildout.cfg
../zlib/buildout.cfg
../ncurses/buildout.cfg
......@@ -30,10 +32,13 @@ configure-options =
--without-libxslt
# build core PostgreSQL + pg_trgm contrib extension for GitLab
# unaccent contrib extension is for peertube
make-targets = install && make -C contrib/pg_trgm/ install && make -C contrib/unaccent/ install
# citext contrib extension is for metabase
make-targets = install && make -C contrib/pg_trgm/ install && make -C contrib/unaccent/ install && make -C contrib/citext/ install
environment =
CPPFLAGS=-I${zlib:location}/include -I${readline:location}/include -I${openssl:location}/include -I${ncurses:location}/lib
LDFLAGS=-L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${readline:location}/lib -Wl,-rpath=${readline:location}/lib -L${openssl:location}/lib -Wl,-rpath=${openssl:location}/lib -L${ncurses:location}/lib -Wl,-rpath=${ncurses:location}/lib -L${perl:location}/libs-c -Wl,-rpath=${perl:location}/libs-c
PATH=${pkgconfig:location}/bin:%(PATH)s
CPPFLAGS=-I${zlib:location}/include -I${readline:location}/include -I${openssl:location}/include -I${ncurses:location}/include
LDFLAGS=-L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${readline:location}/lib -Wl,-rpath=${readline:location}/lib -L${openssl:location}/lib -Wl,-rpath=${openssl:location}/lib -L${ncurses:location}/lib -Wl,-rpath=${ncurses:location}/lib -L${perl:location}/libs-c -Wl,-rpath=${perl:location}/libs-c -Wl,-rpath=${icu:location}/lib
PKG_CONFIG_PATH=${icu:location}/lib/pkgconfig/
[postgresql10]
<= postgresql-common
......
......@@ -18,8 +18,10 @@ parts =
# tune pygolang to install with all and for-tests extras.
# list all_test-dependent eggs that must come through in-tree recipes.
[pygolang]
egg = pygolang[all_test]
depends += ${numpy:egg}
# bin/python is preinstalled with sys.path to pygolang & friends.
[pygolang-python]
......
......@@ -31,7 +31,7 @@ PKG_CONFIG_PATH=${libxml2:location}/lib/pkgconfig:${libxslt:location}/lib/pkgcon
[versions]
xmlsec = 1.3.13
setuptools-scm = 7.0.5
setuptools-scm = 7.0.5:whl
toml = 0.10.2
[versions:python2]
......
......@@ -109,8 +109,8 @@ md5sum = b33775a9ab6eae784b6da9f31be48be3
[debian-amd64-bookworm-netinst.iso]
<= debian-amd64-netinst-base
version = 12.1.0
md5sum = 8d77d1b0bcfef29e4d56dc0fbe23de15
version = 12.4.0
md5sum = a03cf771ba9513d908093101a094ac88
alternate-url = https://cdimage.debian.org/cdimage/release/current/${:arch}/iso-cd/${:filename}
[debian-amd64-netinst.iso]
......
......@@ -50,7 +50,7 @@ CGO_LDFLAGS += -Wl,-rpath=${zlib:location}/lib
recipe = slapos.recipe.build:gitclone
repository = https://lab.nexedi.com/nexedi/wendelin.core.git
branch = master
revision = wendelin.core-2.0.alpha3-7-g885b355
revision = wendelin.core-2.0.alpha3-9-gda765ef
# dir is pretty name as top-level recipe
location = ${buildout:parts-directory}/wendelin.core
git-executable = ${git:location}/bin/git
......@@ -3,8 +3,8 @@
[buildout]
extends =
# test*.cfg first extend from neoppod/software<ZODB-flavour>.cfg to use
# appropriate ZODB and versions of other components.
../../stack/erp5/buildout.cfg
../../software/neoppod/software.cfg
../pytest/buildout.cfg
../scipy/buildout.cfg
......
# SlapOS software release to test wendelin.core/ZODB4-wc2 on Nexedi testing infrastructure.
[buildout]
extends = test.cfg
extends = test-common.cfg
[ZODB]
major = 4-wc2
# SlapOS software release to test wendelin.core/ZODB5 on Nexedi testing infrastructure.
[buildout]
extends =
../../stack/erp5/buildout.cfg
../../software/neoppod/software-zodb5.cfg
test-common.cfg
extends = test-common.cfg
[ZODB]
major = 5
# ZEO[test] needs ZopeUndo
[versions]
ZopeUndo = 5.0
# SlapOS software release to test wendelin.core on Nexedi testing infrastructure.
[buildout]
extends =
../../stack/erp5/buildout.cfg
../../software/neoppod/software.cfg
test-common.cfg
......@@ -10,8 +10,8 @@ extends =
[xmlsec]
recipe = slapos.recipe.cmmi
url = https://www.aleksey.com/xmlsec/download/older-releases/xmlsec1-1.2.34.tar.gz
md5sum = 87b0074e7ae535e061acf8ef64dada1b
url = https://github.com/lsh123/xmlsec/releases/download/xmlsec-1_2_37/xmlsec1-1.2.37.tar.gz
md5sum = 98dd3c884e2816c25c038a6e8af138fb
shared = true
configure-options =
--disable-crypto-dl
......
......@@ -2,5 +2,8 @@
[buildout]
extends =
test-zodb4.cfg
test-common.cfg
test-py2.cfg
[ZODB]
major = 4
......@@ -2,5 +2,8 @@
[buildout]
extends =
test-zodb4-wc2.cfg
test-common.cfg
test-py2.cfg
[ZODB]
major = 4-wc2
# SlapOS software release to test zodbtools/ZODB4-wc2-py3 on Nexedi testing infrastructure.
[buildout]
extends = test-common.cfg
[ZODB]
major = 4-wc2
# SlapOS software release to test zodbtools/ZODB4-py3 on Nexedi testing infrastructure.
[buildout]
extends = test-common.cfg
[ZODB]
major = 4
......@@ -28,7 +28,7 @@ def main():
print(e, file=sys.stderr)
else:
with open(f, 'w') as outfile:
json.dump(obj, outfile, sort_keys=False, indent=2, separators=(',', ': '))
json.dump(obj, outfile, ensure_ascii=False, sort_keys=False, indent=2, separators=(',', ': '))
outfile.write('\n')
sys.exit(exit_code)
......
......@@ -28,7 +28,7 @@ from setuptools import setup, find_packages
import glob
import os
version = '1.0.329'
version = '1.0.351'
name = 'slapos.cookbook'
long_description = open("README.rst").read()
......@@ -72,6 +72,8 @@ setup(name=name,
'zc.buildout', # plays with buildout
'zc.recipe.egg', # for scripts generation
'pytz', # for timezone database
'passlib',
'bcrypt',
],
zip_safe=True,
entry_points={
......
......@@ -45,10 +45,8 @@ class Recipe(GenericBaseRecipe):
elif self.options.get('expected-type') == "ipv4":
template = self.getTemplateFilename('check_ipv4.py.in')
else:
config["expected-value"] = self.options.get('expected-value')
config["expected-not-value"] = self.options.get('expected-not-value')
config["expected-value"] = str(self.options.get('expected-value', ''))
config["expected-not-value"] = str(self.options.get('expected-not-value', ''))
template = self.getTemplateFilename('check_parameter.py.in')
promise = self.createExecutable(
......
......@@ -5,14 +5,14 @@ from __future__ import print_function
import socket
import sys
value = "%(value)s"
expected = "%(expected-value)s"
not_expected = "%(expected-not-value)s"
value = %(value)r
expected = %(expected-value)r
not_expected = %(expected-not-value)r
if expected != "" and value != expected:
print("FAIL: %%s != %%s" %% (value, expected))
sys.exit(127)
if not_expected != "" and value == not_expected:
if not_expected != "" and value == not_expected:
print("FAIL: %%s == %%s" %% (value, not_expected))
sys.exit(127)
......@@ -24,9 +24,14 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import logging
import os
import time
from inotify_simple import INotify, flags
logger = logging.getLogger(__name__)
def subfiles(directory):
"""Return the list of subfiles of a directory, and wait for the newly created
ones.
......@@ -35,10 +40,19 @@ def subfiles(directory):
ALWAYS ITERATE OVER IT !!!*"""
with INotify() as inotify:
inotify.add_watch(directory, flags.CLOSE_WRITE | flags.MOVED_TO)
try:
inotify.add_watch(directory, flags.CLOSE_WRITE | flags.MOVED_TO)
inotify_available = True
except OSError:
logger.warning("Unable to add inotify watch, falling back to polling")
inotify_available = False
names = os.listdir(directory)
while True:
for name in names:
yield os.path.join(directory, name)
names = (event.name for event in inotify.read())
if inotify_available:
names = (event.name for event in inotify.read())
else:
time.sleep(5)
names = os.listdir(directory)
......@@ -131,7 +131,9 @@ class Recipe(GenericSlapRecipe):
new = {}
for k, v in six.iteritems(init):
try:
options[k] = publish_dict[k] = new[v] = init_section.pop(v)
init_section_value = init_section[v]
options[k] = publish_dict[k] = new[v] = init_section_value
del init_section[v]
except KeyError:
pass
if new != override:
......
......@@ -33,12 +33,16 @@ buildout Software Releases and Instances developments.
from __future__ import absolute_import
import errno
import json
import os
import random
import string
import sys
from .librecipe import GenericBaseRecipe
from .publish_early import volatileOptions
from slapos.util import str2bytes
import passlib.hash
class Integer(object):
"""
......@@ -113,7 +117,7 @@ def generatePassword(length):
class Password(object):
"""Generate a password that is only composed of lowercase letters
"""Generate a password.
This recipe only makes sure that ${:passwd} does not end up in `.installed`
file, which is world-readable by default. So be careful not to spread it
......@@ -128,6 +132,11 @@ class Password(object):
- create-once: boolean value which set if storage-path won't be modified
as soon the file is created with the password (not empty).
(default: True)
- passwd: the generated password. Can also be set, to reuse the password
hashing capabilities.
- passwd-*: the hashed password, using schemes supported by passlib.
for example, passwd-sha256-crypt will expose the password hashed
with sha256 crypt algorithm.
If storage-path is empty, the recipe does not save the password, which is
fine it is saved by other means, e.g. using the publish-early recipe.
......@@ -141,24 +150,53 @@ class Password(object):
except KeyError:
self.storage_path = options['storage-path'] = os.path.join(
buildout['buildout']['parts-directory'], name)
passwd = options.get('passwd')
if not passwd:
passwd_dict = {
'': options.get('passwd')
}
if not passwd_dict['']:
if self.storage_path:
self._needs_migration = False
try:
with open(self.storage_path) as f:
passwd = f.read().strip('\n')
content = f.read().strip('\n')
# new format: the file contains password and hashes in json format
try:
passwd_dict = json.loads(content)
if sys.version_info < (3, ):
passwd_dict = {k: v.encode() for k, v in passwd_dict.items()}
except ValueError:
# old format: the file only contains the password in plain text
passwd_dict[''] = content
self._needs_migration = True
except IOError as e:
if e.errno != errno.ENOENT:
raise
if not passwd:
passwd = self.generatePassword(int(options.get('bytes', '16')))
if not passwd_dict['']:
passwd_dict[''] = self.generatePassword(int(options.get('bytes', '16')))
self.update = self.install
options['passwd'] = passwd
options['passwd'] = passwd_dict['']
class HashedPasswordDict(dict):
def __missing__(self, key):
if not key.startswith('passwd-'):
raise KeyError(key)
if key in passwd_dict:
return passwd_dict[key]
handler = getattr(
passlib.hash, key[len('passwd-'):].replace('-', '_'), None)
if handler is None:
raise KeyError(key)
hashed = handler.hash(passwd_dict[''])
passwd_dict[key] = hashed
return hashed
options._data = HashedPasswordDict(options._data)
# Password must not go into .installed file, for 2 reasons:
# security of course but also to prevent buildout to always reinstall.
# publish_early already does it, but this recipe may also be used alone.
volatileOptions(options, ('passwd',))
self.passwd = passwd
self.passwd_dict = passwd_dict
generatePassword = staticmethod(generatePassword)
......@@ -167,19 +205,14 @@ class Password(object):
try:
# The following 2 lines are just an optimization to avoid recreating
# the file with the same content.
if self.create_once and os.stat(self.storage_path).st_size:
if self.create_once and os.stat(self.storage_path).st_size and not self._needs_migration:
return
os.unlink(self.storage_path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
fd = os.open(self.storage_path,
os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_TRUNC, 0o600)
try:
os.write(fd, str2bytes(self.passwd))
finally:
os.close(fd)
with open(self.storage_path, 'w') as f:
json.dump(self.passwd_dict, f)
if not self.create_once:
return self.storage_path
......
import os
import subprocess
import tempfile
import unittest
import zc.buildout.testing
from slapos.recipe import check_parameter
class TestCheckParameter(unittest.TestCase):
def setUp(self):
self.buildout = zc.buildout.testing.Buildout()
def _makeRecipe(self, options):
path = tempfile.NamedTemporaryFile(delete=False).name
self.addCleanup(os.unlink, path)
options.setdefault("path", path)
self.buildout["check-parameter"] = options
recipe = check_parameter.Recipe(
self.buildout, "check-parameter", self.buildout["check-parameter"]
)
return recipe
def test_expected_value_ok(self):
script = self._makeRecipe({"expected-value": "foo", "value": "foo"}).install()
subprocess.check_call(script)
def test_expected_value_not_ok(self):
script = self._makeRecipe({"expected-value": "foo", "value": "bar"}).install()
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_output(script, universal_newlines=True)
self.assertEqual(e.exception.output, "FAIL: bar != foo\n")
def test_expected_value_multi_lines_ok(self):
script = self._makeRecipe(
{"expected-value": "foo\nbar", "value": "foo\nbar"}
).install()
subprocess.check_output(script)
def test_expected_value_multi_lines_not_ok(self):
script = self._makeRecipe({"expected-value": "foo\nbar", "value": "foo"}).install()
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_output(script, universal_newlines=True)
self.assertEqual(e.exception.output, "FAIL: foo != foo\nbar\n")
def test_expected_not_value_ok(self):
script = self._makeRecipe({"expected-not-value": "foo", "value": "bar"}).install()
subprocess.check_call(script)
def test_expected_not_value_not_ok(self):
script = self._makeRecipe({"expected-not-value": "foo", "value": "foo"}).install()
with self.assertRaises(subprocess.CalledProcessError) as e:
subprocess.check_output(script, universal_newlines=True)
self.assertEqual(e.exception.output, "FAIL: foo == foo\n")
import json
import os
import shutil
import tempfile
import unittest
import zc.buildout.testing
import zc.buildout.buildout
import passlib.hash
from slapos.recipe import random
class TestPassword(unittest.TestCase):
def setUp(self):
self.buildout = zc.buildout.testing.Buildout()
parts_directory = tempfile.mkdtemp()
self.buildout['buildout']['parts-directory'] = parts_directory
self.addCleanup(shutil.rmtree, parts_directory)
def _makeRecipe(self, options, section_name="random"):
self.buildout[section_name] = options
recipe = random.Password(
self.buildout, section_name, self.buildout[section_name]
)
return recipe
def test_empty_options(self):
recipe = self._makeRecipe({})
passwd = self.buildout["random"]["passwd"]
self.assertEqual(len(passwd), 16)
recipe.install()
with open(self.buildout["random"]["storage-path"]) as f:
self.assertEqual(json.load(f), {'': passwd})
def test_storage_path(self):
tf = tempfile.NamedTemporaryFile(delete=False)
self.addCleanup(os.unlink, tf.name)
self._makeRecipe({'storage-path': tf.name}).install()
passwd = self.buildout["random"]["passwd"]
self.assertEqual(len(passwd), 16)
with open(tf.name) as f:
self.assertEqual(json.load(f), {'': passwd})
self._makeRecipe({'storage-path': tf.name}, "another").install()
self.assertEqual(self.buildout["another"]["passwd"], passwd)
def test_storage_path_legacy_format(self):
with tempfile.NamedTemporaryFile(delete=False) as tf:
tf.write(b'secret\n')
tf.flush()
self._makeRecipe({'storage-path': tf.name}).install()
passwd = self.buildout["random"]["passwd"]
self.assertEqual(passwd, 'secret')
tf.flush()
with open(tf.name) as f:
self.assertEqual(json.load(f), {'': 'secret'})
self._makeRecipe({'storage-path': tf.name}, "another").install()
self.assertEqual(self.buildout["another"]["passwd"], passwd)
def test_bytes(self):
self._makeRecipe({'bytes': '32'}).install()
passwd = self.buildout["random"]["passwd"]
self.assertEqual(len(passwd), 32)
with open(self.buildout["random"]["storage-path"]) as f:
self.assertEqual(json.load(f), {'': passwd})
def test_volatile(self):
self._makeRecipe({})
options = self.buildout['random']
self.assertIn('passwd', options)
options_items = [(k, v) for k, v in options.items() if k != 'passwd']
copied_options = options.copy()
self.assertEqual(list(copied_options.items()), options_items)
def test_passlib(self):
recipe = self._makeRecipe({})
hashed = self.buildout['random']['passwd-sha256-crypt']
self.assertTrue(
passlib.hash.sha256_crypt.verify(
self.buildout['random']['passwd'], hashed))
hashed = self.buildout['random']['passwd-md5-crypt']
self.assertTrue(
passlib.hash.md5_crypt.verify(
self.buildout['random']['passwd'], hashed))
hashed = self.buildout['random']['passwd-bcrypt']
self.assertTrue(
passlib.hash.bcrypt.verify(
self.buildout['random']['passwd'], hashed))
hashed = self.buildout['random']['passwd-ldap-salted-sha1']
self.assertTrue(
passlib.hash.ldap_salted_sha1.verify(
self.buildout['random']['passwd'], hashed))
with self.assertRaises(zc.buildout.buildout.MissingOption):
self.buildout['random']['passwd-unknown']
with self.assertRaises(zc.buildout.buildout.MissingOption):
self.buildout['random']['unknown']
copied_options = self.buildout['random'].copy()
self.assertEqual(list(copied_options.keys()), ['storage-path'])
recipe.install()
# when buildout runs again, the values are read from the storage
# and even the hashed values are the same
self._makeRecipe({'storage-path': self.buildout['random']['storage-path']}, 'reread')
self.assertEqual(
self.buildout['reread']['passwd'],
self.buildout['random']['passwd'])
self.assertEqual(
self.buildout['reread']['passwd-sha256-crypt'],
self.buildout['random']['passwd-sha256-crypt'])
self.assertEqual(
self.buildout['reread']['passwd-bcrypt'],
self.buildout['random']['passwd-bcrypt'])
self.assertEqual(
self.buildout['reread']['passwd-ldap-salted-sha1'],
self.buildout['random']['passwd-ldap-salted-sha1'])
# values are strings which is important for python2
self.assertIsInstance(self.buildout['reread']['passwd'], str)
self.assertIsInstance(self.buildout['reread']['passwd-ldap-salted-sha1'], str)
def test_passlib_input_passwd(self):
self._makeRecipe({'passwd': 'insecure'})
self.assertEqual(self.buildout['random']['passwd'], 'insecure')
hashed = self.buildout['random']['passwd-sha256-crypt']
self.assertTrue(passlib.hash.sha256_crypt.verify('insecure', hashed))
......@@ -85,6 +85,7 @@ def createFormatTest(path):
self.assertEqual(
(json.dumps(
json.loads(content, object_pairs_hook=collections.OrderedDict),
ensure_ascii=False,
sort_keys=False,
indent=2,
separators=(',', ': ')) + "\n").splitlines(),
......
......@@ -18,6 +18,7 @@
},
"mimetype-entry-addition": {
"description": "The list of entry to add to the cloudooo mimetype registry. Each entry should on one line which format is: \"<source_mimetype> <destination_mimetype> <handler>\"",
"textarea": true,
"type": "string"
}
}
......
......@@ -37,9 +37,12 @@ import base64
import io
import requests
import PIL.Image
import PyPDF2
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
from slapos.testing.utils import ImageComparisonTestCase
setUpModule, _CloudOooTestCase = makeModuleSetUpAndTestCaseClass(
os.path.abspath(
......@@ -148,7 +151,6 @@ class TestWkhtmlToPDF(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase):
'Courier New': 'LiberationSans',
'DejaVu Sans': 'DejaVuSans',
'DejaVu Sans Condensed': 'LiberationSans',
'DejaVu Sans ExtraLight': 'LiberationSans',
'DejaVu Sans Mono': 'DejaVuSansMono',
'DejaVu Serif': 'DejaVuSerif',
'DejaVu Serif Condensed': 'LiberationSans',
......@@ -165,11 +167,11 @@ class TestWkhtmlToPDF(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase):
'Liberation Sans Narrow': 'LiberationSansNarrow',
'Liberation Serif': 'LiberationSerif',
'Linux LibertineG': 'LiberationSans',
'OpenSymbol': {'DejaVuSans', 'OpenSymbol'},
'OpenSymbol': {'NotoSans-Regular', 'OpenSymbol'},
'Palatino': 'LiberationSans',
'Roboto Black': 'LiberationSans',
'Roboto Condensed Light': 'LiberationSans',
'Roboto Condensed Regular': 'LiberationSans',
'Roboto Condensed': 'RobotoCondensed-Regular',
'Roboto Light': 'LiberationSans',
'Roboto Medium': 'LiberationSans',
'Roboto Thin': 'LiberationSans',
......@@ -198,43 +200,42 @@ class TestLibreoffice(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase):
pdf_producer = 'LibreOffice 7.5'
expected_font_mapping = {
'Arial': 'LiberationSans',
'Arial Black': 'DejaVuSans',
'Avant Garde': 'DejaVuSans',
'Bookman': 'DejaVuSans',
'Arial Black': 'NotoSans-Regular',
'Avant Garde': 'NotoSans-Regular',
'Bookman': 'NotoSans-Regular',
'Carlito': 'Carlito',
'Comic Sans MS': 'DejaVuSans',
'Comic Sans MS': 'NotoSans-Regular',
'Courier New': 'LiberationMono',
'DejaVu Sans': 'DejaVuSans',
'DejaVu Sans Condensed': 'DejaVuSansCondensed',
'DejaVu Sans ExtraLight': 'DejaVuSans',
'DejaVu Sans Mono': 'DejaVuSansMono',
'DejaVu Serif': 'DejaVuSerif',
'DejaVu Serif Condensed': 'DejaVuSerifCondensed',
'Garamond': 'DejaVuSerif',
'Garamond': 'NotoSerif-Regular',
'Gentium Basic': 'GentiumBasic',
'Gentium Book Basic': 'GentiumBookBasic',
'Georgia': 'DejaVuSerif',
'Georgia': 'NotoSerif-Regular',
'Helvetica': 'LiberationSans',
'IPAex Gothic': 'IPAexGothic',
'IPAex Mincho': 'IPAexMincho',
'Impact': 'DejaVuSans',
'Impact': 'NotoSans-Regular',
'Liberation Mono': 'LiberationMono',
'Liberation Sans': 'LiberationSans',
'Liberation Sans Narrow': 'LiberationSansNarrow',
'Liberation Serif': 'LiberationSerif',
'Linux LibertineG': 'LinuxLibertineG',
'OpenSymbol': {'OpenSymbol', 'IPAMincho'},
'Palatino': 'DejaVuSerif',
'Palatino': 'NotoSerif-Regular',
'Roboto Black': 'Roboto-Black',
'Roboto Condensed Light': 'RobotoCondensed-Light',
'Roboto Condensed Regular': 'DejaVuSans',
'Roboto Condensed': 'RobotoCondensed-Regular',
'Roboto Light': 'Roboto-Light',
'Roboto Medium': 'Roboto-Medium',
'Roboto Thin': 'Roboto-Thin',
'Times New Roman': 'LiberationSerif',
'Trebuchet MS': 'DejaVuSans',
'Verdana': 'DejaVuSans',
'ZZZdefault fonts when no match': 'DejaVuSans'
'Trebuchet MS': 'NotoSans-Regular',
'Verdana': 'NotoSans-Regular',
'ZZZdefault fonts when no match': 'NotoSans-Regular'
}
def _convert_html_to_pdf(self, src_html):
......@@ -246,6 +247,27 @@ class TestLibreoffice(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase):
).encode())
class TestLibreofficeDrawToPNGConversion(CloudOooTestCase, ImageComparisonTestCase):
__partition_reference__ = 'l'
def test(self):
reference_png = PIL.Image.open(os.path.join('data', f'{self.id()}.png'))
with open(os.path.join('data', f'{self.id()}.odg'), 'rb') as f:
actual_png_data = base64.decodebytes(
self.server.convertFile(
base64.encodebytes(f.read()).decode(),
'odg',
'png',
).encode())
actual_png = PIL.Image.open(io.BytesIO(actual_png_data))
# save a snapshot
with open(os.path.join(self.computer_partition_root_path, self.id() + '.png'), 'wb') as f:
f.write(actual_png_data)
self.assertImagesSame(actual_png, reference_png)
class TestLibreOfficeTextConversion(CloudOooTestCase):
__partition_reference__ = 'txt'
......
[instance]
filename = instance.cfg
md5sum = a4e19280bc672cc98e0fef241c8439ba
md5sum = e4092f606716171a5c9c5980c70573b3
[buildout]
parts =
dream_simulation
dream_platform
dream_test_suite
dream_interpreter
grunt_watch
dream-simulation
dream-platform
dream-test-suite
dream-interpreter
grunt-watch
publish-connection-parameter
dream-platform-url-available
......@@ -22,44 +22,44 @@ url = $${slap-connection:server-url}
key = $${slap-connection:key-file}
cert = $${slap-connection:cert-file}
[dream_platform_parameter]
[dream-platform-parameter]
port = 18080
host = $${instance-parameter:ipv6-random}
url = http://[$${:host}]:$${:port}
# interpreter
[dream_interpreter]
[dream-interpreter]
recipe = slapos.cookbook:wrapper
command-line = ${buildout:bin-directory}/dream_interpreter
wrapper-path = $${buildout:bin-directory}/dream_interpreter
# service
[dream_platform]
[dream-platform]
recipe = slapos.cookbook:wrapper
command-line = ${buildout:bin-directory}/dream_platform --debug --host $${dream_platform_parameter:host} --port $${dream_platform_parameter:port} --log $${directory:log}/dream_platform.log
command-line = ${buildout:bin-directory}/dream_platform --debug --host $${dream-platform-parameter:host} --port $${dream-platform-parameter:port} --log $${directory:log}/dream_platform.log
wrapper-path = $${directory:service}/dream_platform
[dream-platform-url-available]
<= monitor-promise-base
promise = check_url_available
name = $${:_buildout_section_name_}.py
config-url= $${dream_platform_parameter:url}
config-url= $${dream-platform-parameter:url}
[grunt_watch]
[grunt-watch]
recipe = slapos.cookbook:wrapper
command-line = bash -c 'cd ${dream-repository.git:location}; PATH=${nodejs:location}/bin/:$PATH ${dream-repository.git:location}/node_modules/grunt-cli/bin/grunt watch -f > $${directory:log}/grunt.log'
wrapper-path = $${directory:service}/dream_grunt_watch
# CLI
[dream_simulation]
[dream-simulation]
recipe = slapos.cookbook:wrapper
command-line = ${buildout:bin-directory}/dream_simulation
wrapper-path = $${directory:script}/dream_simulation
[dream_test_suite]
[dream-test-suite]
recipe = slapos.cookbook:wrapper
command-line = ${dream_testrunner:script}
command-line = ${dream-testrunner:script}
wrapper-path = $${directory:script}/dream_test_suite
[directory]
......@@ -74,4 +74,4 @@ log = $${:var}/log
[publish-connection-parameter]
recipe = slapos.cookbook:publishurl
url = $${dream_platform_parameter:url}
url = $${dream-platform-parameter:url}
......@@ -9,8 +9,8 @@ extends =
parts =
slapos-cookbook
manpy
dream_testrunner
npm_install
dream-testrunner
npm-install
instance
[gcc]
......@@ -20,22 +20,22 @@ max_version = 0
[python]
part = python2.7
[dream-repository.git]
revision = f3bcf115741886835df8c0ca0fdbf510d77d8db8
[instance]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/instance.cfg
[dream_testrunner]
[dream-testrunner]
recipe = zc.recipe.testrunner
eggs = dream
script = dream_testrunner
initialization =
${manpy:initialization}
[nodejs]
<= nodejs-8.9.4
[npm_install]
[npm-install]
recipe = plone.recipe.command
stop-on-error = true
command =
......@@ -52,8 +52,4 @@ simpy = 3.0.5
zope.dottedname = 4.1.0
tablib = 0.10.0
mysqlclient = 1.3.12
# indirect dependancies
cp.recipe.cmd = 0.5
plone.recipe.command = 1.1
zc.recipe.testrunner = 2.0.0
# End To End Testing
This software release is used to run end-to-end test of SlapOS softwares on an actual SlapOS cloud such as Rapid.Space. Since it can supply softwares and request instances on an actual cloud, it needs a SlaPOS client certificate and the URLs of your tests.
## Input parameters
```
{
"client.crt": <content of client.crt>,
"client.key": <content of client.key>,
"master-url": <url of SlapOS master>,
"tests": [
{
"url": "<url of test1 script>",
"md5sum": "MD5sum of test1 script"
},
{
"url": "<url of test2 python script>",
"md5sum": "MD5sum of test2 python script"
},
...
]
}
```
Example:
(`e2e-parameters.json`)
```
{
"client.crt": "Certificate:...-----END CERTIFICATE-----\n",
"master-url": "https://slap.vifib.com",
"tests": [
{
"url": "https://lab.nexedi.com/lu.xu/slapos/raw/feat/end-to-end-testing/software/end-to-end-testing/test_test.py",
"md5sum": "c074373dbb4154aa924ef5781dade7a0"
}
]
}
```
## Generate client certificate
Follow [How To Set Up SlapOS Client](https://handbook.rapid.space/user/rapidspace-HowTo.Setup.SlapOS.Client) to prepare `slapos-client.cfg` if you don't have one.
A convenience script `generate_parameters.py` is provided to compute these parameters in JSON format from an existing SlapOS client configuration:
```
python3 generate_parameters.py --cfg <absolute path to slapos-client.cfg> -o <output path>
```
## Adding tests
There are 3 example tests available in end-to-end testing SR:
- test_test.py
Simple successful test and failed test
- test_kvm.py
Request a KVM instance with published SR and verify one of the connection parameters
- test_health.py
Request a Monitor instance with published SR and log promises output
All tests should be written in Python with a `.py` extension and should have names that start with `test_`.
Once your test is prepared, you have the option to input a URL and its corresponding md5sum as parameters. This will enable the end-to-end testing instance to automatically detect the test.
## Running tests
### In Nexedi ERP5 automated test environment (testnodes)
When performing tests on a software release (SR) using the ERR5 test suite, you can use "SlapOS.SoftwareReleases.IntegrationTest". Additionally, make sure to fill in the "Slapos Parameters" field with the content of the input parameter mentioned above.
### In Theia locally
#### Using virtualenv
Using slapos.core for quick testing:
```
python3 -m venv testenv
source testenv/bin/activate
pip install -e path/to/my/slapos.core
export SLAPOS_E2E_TEST_CLIENT_CFG=my_test_client_cfg
export SLAPOS_E2E_TEST_LOG_FILE=my_test_log_file
python -m unittest my_test_file_in_development
# edit e2e.py in path/to/my/slapos.core or the instanciated one if improvements are needed
```
#### Using an instance of software/end-to-end-testing
1. Setup and instantiate the runner
```
slapos supply ~/srv/project/slapos/software/end-to-end-testing/software.cfg slaprunner
slapos request <e2e_instance_name> ~/srv/project/slapos/software/end-to-end-testing/software.cfg --parameters-file <e2e_parameter_json_file>
```
Your tests should be listed in <e2e_parameter_json_file> with URL and MD5sum
2. Go to instance directory and run test
```
cd ~/srv/runner/instance/slappartX
./bin/runTestSuite
```
Downloaded tests and the reusable `e2e.py` script can be found in the `~/srv/runner/instance/slappartX/var/tests/` directory.
To quickly test, you have the option to modify the test script directly here(`~/srv/runner/instance/slappartX/var/tests/`). After making the necessary changes, you can relaunch the tests by running `./bin/runTestSuite`.
## FAQ
Q1. What is the difference between `slapos-sr-testing` and `end-to-end-testing`?
- slapos-sr-testing requests SRs on a slapproxy in an SlapOSStandalone inside the slapos-sr-testing instance (same kind of thing as Theia or webrunner).
- end-to-end-testing requests SRs on the actual master, in real compute nodes (COMP-XXX). To do this it needs a slapos certificate which is passed as instance parameter to end-to-end-testing instance in the test suite.
So unlike slapos-sr-testing, end-to-end-testing does not contain the SRs it tests, it merely runs the python tests scripts (like mentioned `test_kvm.py`) and integrates with the ERP5 test suite. This also means we cannot access the files in the partition of the tested SRs, as those are on other computers. All we have access to is what a normal user requesting on panel would have access to.
[instance.cfg]
filename = instance.cfg.in
md5sum = 962830010e0a257d52c22141db3d34cf
import argparse
import configparser
import json
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--cfg', required=True)
parser.add_argument('-o', '--output', required=True)
args = parser.parse_args()
configp = configparser.ConfigParser()
configp.read(args.cfg)
with open(configp.get('slapconsole', 'cert_file')) as f:
crt = f.read()
with open(configp.get('slapconsole', 'key_file')) as f:
key = f.read()
url = configp.get('slapos', 'master_url')
with open(args.output, 'w') as f:
json.dump(
{
'client.crt': crt,
'client.key': key,
'master-url': url
}, f, indent=2)
if __name__ == '__main__':
main()
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"client.crt": {
"type": "string",
"default": "Certificate:\ndefault-client-crt\n-----END CERTIFICATE-----\n"
},
"client.key": {
"type": "string",
"default": "-----BEGIN PRIVATE KEY-----\ndefault-client-key\n-----END PRIVATE KEY-----\n"
},
"master-url": {
"type": "string",
"format": "uri",
"default": "https://slap.vifib.com"
},
"tests": {
"type": "array",
"items": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"md5sum": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Values returned by End to End Testing instanciation",
"additionalProperties": false,
"properties": {},
"type": "object"
}
[buildout]
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
extends =
${nxdtest-instance.cfg:output}
parts =
.nxdtest
[slap-configuration]
recipe = slapos.cookbook:slapconfiguration.serialised
computer = $${slap-connection:computer-id}
partition = $${slap-connection:partition-id}
url = $${slap-connection:server-url}
key = $${slap-connection:key-file}
cert = $${slap-connection:cert-file}
[directory]
recipe = slapos.cookbook:mkdirectory
home = $${buildout:directory}
bin = $${buildout:directory}/bin
etc = $${buildout:directory}/etc
var = $${buildout:directory}/var
cfg = $${buildout:directory}/.slapos
nxdtestdir = $${:var}/nxdtest
log = $${:var}/log
[client.crt]
recipe = slapos.recipe.template
output = $${directory:cfg}/client.crt
inline = $${slap-configuration:configuration.client.crt}
[client.key]
recipe = slapos.recipe.template
output = $${directory:cfg}/client.key
inline = $${slap-configuration:configuration.client.key}
[slapos-client.cfg]
recipe = slapos.recipe.template
output = $${directory:cfg}/slapos-client.cfg
inline =
[slapos]
master_url = $${slap-configuration:configuration.master-url}
[slapconsole]
cert_file = $${client.crt:output}
key_file = $${client.key:output}
[env.sh]
recipe = slapos.recipe.template:jinja2
output = $${directory:cfg}/env.sh
inline =
export HOME=$${directory:home}
[runTestSuite]
# extended from stack/nxdtest
env.sh = $${env.sh:output}
workdir = $${directory:nxdtestdir}
[.nxdtest]
recipe = slapos.recipe.template:jinja2
output = $${runTestSuite:workdir}/.nxdtest
python_for_test = ${python_for_test:executable}
testdir = $${tests:location}
log-output = $${directory:log}/e2e-testing.log
context =
key python_for_test :python_for_test
key testdir :testdir
key slapos_cfg slapos-client.cfg:output
key log_file :log-output
inline =
import os
directory = "{{ testdir }}"
slapos_cfg = {{ repr(slapos_cfg) }}
log_file = {{ repr(log_file) }}
dir_list = os.listdir(directory)
for filename in dir_list:
name, ext = os.path.splitext(filename)
if name.startswith('test') and ext == '.py':
TestCase(
name,
[{{ repr(python_for_test) }} , '-m', 'unittest', '-v', name],
cwd=directory,
env={'SLAPOS_E2E_TEST_CLIENT_CFG': slapos_cfg,
'SLAPOS_E2E_TEST_LOG_FILE': log_file},
summaryf=UnitTest.summary,
)
[tests]
recipe = slapos.recipe.build
tests = $${slap-configuration:configuration.tests}
location = $${directory:var}/tests
install =
import os
os.mkdir(location)
buildout_offline = self.buildout['buildout']['offline']
try:
# Allow to do self.download() which can only be used in "online" mode
self.buildout['buildout']['offline'] = 'false'
for i, test in enumerate(options['tests']):
tmp = self.download(test['url'], test['md5sum'])
path = os.path.join(location, 'test%s.py' % i)
os.rename(tmp, path)
finally:
# reset the parameter
self.buildout['buildout']['offline'] = buildout_offline
[buildout]
extends =
../../component/pygolang/buildout.cfg
../../stack/slapos.cfg
../../stack/nxdtest.cfg
buildout.hash.cfg
parts =
instance.cfg
slapos-cookbook
[instance.cfg]
recipe = slapos.recipe.template
output = ${buildout:directory}/instance.cfg
url = ${:_profile_base_location_}/${:filename}
[e2e.py]
recipe = slapos.recipe.build:download
output = ${buildout:directory}/${:filename}
url = ${:_profile_base_location_}/${:filename}
[python_for_test]
<= python-interpreter
interpreter = python_for_test
executable = ${buildout:bin-directory}/${:interpreter}
depends = ${lxml-python:egg}
eggs =
${pygolang:egg}
slapos.core
websocket-client
requests
opcua
[versions]
websocket-client = 1.4.2
opcua = 0.98.13
{
"name": "End-To-End-Testing",
"description": "End-To-End Testing on SlapOS Cloud",
"serialisation": "json-in-xml",
"software-type": {
"default": {
"title": "default",
"software-type": "default",
"description": "default",
"request": "instance-input-schema.json",
"response": "instance-output-schema.json"
}
}
}
import slapos.testing.e2e as e2e
import time
class HealthTest(e2e.EndToEndTestCase):
@classmethod
def test_health_promise_feed(self):
instance_name = e2e.time.strftime('e2e-test-health-%Y-%B-%d-%H:%M:%S')
product = self.product.slapmonitor
parameter_dict = {}
self.request(
self.product.slapmonitor,
instance_name,
software_type='default',
filter_kw={"computer_guid": "COMP-4057"})
self.waitUntilGreen(instance_name)
self.connection_dict = self.getInstanceInfos(instance_name).connection_dict
resp, url = self.waitUntilMonitorURLReady(instance_name=instance_name)
self.getMonitorPromises(resp.content)
import slapos.testing.e2e as e2e
class KvmTest(e2e.EndToEndTestCase):
def test(self):
instance_name = e2e.time.strftime('e2e-test-kvm-%Y-%B-%d-%H:%M:%S')
# instance_name = 'e2e-kvm-test' # avoid timestamp to reuse instance
self.request(self.product.kvm, instance_name)
self.waitUntilGreen(instance_name)
connection_dict = self.request(self.product.kvm, instance_name)
self.assertIn('url', connection_dict)
import unittest
class Test(unittest.TestCase):
def test_fail(self):
self.assertEqual(0, 1)
def test_succeed(self):
self.assertEqual(0, 0)
......@@ -103,6 +103,10 @@
"null"
]
},
"with-max-rlimit-nofile": {
"description": "Set open file descriptors soft limit to hard limit",
"type": "boolean"
},
"family-override": {
"description": "Family-wide options, possibly overriding global options",
"default": {},
......
......@@ -25,12 +25,26 @@
#
##############################################################################
import hashlib
import itertools
import json
import os
import shutil
import subprocess
import sys
import tempfile
import time
import urllib
from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass
import requests
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from slapos.testing.testcase import ManagedResource, makeModuleSetUpAndTestCaseClass
from slapos.testing.utils import findFreeTCPPort
_setUpModule, SlapOSInstanceTestCase = makeModuleSetUpAndTestCaseClass(
......@@ -200,3 +214,175 @@ class ERP5InstanceTestCase(SlapOSInstanceTestCase, metaclass=ERP5InstanceTestMet
def getComputerPartitionPath(cls, partition_reference):
partition_id = cls.getComputerPartition(partition_reference).getId()
return os.path.join(cls.slap._instance_root, partition_id)
class CaucaseService(ManagedResource):
"""A caucase service.
"""
url: str = None
directory: str = None
_caucased_process: subprocess.Popen = None
def open(self) -> None:
# start a caucased and server certificate.
software_release_root_path = os.path.join(
self._cls.slap._software_root,
hashlib.md5(self._cls.getSoftwareURL().encode()).hexdigest(),
)
caucased_path = os.path.join(software_release_root_path, 'bin', 'caucased')
self.directory = tempfile.mkdtemp()
caucased_dir = os.path.join(self.directory, 'caucased')
os.mkdir(caucased_dir)
os.mkdir(os.path.join(caucased_dir, 'user'))
os.mkdir(os.path.join(caucased_dir, 'service'))
backend_caucased_netloc = f'{self._cls._ipv4_address}:{findFreeTCPPort(self._cls._ipv4_address)}'
self.url = 'http://' + backend_caucased_netloc
self._caucased_process = subprocess.Popen(
[
caucased_path,
'--db', os.path.join(caucased_dir, 'caucase.sqlite'),
'--server-key', os.path.join(caucased_dir, 'server.key.pem'),
'--netloc', backend_caucased_netloc,
'--service-auto-approve-count', '1',
],
# capture subprocess output not to pollute test's own stdout
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
for _ in range(30):
try:
if requests.get(self.url).status_code == 200:
break
except Exception:
pass
time.sleep(1)
else:
raise RuntimeError('caucased failed to start.')
def close(self) -> None:
self._caucased_process.terminate()
self._caucased_process.wait()
self._caucased_process.stdout.close()
shutil.rmtree(self.directory)
@property
def ca_crt_path(self) -> str:
"""Path of the CA certificate from this caucase.
"""
ca_crt_path = os.path.join(self.directory, 'ca.crt.pem')
if not os.path.exists(ca_crt_path):
with open(ca_crt_path, 'w') as f:
f.write(
requests.get(urllib.parse.urljoin(
self.url,
'/cas/crt/ca.crt.pem',
)).text)
return ca_crt_path
class CaucaseCertificate(ManagedResource):
"""A certificate signed by a caucase service.
"""
ca_crt_file: str = None
crl_file: str = None
csr_file: str = None
cert_file: str = None
key_file: str = None
def open(self) -> None:
self.tmpdir = tempfile.mkdtemp()
self.ca_crt_file = os.path.join(self.tmpdir, 'ca-crt.pem')
self.crl_file = os.path.join(self.tmpdir, 'ca-crl.pem')
self.csr_file = os.path.join(self.tmpdir, 'csr.pem')
self.cert_file = os.path.join(self.tmpdir, 'crt.pem')
self.key_file = os.path.join(self.tmpdir, 'key.pem')
def close(self) -> None:
shutil.rmtree(self.tmpdir)
@property
def _caucase_path(self) -> str:
"""path of caucase executable.
"""
software_release_root_path = os.path.join(
self._cls.slap._software_root,
hashlib.md5(self._cls.getSoftwareURL().encode()).hexdigest(),
)
return os.path.join(software_release_root_path, 'bin', 'caucase')
def request(self, common_name: str, caucase: CaucaseService) -> None:
"""Generate certificate and request signature to the caucase service.
This overwrite any previously requested certificate for this instance.
"""
cas_args = [
self._caucase_path,
'--ca-url', caucase.url,
'--ca-crt', self.ca_crt_file,
'--crl', self.crl_file,
]
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
with open(self.key_file, 'wb') as f:
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
))
csr = x509.CertificateSigningRequestBuilder().subject_name(
x509.Name([
x509.NameAttribute(
NameOID.COMMON_NAME,
common_name,
),
])).sign(
key,
hashes.SHA256(),
default_backend(),
)
with open(self.csr_file, 'wb') as f:
f.write(csr.public_bytes(serialization.Encoding.PEM))
csr_id = subprocess.check_output(
cas_args + [
'--send-csr', self.csr_file,
],
).split()[0].decode()
assert csr_id
for _ in range(30):
if not subprocess.call(
cas_args + [
'--get-crt', csr_id, self.cert_file,
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
) == 0:
break
else:
time.sleep(1)
else:
raise RuntimeError('getting service certificate failed.')
with open(self.cert_file) as cert_file:
assert 'BEGIN CERTIFICATE' in cert_file.read()
def revoke(self, caucase: CaucaseService) -> None:
"""Revoke the client certificate on this caucase instance.
"""
subprocess.check_call([
self._caucase_path,
'--ca-url', caucase.url,
'--ca-crt', self.ca_crt_file,
'--crl', self.crl_file,
'--revoke-crt', self.cert_file, self.key_file,
])
This diff is collapsed.
......@@ -32,6 +32,7 @@ import glob
import http.client
import json
import os
import resource
import shutil
import socket
import sqlite3
......@@ -633,8 +634,7 @@ class ZopeTestMixin(ZopeSkinsMixin, CrontabMixin):
)):
os.unlink(logfile)
def _getCrontabCommand(self, crontab_name):
# type: (str) -> str
def _getCrontabCommand(self, crontab_name: str) -> str:
"""Read a crontab and return the command that is executed.
overloaded to use crontab from zope partition
......@@ -1045,8 +1045,7 @@ class TestNEO(ZopeSkinsMixin, CrontabMixin, ERP5InstanceTestCase):
__partition_reference__ = 'n'
__test_matrix__ = matrix((neo,))
def _getCrontabCommand(self, crontab_name):
# type: (str) -> str
def _getCrontabCommand(self, crontab_name: str) -> str:
"""Read a crontab and return the command that is executed.
overloaded to use crontab from neo partition
......@@ -1100,3 +1099,36 @@ class TestNEO(ZopeSkinsMixin, CrontabMixin, ERP5InstanceTestCase):
'var',
'log',
f))
class TestWithMaxRlimitNofileParameter(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
"""Test setting the with-max-rlimit-nofile parameter sets the open fd soft limit to the hard limit.
"""
__partition_reference__ = 'nf'
@classmethod
def getInstanceParameterDict(cls):
return {'_': json.dumps({'with-max-rlimit-nofile': True})}
def test_with_max_rlimit_nofile(self):
with self.slap.instance_supervisor_rpc as supervisor:
all_process_info = supervisor.getAllProcessInfo()
_, current_hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
process_info, = (p for p in all_process_info if p['name'].startswith('zope-'))
self.assertEqual(
resource.prlimit(process_info['pid'], resource.RLIMIT_NOFILE),
(current_hard_limit, current_hard_limit))
class TestUnsetWithMaxRlimitNofileParameter(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
"""Test not setting the with-max-rlimit-nofile parameter doesn't change the soft limit of erp5
"""
__partition_reference__ = 'nnf'
def test_unset_with_max_rlimit_nofile(self) -> None:
with self.slap.instance_supervisor_rpc as supervisor:
all_process_info = supervisor.getAllProcessInfo()
limit = resource.getrlimit(resource.RLIMIT_NOFILE)
process_info, = (p for p in all_process_info if p['name'].startswith('zope-'))
self.assertEqual(
resource.prlimit(process_info['pid'], resource.RLIMIT_NOFILE), limit)
......@@ -36,6 +36,7 @@ import subprocess
import urllib.parse
import MySQLdb
import MySQLdb.connections
from slapos.testing.utils import CrontabMixin, getPromisePluginParameterDict
......@@ -60,8 +61,7 @@ class MariaDBTestCase(ERP5InstanceTestCase):
return "mariadb"
@classmethod
def _getInstanceParameterDict(cls):
# type: () -> dict
def _getInstanceParameterDict(cls) -> dict:
return {
'tcpv4-port': 3306,
'max-connection-count': 5,
......@@ -76,12 +76,10 @@ class MariaDBTestCase(ERP5InstanceTestCase):
}
@classmethod
def getInstanceParameterDict(cls):
# type: () -> dict
def getInstanceParameterDict(cls) -> dict:
return {'_': json.dumps(cls._getInstanceParameterDict())}
def getDatabaseConnection(self):
# type: () -> MySQLdb.connections.Connection
def getDatabaseConnection(self) -> MySQLdb.connections.Connection:
connection_parameter_dict = json.loads(
self.computer_partition.getConnectionParameterDict()['_'])
db_url = urllib.parse.urlparse(connection_parameter_dict['database-list'][0])
......@@ -106,8 +104,7 @@ class TestCrontabs(MariaDBTestCase, CrontabMixin):
'*/srv/backup/*',
)
def test_full_backup(self):
# type: () -> None
def test_full_backup(self) -> None:
self._executeCrontabAtDate('mariadb-backup', '2050-01-01')
full_backup_file, = glob.glob(
os.path.join(
......@@ -121,8 +118,7 @@ class TestCrontabs(MariaDBTestCase, CrontabMixin):
with gzip.open(full_backup_file, 'rt') as dump:
self.assertIn('CREATE TABLE', dump.read())
def test_logrotate_and_slow_query_digest(self):
# type: () -> None
def test_logrotate_and_slow_query_digest(self) -> None:
# slow query digest needs to run after logrotate, since it operates on the rotated
# file, so this tests both logrotate and slow query digest.
......@@ -193,8 +189,7 @@ class TestCrontabs(MariaDBTestCase, CrontabMixin):
class TestMariaDB(MariaDBTestCase):
def test_utf8_collation(self):
# type: () -> None
def test_utf8_collation(self) -> None:
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
cnx.query(
......@@ -219,8 +214,7 @@ class TestMariaDB(MariaDBTestCase):
class TestMroonga(MariaDBTestCase):
def test_mroonga_plugin_loaded(self):
# type: () -> None
def test_mroonga_plugin_loaded(self) -> None:
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
cnx.query("show plugins")
......@@ -229,8 +223,7 @@ class TestMroonga(MariaDBTestCase):
('Mroonga', 'ACTIVE', 'STORAGE ENGINE', 'ha_mroonga.so', 'GPL'),
plugins)
def test_mroonga_normalize_udf(self):
# type: () -> None
def test_mroonga_normalize_udf(self) -> None:
# example from https://mroonga.org/docs/reference/udf/mroonga_normalize.html#usage
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
......@@ -255,8 +248,7 @@ class TestMroonga(MariaDBTestCase):
self.assertEqual((('ABCDあぃうぇ㍑'.encode(),),),
cnx.store_result().fetch_row(maxrows=2))
def test_mroonga_full_text_normalizer(self):
# type: () -> None
def test_mroonga_full_text_normalizer(self) -> None:
# example from https://mroonga.org//docs/tutorial/storage.html#how-to-specify-the-normalizer
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
......@@ -293,8 +285,7 @@ class TestMroonga(MariaDBTestCase):
cnx.store_result().fetch_row(maxrows=2),
)
def test_mroonga_full_text_normalizer_TokenBigramSplitSymbolAlphaDigit(self):
# type: () -> None
def test_mroonga_full_text_normalizer_TokenBigramSplitSymbolAlphaDigit(self) -> None:
# Similar to as ERP5's testI18NSearch with erp5_full_text_mroonga_catalog
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
......@@ -337,8 +328,7 @@ class TestMroonga(MariaDBTestCase):
""")
self.assertEqual(((1,),), cnx.store_result().fetch_row(maxrows=2))
def test_mroonga_full_text_stem(self):
# type: () -> None
def test_mroonga_full_text_stem(self) -> None:
# example from https://mroonga.org//docs/tutorial/storage.html#how-to-specify-the-token-filters
cnx = self.getDatabaseConnection()
with contextlib.closing(cnx):
......
......@@ -46,7 +46,7 @@ from slapos.testing.testcase import (
makeModuleSetUpAndTestCaseClass,
)
old_software_release_url = 'https://lab.nexedi.com/nexedi/slapos/raw/1.0.167.9/software/erp5/software.cfg'
old_software_release_url = 'https://lab.nexedi.com/nexedi/slapos/raw/1.0.167.10/software/erp5/software.cfg'
new_software_release_url = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', 'software.cfg'))
......
......@@ -92,8 +92,8 @@ class WendelinTutorialTestCase(FluentdTestCase):
@classmethod
def measureDict(cls):
return {k: v.encode() for k, v in
zip((b'pressure', b'humidity', b'temperature'), cls._measurementList)}
return {k: v for k, v in
zip(('pressure', 'humidity', 'temperature'), cls._measurementList)}
@classmethod
def setUpClass(cls):
......
......@@ -27,4 +27,4 @@ md5sum = 98faa5ad8cfb23a11d97a459078a1d05
[template-runTestSuite]
filename = runTestSuite.in
md5sum = 2bb3d71a0e04bc8bc828bb3f726ef3ff
md5sum = 5db53d622bd68fb07e078ddc4403a240
......@@ -93,7 +93,7 @@ def main():
firefox_capabilities['marionette'] = True
browser = webdriver.Firefox(
capabilities=firefox_capabilities,
firefox_binary='${firefox-wrapper-68:location}',
firefox_binary='${firefox-wrapper:location}',
executable_path='${geckodriver:location}')
else:
assert target == 'selenium-server', f"Unsupported target {test_runner['target']}"
......
......@@ -22,7 +22,7 @@ parts =
git
eggs
xserver
firefox-68
firefox
xwd
renderjs-install
jio-install
......
[instance-profile]
filename = instance.cfg.in
md5sum = 0d50ed911a41b76b952b63d37853c3a4
md5sum = f753802ad631a57c559d868e525cf81b
......@@ -3,7 +3,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
"mb_password_complexity": {
"mb-password-complexity": {
"title": "Password complexity",
"description": "Check Metabase documentation for more details.",
"type": "string",
......@@ -14,11 +14,25 @@
"strong"
]
},
"mb_password_length": {
"mb-password-length": {
"title": "Password length",
"description": "Password length",
"type": "integer",
"default": 6
},
"mb-aggregated-query-row-limit": {
"title": "Aggregated row limit",
"description": "Maximum number of rows to return for aggregated queries via the API.",
"type": "integer",
"default": 10000,
"maximum": 1048575
},
"mb-unaggregated-query-row-limit": {
"title": "Unaggregated row limit",
"description": "Maximum number of rows to return specifically on `:rows`-type queries via the API. Must be less than the number configured in `mb-aggregated-query-row-limit`",
"type": "integer",
"default": 2000,
"maximum": 1048575
}
}
}
......@@ -31,8 +31,16 @@ slapparameter-dict = $${slap-configuration:configuration}
home = $${buildout:directory}
init =
default_parameters = options.get('slapparameter-dict')
options['mb_password_complexity'] = default_parameters.get('mb_password_complexity', 'normal')
options['mb_password_length'] = default_parameters.get('mb_password_length', '6')
options['mb-password-complexity'] = default_parameters.get(
'mb-password-complexity',
default_parameters.get('mb_password_complexity', 'normal'))
options['mb-password-length'] = default_parameters.get(
'mb-password-length',
default_parameters.get('mb_password_length', '6'))
options['mb-aggregated-query-row-limit'] = default_parameters.get(
'mb-aggregated-query-row-limit', '10000')
options['mb-unaggregated-query-row-limit'] = default_parameters.get(
'mb-unaggregated-query-row-limit', '2000')
[metabase-instance]
recipe = slapos.cookbook:wrapper
......@@ -51,8 +59,10 @@ environment =
MB_DB_USER=$${postgresql:superuser}
MB_DB_PASS=$${postgresql:password}
MB_DB_HOST=$${postgresql:ipv4}
MB_PASSWORD_COMPLEXITY=$${slap-parameter:mb_password_complexity}
MB_PASSWORD_LENGTH=$${slap-parameter:mb_password_length}
MB_PASSWORD_COMPLEXITY=$${slap-parameter:mb-password-complexity}
MB_PASSWORD_LENGTH=$${slap-parameter:mb-password-length}
MB_AGGREGATED_QUERY_ROW_LIMIT=$${slap-parameter:mb-aggregated-query-row-limit}
MB_UNAGGREGATED_QUERY_ROW_LIMIT=$${slap-parameter:mb-unaggregated-query-row-limit}
FONTCONFIG_FILE=$${fontconfig-conf:output}
JAVA_ARGS=-Dorg.quartz.scheduler.instanceId=$${slap-connection:computer-id}.$${slap-connection:partition-id} -Djava.io.tmpdir="$${directory:tmp}"
hash-existing-files =
......
......@@ -20,8 +20,8 @@ parts =
[metabase.jar]
recipe = slapos.recipe.build:download
url = https://downloads.metabase.com/v0.47.0/metabase.jar
md5sum = b81c71668a2177d89690730fabd85d9e
url = https://downloads.metabase.com/v0.48.2/metabase.jar
md5sum = d708a85436da3d5751f0e48ebd10c142
[instance-profile]
recipe = slapos.recipe.template
......
......@@ -14,4 +14,4 @@
# not need these here).
[template-instance]
filename = instance.cfg
md5sum = 7a558c2b9461ec588c9d77bdeef64e4d
md5sum = 5a765463118f8b2a09df6260f56c2175
......@@ -25,7 +25,7 @@ develop-eggs-directory = {{ develop_eggs_directory }}
# needed for the "repo" command (to download many git repositories)
recipe = slapos.recipe.build:gitclone
repository = https://chromium.googlesource.com/chromium/tools/depot_tools.git
branch = master
branch = main
git-executable = {{ git_path }}/bin/git
[customize-path]
......
......@@ -14,12 +14,12 @@
# not need these here).
[template-nextcloud-install.sh]
filename = nextcloud-install.sh.in
md5sum = 965cc84d4c8e39f06850fac361575647
md5sum = f31dfd6fce79fcf1c13cbd96dd366492
[template-nextcloud-config.json]
filename = nextcloud-config.json.in
md5sum = 6f42f0a8c5e5c0c657541a65c4d9ee57
md5sum = 133ad47aec7e16f716eb710ef38823e8
[template-nextcloud-instance]
filename = nextcloud-instance.cfg.in
md5sum = a59b081bd39f61c7361fdb6c54fc2039
md5sum = 65d2fef4aa41fa70e5194d73a8cb2c4a
......@@ -35,6 +35,19 @@
"timeout": 0
},
"logfile": "{{ parameter_dict['data-dir'] }}/nextcloud.log",
"datadirectory": "{{ parameter_dict['data-dir'] }}"
"loglevel" => 2,
"datadirectory": "{{ parameter_dict['data-dir'] }}",
"preview_ffmpeg_path": "{{ parameter_dict['ffmpeg-path'] }}",
"tempdirectory": "{{ parameter_dict['tmp-dir'] }}",
"apps_paths": [
{
"path": "{{ parameter_dict['nextcloud'] }}/apps",
"url": "/apps",
"writable": true
}
],
"default_phone_region": "FR",
"default_locale": "fr_FR",
"default_timezone": "Europe/Paris"
}
}
......@@ -115,19 +115,9 @@
"type": "string",
"format": "uri"
},
"instance.trusted-domain-1": {
"title": "Authorized domain on nextcloud",
"description": "Trusted domain used to connect to Nextcloud instance.",
"type": "string"
},
"instance.trusted-domain-2": {
"title": "Second authorized domain on nextcloud",
"description": "Trusted domain used to connect to Nextcloud instance.",
"type": "string"
},
"instance.trusted-domain-3": {
"title": "Third authorized domain on nextcloud",
"description": "Trusted domain used to connect to Nextcloud instance.",
"instance.trusted-domain-list": {
"title": "Authorized domain(s) on nextcloud",
"description": "Trusted domain(s) used to connect to Nextcloud instance. Space separated.",
"type": "string"
},
"instance.trusted-proxy-list": {
......
......@@ -140,4 +140,4 @@ if [ -f "{{ parameter_dict['nextcloud'] }}/config/CAN_INSTALL" ]; then
rm {{ parameter_dict['nextcloud'] }}/config/CAN_INSTALL
fi
date > {{ parameter_dict['nextcloud'] }}/.slapos-install-done
date > {{ parameter_dict['installed-file'] }}
......@@ -8,6 +8,7 @@ redis = ${directory:srv}/redis
redis-log = ${directory:log}/redis
data = ${directory:srv}/data
backup = ${directory:backup}/nextcloud
tmp = ${buildout:directory}/tmp
[service-redis]
recipe = slapos.cookbook:redis.server
......@@ -42,13 +43,15 @@ rotate-num = 30
[instance-parameter]
nextcloud = ${:document-root}
installed-file = ${directory:etc}/.nextcloud-install-done
admin-user = admin
admin-password = admin
ffmpeg-path = {{ ffmpeg_location }}/bin/ffmpeg
tmp-dir = ${nc-directory:tmp}
trusted-domain-list =
[${apache-php-configuration:ip}]:${apache-php-configuration:port}
${slap-parameter:instance.trusted-domain-1}
${slap-parameter:instance.trusted-domain-2}
${slap-parameter:instance.trusted-domain-3}
${request-frontend:connection-domain}
${slap-parameter:instance.trusted-domain-list}
trusted-proxy-list = ${slap-parameter:instance.trusted-proxy-list}
cli-url = ${slap-parameter:instance.cli-url}
......@@ -69,6 +72,9 @@ collabora-url = ${slap-parameter:instance.collabora-url}
stun-server = ${slap-parameter:instance.stun-server}
turn-server = ${slap-parameter:instance.turn-server}
turn-secret = ${slap-parameter:instance.turn-secret}
# php.ini
php.opcache.revalidate-freq = 60
php.opcache.interned-strings-buffer = 24
[nextcloud-install.sh]
recipe = slapos.recipe.template:jinja2
......@@ -105,7 +111,7 @@ input = inline:#!/bin/bash
echo "Nextcloud is not installed.";
exit 1;
fi
if [ ! -f "${instance-parameter:nextcloud}/.slapos-install-done" ]; then
if [ ! -f "${instance-parameter:installed-file}" ]; then
echo "Nextcloud is not configured.";
exit 1;
fi
......@@ -203,7 +209,5 @@ instance.turn-server =
instance.turn-secret =
instance.cli-url = ${apache-php-configuration:url}
instance.trusted-domain-1 =
instance.trusted-domain-2 =
instance.trusted-domain-3 =
instance.trusted-proxy-list =
instance.trusted-domain-list =
......@@ -2,6 +2,7 @@
extends =
buildout.hash.cfg
../../component/redis/buildout.cfg
../../component/ffmpeg/buildout.cfg
../../stack/lamp/buildout.cfg
[nc-download]
......@@ -28,6 +29,7 @@ context =
key python3_location python3:location
key news_updater_location news-updater:location
key php_location apache-php:location
key ffmpeg_location ffmpeg:location
raw redis_bin ${redis:location}/bin/redis-server
raw redis_cli ${redis:location}/bin/redis-cli
key nextcloud_install_sh template-nextcloud-install.sh:target
......@@ -42,7 +44,6 @@ db-user = nextcloud
[nc-download-unpacked]
recipe = slapos.recipe.build:download-unpacked
shared = true
[news-updater]
<= nc-download-unpacked
......@@ -66,8 +67,8 @@ md5sum = 88adcbc34ef7e461f515ba96b82365d9
[nextcloud-app-snappymail]
<= nc-download-unpacked
url = https://snappymail.eu/repository/nextcloud/snappymail-2.29.1-nextcloud.tar.gz
md5sum = b7500ea4e089d8a9e3fa381d6df3a3b0
url = https://snappymail.eu/repository/nextcloud/snappymail-2.29.4-nextcloud.tar.gz
md5sum = 676bf0fa3b9f0fb9f0208304cf302a26
[nextcloud-app-news]
<= nc-download-unpacked
......
......@@ -92,7 +92,7 @@ class NextCloudTestCase(InstanceTestCase):
mail_smtpname="",
cli_url="https://[%s]:9988/" % self.nextcloud_ipv6,
partition_dir=self.partition_dir,
trusted_domain_list=json.dumps(["[%s]:9988" % self.nextcloud_ipv6]),
trusted_domain_list=json.dumps(["[%s]:9988" % self.nextcloud_ipv6] * 2),
trusted_proxy_list=[],
)
data_dict.update(config_dict)
......@@ -309,8 +309,7 @@ class TestNextCloudParameters(NextCloudTestCase):
'instance.turn-server': 'turn.example.net:5439',
'instance.turn-secret': 'c4f0ead40a49bbbac3c58f7b9b43990f78ebd96900757ae67e10190a3a6b6053',
'instance.cli-url': 'nextcloud.example.com',
'instance.trusted-domain-1': 'nextcloud.example.com',
'instance.trusted-domain-2': 'nextcloud.proxy.com',
'instance.trusted-domain-list': 'nextcloud.example.com nextcloud.proxy.com',
'instance.trusted-proxy-list': '2001:67c:1254:e:89::5df3 127.0.0.1 10.23.1.3',
}
......@@ -345,6 +344,7 @@ class TestNextCloudParameters(NextCloudTestCase):
cli_url="nextcloud.example.com",
partition_dir=self.partition_dir,
trusted_domain_list=json.dumps([
"[%s]:9988" % self.nextcloud_ipv6,
"[%s]:9988" % self.nextcloud_ipv6,
"nextcloud.example.com",
"nextcloud.proxy.com"
......
......@@ -16,35 +16,35 @@
[template]
filename = instance.cfg
md5sum = a9e416eaa3ad7d2ea29cb90ce2c41a60
md5sum = 12591aca22ddc61f9fb1ec7e596873e3
[slaplte.jinja2]
_update_hash_filename_ = slaplte.jinja2
md5sum = c31dffa87765d93327f18ffd89ce36ca
[amarisoft-stats.jinja2.py]
_update_hash_filename_ = amarisoft-stats.jinja2.py
[ru_amarisoft-stats.jinja2.py]
_update_hash_filename_ = ru/amarisoft-stats.jinja2.py
md5sum = c4d5e9fcf460d88bc2b4bcfbdfe554f7
[amarisoft-rf-info.jinja2.py]
_update_hash_filename_ = amarisoft-rf-info.jinja2.py
[ru_amarisoft-rf-info.jinja2.py]
_update_hash_filename_ = ru/amarisoft-rf-info.jinja2.py
md5sum = ab666fdfadbfc7d8a16ace38d295c883
[ru_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/libinstance.jinja2.cfg
md5sum = 6febf4dc601ba5feb30aa402f37265cf
md5sum = d3ae8839b1a7b7a225bc69c3910c0b35
[ru_sdr_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/sdr/libinstance.jinja2.cfg
md5sum = c20b620111a4dc4bc2bcae57c2007cbe
md5sum = de71c63b8df940207409de7e948f7c8c
[ru_lopcomm_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/lopcomm/libinstance.jinja2.cfg
md5sum = abce2deca15b8d7a8c5378e0789f8ce7
md5sum = b2af1e70141216a4db07cca655aa63a7
[ru_sunwave_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/sunwave/libinstance.jinja2.cfg
md5sum = 0450e9fa50844e4d6e51d608625c57f6
md5sum = c855ee7a6132899eb53b8d80ec27701a
[ru_lopcomm_ncclient_common.py]
_update_hash_filename_ = ru/lopcomm/ncclient_common.py
......@@ -80,11 +80,11 @@ md5sum = 52da9fe3a569199e35ad89ae1a44c30e
[template-enb]
_update_hash_filename_ = instance-enb.jinja2.cfg
md5sum = 3b380ac8a44aafc30cc6d87b35860fd6
md5sum = ae49a3a9a97407f9aea30981403ee1a2
[template-gnb]
_update_hash_filename_ = instance-gnb.jinja2.cfg
md5sum = e8e87a50b861d733894eb69e1aefa683
md5sum = 54a0c7c3a2a1c905a15c58c650ee1095
[template-core-network]
_update_hash_filename_ = instance-core-network.jinja2.cfg
......@@ -108,7 +108,7 @@ md5sum = dcaac06553a3222b14c0013a13f4a149
[enb.jinja2.cfg]
filename = config/enb.jinja2.cfg
md5sum = a961cc1469bd2534645470f914f12905
md5sum = 914d781af63f4214e6cc3be4ffe93215
[drb_lte.jinja2.cfg]
filename = config/drb_lte.jinja2.cfg
......@@ -122,10 +122,6 @@ md5sum = 84d3cef8fc7f1c2aed7c348d500f5636
filename = config/sib23.jinja2.asn
md5sum = a1973ba6e43d40e510d61d461c2d13ac
[gnb.jinja2.cfg]
filename = config/gnb.jinja2.cfg
md5sum = a4f91c1c9cfd91d000f4845a88cdb38a
[mme.jinja2.cfg]
filename = config/mme.jinja2.cfg
md5sum = 3d7833ddba3242cedcd74c7db52390c6
......@@ -136,7 +132,7 @@ md5sum = f167b4be5e327b276b42267e0678f577
[ru_dnsmasq.jinja2.cfg]
_update_hash_filename_ = ru/dnsmasq.jinja2.cfg
md5sum = 345e4967d468b00c13d77821bce8a248
md5sum = 9bd5b08f23640f71ad109d186d060f2d
[ims.jinja2.cfg]
filename = config/ims.jinja2.cfg
......
Changelog
=========
Version 1.0.344 (2023-11-03)
-------------
* Set dpc_snr_target to 25 for PUSCH also
Version 1.0.341 (2023-10-20)
-------------
* Publish amarisoft version and license expiration information
* Add network name parameter
Version 1.0.340 (2023-10-20)
-------------
* Update RRH firmware and reset
Version 1.0.339 (2023-10-16)
-------------
* Lopcomm firmware update
* RRH reset (reboot) function added
* Fix cpri_tx_dbm parameter
......
This diff is collapsed.
......@@ -6,16 +6,8 @@ parts =
xamari-xlog-service
{% if slapparameter_dict.get('xlog_fluentbit_forward_host') %}
xlog-fluentbit-service
{% endif %}
amarisoft-stats-service
amarisoft-rf-info-service
{% if ru == "lopcomm" %}
sshd-service
sshd-add-authorized-key
sshd-promise
{% endif %}
check-baseband-latency.py
check-amarisoft-stats-log.py
monitor-base
publish-connection-information
......@@ -179,111 +171,6 @@ wrapper-path = ${directory:service}/${:_buildout_section_name_}
hash-files = ${:fluentbit-config}
{% endif %}
[amarisoft-stats-template]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
log-output = ${directory:var}/log/amarisoft-stats.json.log
context =
section directory directory
key slapparameter_dict slap-configuration:configuration
key log_file :log-output
raw stats_period {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
raw testing {{ slapparameter_dict.get("testing", False) }}
raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ amarisoft_stats_template }}
output = ${directory:bin}/amarisoft-stats.py
[amarisoft-rf-info-template]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
log-output = ${directory:var}/log/amarisoft-rf-info.json.log
context =
section directory directory
key slapparameter_dict slap-configuration:configuration
key log_file :log-output
raw stats_period {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
raw testing {{ slapparameter_dict.get("testing", False) }}
raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ amarisoft_rf_info_template }}
output = ${directory:bin}/amarisoft-rf-info.py
[amarisoft-stats-service]
recipe = slapos.cookbook:wrapper
command-line = ${amarisoft-stats-template:output}
wrapper-path = ${directory:service}/amarisoft-stats
mode = 0775
hash-files =
${amarisoft-stats-template:output}
[amarisoft-rf-info-service]
recipe = slapos.cookbook:wrapper
command-line = ${amarisoft-rf-info-template:output}
wrapper-path = ${directory:service}/amarisoft-rf-info
mode = 0775
hash-files =
${amarisoft-rf-info-template:output}
[user-info]
recipe = slapos.cookbook:userinfo
# Deploy openssh-server
[sshd-port]
recipe = slapos.cookbook:free_port
minimum = 22222
maximum = 22231
ip = ${slap-configuration:ipv6-random}
[sshd-config]
recipe = slapos.recipe.template:jinja2
output = ${directory:etc}/sshd.conf
path_pid = ${directory:run}/sshd.pid
inline =
PidFile ${:path_pid}
Port ${sshd-port:port}
ListenAddress ${slap-configuration:ipv6-random}
Protocol 2
HostKey ${sshd-ssh-host-rsa-key:output}
HostKey ${sshd-ssh-host-ecdsa-key:output}
PasswordAuthentication no
PubkeyAuthentication yes
HostKeyAlgorithms ssh-rsa,ssh-dss,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521
AuthorizedKeysFile ${buildout:directory}/.ssh/authorized_keys
Subsystem sftp {{ openssh_location }}/libexec/sftp-server
[sshd-service]
recipe = slapos.cookbook:wrapper
command-line = {{ openssh_location }}/sbin/sshd -D -e -f ${sshd-config:output}
wrapper-path = ${directory:service}/sshd
hash-files = ${sshd-config:output}
environment =
HOME=${directory:home}
[sshd-add-authorized-key]
recipe = slapos.cookbook:dropbear.add_authorized_key
home = ${buildout:directory}
key = {{ slapparameter_dict.get("user-authorized-key", '') }}
[sshd-ssh-keygen-base]
recipe = plone.recipe.command
output = ${directory:etc}/${:_buildout_section_name_}
command = {{ openssh_output_keygen }} -f ${:output} -N '' ${:extra-args}
[sshd-ssh-host-rsa-key]
<=sshd-ssh-keygen-base
extra-args=-t rsa
[sshd-ssh-host-ecdsa-key]
<=sshd-ssh-keygen-base
extra-args=-t ecdsa -b 521
[sshd-promise]
<= monitor-promise-base
promise = check_socket_listening
name = sshd.py
config-host = ${slap-configuration:ipv6-random}
config-port = ${sshd-port:port}
[config-base]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
......@@ -303,6 +190,8 @@ context =
raw trx {{ trx }}
raw bbu {{ bbu }}
raw ru {{ ru }}
json do_lte true
json do_nr false
import netaddr netaddr
${:extra-context}
......@@ -351,12 +240,6 @@ current-earfcn = {{ ors_version['current-earfcn'] }}
amarisoft-version = {{ lte_version }}
license-expiration = {{ lte_expiration }}
monitor-gadget-url = ${:monitor-base-url}/gadget/software.cfg.html
{% if ru == "lopcomm" %}
ssh-command = ssh ${user-info:pw-name}@${slap-configuration:ipv6-random} -p ${sshd-port:port}
ssh-url = ssh://${user-info:pw-name}@[${slap-configuration:ipv6-random}]:${sshd-port:port}
ru-firmware = {{ru_lopcomm_firmware_filename}}
ru-ipv6 = ${slap-configuration:tap-ipv6-gateway}
{% endif %}
[monitor-instance-parameter]
{% if slapparameter_dict.get("name", None) %}
......@@ -374,14 +257,6 @@ name = ${:_buildout_section_name_}
<= macro.promise
promise = check_baseband_latency
config-testing = {{ slapparameter_dict.get("testing", False) }}
config-amarisoft-stats-log = ${amarisoft-stats-template:log-output}
config-amarisoft-stats-log = ${ru_amarisoft-stats-template:log-output}
config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
config-min-rxtx-delay = {{ slapparameter_dict.get("min_rxtx_delay", 0) }}
[check-amarisoft-stats-log.py]
<= macro.promise
promise = check_amarisoft_stats_log
output = ${directory:plugins}/check-amarisoft-stats-log.py
config-testing = {{ slapparameter_dict.get("testing", False) }}
config-amarisoft-stats-log = ${amarisoft-stats-template:log-output}
config-stats-period = {{ slapparameter_dict.get("enb_stats_fetch_period", 60) }}
......@@ -188,7 +188,7 @@ context =
raw testing {{ slapparameter_dict.get("testing", False) }}
raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ amarisoft_stats_template }}
url = {{ ru_amarisoft_stats_template }}
output = ${directory:bin}/amarisoft-stats.py
[amarisoft-rf-info-template]
......@@ -203,7 +203,7 @@ context =
raw testing {{ slapparameter_dict.get("testing", False) }}
raw python_path {{ buildout_directory}}/bin/pythonwitheggs
mode = 0775
url = {{ amarisoft_rf_info_template }}
url = {{ ru_amarisoft_rf_info_template }}
output = ${directory:bin}/amarisoft-rf-info.py
[amarisoft-stats-service]
......@@ -242,6 +242,8 @@ context =
raw trx {{ trx }}
raw bbu {{ bbu }}
raw ru {{ ru }}
json do_lte false
json do_nr true
import netaddr netaddr
${:extra-context}
......@@ -255,11 +257,13 @@ output = ${directory:etc}/drb.cfg
{% if slapparameter_dict.get("gnb_config_link", None) %}
url = ${gnb-config-dl:target}
{% else %}
url = {{ gnb_template }}
url = {{ enb_template }}
{% endif %}
output = ${directory:etc}/gnb.cfg
extra-context =
key drb_file drb-config:output
import-list =
rawfile slaplte.jinja2 {{ slaplte_template }}
[publish-connection-information]
<= monitor-publish
......
......@@ -279,8 +279,8 @@ extra-context =
raw slaplte_template ${slaplte.jinja2:target}
raw drb_lte_template ${drb_lte.jinja2.cfg:target}
raw sib23_template ${sib23.jinja2.asn:target}
raw amarisoft_stats_template ${amarisoft-stats.jinja2.py:target}
raw amarisoft_rf_info_template ${amarisoft-rf-info.jinja2.py:target}
raw ru_amarisoft_stats_template ${ru_amarisoft-stats.jinja2.py:target}
raw ru_amarisoft_rf_info_template ${ru_amarisoft-rf-info.jinja2.py:target}
raw ru_lopcomm_stats_template ${ru_lopcomm_stats.jinja2.py:target}
raw ru_lopcomm_config_template ${ru_lopcomm_config.jinja2.py:target}
raw ru_lopcomm_software_template ${ru_lopcomm_software.jinja2.py:target}
......@@ -316,10 +316,11 @@ extra-context =
key lte_expiration amarisoft:lte-expiration
key enb amarisoft:enb
key sdr amarisoft:sdr
raw gnb_template ${gnb.jinja2.cfg:target}
raw enb_template ${enb.jinja2.cfg:target}
raw slaplte_template ${slaplte.jinja2:target}
raw drb_nr_template ${drb_nr.jinja2.cfg:target}
raw amarisoft_stats_template ${amarisoft-stats.jinja2.py:target}
raw amarisoft_rf_info_template ${amarisoft-rf-info.jinja2.py:target}
raw ru_amarisoft_stats_template ${ru_amarisoft-stats.jinja2.py:target}
raw ru_amarisoft_rf_info_template ${ru_amarisoft-rf-info.jinja2.py:target}
raw openssl_location ${openssl:location}
raw default_nr_bandwidth ${default-params:default-nr-bandwidth}
raw default_nr_ssb_pos_bitmap ${default-params:default-nr-ssb-pos-bitmap}
......
......@@ -33,3 +33,9 @@ stop-on-error = true
[setcap-netcapdo]
<= setcap
exe = ${netcapdo:exe}
[ru_amarisoft-stats.jinja2.py]
<= download-base
[ru_amarisoft-rf-info.jinja2.py]
<= download-base
......@@ -7,7 +7,8 @@ port=5354
{%- set plen = netaddr.IPNetwork(vtap.network).prefixlen %}
# {{ cell_ref }} @ {{ ru_tap }}
dhcp-range=tag:{{ ru_tap }},{{ vtap.gateway }},{{ vtap.gateway }},static,{{ plen }},5m
{#- TODO consider using /128 as we give only 1 address to RU #}
dhcp-range=tag:{{ ru_tap }},{{ vtap.gateway }},{{ vtap.gateway }},static,{{ max(plen,64) }},5m
dhcp-host={{ cell.ru_mac_addr }},tag:{{ ru_tap }},[{{ vtap.gateway }}]
# option 17 used for RU callhome
# dhcp-option=option6:17,[{{ vtap.addr }}]
......
......@@ -35,5 +35,5 @@ destination = ${buildout:directory}/ncclient_common.py
[ru_lopcomm_firmware-dl]
recipe = slapos.recipe.build:download
url = https://lab.nexedi.com/nexedi/ors-utils/raw/master/lopcomm-firmware/${:filename}
filename = PR.PRM61C70V1004.006.tar.gz
md5sum = 5139019aae77c2f3178a99915f1c57dc
filename = PR.PRM61C70V1005.004.tar.gz
md5sum = f16413604a8c7631fc6e3782fa9a2695
......@@ -3,3 +3,7 @@
{%- macro buildout_ru(ru_ref, cell) %}
{#- nothing SDR-specific #}
{%- endmacro %}
{%- macro buildout() %}
{#- nothing SDR-specific #}
{%- endmacro %}
......@@ -3,3 +3,7 @@
{%- macro buildout_ru(ru_ref, cell) %}
{#- nothing SunWave-specific #}
{%- endmacro %}
{%- macro buildout() %}
{#- nothing SunWave-specific #}
{%- endmacro %}
......@@ -26,7 +26,6 @@ parts +=
dnsmasq-core-network.jinja2.cfg
ims.jinja2.cfg
enb.jinja2.cfg
gnb.jinja2.cfg
ue_db.jinja2.cfg
ue-lte.jinja2.cfg
ue-nr.jinja2.cfg
......@@ -58,12 +57,6 @@ output = ${buildout:directory}/template.cfg
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:_update_hash_filename_}
[amarisoft-stats.jinja2.py]
<= download-base
[amarisoft-rf-info.jinja2.py]
<= download-base
[template-enb]
<= download-base
......@@ -124,9 +117,6 @@ filename = enb.jinja2.cfg
[sib23.jinja2.asn]
<= copy-config-to-instance
filename = sib23.jinja2.asn
[gnb.jinja2.cfg]
<= copy-config-to-instance
filename = gnb.jinja2.cfg
[ue_db.jinja2.cfg]
<= copy-config-to-instance
filename = ue_db.jinja2.cfg
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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