Commit 7537a43c authored by Rafael Monnerat's avatar Rafael Monnerat

Update Release Candidate

parents af575a66 28b8b07b
......@@ -18,12 +18,12 @@ shared = true
url = http://fontconfig.org/release/fontconfig-2.12.6.tar.bz2
md5sum = 733f5e2371ca77b69707bd7b30cc2163
pkg_config_depends = ${freetype:pkg_config_depends}:${freetype:location}/lib/pkgconfig:${libxml2:location}/lib/pkgconfig
# XXX-Cedric : should we use --with-add-fonts={somefont:location}/share,{someotherfont:location}/share?
configure-options =
--disable-static
--disable-docs
--enable-libxml2
--with-default-fonts=${fonts:location}
--with-add-fonts=no
environment =
PATH=${pkgconfig:location}/bin:${gperf:location}/bin:%(PATH)s
PKG_CONFIG_PATH=${:pkg_config_depends}
......
......@@ -9,6 +9,7 @@ parts =
ipa-fonts
ocrb-fonts
android-fonts
dejavu-fonts
[fonts]
location = ${buildout:parts-directory}/${:_buildout_section_name_}
......@@ -50,6 +51,13 @@ md5sum = 2d41d5342eb5f61591ddeec5b80da74d
environment =
PATH=${xz-utils:location}/bin:%(PATH)s
# The DejaVu fonts are a font family based upon Bitstream Vera v1.10. Its purpose is to
# provide a wider range of characters while maintaining the original look-and-feel
[dejavu-fonts]
<= fonts-base
url = https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.tar.bz2
md5sum = d0efec10b9f110a32e9b8f796e21782c
# Microsoft's TrueType core fonts
# non-free so not enabled by default
[msttcore-fonts]
......
......@@ -9,12 +9,13 @@ extends =
../gtk-2/buildout.cfg
../pkgconfig/buildout.cfg
../zlib/buildout.cfg
../libexpat/buildout.cfg
[graphviz]
recipe = slapos.recipe.cmmi
shared = true
url = http://www.graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.38.0.tar.gz
md5sum = 5b6a829b2ac94efcd5fa3c223ed6d3ae
url = https://ftp.osuosl.org/pub/blfs/conglomeration/graphviz/graphviz-2.40.1.tar.gz
md5sum = 4ea6fd64603536406166600bcc296fc8
pkg_config_depends = ${pango:location}/lib/pkgconfig:${pango:pkg_config_depends}
configure-options =
--with-included-ltdl
......@@ -22,6 +23,7 @@ configure-options =
--with-zlibdir=${zlib:location}/lib
--with-freetype2
--with-fontconfig
--with-expat
--disable-swig
--disable-sharp
--disable-go
......@@ -37,7 +39,6 @@ configure-options =
--disable-ruby
--disable-tcl
--without-x
--without-expat
--without-devil
--without-webp
--without-poppler
......@@ -59,5 +60,5 @@ configure-options =
environment =
PATH=${pkgconfig:location}/bin:%(PATH)s
PKG_CONFIG_PATH=${:pkg_config_depends}
CPPFLAGS=-I${zlib:location}/include
LDFLAGS=-L${bzip2:location}/lib -Wl,-rpath=${bzip2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib
CPPFLAGS=-I${zlib:location}/include -I${libexpat:location}/include
LDFLAGS=-L${bzip2:location}/lib -Wl,-rpath=${bzip2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${libexpat:location}/lib -Wl,-rpath=${libexpat:location}/lib
[buildout]
parts =
msgpack-numpy
[msgpack-numpy]
recipe = zc.recipe.egg:custom
egg = msgpack-numpy
......@@ -4,4 +4,4 @@ parts =
[msgpack-python]
recipe = zc.recipe.egg:custom
egg = msgpack-python
egg = msgpack
......@@ -43,7 +43,7 @@ make-options =
make-targets =
install_sw && x=${:location}/etc/ssl/certs && rm -f $x/* &&
for i in ${ca-certificates:location}/certs/*/*.crt; do
ln -sv $i $x/`${:location}/bin/openssl x509 -hash -noout -in $i`.0
ln -sfv $i $x/`${:location}/bin/openssl x509 -hash -noout -in $i`.0
; done
environment =
PERL=${perl:location}/bin/perl
......
......@@ -54,6 +54,9 @@ install-inc =
# - extra arguments passed to perl Makefile.PL
extra-configure-args =
# - extra environ varialbles for configure and make
extra-env =
# Outputs:
# - site_perl, to use in inc in dependent packages
site_perl=${:location}/lib/site_perl/${perl:version}:${:location}:${:inc}
......@@ -64,12 +67,14 @@ perl-PATH = ${:location}/bin/
# dependencies
perl-bin = ${:perl-PATH}/perl
perl-PERL5LIB =
# Implementation
recipe = slapos.recipe.cmmi
url = https://www.cpan.org/modules/by-module/${:module}-${:version}.tar.gz
configure-command =
PERL5LIB="${:inc}:${:install-inc}" \
${:extra-env}
PERL5LIB="${:inc}:${:install-inc}:${:perl-PERL5LIB}" \
${perl:location}/bin/perl \
Makefile.PL \
PREFIX=${:location} \
......@@ -80,7 +85,7 @@ make-options =
INSTALLSITESCRIPT=${:location}/perl-bin/
make-binary=
PERL5LIB="${:inc}:${:install-inc}" make
${:extra-env} PERL5LIB="${:inc}:${:install-inc}:${:perl-PERL5LIB}" make
# this post-make-hook is same for all users of the macro.
post-make-hook = ${:_profile_base_location_}/../../component/perl/perl-CPAN-package-create-wrapper.py#d012f7ba3300b14b2c6b173351d410be:post_make_hook
......
......@@ -12,6 +12,10 @@ recipe = zc.recipe.egg:custom
egg = cryptography
environment = python-cryptography-env
setup-eggs = ${python-cffi:egg}
library-dirs =
${openssl:location}/lib/
rpath =
${openssl:location}/lib/
[python-cryptography-env]
PATH = ${pkgconfig:location}/bin:%(PATH)s
......
......@@ -29,7 +29,7 @@ unset CONFIG_SITE
# Bootstrap SlapOS, using forked version of buildout.
#
wget https://bootstrap.pypa.io/bootstrap-buildout.py
python -S bootstrap-buildout.py --buildout-version 2.5.2+slapos009 \
python -S bootstrap-buildout.py --buildout-version 2.5.2+slapos013 \
-f http://www.nexedi.org/static/packages/source/slapos.buildout/
#
......
[buildout]
# You need to define rootdir and destdir on an upper level
#rootdir = %TARGET_DIRECTORY%
#destdir = %BUILD_ROOT_DIRECTORY%
#builddir = %BUILD_DIRECTORY%
extends =
buildout.cfg
# Don't load extensions
extensions =
slapos.rebootstrap
extends-cache = extends-cache
download-cache = download-cache
# Uguu, upstream buildout.cfg must be patched as it works the other way
# around from a packager point of view at least, thus at the end static
# path, such as Python HOME directory, are wrong...
#
# Currently:
# ./configure --prefix=BUILD_DIRECTORY && make install
# Instead of:
# ./configure --prefix=INSTALL_DIRECTORY && make install DESTDIR=BUILD_DIRECTORY
[python2.7]
configure-options +=
--prefix=${buildout:rootdir}/parts/${:_buildout_section_name_}
environment +=
DESTDIR=${buildout:destdir}
[gettext]
# Add gettext library path to RPATH as its binaries are used to build
# glib for example
environment =
PATH=${perl:location}/bin:${lunzip:location}/bin:%(PATH)s
CPPFLAGS=-I${libxml2:location}/include -I${zlib:location}/include -I${ncurses:location}/include
LDFLAGS=-L${libxml2:location}/lib -Wl,-rpath=${libxml2:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib -L${ncurses:location}/lib -Wl,-rpath=${ncurses:location}/lib -Wl,-rpath=${buildout:builddir}/parts/${:_buildout_section_name_}/lib
[bison]
configure-options +=
--prefix=${buildout:rootdir}/parts/${:_buildout_section_name_}
make-options +=
DESTDIR=${buildout:destdir}
environment +=
PERL5LIB=${perl:location}/lib/5.26.1/
[intltool]
environment +=
PERL5LIB=${perl:location}/lib/5.26.1/
[autoconf]
environment +=
PERL5LIB=${perl:location}/lib/5.26.1/
[automake]
environment +=
PERL5LIB=${perl:location}/lib/5.26.1/
[firewalld]
environment +=
PERL5LIB=${perl:location}/lib/5.26.1/
[dbus]
location = ${buildout:parts-directory}/${:_buildout_section_name_}
configure-options +=
--prefix=${buildout:rootdir}/parts/${:_buildout_section_name_}
make-options +=
DESTDIR=${buildout:destdir}
environment +=
LDFLAGS=-L${libexpat:location}/lib -L${buildout:parts-directory}/${:_buildout_section_name_}/lib -Wl,-rpath=${buildout:parts-directory}/${:_buildout_section_name_}/lib
LD_LIBRARY_PATH=${buildout:parts-directory}/${:_buildout_section_name_}/lib
post-install =
mkdir -p ${buildout:destdir}/parts/${:_buildout_section_name_}/var/run/dbus
[dbus-glib]
location = ${buildout:parts-directory}/${:_buildout_section_name_}
environment +=
CPPFLAGS=-I${libexpat:location}/include -I${dbus:location}/include/dbus-1.0 -I${dbus:location}/lib/dbus-1.0/include
LDFLAGS=-L${libexpat:location}/lib -L${gettext:location}/lib -Wl,-rpath=${zlib:location}/lib -L${dbus:location}/lib -Wl,-rpath=${dbus:location}/lib
LD_LIBRARY_PATH=${dbus:location}/lib
DBUS_CFLAGS=-I${dbus:location}/include/dbus-1.0 -I${dbus:location}/lib/dbus-1.0/include
[dbus-python]
pre-configure =
sed -i 's#/opt/slapos/parts/dbus/lib/libdbus-1.la#${dbus:location}/lib/libdbus-1.la#' ${dbus-glib:location}/lib/libdbus-glib-1.la
environment +=
LD_LIBRARY_PATH=${dbus:location}/lib
LDFLAGS=-L${glib:location}/lib -Wl,-rpath=${glib:location}/lib -L${dbus:location}/lib -Wl,-rpath=${dbus:location}/lib
post-install =
sed -i 's#${dbus:location}/lib/libdbus-1.la#/opt/slapos/parts/dbus/lib/libdbus-1.la#' ${dbus-glib:location}/lib/libdbus-glib-1.la
[openssl]
prefix = ${buildout:rootdir}/parts/${:_buildout_section_name_}
location = ${buildout:parts-directory}/${:_buildout_section_name_}
make-options +=
INSTALL_PREFIX=${buildout:destdir}
environment =
PERL5LIB=${perl:location}/lib/5.26.1/
PERL=${perl:location}/bin/perl
[bison-go]
<= bison
configure-options =
--prefix=${buildout:parts-directory}/${:_buildout_section_name_}
make-options =
[gobject-introspection]
pre-configure =
ln -s ${python2.7:location}/bin/python2.7 ${python2.7:location}/bin/python2.
sed -i 's#!/opt/slapos/parts/python2.7/bin/python2.7#!${python2.7:location}/bin/python2.7#' ${python2.7:location}/bin/python-config
libtoolize -c -f
aclocal -I${pkgconfig:location}/share/aclocal -I${gettext:location}/share/aclocal -I${libtool:location}/share/aclocal -I${glib:location}/share/aclocal
./autogen.sh
configure-options +=
--enable-shared
environment +=
PATH=${autoconf:location}/bin:${automake:location}/bin:${pkgconfig:location}/bin:${libtool:location}/bin:${intltool:location}/bin:${gettext:location}/bin:${glib:location}/bin:${flex:location}/bin:${bison-go:location}/bin:%(PATH)s
GIR_DIR=${buildout:parts-directory}/${:_buildout_section_name_}/share/gir-1.0
CPPFLAGS=-I${glib:location}/include/glib-2.0 -I${glib:location}/lib/glib-2.0/include -I${python2.7:location}/include/python2.7
LDFLAGS=-L${glib:location}/lib -Wl,-rpath=${glib:location}/lib -L${libffi:location}/lib -Wl,-rpath=${libffi:location}/lib -lffi -L${python2.7:location}/lib
ACLOCAL_PATH=${pkgconfig:location}/share/aclocal:${gettext:location}/share/aclocal:${libtool:location}/share/aclocal:${glib:location}/share/aclocal:${intltool:location}/share/aclocal
M4=${m4:location}/bin/m4
PERL5LIB=${perl:location}/lib/5.26.1/
post-install =
sed -i 's#!${python2.7:location}/bin/python2.7#!/opt/slapos/parts/python2.7/bin/python2.7#' ${python2.7:location}/bin/python-config
rm -rf ${bison-go:location}
[pygobject3]
pre-configure +=
sed -i 's#!/opt/slapos/parts/python2.7/bin/python2.7#!${python2.7:location}/bin/python2.7#' ${python2.7:location}/bin/python-config
environment +=
CPPFLAGS=-I${glib:location}/include/glib-2.0 -I${glib:location}/lib/glib-2.0/include -I${gettext:location}/include -I${libffi:location}/include -I${python2.7:location}/include/python2.7
LDFLAGS=-L${glib:location}/lib -Wl,-rpath=${glib:location}/lib -L${gettext:location}/lib -Wl,-rpath=${gettext:location}/lib -L${python2.7:location}/lib
post-install =
sed -i 's#!${python2.7:location}/bin/python2.7#!/opt/slapos/parts/python2.7/bin/python2.7#' ${python2.7:location}/bin/python-config
[ncurses]
configure-options =
--prefix=${buildout:parts-directory}/${:_buildout_section_name_}
--with-shared
--without-ada
--without-manpages
--without-tests
--without-normal
--without-debug
--without-gpm
--enable-rpath
[flex]
environment +=
BISON_PKGDATADIR=${bison:location}/share/bison/
[perl-CPAN-package]
perl-PERL5LIB=${perl:location}/lib/5.26.1/
pre-configure =
sed -i "s#'/opt/slapos/parts/perl#'${perl:location}#" ${perl:location}/lib/5.26.1/*-linux-thread-multi/Config.pm
sed -i "s#'/opt/slapos/parts/site_perl#'${buildout:destdir}/parts/site_perl#" ${perl:location}/lib/5.26.1/*-linux-thread-multi/Config.pm
post-install =
sed -i "s#'${perl:location}#'/opt/slapos/parts/perl#" ${perl:location}/lib/5.26.1/*-linux-thread-multi/Config.pm
sed -i "s#'${buildout:destdir}/parts/site_perl#'/opt/slapos/parts/site_perl#" ${perl:location}/lib/5.26.1/*-linux-thread-multi/Config.pm
[perl]
location = ${buildout:destdir}/parts/perl
configure-command =
sh Configure -des \
-Dprefix=${buildout:rootdir}/parts/${:_buildout_section_name_} \
-Dsiteprefix=${buildout:rootdir}/parts/site_${:_buildout_section_name_} \
-Dcflags=-I${gdbm:location}/include \
-Dldflags="-L${gdbm:location}/lib -Wl,-rpath=${gdbm:location}/lib" \
-Ui_db \
-Dnoextensions=ODBM_File \
-Dusethreads
environment +=
DESTDIR=${buildout:destdir}
[versions]
# More recently version of cliff uses pbr which will break package build on OBS.
cliff = 1.4.5
......@@ -23,6 +23,13 @@ strip-top-level-dir = true
url = http://www-us.apache.org/dist/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz
md5sum = 8bfbb358b51f90374067879f8db1e91c
[tomcat9]
recipe = hexagonit.recipe.download
ignore-existing = true
strip-top-level-dir = true
url = https://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.12/bin/apache-tomcat-9.0.12.tar.gz
md5sum = 7283da4a3a6e939adcd8f919be4ba41a
[tomcat7-output]
# Shared binary location to ease migration
recipe = plone.recipe.command
......
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#"
}
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [
{
"$ref": "#/definitions/positiveInteger"
},
{
"default": 0
}
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": {
"$ref": "#/definitions/positiveInteger"
},
"minLength": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#"
}
],
"default": {}
},
"items": {
"anyOf": [
{
"$ref": "#"
},
{
"$ref": "#/definitions/schemaArray"
}
],
"default": {}
},
"maxItems": {
"$ref": "#/definitions/positiveInteger"
},
"minItems": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": {
"$ref": "#/definitions/positiveInteger"
},
"minProperties": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"additionalProperties": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#"
}
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": {
"$ref": "#"
},
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{
"$ref": "#"
},
{
"$ref": "#/definitions/stringArray"
}
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{
"$ref": "#/definitions/simpleTypes"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/simpleTypes"
},
"minItems": 1,
"uniqueItems": true
}
]
},
"allOf": {
"$ref": "#/definitions/schemaArray"
},
"anyOf": {
"$ref": "#/definitions/schemaArray"
},
"oneOf": {
"$ref": "#/definitions/schemaArray"
},
"not": {
"$ref": "#"
}
},
"dependencies": {
"exclusiveMaximum": [
"maximum"
],
"exclusiveMinimum": [
"minimum"
]
},
"default": {}
}
......@@ -34,23 +34,36 @@ import slapos.test
import jsonschema
def getSchemaValidator(filename):
schema_json_file = "/".join(slapos.test.__file__.split("/")[:-1])
schema_json_file += "/%s" % filename
with open(schema_json_file, "r") as json_file:
json_dict = json.loads(json_file.read())
json_file.close()
return json_dict
def createValidatorTest(path, json_dict):
# Test that json is valid
def createInstanceParameterSchemaValidatorTest(path):
# Test that json is a valid json schema, supports several
# validator, depending on the `$schema` defined in the json.
validator_dict = {
"http://json-schema.org/draft-03/schema#": jsonschema.Draft3Validator,
"http://json-schema.org/draft-04/schema": jsonschema.Draft4Validator,
"http://json-schema.org/draft-04/schema#": jsonschema.Draft4Validator,
"http://json-schema.org/draft-06/schema#": jsonschema.Draft6Validator,
"http://json-schema.org/draft-07/schema#": jsonschema.Draft7Validator,
}
def run(self, *args, **kwargs):
with open(path, "r") as json_file:
self.assertEqual(jsonschema.validate(json.load(json_file), json_dict), None)
json_dict = json.load(json_file)
validator = validator_dict.get(
json_dict.get('$schema'),
jsonschema.Draft7Validator)
validator.check_schema(json_dict)
return run
def createSoftwareCfgValidatorTest(path, software_cfg_schema):
# Test that software json follows the schema for softwares json,
# which is defined in schema.json in this directory
def run(self, *args, **kwargs):
with open(path, "r") as json_file:
jsonschema.validate(json.load(json_file), software_cfg_schema)
return run
def createFormatTest(path, json_dict):
def createFormatTest(path):
# Test that json match our formatting rules
def run(self, *args, **kwargs):
with open(path, "r") as json_file:
......@@ -69,23 +82,26 @@ def createFormatTest(path, json_dict):
def generateSoftwareCfgTest():
json_dict = getSchemaValidator("schema.json")
software_cfg_schema = json.load(
open(os.path.join(
os.path.dirname(slapos.test.__file__),
"schema.json"), 'r'))
base_path = "/".join(slapos.test.__file__.split("/")[:-3])
for path in glob.glob("%s/software/*/software.cfg.json" % base_path):
test_name = "test_%s_software_cfg_json" % path.split("/")[-2]
setattr(TestJSONSchemaValidation, test_name, createValidatorTest(path, json_dict))
setattr(TestJSONSchemaValidation, test_name + '_format', createFormatTest(path, json_dict))
setattr(TestJSONSchemaValidation, test_name, createSoftwareCfgValidatorTest(path, software_cfg_schema))
setattr(TestJSONSchemaValidation, test_name + '_format', createFormatTest(path))
def generateJSONSchemaTest():
json_dict = getSchemaValidator("metaschema.json")
base_path = "/".join(slapos.test.__file__.split("/")[:-3])
for path in glob.glob("%s/software/*/*schema.json" % base_path):
software_type = path.split("/")[-2]
filename = path.split("/")[-1].replace("-", "_").replace(".", "_")
test_name = "test_schema_%s_%s" % (software_type, filename)
setattr(TestJSONSchemaValidation, test_name, createValidatorTest(path, json_dict))
setattr(TestJSONSchemaValidation, test_name + '_format', createFormatTest(path, json_dict))
setattr(TestJSONSchemaValidation, test_name, createInstanceParameterSchemaValidatorTest(path))
setattr(TestJSONSchemaValidation, test_name + '_format', createFormatTest(path))
class TestJSONSchemaValidation(unittest.TestCase):
pass
......
......@@ -2,6 +2,9 @@ Generally things to be done with ``caddy-frontend``:
* return warning on not implemented keys (from ``apache-frontend`` perspective) in master and slave request
* tests: add assertion with results of promises in etc/promise for each partition
* tests: compare python objects where possible instead of instead of strings (eg. load ``rejected-slave-dict``)
* tests: swich to `cryptography <https://pypi.org/project/cryptography/>`_ for certificate management
* README: cleanup the documentation, explain various specifics
* check the whole frontend slave snippet with ``caddy -validate`` during buildout run, and reject if does not pass validation
* ``apache-ca-certificate`` shall be merged with ``apache-certificate``
......
......@@ -26,11 +26,11 @@ md5sum = ab1795f92e32655d05c662c965d2b1f5
[template-apache-replicate]
filename = instance-apache-replicate.cfg.in
md5sum = 44d50bf8391b5a73b2ab72923efe6437
md5sum = 6a86edb96b171fbd0a59d0adc9cc906b
[template-slave-list]
filename = templates/apache-custom-slave-list.cfg.in
md5sum = 69992ad1dffe1c23237f3ef97193c95c
md5sum = b30bcd11c545d86c30d05db9cecb4f80
[template-slave-configuration]
filename = templates/custom-virtualhost.conf.in
......@@ -46,7 +46,7 @@ md5sum = 7c987ad75fcce6f5b925c7696ff41971
[template-custom-slave-list]
filename = templates/apache-custom-slave-list.cfg.in
md5sum = 69992ad1dffe1c23237f3ef97193c95c
md5sum = b30bcd11c545d86c30d05db9cecb4f80
[caddy-backend-url-validator]
filename = templates/caddy-backend-url-validator.in
......@@ -98,7 +98,7 @@ md5sum = 176cbca2070734a185a7ae5a4d1181c5
[template-nginx-notebook-slave-virtualhost]
filename = templates/nginx-notebook-slave.conf.in
md5sum = e018935e2cec2368991f743cab725741
md5sum = 2b765db72191197122554df17ad471d1
[template-apache-lazy-script-call]
filename = templates/apache-lazy-script-call.sh.in
......
......@@ -122,6 +122,9 @@ context =
{% do slave_error_list.append('slave https-url %r invalid' % (slave['https-url'],)) %}
{% endif %}
{% endif %}
{% if slave.get('ssl_ca_crt') and not (slave.get('ssl_crt') and slave.get('ssl_key')) %}
{% do slave_error_list.append('ssl_ca_crt is present, so ssl_crt and ssl_key are required') %}
{% endif %}
{% if slave.get('ssl_key') and slave.get('ssl_crt') %}
{% set key_popen = popen([openssl, 'rsa', '-noout', '-modulus']) %}
{% set crt_popen = popen([openssl, 'x509', '-noout', '-modulus']) %}
......
......@@ -139,7 +139,7 @@ bytes = 8
{# Set Slave Certificates if needed #}
{# Set ssl certificates for each slave #}
{% for cert_name in ('ssl_ca_crt', 'ssl_csr', 'ssl_proxy_ca_crt')%}
{% for cert_name in ('ssl_csr', 'ssl_proxy_ca_crt')%}
{% if cert_name in slave_instance %}
{% set cert_title = '%s-%s' % (slave_reference, cert_name.replace('ssl_', '')) %}
{% set cert_file = '/'.join([custom_ssl_directory, cert_title.replace('-','.')]) %}
......
......@@ -13,9 +13,6 @@ https://{{ slave_parameter.get('custom_domain') }}:{{ slave_parameter['nginx_htt
errors {{ slave_parameter.get('error_log') }}
tls {{ slave_parameter.get('path_to_ssl_crt', slave_parameter.get('login_certificate')) }} {{ slave_parameter.get('path_to_ssl_key', slave_parameter.get('login_key')) }} {
{%- if slave_parameter.get('path_to_ssl_ca_crt') %}
clients {{ slave_parameter.get('path_to_ssl_ca_crt') }}
{%- endif %}
alpn http/1.1
}
......
-----BEGIN CERTIFICATE-----
MIIDETCCAfkCCQDaYBkI56KXrjANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQGEwJY
WDEOMAwGA1UECAwFU3RhdGUxGTAXBgNVBAoMEFdpbGRjYXJkIFJvb3QgQ0EwHhcN
MTgxMTIxMTAwMzM4WhcNMjgxMTE4MTAwMzM4WjBdMQswCQYDVQQGEwJBVTETMBEG
A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg
THRkMRYwFAYDVQQDDA0qLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA1A+TzPOPC3qPLq3KfmFpoa6Iubh1OhNFfH7fvCJQ1czTcFLe
npG4uTjXwcoHbPb6CWq5hfUSY/ucI6mW93gsoW5vsbPFmUFe96fA6uJV17j2IppM
vNHhcNxNbKxpGnStNO5HTW7q1Qk2yvcx4cL/bPCEaNFwBK1O+NeNz0ZDW8dGifYU
aVj+qpVlvTi//j8P4FOwSVYvXdMqGH5WaaTquJPVEGg+704tzUxDbCUXrbCVzFgM
d69sQg0sJQ9MddrsWkQSgcE7cffdC1JdGHCJ/B87iO3pjH2VjFth8EFMcQCn8V8e
Nz0OpkcsZXuFg3L/3EtMV3ZXSlT6GDxaEcNuvQIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQB4mIEwylSudRONRRMgHDkhMlb8/O2MERYrBmsqatg3eU6/LYZAk/okUI6p
aBkQ3GnUmA+gQnkhk4hffRk7NqtMq3r5MEcWunu61i45sXnsQh9myHEAeGfDw3wz
2rkdXAY2jNeQhTBEsErgwKuN86BTFML9cNg2gTKLBbNC1rSJjoMqcKHxrAcsUBip
bXDQMmNIQkzsc3ml6+17/qfu8+mTZ7J5kEkSbbwRD690LiR6Ltua22GAvuddt53S
ieyOVVxCDlItquuGfuQ3ay8zlyQjYmoPI5AXE5Wv9W4mF7agc+SYe3myL3xlRbC+
RD/fQtvjbf/bt9lkgs9DQoITaYvc
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1A+TzPOPC3qPLq3KfmFpoa6Iubh1OhNFfH7fvCJQ1czTcFLe
npG4uTjXwcoHbPb6CWq5hfUSY/ucI6mW93gsoW5vsbPFmUFe96fA6uJV17j2IppM
vNHhcNxNbKxpGnStNO5HTW7q1Qk2yvcx4cL/bPCEaNFwBK1O+NeNz0ZDW8dGifYU
aVj+qpVlvTi//j8P4FOwSVYvXdMqGH5WaaTquJPVEGg+704tzUxDbCUXrbCVzFgM
d69sQg0sJQ9MddrsWkQSgcE7cffdC1JdGHCJ/B87iO3pjH2VjFth8EFMcQCn8V8e
Nz0OpkcsZXuFg3L/3EtMV3ZXSlT6GDxaEcNuvQIDAQABAoIBAQCWdkcEUHvaRSd6
k0ztxuhQE6pnO/3RKwNOhibxMdfxGtebBvF1yScsJKzRjysdoU9fhx4DchOOZWQv
2ZCIHfhswhL2HvvA9aUQSzKSde06lr3tZ1WzU6eFkIpO5TXd05Nhzv9AbcapSVRb
RnFaIiVhgnYweQnmB6HU5fx0aQI6BytP34t3rEZqdy+eYqtq1ZgYC7iXQJct08Sy
0syR5boW2fKZZin78I+uOWfhD3uUDz7SnetwIEWuaJ/oYXv2YFqm+68XRSo4yi2G
FlF3CgwecJCaHyEhxMQojlgM61EvEZ0v1FvoMyyQiNmWVSAtbd6BAD5YrdUzk1QO
mzr3LuTxAoGBAPbnNp/0g25IYj857Q++hSjWsyjLLMfX6+hPsOv2ICL46+xU9P4s
EeIe8PGgRvUkXiNZ7LRtipsFOB+qNYHknIRtLICyYXfumJH4+05XawHIPWdZNw1X
762VsiLEHj1nx3tbEpiCApxYJTXat5/3skjibsNjkuV5JAfsiDHPHeOrAoGBANvf
u1GI2mtUDZ1EJbxJUm3pCUg1aw8jL83OC9miTT36m3V/TiZvc6NEbWM5S8xprwJ1
FG+MHchTgG3rZR6UhfK7OPb7jD0r1aVnA30NX3NS1zKfMEp2Ry83w9LJX70JbZsg
ipo09UXSyE1EaeGGIEQ0xqCN/nRiy2KDh4h1gY83AoGAUH50ypU2vB+RGDfUV4uv
ce79HdGPWd/FI0nHzkXBmGU61SOlc6/+bI/V0ZCFUap3nmLUzsXfqEZ9U6V0KFLV
zD6jgZmmOSlqSDy6AYJyenRDwIvPbOQ8WYUyPC9gBHjvCgJY/6tzGnGKQBJ8RwTD
9QsNPVobLADghEzS4ho6Dl0CgYBjCBxIlwk5ujv/j4gnjCbSVlnV6il0QfbwDVQN
DCsaNVv7ygEbEqvU56cVP+NCCH/I7Y7sxwFLD0ETQSjkYyUJtQXtSFNb4fhybTmH
A5TwTmma5VRM1YUuYUGUGRtD+5Egg8GpvxyR/GQ3WQ8PgufZkKO+APaQ2Uad8nwD
HFnkdQKBgG0OlIKuVeLTVhPcQvOmiDEBeAVo1zc4zmA/JLk/euxesEVL9A8xuxsF
ao0pLvpk/EWQGElxNLNJxbn7AB4uXlpsAvV3xBM88pQIuj2paix1CqSlgfR2F2jE
t3470UVZV22ECV8vQK/of2byELrMscLExLgKW1lAIqqZ77BntFO9
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDQzCCAiugAwIBAgIJAOShMXfabB7nMA0GCSqGSIb3DQEBCwUAMDgxCzAJBgNV
BAYTAlhYMQ4wDAYDVQQIDAVTdGF0ZTEZMBcGA1UECgwQV2lsZGNhcmQgUm9vdCBD
QTAeFw0xODExMjExMDAxMjVaFw00NjEyMDQxMDAxMjVaMDgxCzAJBgNVBAYTAlhY
MQ4wDAYDVQQIDAVTdGF0ZTEZMBcGA1UECgwQV2lsZGNhcmQgUm9vdCBDQTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKq/9H+MvMt5B0vIC2/uEz25jqxT
Wv36v9/HldZLvEwzOiEd0n53ZZFqVlfOoYyCIoWw1+SPwaAc8Oad8ELfoPUasV65
xWki9F/tesgPZpyTO7vgpQfX5JVWNw28s13BgRkOO95h4t2S2t1K6sckC0M/B0o3
wDs/M+74i6wUTHNNXVRejeNPlj9ZSKyfe8rwvY4aNkvW/TKKbaY1yXpQhbeZfU8j
bk4tv4VOpIIoK7wWnSOcFHMANPqrIhygazI1zdsyySEssQ2TAepUb/zgZgk2IQ61
GT+h7NVIoYZJKcAYlLapsZJV1d3Ec9y57zTpyfbWsQhmKHCasZeZK5gYqXECAwEA
AaNQME4wHQYDVR0OBBYEFLZTKR+QsKR9ivZi4uFssy6sB80XMB8GA1UdIwQYMBaA
FLZTKR+QsKR9ivZi4uFssy6sB80XMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAI8O1u6LnmBkWw3rPOTp+9DD2l0+tU3e51KZfp98Fm3Lo8qF5spAB+Ue
OiBax9uENzjcHm7T7KdJrQNK6Mmat0VsD1WTR0TK1eBr9+hOh9EE1H5mEmSL0LOs
ABcCW8DlDv9axWMkFEaJjLfYRUQdvUkb3BwlXo2oq8ectk5ZqS1IF873htYStkvc
SvrzFpaMhYUIr2e7bvFEJ8XTz9l4eymdOBg3j89gf9OkmPa3FE6Qf7etTkVyOr1t
7DIuucv2JkWqHnABIWsLgj4bdLWWULASA7FkI0lHxp5/9/OBb7kVlcgQg6c9Wed7
VA9NaSB3jnBubpEbijnekHqPYO0Bf38=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqr/0f4y8y3kHS8gLb+4TPbmOrFNa/fq/38eV1ku8TDM6IR3S
fndlkWpWV86hjIIihbDX5I/BoBzw5p3wQt+g9RqxXrnFaSL0X+16yA9mnJM7u+Cl
B9fklVY3DbyzXcGBGQ473mHi3ZLa3UrqxyQLQz8HSjfAOz8z7viLrBRMc01dVF6N
40+WP1lIrJ97yvC9jho2S9b9MoptpjXJelCFt5l9TyNuTi2/hU6kgigrvBadI5wU
cwA0+qsiHKBrMjXN2zLJISyxDZMB6lRv/OBmCTYhDrUZP6Hs1UihhkkpwBiUtqmx
klXV3cRz3LnvNOnJ9taxCGYocJqxl5krmBipcQIDAQABAoIBAHOAnbeaUCujlxfg
Hjx8428hki1nxWmAsUKDFAx99sXk8TFtpvH9eis/r2B+WjFd5lRhJ+lohSX17c9S
jy/tbkfe4pSdPbi8+Gnbju693D+WKRYSBBCmLe4G//6+4uZM+zMjucPYm0ofCQYg
o2hKLYQzoo7F37c0LcE9R94DbSOg2tY6HkKNhXoo0KBY424nIlZ82ZvsS7Q+dduB
txbFvzj0cnrYAJv8QIzcedaFGPN7tkLMZ+PS2rXPG/CkPevKgGwjOx+YyKgQk8GU
O+YvMQ9/zhOz4ak76UOZF+a21DrVqsKj0BK90SEq10MUnd1riQJ2z2jD8SSChgb6
rRc4+AECgYEA1NTSkhfxwfgWIjucnWPrtzNLyVqg5Ss+X/XZfN0LNvMje+AZ4wfz
pLAf8cxnewgNqpR1PeUrjoUwcKrHX7M/MhfEhPKo2LyYDsNtNDcRk3JP047FrDnL
beVc7lIfsCzuuQHgnUFhBE+8qP69VsHWBq1iQF2NUdW8HLjxTSuAQFECgYEAzWIR
U/r5QijUE6fcev4FvPBCCF3+c64UEuXV/W4ZURWzOEAcKh9TAHiTKSQ3MDQK7IQY
YtRbgePnA8Tc0Xj0jSxMtWTX1FanxftosRNgsZD1VKnBViwImuOeZHZYq83qPGT9
FNUvGMAEAHevNdSdI2k9RSzrB2Lhei6wEYHJryECgYEAoAOWgYKBID2enoRFHsw2
N5nYe/2ohEQ79DfKGaezO9AXuJXnwJqE4ygMDGaK0qReagaOE0gOtGuM3Nh5Z4lD
lSzrcq1ipvk8NbVWkHBqxXmnbL6l/fPB79EHSqLx8ioGHZC8yF6US4KLrF9CCU1Y
1dJb0VrE2mcgtFOUEFoJZdECgYAsGxVRjaIdvRreJbxJhWfCDW6A0X6lZQrWjBkK
VayGJzzXpZzmxtdSUJJ50VcwuNxnsm5yOtxz5ndj7dDmAy2xa4QFqGRZK0rYT4dK
D7lCKLkmt1XXpZkreho3xNqB+rSEx8M5yBZXIFU7rHgp/UDJq/4GbwECExAM5x3U
hKTFQQKBgQCPGglKFvkRkvYonwDmLRiCjBPnpK5YeL/AYJxeD3V9b8V5Arc9ce9y
PgFgk93RjGlEfLSN0Xbqc6GPIGwg6f5qyHvQ1BpCwupr3lhdsTxwIKbGpAH4Zvmz
4COcrvkF9gAHaIiH97nLy/9h2EawKqKgJv3R0wfdKvRw4iW/4j4aPw==
-----END RSA PRIVATE KEY-----
......@@ -385,7 +385,8 @@ class SlaveHttpFrontendTestCase(HttpFrontendTestCase):
partition_parameter_kw=partition_parameter_kw,
shared=True
)
cls.runComputerPartition()
# run partition 4 more times for slaves to be setup
cls.runComputerPartition(max_quantity=4)
for slave_reference, partition_parameter_kw in cls\
.getSlaveParameterDictDict().items():
slave_instance = request(
......@@ -625,6 +626,29 @@ http://apachecustomhttpsaccepted.example.com:%%(http_port)s {
'ssl_crt': open('customdomainsslcrtsslkey.example.com.crt').read(),
'ssl_key': open('customdomainsslcrtsslkey.example.com.key').read(),
},
'custom_domain_ssl_crt_ssl_key_ssl_ca_crt': {
'url': cls.backend_url,
'custom_domain': 'customdomainsslcrtsslkeysslcacrt.example.com',
'ssl_crt': open('CA.wildcard.example.com.crt').read(),
'ssl_key': open('CA.wildcard.example.com.key').read(),
'ssl_ca_crt': open('CA.wildcard.example.com.root.crt').read(),
},
'ssl_ca_crt_only': {
'url': cls.backend_url,
'ssl_ca_crt': open('CA.wildcard.example.com.root.crt').read(),
},
'ssl_ca_crt_garbage': {
'url': cls.backend_url,
'ssl_crt': open('CA.wildcard.example.com.crt').read(),
'ssl_key': open('CA.wildcard.example.com.key').read(),
'ssl_ca_crt': 'some garbage',
},
'ssl_ca_crt_does_not_match': {
'url': cls.backend_url,
'ssl_crt': open('wildcard.example.com.crt').read(),
'ssl_key': open('wildcard.example.com.key').read(),
'ssl_ca_crt': open('CA.wildcard.example.com.root.crt').read(),
},
'type-zope': {
'url': cls.backend_url,
'type': 'zope',
......@@ -774,13 +798,15 @@ http://apachecustomhttpsaccepted.example.com:%%(http_port)s {
expected_parameter_dict = {
'monitor-base-url': None,
'domain': 'example.com',
'accepted-slave-amount': '37',
'rejected-slave-amount': '3',
'slave-amount': '40',
'accepted-slave-amount': '40',
'rejected-slave-amount': '4',
'slave-amount': '44',
'rejected-slave-dict':
'{"_apache_custom_http_s-rejected": ["slave not authorized"], '
'"_caddy_custom_http_s": ["slave not authorized"], '
'"_caddy_custom_http_s-rejected": ["slave not authorized"], '
'"_caddy_custom_http_s": ["slave not authorized"]}'
'"_ssl_ca_crt_only": ["ssl_ca_crt is present, so ssl_crt and ssl_key '
'are required"]}'
}
self.assertEqual(
......@@ -1198,13 +1224,96 @@ http://apachecustomhttpsaccepted.example.com:%%(http_port)s {
# Caddy: Need to implement similar thing like check-error-on-apache-log
raise NotImplementedError(self.id())
@skip('Feature postponed')
def test_ssl_ca_crt(self):
raise NotImplementedError(self.id())
parameter_dict = self.slave_connection_parameter_dict_dict[
'custom_domain_ssl_crt_ssl_key_ssl_ca_crt']
self.assertLogAccessUrlWithPop(
parameter_dict, 'custom_domain_ssl_crt_ssl_key_ssl_ca_crt')
self.assertEqual(
{
'domain': 'customdomainsslcrtsslkeysslcacrt.example.com',
'replication_number': '1',
'url': 'http://customdomainsslcrtsslkeysslcacrt.example.com',
'site_url': 'http://customdomainsslcrtsslkeysslcacrt.example.com',
'secure_access':
'https://customdomainsslcrtsslkeysslcacrt.example.com',
'public-ipv4': LOCAL_IPV4,
},
parameter_dict
)
@skip('Feature postponed')
def test_path_to_ssl_ca_crt(self):
raise NotImplementedError(self.id())
result = self.fakeHTTPSResult(
parameter_dict['domain'], parameter_dict['public-ipv4'], 'test-path')
self.assertEqual(
open('CA.wildcard.example.com.crt').read(),
der2pem(result.peercert))
self.assertEqualResultJson(result, 'Path', '/test-path')
def test_ssl_ca_crt_only(self):
parameter_dict = self.slave_connection_parameter_dict_dict[
'ssl_ca_crt_only']
self.assertEqual(
parameter_dict,
{
'request-error-list': '["ssl_ca_crt is present, so ssl_crt and '
'ssl_key are required"]'}
)
def test_ssl_ca_crt_garbage(self):
parameter_dict = self.slave_connection_parameter_dict_dict[
'ssl_ca_crt_garbage']
self.assertLogAccessUrlWithPop(
parameter_dict, 'ssl_ca_crt_garbage')
self.assertEqual(
{
'domain': 'sslcacrtgarbage.example.com',
'replication_number': '1',
'url': 'http://sslcacrtgarbage.example.com',
'site_url': 'http://sslcacrtgarbage.example.com',
'secure_access':
'https://sslcacrtgarbage.example.com',
'public-ipv4': LOCAL_IPV4,
},
parameter_dict
)
result = self.fakeHTTPSResult(
parameter_dict['domain'], parameter_dict['public-ipv4'], 'test-path')
self.assertEqual(
open('CA.wildcard.example.com.crt').read(),
der2pem(result.peercert))
self.assertEqualResultJson(result, 'Path', '/test-path')
def test_ssl_ca_crt_does_not_match(self):
parameter_dict = self.slave_connection_parameter_dict_dict[
'ssl_ca_crt_does_not_match']
self.assertLogAccessUrlWithPop(
parameter_dict, 'ssl_ca_crt_does_not_match')
self.assertEqual(
{
'domain': 'sslcacrtdoesnotmatch.example.com',
'replication_number': '1',
'url': 'http://sslcacrtdoesnotmatch.example.com',
'site_url': 'http://sslcacrtdoesnotmatch.example.com',
'secure_access':
'https://sslcacrtdoesnotmatch.example.com',
'public-ipv4': LOCAL_IPV4,
},
parameter_dict
)
result = self.fakeHTTPSResult(
parameter_dict['domain'], parameter_dict['public-ipv4'], 'test-path')
self.assertEqual(
open('wildcard.example.com.crt').read(),
der2pem(result.peercert))
self.assertEqualResultJson(result, 'Path', '/test-path')
def test_https_only(self):
parameter_dict = self.slave_connection_parameter_dict_dict[
......
......@@ -9,6 +9,8 @@ TestSlave-1/var/log/httpd/_custom_domain_access_log
TestSlave-1/var/log/httpd/_custom_domain_error_log
TestSlave-1/var/log/httpd/_custom_domain_ssl_crt_ssl_key_access_log
TestSlave-1/var/log/httpd/_custom_domain_ssl_crt_ssl_key_error_log
TestSlave-1/var/log/httpd/_custom_domain_ssl_crt_ssl_key_ssl_ca_crt_access_log
TestSlave-1/var/log/httpd/_custom_domain_ssl_crt_ssl_key_ssl_ca_crt_error_log
TestSlave-1/var/log/httpd/_custom_domain_wildcard_access_log
TestSlave-1/var/log/httpd/_custom_domain_wildcard_error_log
TestSlave-1/var/log/httpd/_disabled-cookie-list_access_log
......@@ -51,6 +53,10 @@ TestSlave-1/var/log/httpd/_ssl-proxy-verify-unverified_access_log
TestSlave-1/var/log/httpd/_ssl-proxy-verify-unverified_error_log
TestSlave-1/var/log/httpd/_ssl-proxy-verify_ssl_proxy_ca_crt_access_log
TestSlave-1/var/log/httpd/_ssl-proxy-verify_ssl_proxy_ca_crt_error_log
TestSlave-1/var/log/httpd/_ssl_ca_crt_does_not_match_access_log
TestSlave-1/var/log/httpd/_ssl_ca_crt_does_not_match_error_log
TestSlave-1/var/log/httpd/_ssl_ca_crt_garbage_access_log
TestSlave-1/var/log/httpd/_ssl_ca_crt_garbage_error_log
TestSlave-1/var/log/httpd/_type-notebook_access_log
TestSlave-1/var/log/httpd/_type-notebook_error_log
TestSlave-1/var/log/httpd/_type-redirect_access_log
......
......@@ -6,6 +6,8 @@ TestSlave-1/etc/monitor-promise/check-_custom_domain-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_custom_domain-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_custom_domain_ssl_crt_ssl_key-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_custom_domain_ssl_crt_ssl_key-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_custom_domain_ssl_crt_ssl_key_ssl_ca_crt-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_custom_domain_ssl_crt_ssl_key_ssl_ca_crt-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_custom_domain_wildcard-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_custom_domain_wildcard-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_disabled-cookie-list-error-log-last-day
......@@ -51,6 +53,10 @@ TestSlave-1/etc/monitor-promise/check-_ssl-proxy-verify-unverified-error-log-las
TestSlave-1/etc/monitor-promise/check-_ssl-proxy-verify-unverified-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_ssl-proxy-verify_ssl_proxy_ca_crt-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_ssl-proxy-verify_ssl_proxy_ca_crt-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_ssl_ca_crt_does_not_match-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_ssl_ca_crt_does_not_match-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_ssl_ca_crt_garbage-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_ssl_ca_crt_garbage-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_type-eventsource-error-log-last-day
TestSlave-1/etc/monitor-promise/check-_type-eventsource-error-log-last-hour
TestSlave-1/etc/monitor-promise/check-_type-notebook-error-log-last-day
......
......@@ -260,7 +260,7 @@ class SlapOSInstanceTestCase(unittest.TestCase):
@classmethod
def runComputerPartition(cls):
def runComputerPartition(cls, max_quantity=None):
"""Instanciate the software.
This is the equivalent of doing:
......@@ -272,6 +272,9 @@ class SlapOSInstanceTestCase(unittest.TestCase):
This can be called by tests to simulate re-request with different parameters.
"""
run_cp_kw = {}
if max_quantity is not None:
run_cp_kw['max_quantity'] = max_quantity
logger = logging.getLogger()
logger.level = logging.DEBUG
stream = StringIO.StringIO()
......@@ -286,7 +289,8 @@ class SlapOSInstanceTestCase(unittest.TestCase):
cls.instance_status_dict = cls.slapos_controler.runComputerPartition(
cls.config,
cluster_configuration=instance_parameter_dict,
environment=os.environ)
environment=os.environ,
**run_cp_kw)
stream.seek(0)
stream.flush()
message = ''.join(stream.readlines()[-100:])
......
......@@ -297,7 +297,6 @@
"description": "Instantiate a server. If missing, 'storage-dict' must contain the necessary properties to mount the ZODB. The partition reference is 'zodb'.",
"$ref": "./instance-zeo-schema.json"
}
}
},
{
......@@ -363,27 +362,27 @@
"$ref": "../caucase/instance-caucase-input-schema.json"
},
"type": "object"
}
},
"test-runner": {
"description": "Test runner parameters.",
"properties": {
"enabled": {
"description": "Generate helper scripts to run test suite.",
"default": true,
"type": "boolean"
},
"node-count": {
"description": "Number of tests this instance can execute in parrallel. This must be at least equal to the number of nodes configured on testnode running the test",
"default": 3,
"type": "integer"
},
"extra-database-count": {
"description": "Number of extra databases this instance tests will need.",
"default": 3,
"type": "integer"
}
},
"type": "object"
"test-runner": {
"description": "Test runner parameters.",
"properties": {
"enabled": {
"description": "Generate helper scripts to run test suite.",
"default": true,
"type": "boolean"
},
"node-count": {
"description": "Number of tests this instance can execute in parrallel. This must be at least equal to the number of nodes configured on testnode running the test",
"default": 3,
"type": "integer"
},
"extra-database-count": {
"description": "Number of extra databases this instance tests will need.",
"default": 3,
"type": "integer"
}
},
"type": "object"
}
}
}
......@@ -54,8 +54,7 @@ branch = master
[erp5.util-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/erp5.git
revision = 69013fa0fb67501089c776ab5e75d7bbf2e0e3bc
branch = master
revision = 55d7381c54a2e90aff2c4a88943883c35ea111de
[configuration]
test_software_release = ${:_profile_base_location_}/../../../caddy-frontend/software.cfg
......
# PlantUML test
This software release is simply to run the test suite from `../../plantuml/test/setup.py`
Nexedi staff can see the results of this test from the test suite
`SLAPOS-PLANTUML-TEST` in test result module.
# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
# The only allowed lines here are (regexes):
# - "^#" comments, copied verbatim
# - "^[" section beginings, copied verbatim
# - lines containing an "=" sign which must fit in the following categorie.
# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
# But avoid directories, they are not portable.
# Copied verbatim.
# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
# by the re-generation script.
# Re-generated.
# - other lines are copied verbatim
# Substitution (${...:...}), extension ([buildout] extends = ...) and
# section inheritance (< = ...) are NOT supported (but you should really
# not need these here).
[template]
filename = instance.cfg.in
md5sum = 4ab7207a0440a904b4374add9b744312
[buildout]
parts =
slapos-test-runner
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
offline = true
[slap-configuration]
recipe = slapos.cookbook:slapconfiguration
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}
[download-source]
recipe = slapos.recipe.build:gitclone
git-executable = ${git:location}/bin/git
[slapos]
<= download-source
repository = ${slapos-repository:location}
[create-directory]
recipe = slapos.cookbook:mkdirectory
bin = $${buildout:directory}/bin
working-dir = $${buildout:directory}/tmp/
[slapos-test-runner]
recipe = slapos.cookbook:wrapper
wrapper-path = $${create-directory:bin}/runTestSuite
command-line =
${buildout:bin-directory}/runTestSuite
--python_interpreter=${buildout:bin-directory}/${eggs:interpreter}
--source_code_path_list=$${slapos:location}/software/plantuml/test
# XXX slapos.cookbook:wrapper does not allow extending env, so we add some default $PATH entries
environment =
PATH=${buildout:bin-directory}:/usr/bin/:/bin/
LOCAL_IPV4=$${slap-configuration:ipv4-random}
GLOBAL_IPV6=$${slap-configuration:ipv6-random}
SLAPOS_TEST_WORKING_DIR=$${create-directory:working-dir}
[buildout]
extends =
../../../../component/git/buildout.cfg
../../../../component/pillow/buildout.cfg
../../../../stack/slapos.cfg
./buildout.hash.cfg
parts =
slapos-cookbook
eggs
template
[setup-develop-egg]
recipe = zc.recipe.egg:develop
[slapos.test.plantuml-setup]
<= setup-develop-egg
egg = slapos.test.plantuml
setup = ${slapos-repository:location}/software/plantuml/test/
[eggs]
recipe = zc.recipe.egg
eggs =
${pillow-python:egg}
${slapos.test.plantuml-setup:egg}
slapos.core
entry-points =
runTestSuite=erp5.util.testsuite:runTestSuite
scripts =
runTestSuite
slapos
interpreter=
python_for_test
[git-clone-repository]
recipe = slapos.recipe.build:gitclone
git-executable = ${git:location}/bin/git
forbid-download-cache = true
branch = master
[slapos-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/slapos.git
[template]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/template.cfg
mode = 640
[versions]
erp5.util = 0.4.56
slapos.recipe.template = 4.3
plantuml = 0.1.1
httplib2 = 0.11.3
image = 1.5.25
Pillow = 5.3.0
\ No newline at end of file
......@@ -27,4 +27,4 @@ md5sum = 9f22db89a2679534aa8fd37dbca86782
[template-runTestSuite]
filename = runTestSuite.in
md5sum = bd9ff3543f0dfaf2702624e3ed74d334
md5sum = b44268d46a41042a879f47babb66c922
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Parameters to instantiate JSTestNode",
"additionalProperties": false,
"required": [
"test-suite",
"test-runner"
],
"properties": {
"test-suite": {
"description": "The test suite to run",
......@@ -18,68 +22,86 @@
"default": "(the web server started by this instance)",
"example": "https://softinst1234.host.vifib.net/"
},
"oneOf": [
{
"title": "selenium server",
"description": "Configuration for running tests on selenium server",
"properties": {
"target": {
"description": "Target system",
"const": "selenium-server"
},
"server-url": {
"description": "URL of the selenium server",
"type": "string"
},
"verify-server-certificate": {
"description": "Verify the SSL/TLS Certificats of the selenium server when using HTTPS",
"type": "boolean",
"default": true
},
"server-ca-certificate": {
"description": "PEM encoded bundle of CA Certificates to verify the SSL/TLS Certificate of the selenium server when using HTTPS",
"type": "string",
"default": "root certificates from http://certifi.io/en/latest/"
},
"desired-capabilities": {
"description": "Desired browser capabilities",
"type": "object",
"properties": {
"browserName": {
"description": "Name of the browser being used, for example firefox, chrome",
"type": "string",
"required": true
},
"version": {
"description": "The browser version",
"type": "string"
}
"test-runner": {
"oneOf": [
{
"type": "object",
"title": "Selenium Server",
"description": "Configuration for Selenium server",
"additionalProperties": false,
"required": [
"desired-capabilities",
"server-url",
"target"
],
"properties": {
"target": {
"description": "Target system",
"type": "string",
"const": "selenium-server"
},
"server-url": {
"description": "URL of the selenium server",
"type": "string",
"format": "uri"
},
"verify-server-certificate": {
"description": "Verify the SSL/TLS Certificats of the selenium server when using HTTPS",
"type": "boolean",
"default": true
},
"server-ca-certificate": {
"description": "PEM encoded bundle of CA Certificates to verify the SSL/TLS Certificate of the selenium server when using HTTPS",
"type": "string",
"default": "root certificates from http://certifi.io/en/latest/"
},
"additionalProperties": true
"desired-capabilities": {
"description": "Desired browser capabilities",
"required": [
"browserName"
],
"type": "object",
"properties": {
"browserName": {
"description": "Name of the browser being used, for example firefox, chrome",
"type": "string"
},
"version": {
"description": "The browser version",
"type": "string"
}
}
}
}
}
},
{
"title": "firefox",
"description": "Configuration for running tests on local firefox process",
"properties": {
"target": {
"description": "Target system",
"const": "firefox",
"default": "firefox"
},
{
"type": "object",
"title": "Firefox",
"description": "Configuration for Firefox",
"additionalProperties": false,
"properties": {
"target": {
"description": "Target system",
"const": "firefox",
"type": "string",
"default": "firefox"
}
}
}
},
{
"title": "node",
"description": "Configuration for running tests on local nodejs",
"properties": {
"target": {
"description": "Target system",
"const": "node"
},
{
"type": "object",
"title": "NodeJS",
"description": "Configuration for NodeJS",
"additionalProperties": false,
"properties": {
"target": {
"description": "Target system",
"const": "node",
"type": "string"
}
}
}
}
]
]
}
}
}
......@@ -25,8 +25,14 @@ BASE_URL = 'http://[$${nginx-configuration:ip}]:$${nginx-configuration:port}/'
ETC_DIRECTORY = '$${directory:etc}'
def main():
parsed_parameters = json.load(
open('$${runTestSuite-config-file:rendered}', 'rb'))
test_runner = parsed_parameters.get('test-runner', {})
parser = argparse.ArgumentParser(description='Run a test suite.')
parser.add_argument('--test_suite', help='The test suite name')
parser.add_argument('--test_suite', help='The test suite name',
default=parsed_parameters.get('test-suite', ''),
required=not parsed_parameters.has_key('test-suite'))
parser.add_argument('--test_suite_title', help='The test suite title')
parser.add_argument('--test_node_title', help='The test node title')
parser.add_argument('--project_title', help='The project title')
......@@ -40,9 +46,6 @@ def main():
args = parser.parse_args()
parsed_parameters = json.load(
open('$${runTestSuite-config-file:rendered}', 'rb'))
is_browser_running = False
try:
test_suite_title = args.test_suite_title or args.test_suite
......@@ -62,7 +65,7 @@ def main():
##########################
# Run all tests
##########################
target = parsed_parameters.get('target', 'firefox')
target = test_runner.get('target', 'firefox')
if target == 'node':
# Execute NodeJS tests
result_string = check_output(['${nodejs-output:node}', '${jio-repository.git:location}/test/node.js'],
......@@ -92,24 +95,24 @@ def main():
firefox_binary='${firefox-wrapper:location}',
executable_path='${geckodriver:location}')
else:
assert target == 'selenium-server', "Unsupported target {}".format(parsed_parameters['target'])
assert target == 'selenium-server', "Unsupported target {}".format(test_runner['target'])
# use a remote connection which verifies TLS certificate
# workaround for https://github.com/SeleniumHQ/selenium/issues/6534
executor = RemoteConnection(parsed_parameters['server-url'], keep_alive=True)
executor = RemoteConnection(test_runner['server-url'], keep_alive=True)
cert_reqs = 'CERT_REQUIRED'
ca_certs = certifi.where()
if not parsed_parameters.get('verify-server-certificate', True):
if not test_runner.get('verify-server-certificate', True):
cert_reqs = 'CERT_NONE'
ca_certs = None
if parsed_parameters.get('server-ca-certificate'):
if test_runner.get('server-ca-certificate'):
ca_certs = os.path.join(ETC_DIRECTORY, "cacerts.pem")
with open(ca_certs, 'w') as f:
f.write(parsed_parameters.get('server-ca-certificate'))
f.write(test_runner.get('server-ca-certificate'))
executor._conn = urllib3.PoolManager(cert_reqs=cert_reqs, ca_certs=ca_certs)
browser = webdriver.Remote(
command_executor=executor,
desired_capabilities=parsed_parameters['desired-capabilities'],
desired_capabilities=test_runner['desired-capabilities'],
)
# adjust path for remote test url
......
......@@ -368,11 +368,7 @@
"title": "NBD hostname or IP",
"description": "hostname (or IP) of the NBD server containing the boot image.",
"type": "string",
"format": [
"host-name",
"ip-address",
"ipv6"
],
"format": "internet-address",
"default": "debian.nbd.vifib.net"
},
"nbd-port": {
......
......@@ -64,11 +64,7 @@
"title": "NBD hostname or IP",
"description": "hostname (or IP) of the NBD server containing the boot image.",
"type": "string",
"format": [
"host-name",
"ip-address",
"ipv6"
],
"format": "internet-address",
"default": "debian.nbd.vifib.net"
},
"nbd-port": {
......
......@@ -172,11 +172,7 @@
"title": "NBD hostname",
"description": "hostname (or IP) of the NBD server containing the boot image.",
"type": "string",
"format": [
"host-name",
"ip-address",
"ipv6"
],
"format": "internet-address",
"default": "debian.nbd.vifib.net"
},
"nbd-port": {
......@@ -191,11 +187,7 @@
"title": "Second NBD hostname",
"description": "hostname (or IP) of the second NBD server (containing drivers for example).",
"type": "string",
"format": [
"host-name",
"ip-address",
"ipv6"
]
"format": "internet-address"
},
"nbd2-port": {
"title": "Second NBD port",
......
# PlantUML
http://plantuml.com/
PlantUML is a service rendering UML diagrams defined in a simple and intuitive
language.
Each diagram has a unique URL which is made of an encoded version of the
diagram code.
Diagrams can be rendered as png, svg or ascii art text.
See http://plantuml.com/PlantUML_Language_Reference_Guide.pdf for a full
reference on the diagram language.
# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
# The only allowed lines here are (regexes):
# - "^#" comments, copied verbatim
# - "^[" section beginings, copied verbatim
# - lines containing an "=" sign which must fit in the following categorie.
# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
# Copied verbatim.
# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
# by the re-generation script.
# Re-generated.
# - other lines are copied verbatim
# Substitution (${...:...}), extension ([buildout] extends = ...) and
# section inheritance (< = ...) are NOT supported (but you should really
# not need these here).
[instance]
filename = instance.cfg.in
md5sum = 8915151103355dd59da31979a14e59fd
[tomcat-server-xml]
filename = server.xml.in
md5sum = fdfa7eb249082855039ca98f310324e9
[font.conf]
filename = font.conf.in
md5sum = caa3463c9c3766ac5f2396a517d6f926
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<cachedir>$${:fontcache}</cachedir>
<!-- installed fonts: $${:installed-fonts} -->
<dir>${fonts:location}</dir>
<dir>$${:fonts}</dir>
<include>${fontconfig:location}/etc/fonts/conf.d</include>
</fontconfig>
\ No newline at end of file
[buildout]
parts =
promises
publish-connection-parameter
eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
offline = true
[fontconfig-conf]
recipe = slapos.recipe.template
url = ${font.conf:output}
output = $${directory:etc}/font.conf
fonts = $${directory:fonts}
fontcache = $${directory:fontcache}
installed-fonts =
${liberation-fonts:location}
${ipaex-fonts:location}
${ipa-fonts:location}
${ocrb-fonts:location}
${android-fonts:location}
${dejavu-fonts:location}
[tomcat-server-xml]
recipe = slapos.recipe.template
url = ${tomcat-server-xml:output}
output = $${directory:catalina_conf}/server.xml
ip = $${instance-parameter:ipv6-random}
port = 8899
scheme = https
[tomcat-web-xml]
recipe = plone.recipe.command
command = [ -f $${:location} ] || cp ${tomcat9:location}/conf/web.xml $${:location}
location = $${directory:catalina_conf}/web.xml
[tomcat-keystore]
recipe = plone.recipe.command
command =
${java-re-8-output:keytool} \
-genkeypair \
-alias "tomcat" \
-keyalg RSA \
-keypass "$${:pass}" \
-dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=Country" \
-keystore "$${:file}" \
-storepass "$${:pass}"
file = $${directory:catalina_base}/.keystore
pass = insecure
[tomcat-instance]
recipe = slapos.cookbook:wrapper
wrapper-path = $${directory:services}/$${:_buildout_section_name_}
command-line = ${tomcat9:location}/bin/catalina.sh run
environment =
JRE_HOME=${java-re-8:location}
CATALINA_BASE=$${directory:catalina_base}
GRAPHVIZ_DOT=${graphviz:location}/bin/dot
FONTCONFIG_FILE=$${fontconfig-conf:output}
LD_LIBRARY_PATH=${fontconfig:location}/lib:${freetype:location}/lib
# XXX java is still loading system fonts ... ( even with $JAVA_FONTS or -Djava.awt.fonts )
# related links:
# https://docs.oracle.com/javase/8/docs/technotes/guides/intl/fontconfig.html
# https://bugs.openjdk.java.net/browse/JDK-7175487
hash-files =
$${buildout:directory}/software_release/buildout.cfg
$${tomcat-server-xml:output}
ip = $${tomcat-server-xml:ip}
port = $${tomcat-server-xml:port}
scheme = $${tomcat-server-xml:scheme}
hostname = [$${:ip}]
url = $${:scheme}://$${:hostname}:$${:port}
needs = $${tomcat-web-xml:location}
[promises]
recipe =
instance-promises =
$${tomcat-listen-promise:path}
[check-port-listening-promise]
recipe = slapos.cookbook:check_port_listening
path = $${directory:promises}/$${:_buildout_section_name_}
[tomcat-listen-promise]
<= check-port-listening-promise
hostname= $${tomcat-instance:ip}
port = $${tomcat-instance:port}
[publish-connection-parameter]
recipe = slapos.cookbook:publish
url = $${tomcat-instance:url}
[instance-parameter]
recipe = slapos.cookbook:slapconfiguration
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
etc = $${buildout:directory}/etc
var = $${buildout:directory}/var
srv = $${buildout:directory}/srv
bin = $${buildout:directory}/bin
tmp = $${buildout:directory}/tmp
services = $${:etc}/service
promises = $${:etc}/promise
fonts = $${:srv}/fonts/
fontcache = $${buildout:directory}/.fontcache/
# tomcat directories
catalina_base = $${:var}/tomcat
catalina_logs = $${:catalina_base}/logs
catalina_temp = $${:catalina_base}/temp
catalina_webapps = $${:catalina_base}/webapps
catalina_work = $${:catalina_base}/work
catalina_conf = $${:catalina_base}/conf
\ No newline at end of file
<?xml version='1.0' encoding='utf-8'?>
<Server port="-1" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"
address="$${tomcat-server-xml:ip}"
port="$${tomcat-server-xml:port}"
maxThreads="10"
scheme="$${tomcat-server-xml:scheme}"
secure="true"
clientAuth="false"
SSLEnabled="true"
keystorePass="$${tomcat-keystore:pass}"
keystoreFile="$${tomcat-keystore:file}"
/>
<Engine name="Catalina" defaultHost="localhost">
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs" prefix="localhost_access_log." suffix=".log"
pattern="common"/>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="${plantuml.war:location}/plantuml.war"
privileged="true">
</Context>
</Host>
</Engine>
</Service>
</Server>
\ No newline at end of file
[buildout]
extends =
../../stack/slapos.cfg
../../stack/nodejs.cfg
../../component/fontconfig/buildout.cfg
../../component/freetype/buildout.cfg
../../component/graphviz/buildout.cfg
../../component/java/buildout.cfg
../../component/tomcat/buildout.cfg
../../component/fonts/buildout.cfg
buildout.hash.cfg
parts =
slapos-cookbook
instance
[instance]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/instance.cfg
[tomcat-server-xml]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/${:_buildout_section_name_}
[font.conf]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/${:_buildout_section_name_}
[plantuml.war]
recipe = slapos.recipe.build:download
# XXX the war from sourceforge has no version in URL
url = https://netcologne.dl.sourceforge.net/project/plantuml/plantuml.war#2018-10-21
md5sum = f956cd28b18ec34740bb1757276f9641
[versions]
slapos.recipe.template = 4.3
Tests for PlantUML software release
┌───┐ ┌─────┐
│Bob│ │Alice│
└─┬─┘ └──┬──┘
│ hello │
│──────────────>│
│ │
│ Go Away │
│<──────────────│
┌─┴─┐ ┌──┴──┐
│Bob│ │Alice│
└───┘ └─────┘
##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from setuptools import setup, find_packages
import glob
import os
version = '0.0.1.dev0'
name = 'slapos.test.plantuml'
long_description = open("README.md").read()
setup(name=name,
version=version,
description="Test for SlapOS' PlantUML",
long_description=long_description,
long_description_content_type='text/markdown',
maintainer="Nexedi",
maintainer_email="info@nexedi.com",
url="https://lab.nexedi.com/nexedi/slapos",
packages=find_packages(),
install_requires=[
'slapos.core',
'slapos.libnetworkcache',
'erp5.util',
'supervisor',
'psutil',
'plantuml',
'requests'
],
zip_safe=True,
test_suite='test',
)
##############################################################################
# coding: utf-8
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import os
import textwrap
import hashlib
from io import BytesIO
from PIL import Image
import requests
import plantuml
import utils
# for development: debugging logs and install Ctrl+C handler
if os.environ.get('DEBUG'):
import logging
logging.basicConfig(level=logging.DEBUG)
import unittest
unittest.installHandler()
class PlantUMLTestCase(utils.SlapOSInstanceTestCase):
@classmethod
def getSoftwareURLList(cls):
return (os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), )
class TestSimpleDiagram(PlantUMLTestCase):
def setUp(self):
self.url = self.computer_partition.getConnectionParameterDict()["url"]
self.plantuml = plantuml.PlantUML(
url='{}/png/'.format(self.url),
http_opts={"disable_ssl_certificate_validation": True}
)
def assertImagesSimilar(self, i1, i2, tolerance=5):
"""Assert images difference between images is less than `tolerance` %.
taken from https://rosettacode.org/wiki/Percentage_difference_between_images
"""
pairs = zip(i1.getdata(), i2.getdata())
if len(i1.getbands()) == 1:
# for gray-scale jpegs
dif = sum(abs(p1-p2) for p1,p2 in pairs)
else:
dif = sum(abs(c1-c2) for p1,p2 in pairs for c1,c2 in zip(p1,p2))
ncomponents = i1.size[0] * i1.size[1] * 3
self.assertLessEqual((dif / 255.0 * 100) / ncomponents, tolerance)
def assertImagesSame(self, i1, i2):
"""Assert images are exactly same."""
self.assertImagesSimilar(i1, i2, 0)
def test_sequence_diagram(self):
png = self.plantuml.processes(textwrap.dedent("""\
@startuml
Bob -> Alice : hello
Alice -> Bob : Go Away
@enduml
"""))
# we cannot just compare the hash of the image against a reference that can be found with
# http://www.plantuml.com/plantuml/png/SoWkIImgAStDuNBAJrBGjLDmpCbCJbMmKiX8pSd9vuBmWC8WMIi5ztm5n_B4IYw7rBmKe1u0
# because plantuml include information about the server in the output image metadata ( you can
# use http://exif.regex.info/exif.cgi to see metadata )
# So we process the image to remove metadata.
reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_sequence_diagram.png"))
self.assertImagesSame(Image.open(BytesIO(png)), reference)
def test_class_diagram(self):
"""Class diagram require a working graphviz installation"""
png = self.plantuml.processes(textwrap.dedent("""\
@startuml
class Car
Driver - Car : drives >
Car *- Wheel : have 4 >
Car -- Person : < owns
@enduml
"""))
# rendering is not exactly same on class diagrams, because of fonts and maybe also something in graphviz.
# We just compare that image are similar.
# http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKhEIImkLd1EBEBYSYdAB4ijKj05yHIi5590t685EouGLqjN8JmZDJK7A9wHM9QgO08LrzLL24WjAixF0qhOAEINvnLpSJcavgK0ZGO0
reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_class_diagram.png"))
self.assertImagesSimilar(Image.open(BytesIO(png)), reference)
def test_fonts(self):
"""Test slapos provided fonts are used"""
png = self.plantuml.processes(textwrap.dedent("""\
@startuml
listfonts 私は申し訳ありません:私は日本語を話さない。Je ne parle pas japonais.
@enduml
"""))
# URL on the reference implementation would be
# http://www.plantuml.com/plantuml/png/SoWkIImgAStDuSh9B2v9oyyhALPulhpnSUFwvrCsFswS_c85a6nwtDJrk77VuyRPZviclzyp2wBWsVIbp-QiUR5gtkEcIIzMRdpSEFLnuwh7ZIsF6vgyKXNoKXKA4ejoG6InGbPYGNvUOcQn7fT3QbuAq3O0
# but we don't have same fonts, so we compare against the fonts of a slapos instance.
reference = Image.open(os.path.join(os.path.dirname(__file__), "data", "test_fonts.png"))
self.assertImagesSimilar(Image.open(BytesIO(png)), reference)
def test_editor(self):
"""Test the embedded editor"""
r = requests.get('{}/uml/'.format(self.url), verify=False)
self.assertEqual(r.status_code, requests.codes.ok)
def test_svg(self):
"""Test svg rendering"""
image_key = plantuml.deflate_and_encode(textwrap.dedent("""\
@startuml
Bob -> Alice : hello
Alice -> Bob : Go Away
@enduml
"""))
svg = requests.get('{}/svg/{}'.format(self.url, image_key), verify=False).text
self.assertIn('<?xml version="1.0" encoding="UTF-8"', svg)
def test_ascii_art(self):
"""Test ascii art rendering"""
image_key = plantuml.deflate_and_encode(textwrap.dedent("""\
@startuml
Bob -> Alice : hello
Alice -> Bob : Go Away
@enduml
"""))
aa = requests.get('{}/txt/{}'.format(self.url, image_key), verify=False).content
with open(os.path.join(os.path.dirname(__file__), "data", "test_ascii_art.txt"), 'rb') as reference:
self.assertEqual(aa, reference.read())
class ServicesTestCase(PlantUMLTestCase):
@staticmethod
def generateHashFromFiles(file_list):
hasher = hashlib.md5()
for path in file_list:
with open(path, 'r') as afile:
buf = afile.read()
hasher.update("%s\n" % len(buf))
hasher.update(buf)
hash = hasher.hexdigest()
return hash
def test_hashes(self):
hash_files = [
'software_release/buildout.cfg',
'var/tomcat/conf/server.xml'
]
expected_process_names = [
'tomcat-instance-{hash}-on-watch',
]
supervisor = self.getSupervisorRPCServer().supervisor
process_names = [process['name']
for process in supervisor.getAllProcessInfo()]
hash_files = [os.path.join(self.computer_partition_root_path, path)
for path in hash_files]
for name in expected_process_names:
h = ServicesTestCase.generateHashFromFiles(hash_files)
expected_process_name = name.format(hash=h)
self.assertIn(expected_process_name, process_names)
##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
import os
import socket
from contextlib import closing
import logging
import StringIO
import xmlrpclib
import supervisor.xmlrpc
from erp5.util.testnode.SlapOSControler import SlapOSControler
from erp5.util.testnode.ProcessManager import ProcessManager
# Utility functions
def findFreeTCPPort(ip=''):
"""Find a free TCP port to listen to.
"""
family = socket.AF_INET6 if ':' in ip else socket.AF_INET
with closing(socket.socket(family, socket.SOCK_STREAM)) as s:
s.bind((ip, 0))
return s.getsockname()[1]
# TODO:
# - allow requesting multiple instances ?
class SlapOSInstanceTestCase(unittest.TestCase):
"""Install one slapos instance.
This test case install software(s) and request one instance during `setUpClass`
and destroy the instance during `tearDownClass`.
Software Release URL, Instance Software Type and Instance Parameters can be defined
on the class.
All tests from the test class will run with the same instance.
The following class attributes are available:
* `computer_partition`: the `slapos.core.XXX` computer partition instance.
* `computer_partition_root_path`: the path of the instance root directory,
"""
# Methods to be defined by subclasses.
@classmethod
def getSoftwareURLList(cls):
"""Return URL of software releases to install.
To be defined by subclasses.
"""
raise NotImplementedError()
@classmethod
def getInstanceParameterDict(cls):
"""Return instance parameters
To be defined by subclasses if they need to request instance with specific
parameters.
"""
return {}
@classmethod
def getInstanceSoftwareType(cls):
"""Return software type for instance, default "default"
To be defined by subclasses if they need to request instance with specific
software type.
"""
return "default"
# Utility methods.
def getSupervisorRPCServer(self):
"""Returns a XML-RPC connection to the supervisor used by slapos node
Refer to http://supervisord.org/api.html for details of available methods.
"""
# xmlrpc over unix socket https://stackoverflow.com/a/11746051/7294664
return xmlrpclib.ServerProxy(
'http://slapos-supervisor',
transport=supervisor.xmlrpc.SupervisorTransport(
None,
None,
# XXX hardcoded socket path
serverurl="unix://{working_directory}/inst/supervisord.socket".format(
**self.config)))
# Unittest methods
@classmethod
def setUpClass(cls):
"""Setup the class, build software and request an instance.
If you have to override this method, do not forget to call this method on
parent class.
"""
try:
cls.setUpWorkingDirectory()
cls.setUpConfig()
cls.setUpSlapOSController()
cls.runSoftwareRelease()
# XXX instead of "runSoftwareRelease", it would be better to be closer to slapos usage:
# cls.supplySoftwares()
# cls.installSoftwares()
cls.runComputerPartition()
# XXX instead of "runComputerPartition", it would be better to be closer to slapos usage:
# cls.requestInstances()
# cls.createInstances()
# cls.requestInstances()
except BaseException:
cls.stopSlapOSProcesses()
cls.setUp = lambda self: self.fail('Setup Class failed.')
raise
@classmethod
def tearDownClass(cls):
"""Tear down class, stop the processes and destroy instance.
"""
cls.stopSlapOSProcesses()
# Implementation
@classmethod
def stopSlapOSProcesses(cls):
if hasattr(cls, '_process_manager'):
cls._process_manager.killPreviousRun()
@classmethod
def setUpWorkingDirectory(cls):
"""Initialise the directories"""
cls.working_directory = os.environ.get(
'SLAPOS_TEST_WORKING_DIR',
os.path.join(os.path.dirname(__file__), '.slapos'))
# To prevent error: Cannot open an HTTP server: socket.error reported
# AF_UNIX path too long This `working_directory` should not be too deep.
# Socket path is 108 char max on linux
# https://github.com/torvalds/linux/blob/3848ec5/net/unix/af_unix.c#L234-L238
# Supervisord socket name contains the pid number, which is why we add
# .xxxxxxx in this check.
if len(cls.working_directory + '/inst/supervisord.socket.xxxxxxx') > 108:
raise RuntimeError('working directory ( {} ) is too deep, try setting '
'SLAPOS_TEST_WORKING_DIR'.format(cls.working_directory))
if not os.path.exists(cls.working_directory):
os.mkdir(cls.working_directory)
@classmethod
def setUpConfig(cls):
"""Create slapos configuration"""
cls.config = {
"working_directory": cls.working_directory,
"slapos_directory": cls.working_directory,
"log_directory": cls.working_directory,
"computer_id": 'slapos.test', # XXX
'proxy_database': os.path.join(cls.working_directory, 'proxy.db'),
'partition_reference': cls.__name__,
# "proper" slapos command must be in $PATH
'slapos_binary': 'slapos',
}
# Some tests are expecting that local IP is not set to 127.0.0.1
ipv4_address = os.environ.get('LOCAL_IPV4', '127.0.1.1')
ipv6_address = os.environ['GLOBAL_IPV6']
cls.config['proxy_host'] = cls.config['ipv4_address'] = ipv4_address
cls.config['ipv6_address'] = ipv6_address
cls.config['proxy_port'] = findFreeTCPPort(ipv4_address)
cls.config['master_url'] = 'http://{proxy_host}:{proxy_port}'.format(
**cls.config)
@classmethod
def setUpSlapOSController(cls):
"""Create the a "slapos controller" and supply softwares from `getSoftwareURLList`.
This is equivalent to:
slapos proxy start
for sr in getSoftwareURLList; do
slapos supply $SR $COMP
done
"""
cls._process_manager = ProcessManager()
# XXX this code is copied from testnode code
cls.slapos_controler = SlapOSControler(
cls.working_directory,
cls.config
)
slapproxy_log = os.path.join(cls.config['log_directory'], 'slapproxy.log')
logger = logging.getLogger(__name__)
logger.debug('Configured slapproxy log to %r', slapproxy_log)
cls.software_url_list = cls.getSoftwareURLList()
cls.slapos_controler.initializeSlapOSControler(
slapproxy_log=slapproxy_log,
process_manager=cls._process_manager,
reset_software=False,
software_path_list=cls.software_url_list)
# XXX we should check *earlier* if that pidfile exist and if supervisord
# process still running, because if developer started supervisord (or bugs?)
# then another supervisord will start and starting services a second time
# will fail.
cls._process_manager.supervisord_pid_file = os.path.join(
cls.slapos_controler.instance_root, 'var', 'run', 'supervisord.pid')
@classmethod
def runSoftwareRelease(cls):
"""Run all the software releases that were supplied before.
This is the equivalent of `slapos node software`.
The tests will be marked file if software building fail.
"""
logger = logging.getLogger()
logger.level = logging.DEBUG
stream = StringIO.StringIO()
stream_handler = logging.StreamHandler(stream)
logger.addHandler(stream_handler)
try:
cls.software_status_dict = cls.slapos_controler.runSoftwareRelease(
cls.config, environment=os.environ)
stream.seek(0)
stream.flush()
message = ''.join(stream.readlines()[-100:])
assert cls.software_status_dict['status_code'] == 0, message
finally:
logger.removeHandler(stream_handler)
del stream
@classmethod
def runComputerPartition(cls):
"""Instanciate the software.
This is the equivalent of doing:
slapos request --type=getInstanceSoftwareType --parameters=getInstanceParameterDict
slapos node instance
and return the slapos request instance parameters.
This can be called by tests to simulate re-request with different parameters.
"""
logger = logging.getLogger()
logger.level = logging.DEBUG
stream = StringIO.StringIO()
stream_handler = logging.StreamHandler(stream)
logger.addHandler(stream_handler)
if cls.getInstanceSoftwareType() != 'default':
raise NotImplementedError
instance_parameter_dict = cls.getInstanceParameterDict()
try:
cls.instance_status_dict = cls.slapos_controler.runComputerPartition(
cls.config,
cluster_configuration=instance_parameter_dict,
environment=os.environ)
stream.seek(0)
stream.flush()
message = ''.join(stream.readlines()[-100:])
assert cls.instance_status_dict['status_code'] == 0, message
finally:
logger.removeHandler(stream_handler)
del stream
# FIXME: similar to test node, only one (root) partition is really
# supported for now.
computer_partition_list = []
for i in range(len(cls.software_url_list)):
computer_partition_list.append(
cls.slapos_controler.slap.registerOpenOrder().request(
cls.software_url_list[i],
# This is how testnode's SlapOSControler name created partitions
partition_reference='testing partition {i}'.format(
i=i, **cls.config),
partition_parameter_kw=instance_parameter_dict))
# expose some class attributes so that tests can use them:
# the ComputerPartition instances, to getInstanceParameterDict
cls.computer_partition = computer_partition_list[0]
# the path of the instance on the filesystem, for low level inspection
cls.computer_partition_root_path = os.path.join(
cls.config['working_directory'],
'inst',
cls.computer_partition.getId())
......@@ -15,5 +15,5 @@
[template]
filename = instance.cfg
md5sum = d361db5f94e8c568e2aa44014d0ba91b
md5sum = f27dd22ce5b925dd81f2fcd988b10691
......@@ -26,6 +26,10 @@ srv = $${buildout:directory}/srv
recipe = slapos.recipe.build:gitclone
git-executable = ${git:location}/bin/git
[kedifa]
<= download-source
repository = ${kedifa-repository:location}
[caucase]
<= download-source
repository = ${caucase-repository:location}
......@@ -69,7 +73,7 @@ wrapper-path = $${create-directory:bin}/runTestSuite
command-line =
${buildout:bin-directory}/runTestSuite
--python_interpreter=${buildout:bin-directory}/${eggs:interpreter}
--source_code_path_list=$${caucase:location},$${erp5.util:location},$${slapos.cookbook:location},$${slapos.core:location},$${slapos.recipe.build:location},$${slapos.recipe.cmmi:location},$${slapos.recipe.template:location},$${slapos.toolbox:location},$${slapos.libnetworkcache:location}
--source_code_path_list=$${kedifa:location},$${caucase:location},$${erp5.util:location},$${slapos.cookbook:location},$${slapos.core:location},$${slapos.recipe.build:location},$${slapos.recipe.cmmi:location},$${slapos.recipe.template:location},$${slapos.toolbox:location},$${slapos.libnetworkcache:location}
# Notes about environment:
# * slapos.cookbook:wrapper does not seem to allow "extending" PATH. Tests
......
......@@ -34,6 +34,11 @@ recipe = zc.recipe.egg:develop
egg = caucase
setup = ${caucase-repository:location}
[kedifa-setup]
<= setup-develop-egg
egg = kedifa
setup = ${kedifa-repository:location}
[slapos.libnetworkcache-setup]
<= setup-develop-egg
egg = slapos.libnetworkcache
......@@ -93,6 +98,7 @@ eggs =
${bcrypt:egg}
dnspython
Jinja2
${kedifa-setup:egg}
${caucase-setup:egg}
${erp5.util-setup:egg}
${slapos.cookbook-setup:egg}
......@@ -119,6 +125,10 @@ git-executable = ${git:location}/bin/git
forbid-download-cache = true
branch = master
[kedifa-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/kedifa.git
[caucase-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/caucase.git
......@@ -189,7 +199,7 @@ pycurl = 7.43.0.2
pyflakes = 2.0.0
smmap2 = 2.0.4
zope.testing = 4.6.2
urllib3 = 1.24.1
# Required by:
# caucase
PyJWT = 1.6.4
......@@ -3,12 +3,14 @@ versions = versions
extends =
../../component/wendelin.core/buildout.cfg
../../component/msgpack-python/buildout.cfg
../../component/msgpack-numpy/buildout.cfg
../../component/scipy/buildout.cfg
../../software/erp5/software.cfg
parts +=
wendelin
scipy
msgpack-python
msgpack-numpy
ipython
wendelin.core
jupyter
......@@ -25,6 +27,7 @@ eggs +=
astor
${scipy:egg}
${msgpack-python:egg}
${msgpack-numpy:egg}
${wendelin.core:egg}
${ipython:egg}
......@@ -88,5 +91,6 @@ revision = 262f87ea056ceba6f90dff0ba76b2c70867e0f9a
revision = 318efce0bfe8a0682d316a52051661277035a17d
[versions]
msgpack-python = 0.5.6
msgpack = 0.5.6
msgpack-numpy = 0.4.4.2
wendelin.core = 0.12
......@@ -22,7 +22,7 @@ md5sum = 0c0d98a68230cd0ad36046bb25b35f4a
[mariadb-start-clone-from-backup]
filename = instance-mariadb-start-clone-from-backup.sh.in
md5sum = 1af531c51f575a1d1362f2ca2d61620d
md5sum = e405227118a70fbf949a6414469989db
[template-mariadb]
filename = instance-mariadb.cfg.in
......
......@@ -88,6 +88,7 @@ zcat "$BACKUP" | "$CLIENT" -u root
echo "Configuring server as slave..."
if [ "$MASTER_USE_GTID" -eq 1 ]; then
"$CLIENT" -u root -e "$SQL_SET_GTID"
MASTER_USE_GTID_SQL="current_pos"
else
MASTER_USE_GTID_SQL="NO"
......@@ -104,8 +105,14 @@ fi
MASTER_SSL_VERIFY_SERVER_CERT=1,
MASTER_USE_GTID=$MASTER_USE_GTID_SQL;
"
"$CLIENT" -u root -e "$SQL_CHANGE_MASTER"
test "$MASTER_USE_GTID" -eq 1 && "$CLIENT" -u root -e "$SQL_SET_GTID"
if [ "$MASTER_USE_GTID" -eq 0 ]; then
# No GTID, use binlog name & offset as provided by backup file.
# Example: CHANGE MASTER TO MASTER_LOG_FILE='binlog.003447', MASTER_LOG_POS=360;
# Notes:
# - Must happen after setting MASTER_HOST & MASTER_PORT.
# - Implicitly sets MASTER_USE_GTID=NO if it was set before.
"$CLIENT" -u root -e "$SQL_CHANGE_MASTER"
fi
"$CLIENT" -u root -e "START SLAVE;"
echo "Stopping mariadb..."
......
......@@ -98,7 +98,7 @@ eggs =
slapos.libnetworkcache
[versions]
setuptools = 33.1.1
setuptools = 40.4.3
# Use SlapOS patched zc.buildout
zc.buildout = 2.5.2+slapos013
# Use SlapOS patched zc.recipe.egg (zc.recipe.egg 2.x is for Buildout 2)
......@@ -133,12 +133,12 @@ pyOpenSSL = 18.0.0
pyparsing = 2.2.0
pytz = 2016.10
requests = 2.13.0
six = 1.10.0
six = 1.11.0
slapos.cookbook = 1.0.75
slapos.core = 1.4.11
slapos.core = 1.4.13
slapos.extension.strip = 0.4
slapos.extension.shared = 1.0
slapos.libnetworkcache = 0.15
slapos.libnetworkcache = 0.16
slapos.rebootstrap = 4.1
slapos.recipe.build = 0.39
slapos.recipe.cmmi = 0.8
......@@ -149,7 +149,7 @@ xml-marshaller = 0.9.7
paramiko = 2.1.3
# Required by:
# slapos.core==1.4.8
# slapos.core==1.4.13
Flask = 0.12
# Required by:
......@@ -193,23 +193,31 @@ erp5.util = 0.4.51
feedparser = 5.2.1
# Required by:
# jsonschema==2.6.0
# jsonschema==3.0.0a3
functools32 = 3.2.3.post2
# Required by:
# jsonschema==3.0.0a3
attrs = 18.2.0
# Required by:
# jsonschema==3.0.0a3
pyrsistent = 0.14.5
# Required by:
# cryptography==1.8.1
ipaddress = 1.0.18
# Required by:
# slapos.cookbook==1.0.62
jsonschema = 2.6.0
jsonschema = 3.0.0a3
# Required by:
# slapos.toolbox==0.81
lockfile = 0.12.2
# Required by:
# slapos.core==1.4.8
# slapos.core==1.4.13
# XXX 'slapos node format' raises an exception with netifaces 0.10.5.
netifaces = 0.10.4
......@@ -238,15 +246,15 @@ python-dateutil = 2.7.3
rpdb = 0.1.5
# Required by:
# slapos.core==1.4.8
# slapos.core==1.4.13
supervisor = 3.3.3
# Required by:
# slapos.core==1.4.8
# slapos.core==1.4.13
uritemplate = 3.0.0
# Required by:
# slapos.core==1.4.8
# slapos.core==1.4.13
zope.interface = 4.3.3
[networkcache]
......
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