Commit 100c2963 authored by Jérome Perrin's avatar Jérome Perrin

PlantUML

SlapOS instance for a self hosted version of http://www.plantuml.com/

README for this slapos software:

# 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.

/reviewed-on nexedi/slapos!435
parents 5486d8c6 4d1c91aa
......@@ -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
......@@ -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
......
# 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
# 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())
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