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
......@@ -34,6 +34,7 @@ import time
import errno
import socket
import shutil
import collections
from six.moves import urllib
from six.moves import http_client
......@@ -43,6 +44,13 @@ try:
except ImportError:
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 zope.interface
import psutil
......@@ -151,10 +159,27 @@ class SupervisorConfigWriter(ConfigWriter):
class SlapOSConfigWriter(ConfigWriter):
"""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):
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()
standalone_slapos._shared_part_list)
partition_forward_configuration = '\n'.join(self._getPartitionForwardConfiguration())
with open(path, 'w') as f:
f.write(
textwrap.dedent(
......@@ -176,6 +201,8 @@ class SlapOSConfigWriter(ConfigWriter):
host = {standalone_slapos._server_ip}
port = {standalone_slapos._server_port}
database_uri = {standalone_slapos._proxy_database}
{partition_forward_configuration}
""").format(**locals()))
......@@ -195,6 +222,46 @@ class SlapOSCommandWriter(ConfigWriter):
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)
class StandaloneSlapOS(object):
"""A SlapOS that can be embedded in other applications, also useful for testing.
......@@ -215,7 +282,10 @@ class StandaloneSlapOS(object):
shared_part_list=(),
software_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`.
Arguments:
......@@ -226,6 +296,7 @@ class StandaloneSlapOS(object):
* `software_root` -- directory to install software, default to "soft" 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`.
* `partition_forward_configuration` -- configuration of partition request forwarding to external SlapOS master.
Error cases:
* `PathTooDeepError` when `base_directory` is too deep. Because of limitation
......@@ -241,6 +312,7 @@ class StandaloneSlapOS(object):
self._base_directory = base_directory
self._shared_part_list = list(shared_part_list)
self._partition_forward_configuration = list(partition_forward_configuration)
self._slapos_commands = {
'slapos-node-software': {
......
......@@ -37,11 +37,14 @@ import errno
import time
import multiprocessing
from contextlib import closing
from six.moves.configparser import ConfigParser
import psutil
from slapos.slap.standalone import StandaloneSlapOS
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_IPV6 = os.environ['SLAPOS_TEST_IPV6']
......@@ -165,6 +168,89 @@ class TestSlapOSStandaloneSetup(unittest.TestCase):
with self.assertRaises(BaseException):
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):
# 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