Commit 35473106 authored by Guillaume Hervier's avatar Guillaume Hervier

software/slaprunner: auto-restart services on SR upgrade.

/reviewed-on !409
parents a398f5cb e0add1cd
# slaprunner test
This software release is simply to run the test suite from `../slaprunner/test/setup.py`
Nexedi staff can see the results of this test from the test suite
`SLAPOS-SLAPRUNNER-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
# 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 = 6fa1420739f58e722631564b08727060
[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/slaprunner/test
# XXX slapos.cookbook:wrapper does not allow extending env, so we add some default $PATH entries ( not sure they are needed )
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
../../../../stack/slapos.cfg
./buildout.hash.cfg
parts =
slapos-cookbook
eggs
template
[setup-develop-egg]
recipe = zc.recipe.egg:develop
[slapos.test.slaprunner-setup]
<= setup-develop-egg
egg = slapos.test.slaprunner
setup = ${slapos-repository:location}/software/slaprunner/test/
[erp5.util-setup]
<= setup-develop-egg
egg = erp5.util[testnode]
setup = ${erp5.util-repository:location}
[eggs]
recipe = zc.recipe.egg
eggs =
${slapos.test.slaprunner-setup:egg}
${erp5.util-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
# XXX we need an unreleased ( 0.4.51 ) version of erp5.util runTestSuite
# later we can stop fetching it from git and just use egg
[erp5.util-repository]
<= git-clone-repository
repository = https://lab.nexedi.com/nexedi/erp5.git
revision = 69013fa0fb67501089c776ab5e75d7bbf2e0e3bc
[template]
recipe = slapos.recipe.template
url = ${:_profile_base_location_}/${:filename}
output = ${buildout:directory}/template.cfg
mode = 640
[versions]
# clear the version of tested eggs, to make sure we installed the developped ones
slapos.test.slaprunner =
erp5.util =
#erp5.util = 0.4.51
pyasn1 = 0.4.2
slapos.recipe.template = 4.3
pysftp = 0.2.9
...@@ -18,7 +18,7 @@ md5sum = 713db528880282d568278f09458d2aab ...@@ -18,7 +18,7 @@ md5sum = 713db528880282d568278f09458d2aab
[template-runner] [template-runner]
filename = instance-runner.cfg filename = instance-runner.cfg
md5sum = e12255a8c946b3eb8c6373fff481339f md5sum = cd855670076979919c0fd00cc0f5938c
[template-runner-import-script] [template-runner-import-script]
filename = template/runner-import.sh.jinja2 filename = template/runner-import.sh.jinja2
......
...@@ -4,6 +4,8 @@ parts = ...@@ -4,6 +4,8 @@ parts =
nginx-launcher nginx-launcher
certificate-authority certificate-authority
ca-nginx ca-nginx
certificate-authority-service
ca-nginx-service
logrotate-entry-nginx logrotate-entry-nginx
gunicorn-launcher gunicorn-launcher
gunicorn-graceful gunicorn-graceful
...@@ -16,10 +18,13 @@ parts = ...@@ -16,10 +18,13 @@ parts =
runner-sshd-graceful runner-sshd-graceful
runner-sshd-promise runner-sshd-promise
runner-sshkeys-authority runner-sshkeys-authority
runner-sshkeys-authority-service
runner-sshkeys-sshd runner-sshkeys-sshd
runner-sshkeys-sshd-service
runtestsuite runtestsuite
symlinks symlinks
shellinabox shellinabox
shellinabox-service
slapos-cfg slapos-cfg
cron-entry-prepare-software cron-entry-prepare-software
deploy-instance-parameters deploy-instance-parameters
...@@ -202,8 +207,9 @@ default_repository_branch = $${slap-parameter:slapos-reference} ...@@ -202,8 +207,9 @@ default_repository_branch = $${slap-parameter:slapos-reference}
[slaprunner-supervisord-wrapper] [slaprunner-supervisord-wrapper]
recipe = slapos.cookbook:wrapper recipe = slapos.cookbook:wrapper
# XXX hardcoded locations # XXX hardcoded locations
command-line = $${buildout:directory}/bin/slapos node supervisord --cfg $${directory:etc}/slapos.cfg -n command-line = $${directory:bin}/slapos node supervisord --cfg $${directory:etc}/slapos.cfg -n
wrapper-path = $${directory:services}/slaprunner-supervisord wrapper-path = $${directory:services}/slaprunner-supervisord
hash-files = $${buildout:directory}/software_release/buildout.cfg
[test-runner] [test-runner]
...@@ -224,7 +230,7 @@ arguments = --server_url=$${slap-connection:server-url} --key_file=$${slap-conne ...@@ -224,7 +230,7 @@ arguments = --server_url=$${slap-connection:server-url} --key_file=$${slap-conne
command-line = ${buildout:directory}/bin/slaprunnertest $${:arguments} command-line = ${buildout:directory}/bin/slaprunnertest $${:arguments}
wrapper-path = $${directory:bin}/runTestSuite wrapper-path = $${directory:bin}/runTestSuite
environment = PATH=$${shell-environment:path} environment = PATH=$${shell-environment:path}
RUNNER_CONFIG=$${slapos-cfg:rendered} RUNNER_CONFIG=$${slapos-cfg:rendered}
# Deploy openssh-server # Deploy openssh-server
[runner-sshd-port] [runner-sshd-port]
...@@ -287,9 +293,15 @@ keys = $${directory:sshkeys}/runner-keys/ ...@@ -287,9 +293,15 @@ keys = $${directory:sshkeys}/runner-keys/
recipe = slapos.cookbook:sshkeys_authority recipe = slapos.cookbook:sshkeys_authority
request-directory = $${runner-sshkeys-directory:requests} request-directory = $${runner-sshkeys-directory:requests}
keys-directory = $${runner-sshkeys-directory:keys} keys-directory = $${runner-sshkeys-directory:keys}
wrapper = $${directory:services}/runner_sshkeys_authority wrapper = $${directory:bin}/runner_sshkeys_authority
keygen-binary = ${openssh:location}/bin/ssh-keygen keygen-binary = ${openssh:location}/bin/ssh-keygen
[runner-sshkeys-authority-service]
recipe = slapos.cookbook:wrapper
command-line = $${runner-sshkeys-authority:wrapper}
wrapper-path = $${directory:services}/runner-sshkeys-authority
hash-files = $${buildout:directory}/software_release/buildout.cfg
[runner-sshkeys-sshd] [runner-sshkeys-sshd]
<= runner-sshkeys-authority <= runner-sshkeys-authority
recipe = slapos.cookbook:sshkeys_authority.request recipe = slapos.cookbook:sshkeys_authority.request
...@@ -298,7 +310,13 @@ type = rsa ...@@ -298,7 +310,13 @@ type = rsa
executable = $${runner-sshd-server:output} executable = $${runner-sshd-server:output}
public-key = $${runner-sshd-raw-server:rsa-keyfile}.pub public-key = $${runner-sshd-raw-server:rsa-keyfile}.pub
private-key = $${runner-sshd-raw-server:rsa-keyfile} private-key = $${runner-sshd-raw-server:rsa-keyfile}
wrapper = $${directory:services}/runner-sshd wrapper = $${directory:bin}/runner-sshd
[runner-sshkeys-sshd-service]
recipe = slapos.cookbook:wrapper
command-line = $${runner-sshkeys-sshd:wrapper}
wrapper-path = $${directory:services}/runner-sshd
hash-files = $${buildout:directory}/software_release/buildout.cfg
[runner-sshd-add-authorized-key] [runner-sshd-add-authorized-key]
recipe = slapos.cookbook:dropbear.add_authorized_key recipe = slapos.cookbook:dropbear.add_authorized_key
...@@ -411,6 +429,7 @@ access-url = https://[$${httpd-parameters:global_ip}]:$${httpd-parameters:global ...@@ -411,6 +429,7 @@ access-url = https://[$${httpd-parameters:global_ip}]:$${httpd-parameters:global
wait-for-files = wait-for-files =
$${ca-nginx:cert-file} $${ca-nginx:cert-file}
$${ca-nginx:key-file} $${ca-nginx:key-file}
hash-files = $${buildout:directory}/software_release/buildout.cfg
[logrotate-entry-apache-httpd] [logrotate-entry-apache-httpd]
<= logrotate-entry-base <= logrotate-entry-base
...@@ -461,6 +480,7 @@ wrapper-path = $${gunicorn:bin_launcher} ...@@ -461,6 +480,7 @@ wrapper-path = $${gunicorn:bin_launcher}
environment = PATH=$${shell-environment:path} environment = PATH=$${shell-environment:path}
RUNNER_CONFIG=$${slaprunner:slapos.cfg} RUNNER_CONFIG=$${slaprunner:slapos.cfg}
LANG=en_GB.UTF-8 LANG=en_GB.UTF-8
hash-files = $${buildout:directory}/software_release/buildout.cfg
[gunicorn-graceful] [gunicorn-graceful]
recipe = slapos.cookbook:wrapper recipe = slapos.cookbook:wrapper
...@@ -476,7 +496,7 @@ recipe = slapos.cookbook:certificate_authority ...@@ -476,7 +496,7 @@ recipe = slapos.cookbook:certificate_authority
openssl-binary = ${openssl:location}/bin/openssl openssl-binary = ${openssl:location}/bin/openssl
ca-dir = $${directory:ca-dir} ca-dir = $${directory:ca-dir}
requests-directory = $${cadirectory:requests} requests-directory = $${cadirectory:requests}
wrapper = $${directory:services}/certificate_authority wrapper = $${directory:bin}/certificate_authority
ca-private = $${cadirectory:private} ca-private = $${cadirectory:private}
ca-certs = $${cadirectory:certs} ca-certs = $${cadirectory:certs}
ca-newcerts = $${cadirectory:newcerts} ca-newcerts = $${cadirectory:newcerts}
...@@ -496,10 +516,22 @@ recipe = slapos.cookbook:certificate_authority.request ...@@ -496,10 +516,22 @@ recipe = slapos.cookbook:certificate_authority.request
key-file = $${cadirectory:certs}/nginx_frontend.key key-file = $${cadirectory:certs}/nginx_frontend.key
cert-file = $${cadirectory:certs}/nginx_frontend.crt cert-file = $${cadirectory:certs}/nginx_frontend.crt
executable = $${nginx-launcher:rendered} executable = $${nginx-launcher:rendered}
wrapper = $${directory:services}/nginx-frontend wrapper = $${directory:bin}/nginx-frontend
# Put domain name # Put domain name
name = example.com name = example.com
[ca-nginx-service]
recipe = slapos.cookbook:wrapper
command-line = $${directory:bin}/nginx-frontend
wrapper-path = $${directory:services}/nginx-frontend
hash-files = $${buildout:directory}/software_release/buildout.cfg
[certificate-authority-service]
recipe = slapos.cookbook:wrapper
command-line = $${directory:bin}/certificate_authority
wrapper-path = $${directory:services}/certificate_authority
hash-files = $${buildout:directory}/software_release/buildout.cfg
#-------------------- #--------------------
#-- #--
#-- Request frontend #-- Request frontend
...@@ -527,14 +559,14 @@ check-secure = 1 ...@@ -527,14 +559,14 @@ check-secure = 1
[request-httpd-frontend] [request-httpd-frontend]
<= slap-connection <= slap-connection
recipe = slapos.cookbook:requestoptional recipe = slapos.cookbook:requestoptional
# XXX - Unfortunately, we still call webrunner httpd frontend "Monitor Frontend" otherwise # XXX - Unfortunately, we still call webrunner httpd frontend "Monitor Frontend" otherwise
# buildout will ignore previous frontend that was created and create a new one (in case of upgrade) # buildout will ignore previous frontend that was created and create a new one (in case of upgrade)
name = Monitor Frontend name = Monitor Frontend
# XXX We have hardcoded SR URL here. # XXX We have hardcoded SR URL here.
software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
slave = true slave = true
config-url = $${apache-httpd:access-url} config-url = $${apache-httpd:access-url}
config-domain = config-domain =
return = secure_access domain return = secure_access domain
[httpd-frontend-promise] [httpd-frontend-promise]
...@@ -638,8 +670,8 @@ slaprunner-httpd-port = $${:monitor-port} ...@@ -638,8 +670,8 @@ slaprunner-httpd-port = $${:monitor-port}
# XXX - for backward compatibility, monitor-port was for slaprunner httpd server # XXX - for backward compatibility, monitor-port was for slaprunner httpd server
monitor-port = 9686 monitor-port = 9686
instance-name = instance-name =
monitor-cors-domains = monitor-cors-domains =
monitor-interface-url = monitor-interface-url =
# XXX - define a new port for monitor here and use monitor-port for backward compatibility # XXX - define a new port for monitor here and use monitor-port for backward compatibility
monitor-httpd-port = 8386 monitor-httpd-port = 8386
...@@ -667,7 +699,7 @@ recipe = slapos.recipe.template:jinja2 ...@@ -667,7 +699,7 @@ recipe = slapos.recipe.template:jinja2
# We cannot use slapos.cookbook:wrapper here because this recipe escapes too much # We cannot use slapos.cookbook:wrapper here because this recipe escapes too much
socket = $${directory:run}/siab.sock socket = $${directory:run}/siab.sock
mode = 0700 mode = 0700
rendered = $${directory:services}/shellinaboxd rendered = $${directory:bin}/shellinaboxd
template = inline: template = inline:
#!/bin/sh #!/bin/sh
exec ${shellinabox:location}/bin/shellinaboxd \ exec ${shellinabox:location}/bin/shellinaboxd \
...@@ -676,6 +708,12 @@ template = inline: ...@@ -676,6 +708,12 @@ template = inline:
--unixdomain-only=$${:socket}:$(id -u):$(id -g):0600 \ --unixdomain-only=$${:socket}:$(id -u):$(id -g):0600 \
--service "/:$(id -u):$(id -g):HOME:$${shell-environment:shell} -l" --service "/:$(id -u):$(id -g):HOME:$${shell-environment:shell} -l"
[shellinabox-service]
recipe = slapos.cookbook:wrapper
command-line = $${directory:bin}/shellinaboxd
wrapper-path = $${directory:services}/shellinaboxd
hash-files = $${buildout:directory}/software_release/buildout.cfg
[shell-environment] [shell-environment]
shell = ${bash:location}/bin/bash shell = ${bash:location}/bin/bash
path = ${nano:location}/bin:${vim:location}/bin:${screen:location}/bin:${git:location}/bin:${curl:location}/bin:${python2.7:location}/bin:${tig:location}/bin:${zip:location}/bin:${mosh:location}/bin:${bash:location}/bin:$${buildout:directory}/bin/:/usr/bin:/bin/ path = ${nano:location}/bin:${vim:location}/bin:${screen:location}/bin:${git:location}/bin:${curl:location}/bin:${python2.7:location}/bin:${tig:location}/bin:${zip:location}/bin:${mosh:location}/bin:${bash:location}/bin:$${buildout:directory}/bin/:/usr/bin:/bin/
...@@ -743,7 +781,7 @@ context = ...@@ -743,7 +781,7 @@ context =
raw shell $${shell-environment:shell} raw shell $${shell-environment:shell}
key instance_name slap-parameter:instance-name key instance_name slap-parameter:instance-name
key workdir runnerdirectory:home key workdir runnerdirectory:home
#--------------------------- #---------------------------
#-- #--
#-- supervisord managing slaprunner automation features #-- supervisord managing slaprunner automation features
...@@ -808,6 +846,7 @@ context = ...@@ -808,6 +846,7 @@ context =
recipe = slapos.cookbook:wrapper recipe = slapos.cookbook:wrapper
command-line = $${buildout:directory}/bin/supervisord -c $${supervisord-conf:rendered} --nodaemon command-line = $${buildout:directory}/bin/supervisord -c $${supervisord-conf:rendered} --nodaemon
wrapper-path = $${directory:services}/supervisord wrapper-path = $${directory:services}/supervisord
hash-files = $${buildout:directory}/software_release/buildout.cfg
[logrotate-entry-supervisord] [logrotate-entry-supervisord]
<= logrotate-entry-base <= logrotate-entry-base
...@@ -826,7 +865,7 @@ path = $${directory:promises}/supervisord ...@@ -826,7 +865,7 @@ path = $${directory:promises}/supervisord
hostname = $${slaprunner:ipv4} hostname = $${slaprunner:ipv4}
port = $${supervisord:port} port = $${supervisord:port}
# XXX Monitor # XXX Monitor
[monitor-instance-parameter] [monitor-instance-parameter]
monitor-httpd-port = $${slap-parameter:monitor-httpd-port} monitor-httpd-port = $${slap-parameter:monitor-httpd-port}
{% if slapparameter_dict.get('name', '') -%} {% if slapparameter_dict.get('name', '') -%}
......
Tests for slaprunner Software Release
##############################################################################
#
# 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.slaprunner'
long_description = open("README.md").read()
setup(name=name,
version=version,
description="Test for SlapOS' slaprunner",
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',
],
zip_safe=True,
test_suite='test',
)
##############################################################################
#
# 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 shutil
import urlparse
import tempfile
import requests
import socket
import StringIO
import subprocess
import json
import psutil
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 InstanceTestCase(utils.SlapOSInstanceTestCase):
@classmethod
def getSoftwareURLList(cls):
return (os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'software.cfg')), )
class ServicesTestCase(InstanceTestCase):
@staticmethod
def generateHashFromFiles(file_list):
import hashlib
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',
]
expected_process_names = [
'slaprunner-supervisord-{hash}-on-watch',
'runner-sshkeys-authority-{hash}-on-watch',
'runner-sshd-{hash}-on-watch',
'slaprunner-httpd-{hash}-on-watch',
'gunicorn-{hash}-on-watch',
'nginx-frontend-{hash}-on-watch',
'certificate_authority-{hash}-on-watch',
'shellinaboxd-{hash}-on-watch',
'supervisord-{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 computer partition instance, implementing
`slapos.slap.interface.slap.IComputerPartition`.
* `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 Exception:
cls.stopSlapOSProcesses()
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