Commit 4825a8c4 authored by Jérome Perrin's avatar Jérome Perrin

slap/standalone: support setting multi-master in slapos.cfg

This allow to use standalone slapos with some requests being forwarded
to another slapos master. The intended use case is to make have frontend
requests forwarded to "real" slapos master when embedding slapos in
theia or slaprunner.
parent c755cba8
Pipeline #9350 canceled with stage
in 0 seconds
...@@ -34,6 +34,7 @@ import time ...@@ -34,6 +34,7 @@ import time
import errno import errno
import socket import socket
import shutil import shutil
import collections
from six.moves import urllib from six.moves import urllib
from six.moves import http_client from six.moves import http_client
...@@ -43,6 +44,13 @@ try: ...@@ -43,6 +44,13 @@ try:
except ImportError: except ImportError:
import subprocess import subprocess
try:
from typing import TYPE_CHECKING, Optional, Iterable, Dict, Union
if TYPE_CHECKING:
import subprocess
except ImportError: # XXX to be removed once we depend on typing
pass
import xml_marshaller import xml_marshaller
import zope.interface import zope.interface
import psutil import psutil
...@@ -151,10 +159,27 @@ class SupervisorConfigWriter(ConfigWriter): ...@@ -151,10 +159,27 @@ class SupervisorConfigWriter(ConfigWriter):
class SlapOSConfigWriter(ConfigWriter): class SlapOSConfigWriter(ConfigWriter):
"""Write slapos configuration at etc/slapos.cfg """Write slapos configuration at etc/slapos.cfg
""" """
def _getPartitionForwardConfiguration(self):
# type: () -> Iterable[str]
for pfc in self._standalone_slapos._partition_forward_configuration:
software_release_list = '\n '.join(pfc.software_release_list)
config = '[multimaster/{pfc.master_url}]\n'.format(pfc=pfc)
if pfc.cert:
config += 'cert = {pfc.cert}\n'.format(pfc=pfc)
if pfc.key:
config += 'key = {pfc.key}\n'.format(pfc=pfc)
config += 'software_release_list =\n {}\n'.format('\n '.join(pfc.software_release_list))
if isinstance(pfc, PartitionForwardAsPartitionConfiguration):
config += "computer = {pfc.computer}\n".format(pfc=pfc)
config += "partition = {pfc.partition}\n".format(pfc=pfc)
yield config
def writeConfig(self, path): def writeConfig(self, path):
standalone_slapos = self._standalone_slapos # type: StandaloneSlapOS # type: (str) -> None
standalone_slapos = self._standalone_slapos
read_only_shared_part_list = '\n '.join( # pylint: disable=unused-variable; used in format() read_only_shared_part_list = '\n '.join( # pylint: disable=unused-variable; used in format()
standalone_slapos._shared_part_list) standalone_slapos._shared_part_list)
partition_forward_configuration = '\n'.join(self._getPartitionForwardConfiguration())
with open(path, 'w') as f: with open(path, 'w') as f:
f.write( f.write(
textwrap.dedent( textwrap.dedent(
...@@ -176,6 +201,8 @@ class SlapOSConfigWriter(ConfigWriter): ...@@ -176,6 +201,8 @@ class SlapOSConfigWriter(ConfigWriter):
host = {standalone_slapos._server_ip} host = {standalone_slapos._server_ip}
port = {standalone_slapos._server_port} port = {standalone_slapos._server_port}
database_uri = {standalone_slapos._proxy_database} database_uri = {standalone_slapos._proxy_database}
{partition_forward_configuration}
""").format(**locals())) """).format(**locals()))
...@@ -195,6 +222,46 @@ class SlapOSCommandWriter(ConfigWriter): ...@@ -195,6 +222,46 @@ class SlapOSCommandWriter(ConfigWriter):
os.chmod(path, 0o755) os.chmod(path, 0o755)
class PartitionForwardConfiguration(object):
"""Specification of request forwarding to another master, requested as user.
"""
def __init__(
self,
master_url,
cert=None,
key=None,
software_release_list=(),
):
# type: (str, Optional[str], Optional[str], Iterable[str]) -> None
self.master_url = master_url
self.cert = cert
self.key = key
self.software_release_list = list(software_release_list)
class PartitionForwardAsPartitionConfiguration(PartitionForwardConfiguration):
"""Specification of request forwarding to another master, requested as partition.
"""
def __init__(
self,
master_url,
computer,
partition,
cert=None,
key=None,
software_release_list=(),
):
# type: (str, str, str, Optional[str], Optional[str], Iterable[str]) -> None
super(PartitionForwardAsPartitionConfiguration, self).__init__(
master_url,
cert,
key,
software_release_list,
)
self.computer = computer
self.partition = partition
@zope.interface.implementer(ISupply, IRequester) @zope.interface.implementer(ISupply, IRequester)
class StandaloneSlapOS(object): class StandaloneSlapOS(object):
"""A SlapOS that can be embedded in other applications, also useful for testing. """A SlapOS that can be embedded in other applications, also useful for testing.
...@@ -215,7 +282,10 @@ class StandaloneSlapOS(object): ...@@ -215,7 +282,10 @@ class StandaloneSlapOS(object):
shared_part_list=(), shared_part_list=(),
software_root=None, software_root=None,
instance_root=None, instance_root=None,
shared_part_root=None): shared_part_root=None,
partition_forward_configuration=(),
):
# type: (str, str, int, str, Iterable[str], Optional[str], Optional[str], Optional[str], Iterable[Union[PartitionForwardConfiguration, PartitionForwardAsPartitionConfiguration]]) -> None
"""Constructor, creates a standalone slapos in `base_directory`. """Constructor, creates a standalone slapos in `base_directory`.
Arguments: Arguments:
...@@ -226,6 +296,7 @@ class StandaloneSlapOS(object): ...@@ -226,6 +296,7 @@ class StandaloneSlapOS(object):
* `software_root` -- directory to install software, default to "soft" in `base_directory` * `software_root` -- directory to install software, default to "soft" in `base_directory`
* `instance_root` -- directory to create instances, default to "inst" in `base_directory` * `instance_root` -- directory to create instances, default to "inst" in `base_directory`
* `shared_part_root` -- directory to hold shared parts software, default to "shared" in `base_directory`. * `shared_part_root` -- directory to hold shared parts software, default to "shared" in `base_directory`.
* `partition_forward_configuration` -- configuration of partition request forwarding to external SlapOS master.
Error cases: Error cases:
* `PathTooDeepError` when `base_directory` is too deep. Because of limitation * `PathTooDeepError` when `base_directory` is too deep. Because of limitation
...@@ -241,6 +312,7 @@ class StandaloneSlapOS(object): ...@@ -241,6 +312,7 @@ class StandaloneSlapOS(object):
self._base_directory = base_directory self._base_directory = base_directory
self._shared_part_list = list(shared_part_list) self._shared_part_list = list(shared_part_list)
self._partition_forward_configuration = list(partition_forward_configuration)
self._slapos_commands = { self._slapos_commands = {
'slapos-node-software': { 'slapos-node-software': {
......
...@@ -37,11 +37,14 @@ import errno ...@@ -37,11 +37,14 @@ import errno
import time import time
import multiprocessing import multiprocessing
from contextlib import closing from contextlib import closing
from six.moves.configparser import ConfigParser
import psutil import psutil
from slapos.slap.standalone import StandaloneSlapOS from slapos.slap.standalone import StandaloneSlapOS
from slapos.slap.standalone import SlapOSNodeCommandError from slapos.slap.standalone import SlapOSNodeCommandError
from slapos.slap.standalone import PartitionForwardConfiguration
from slapos.slap.standalone import PartitionForwardAsPartitionConfiguration
SLAPOS_TEST_IPV4 = os.environ['SLAPOS_TEST_IPV4'] SLAPOS_TEST_IPV4 = os.environ['SLAPOS_TEST_IPV4']
SLAPOS_TEST_IPV6 = os.environ['SLAPOS_TEST_IPV6'] SLAPOS_TEST_IPV6 = os.environ['SLAPOS_TEST_IPV6']
...@@ -165,6 +168,89 @@ class TestSlapOSStandaloneSetup(unittest.TestCase): ...@@ -165,6 +168,89 @@ class TestSlapOSStandaloneSetup(unittest.TestCase):
with self.assertRaises(BaseException): with self.assertRaises(BaseException):
standalone1.stop() standalone1.stop()
def test_partition_forward(self):
# type: () -> None
working_dir = tempfile.mkdtemp(prefix=__name__)
self.addCleanup(shutil.rmtree, working_dir)
partition_forward_config = [
PartitionForwardConfiguration(
'https://slapos1.example.com',
'path/to/cert',
'path/to/key',
software_release_list=('https://example.com/software-1.cfg', ),
),
PartitionForwardConfiguration(
'https://slapos2.example.com',
software_release_list=('https://example.com/software-2.cfg', ),
),
PartitionForwardAsPartitionConfiguration(
'https://slapos3.example.com',
'computer',
'partition',
'path/to/cert',
'path/to/key',
software_release_list=('https://example.com/software-3.cfg', ),
),
PartitionForwardAsPartitionConfiguration(
'https://slapos4.example.com',
'computer',
'partition',
software_release_list=('https://example.com/software-4.cfg', ),
),
]
standalone = StandaloneSlapOS(
working_dir,
SLAPOS_TEST_IPV4,
SLAPOS_TEST_PORT,
partition_forward_configuration=partition_forward_config,
)
self.addCleanup(standalone.stop)
config_parser = ConfigParser()
config_parser.read([os.path.join(working_dir, 'etc', 'slapos.cfg')])
self.assertTrue(
config_parser.has_section('multimaster/https://slapos1.example.com'))
self.assertEqual(
'path/to/cert',
config_parser.get('multimaster/https://slapos1.example.com', 'cert'))
self.assertEqual(
'path/to/key',
config_parser.get('multimaster/https://slapos1.example.com', 'key'))
self.assertEqual(
'https://example.com/software-1.cfg',
config_parser.get(
'multimaster/https://slapos1.example.com',
'software_release_list').strip())
self.assertFalse(
config_parser.has_option(
'multimaster/https://slapos2.example.com', 'computer'))
self.assertFalse(
config_parser.has_option(
'multimaster/https://slapos2.example.com', 'partition'))
self.assertTrue(
config_parser.has_section('multimaster/https://slapos2.example.com'))
self.assertFalse(
config_parser.has_option(
'multimaster/https://slapos2.example.com', 'cert'))
self.assertFalse(
config_parser.has_option(
'multimaster/https://slapos2.example.com', 'key'))
self.assertTrue(
config_parser.has_section('multimaster/https://slapos3.example.com'))
self.assertEqual(
'computer',
config_parser.get(
'multimaster/https://slapos3.example.com', 'computer'))
self.assertEqual(
'partition',
config_parser.get(
'multimaster/https://slapos3.example.com', 'partition'))
self.assertTrue(
config_parser.has_section('multimaster/https://slapos4.example.com'))
class SlapOSStandaloneTestCase(unittest.TestCase): class SlapOSStandaloneTestCase(unittest.TestCase):
# This test case takes care of stopping the standalone instance # This test case takes care of stopping the standalone instance
......
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