Commit 07d17076 authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼

Update Release Candidate

parents cd50b959 e7e65e8a
......@@ -62,9 +62,6 @@ md5sum = 3f76825f195e52d4b10c70040681a275
[openssl]
<= openssl-3.0
[openssl:python2]
<= openssl-1.1
[openssl-output]
# Shared binary location to ease migration
recipe = plone.recipe.command
......
......@@ -24,7 +24,7 @@ patches =
configure-command = make
configure-options = makefiles CCARGS=${:configure-options-CCARGS} AUXLIBS=${:configure-options-AUXLIBS}
configure-options-CCARGS = '-DUSE_SASL_AUTH -DUSE_CYRUS_SASL -DUSE_TLS -DHAS_PCRE -DHAS_DB -I${libdb:location}/include -I${pcre:location}/include -I${openssl:location}/include -I${cyrus-sasl:location}/include/sasl -I${libnsl:location}/include'
configure-options-AUXLIBS = '-L${openssl:location}/lib -L${pcre:location}/lib -L${libdb:location}/lib -L${cyrus-sasl:location}/lib -L${libtirpc:location}/lib -L${libnsl:location}/lib -lssl -lpcre -ldb -lcrypto -lsasl2 -Wl,-rpath=${openssl:location}/lib -Wl,-rpath=${pcre:location}/lib -Wl,-rpath=${libdb:location}/lib -Wl,-rpath=${cyrus-sasl:location}/lib -Wl,-rpath=${libnsl:location}/lib'
configure-options-AUXLIBS = '-L${openssl:location}/lib -L${pcre:location}/lib -L${libdb:location}/lib -L${cyrus-sasl:location}/lib -L${libtirpc:location}/lib -L${libnsl:location}/lib -lnsl -lssl -lpcre -ldb -lcrypto -lsasl2 -Wl,-rpath=${openssl:location}/lib -Wl,-rpath=${pcre:location}/lib -Wl,-rpath=${libdb:location}/lib -Wl,-rpath=${cyrus-sasl:location}/lib -Wl,-rpath=${libnsl:location}/lib'
make-targets = non-interactive-package install_root=${:location}
environment =
PATH=${patch:location}/bin:${m4:location}/bin:%(PATH)s
......@@ -17,6 +17,9 @@ extends =
parts =
python2.7
[openssl]
<= openssl-1.1
[bootstrap2.7]
recipe = zc.recipe.egg
eggs = zc.buildout
......@@ -50,7 +53,10 @@ patches = ${python2.7-lib-patches:patches}
url =
http://www.python.org/ftp/python/${:package_version}/Python-${:package_version}${:package_version_suffix}.tar.xz
pre-configure =
sed -i -e "s/if 'curses' in ln:/if 'curses' in ln.split()[0]:/" setup.py
sed -i \
-e "s/if 'curses' in ln:/if 'curses' in ln.split()[0]:/" \
-e '/"SQLITE_OMIT_LOAD_EXTENSION"/s/^/#/' \
setup.py
configure-options =
--enable-ipv6
--enable-unicode=ucs4
......
......@@ -34,6 +34,7 @@ pre-configure =
sed -i -e "s/if 'curses' in ln:/if 'curses' in ln.split()[0]:/" setup.py
configure-options =
--enable-ipv6
--enable-loadable-sqlite-extensions
--without-ensurepip
--with-system-expat
--with-system-ffi
......
......@@ -16,11 +16,14 @@ md5sum = 79f2507907721b770cbec98195cecece
configure-options =
--disable-static
--disable-tcl
--enable-readline
--enable-update-limit
--with-readline-inc=-I${readline:location}/include
post-install =
gcc -I%(location)s/include -fPIC -shared ext/misc/cksumvfs.c -o %(location)s/lib/cksumvfs.so
# Increase MAX_VARIABLE_NUMBER like many os. For example:
# https://git.archlinux.org/svntogit/packages.git/tree/trunk/PKGBUILD?h=packages/sqlite
# NEO needs SQLITE_ENABLE_UPDATE_DELETE_LIMIT to drop partitions.
environment =
CPPFLAGS=-I${readline:location}/include -I${ncurses:location}/include -I${zlib:location}/include -DSQLITE_MAX_VARIABLE_NUMBER=250000 -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1
LDFLAGS=-L@@LOCATION@@ -Wl,-rpath=${readline:location}/lib -Wl,-rpath=${ncurses:location}/lib -L${readline:location}/lib -L${ncurses:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib
CPPFLAGS=-I${zlib:location}/include -DSQLITE_MAX_VARIABLE_NUMBER=250000
LDFLAGS=-Wl,-rpath=@@LOCATION@@/lib -L${readline:location}/lib -Wl,-rpath=${readline:location}/lib -L${zlib:location}/lib -Wl,-rpath=${zlib:location}/lib
PATH=${tcl:location}/bin:${xz-utils:location}/bin:%(PATH)s
......@@ -24,8 +24,8 @@ min_version = 8
[trafficserver]
recipe = slapos.recipe.cmmi
url = https://dlcdn.apache.org/trafficserver/trafficserver-9.2.3.tar.bz2
md5sum = 5e37ec924667052d655de2ebab98ad3f
url = https://dlcdn.apache.org/trafficserver/trafficserver-9.2.4.tar.bz2
md5sum = 5a889ba1be6f325e4b523df85616f30b
shared = true
patch-options = -p1
configure-options =
......
[yappi]
recipe = zc.recipe.egg:custom
egg = yappi
[versions]
yappi = 1.5.1
......@@ -28,7 +28,7 @@ from setuptools import setup, find_packages
import glob
import os
version = '1.0.351'
version = '1.0.360'
name = 'slapos.cookbook'
long_description = open("README.rst").read()
......
......@@ -27,7 +27,6 @@ keep_log_days = %(keep_log_days)s
# Binaries
git_binary = %(git_binary)s
slapos_binary = %(slapos_binary)s
zip_binary = %(zip_binary)s
[environment]
PATH = %(PATH)s
......
......@@ -89,9 +89,17 @@ class NeoBaseRecipe(GenericBaseRecipe):
)
args += self._getOptionList()
args += shlex.split(options.get('extra-options', ''))
environment = {}
for line in (options.get('environment') or '').splitlines():
line = line.strip()
if line:
k, v = line.split('=', 1)
environment[k.rstrip()] = v.lstrip()
private_tmpfs = self.parsePrivateTmpfs()
kw = {'private_tmpfs': private_tmpfs} if private_tmpfs else {}
return self.createWrapper(options['wrapper'], args, **kw)
return self.createWrapper(options['wrapper'], args, env=environment, **kw)
def _getBindingAddress(self):
options = self.options
......
......@@ -131,6 +131,11 @@
"default": false,
"type": "boolean"
},
"xml-rpc": {
"description": "Serve XML-RPC queries",
"default": false,
"type": "boolean"
},
"activity-timeout": {
"description": "Override global activity timeout",
"type": [
......@@ -585,6 +590,12 @@
"examples": [
"https://user:password@example.com/{test_result_id}/{test_name}.coverage.sqlite3"
]
},
"fail-under": {
"description": "Make the test reporting coverage fail if combined coverage is below this percentage value.",
"type": "number",
"minimum": 0,
"maximum": 100
}
}
},
......
......@@ -190,6 +190,19 @@ class TestDefaultParameters(ERP5InstanceTestCase, TestPublishedURLIsReachableMix
installed['request-frontend-default']['connection-secure_access'],
self.getRootPartitionConnectionParameterDict()['url-frontend-default'])
def test_xml_rpc_disabled(self):
param_dict = self.getRootPartitionConnectionParameterDict()
# don't verify certificate
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
with xmlrpc.client.ServerProxy(
param_dict['family-default-v6'],
context=ssl_context,
) as cli:
with self.assertRaises(xmlrpc.client.ProtocolError):
cli.getId()
class TestExternalCaucase(ERP5InstanceTestCase, TestPublishedURLIsReachableMixin):
"""Test providing the URL of an external caucase in parameters.
......@@ -654,8 +667,22 @@ class TestWatchActivities(ERP5InstanceTestCase):
class ZopeSkinsMixin:
"""Mixins with utility methods to test zope behaviors.
"""Mixins with utility methods to test zope behaviors, needs XML-RPC enabled
for family `default`
"""
@classmethod
def getInstanceParameterDict(cls):
return {
'_':
json.dumps({
"family-override": {
"default": {
"xml-rpc": True,
}
}
})
}
@classmethod
def _setUpClass(cls):
super()._setUpClass()
......@@ -753,6 +780,11 @@ class ZopeTestMixin(ZopeSkinsMixin, CrontabMixin):
"port-base": 2210,
},
},
"family-override": {
"default": {
"xml-rpc": True
}
}
}),
}
......@@ -1138,11 +1170,15 @@ class TestZopePublisherTimeout(ZopeSkinsMixin, ERP5InstanceTestCase):
json.dumps({
# a default timeout of 3
"publisher-timeout": 3,
# and a family without timeout
"family-override": {
"no-timeout": {
# and a family without timeout
"publisher-timeout": None,
},
# enable XML-RPC for ZopeSkinsMixin
"default": {
"xml-rpc": True,
}
},
"zope-partition-dict": {
# a family to process activities, so that our test
......@@ -1207,6 +1243,12 @@ class TestCloudooo(ZopeSkinsMixin, ERP5InstanceTestCase):
'https://cloudooo2.example.com/',
],
'cloudooo-retry-count': 123,
# enable XML-RPC for ZopeSkinsMixin
"family-override": {
"default": {
"xml-rpc": True,
}
}
})
}
......
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"description": "Values returned by Fluentd instantiation",
"additionalProperties": false,
"properties": {},
"type": "object"
}
......@@ -7,6 +7,7 @@
"title": "Default",
"description": "Fluentd",
"request": "instance-input-schema.json",
"response": "instance-output-schema.json",
"index": 0
}
}
......
......@@ -59,7 +59,7 @@ md5sum = 6328f99728284847b8dd1146aadeae1b
[template-kvm-run]
filename = template/template-kvm-run.in
md5sum = 1663af08ea7afa8d3fa091bf0c2ea1ca
md5sum = f0190843e3979742fe9e29b8a607539f
[template-kvm-controller]
filename = template/kvm-controller-run.in
......
......@@ -176,6 +176,8 @@ if len(disk_info_list) == 1 and not os.path.exists(disk_info_list[0]['path']) an
if image_config['error-amount'] == 0:
image = image_config['image-list'][0]
downloaded_image = os.path.join(image_config['destination-directory'], image['destination'])
if not os.path.exists(downloaded_image):
raise ValueError('virtual-hard-drive-url not present yet')
# previous version was using disk in place, but here it would result with
# redownload, so copy it
if virtual_hard_drive_gzipped == 'true':
......
......@@ -30,6 +30,7 @@ import http.server
import json
import os
import glob
import gzip
import hashlib
import psutil
import re
......@@ -116,6 +117,48 @@ bootstrap_machine_param_dict = {
class KVMTestCase(InstanceTestCase):
@classmethod
def findQemuTools(cls):
with open(os.path.join(
cls.slap.software_directory,
hashlib.md5(cls.getSoftwareURL().encode()).hexdigest(),
'.installed.cfg'
)) as fh:
location_cfg = fh.read()
qemu_location = [
q for q in location_cfg.splitlines()
if q.startswith('location') and '/qemu/' in q]
assert (len(qemu_location) == 1)
qemu_location = qemu_location[0].split('=')[1].strip()
cls.qemu_nbd = os.path.join(qemu_location, 'bin', 'qemu-nbd')
assert (os.path.exists(cls.qemu_nbd))
cls.qemu_img = os.path.join(qemu_location, 'bin', 'qemu-img')
assert (os.path.exists(cls.qemu_img))
def getRunningImageList(
self, kvm_instance_partition,
_match_cdrom=re.compile('file=(.+),media=cdrom$').match,
_sub_iso=re.compile(r'(/debian)(-[^-/]+)(-[^/]+-netinst\.iso)$').sub,
):
with self.slap.instance_supervisor_rpc as instance_supervisor:
kvm_pid = next(q for q in instance_supervisor.getAllProcessInfo()
if 'kvm-' in q['name'])['pid']
sub_shared = re.compile(r'^%s/[^/]+/[0-9a-f]{32}/'
% re.escape(self.slap.shared_directory)).sub
image_list = []
for entry in psutil.Process(kvm_pid).cmdline():
m = _match_cdrom(entry)
if m:
path = m.group(1)
image_list.append(
_sub_iso(
r'\1-${ver}\3',
sub_shared(
r'${shared}/',
path.replace(kvm_instance_partition, '${inst}')
)))
return image_list
@classmethod
def _findTopLevelPartitionPath(cls, path):
index = 0
......@@ -878,6 +921,21 @@ class HttpHandler(http.server.SimpleHTTPRequestHandler):
class FakeImageServerMixin(KvmMixin):
@classmethod
def setUpClass(cls):
try:
cls.findQemuTools()
cls.startImageHttpServer()
super().setUpClass()
except BaseException:
cls.stopImageHttpServer()
raise
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.stopImageHttpServer()
@classmethod
def startImageHttpServer(cls):
cls.image_source_directory = tempfile.mkdtemp()
......@@ -908,10 +966,30 @@ class FakeImageServerMixin(KvmMixin):
cls.image_source_directory, cls.fake_image3_md5sum), 'wb') as fh:
fh.write(fake_image3_content)
# real fake image
real_image_input = os.path.join(cls.image_source_directory, 'real.img')
subprocess.check_call([
cls.qemu_img, "create", "-f", "qcow2", real_image_input, "1M"])
with open(real_image_input, 'rb') as fh:
real_image_content = fh.read()
cls.real_image_md5sum = hashlib.md5(real_image_content).hexdigest()
with open(os.path.join(
cls.image_source_directory, cls.real_image_md5sum), 'wb') as fh:
fh.write(real_image_content)
real_gzip_content = gzip.compress(real_image_content)
cls.real_gzip_md5sum = hashlib.md5(real_gzip_content).hexdigest()
with open(os.path.join(
cls.image_source_directory, cls.real_gzip_md5sum), 'wb') as fh:
fh.write(real_gzip_content)
url = 'http://%s:%s' % server.server_address
cls.fake_image = '/'.join([url, cls.fake_image_md5sum])
cls.fake_image2 = '/'.join([url, cls.fake_image2_md5sum])
cls.fake_image3 = '/'.join([url, cls.fake_image3_md5sum])
cls.real_image = '/'.join([url, cls.real_image_md5sum])
cls.real_gzip = '/'.join([url, cls.real_gzip_md5sum])
old_dir = os.path.realpath(os.curdir)
os.chdir(cls.image_source_directory)
......@@ -937,7 +1015,251 @@ class FakeImageServerMixin(KvmMixin):
@skipUnlessKvm
class TestBootImageUrlList(KVMTestCase, FakeImageServerMixin):
class TestInstanceNbd(KVMTestCase):
__partition_reference__ = 'in'
kvm_instance_partition_reference = 'in0'
@classmethod
def startNbdServer(cls):
cls.nbd_directory = tempfile.mkdtemp()
img_1 = os.path.join(cls.nbd_directory, 'one.qcow')
img_2 = os.path.join(cls.nbd_directory, 'two.qcow')
subprocess.check_call([cls.qemu_img, "create", "-f", "qcow", img_1, "1M"])
subprocess.check_call([cls.qemu_img, "create", "-f", "qcow", img_2, "1M"])
nbd_list = [cls.qemu_nbd, '-r', '-t', '-e', '32767']
cls.nbd_1_port = findFreeTCPPort(cls.ipv6_address_pure)
cls.nbd_1 = subprocess.Popen(
nbd_list + [
'-b', cls.ipv6_address_pure, '-p', str(cls.nbd_1_port), img_1])
cls.nbd_1_uri = '[%s]:%s' % (cls.ipv6_address_pure, cls.nbd_1_port)
cls.nbd_2_port = findFreeTCPPort(cls.ipv6_address_pure)
cls.nbd_2 = subprocess.Popen(
nbd_list + [
'-b', cls.ipv6_address_pure, '-p', str(cls.nbd_2_port), img_2])
cls.nbd_2_uri = '[%s]:%s' % (cls.ipv6_address_pure, cls.nbd_2_port)
@classmethod
def stopNbdServer(cls):
cls.nbd_1.terminate()
cls.nbd_2.terminate()
shutil.rmtree(cls.nbd_directory)
@classmethod
def setUpClass(cls):
# we need qemu-nbd binary location
# it's to hard to put qemu in software/slapos-sr-testing
# so let's find it here
# let's find our software .installed.cfg
cls.ipv6_address_pure = cls._ipv6_address.split('/')[0]
cls.findQemuTools()
cls.startNbdServer()
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.stopNbdServer()
@classmethod
def getInstanceParameterDict(cls):
return {
"nbd-host": cls.ipv6_address_pure,
"nbd-port": cls.nbd_1_port
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.assertEqual(
[f'nbd:{self.nbd_1_uri}', '${shared}/debian-${ver}-amd64-netinst.iso'],
self.getRunningImageList(kvm_partition)
)
@skipUnlessKvm
class TestInstanceNbdWithVirtualHardDriveUrl(
FakeImageServerMixin, TestInstanceNbd):
__partition_reference__ = 'inbvhdu'
kvm_instance_partition_reference = 'inbvhdu0'
@classmethod
def getInstanceParameterDict(cls):
return {
"nbd-host": cls.ipv6_address_pure,
"nbd-port": cls.nbd_1_port,
"virtual-hard-drive-url": cls.real_image,
"virtual-hard-drive-md5sum": cls.real_image_md5sum
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.assertEqual(
[f'nbd:{self.nbd_1_uri}', '${shared}/debian-${ver}-amd64-netinst.iso'],
self.getRunningImageList(kvm_partition)
)
image_repository = os.path.join(
kvm_partition,
'srv', 'virtual-hard-drive-url-repository')
self.assertEqual(
[self.getInstanceParameterDict()['virtual-hard-drive-md5sum']],
os.listdir(image_repository)
)
destination_image = os.path.join(kvm_partition, 'srv', 'virtual.qcow2')
# compare result of qemu-img info of repository and the one
qemu_img_list = [self.qemu_img, 'info', '-U', '--output', 'json']
source_image_info_json = json.loads(subprocess.check_output(
qemu_img_list + [
os.path.join(self.image_source_directory, self.real_image_md5sum)]))
destination_image_info_json = json.loads(subprocess.check_output(
qemu_img_list + [destination_image]))
source_image_info_json.pop('filename')
destination_image_info_json.pop('filename')
# the best possible way to assure that provided image is used is by
# comparing the result of qemu-img info for both
self.assertEqual(
source_image_info_json,
destination_image_info_json
)
@skipUnlessKvm
class TestInstanceNbdWithBootImageUrlList(
FakeImageServerMixin, TestInstanceNbd):
__partition_reference__ = 'inbiul'
kvm_instance_partition_reference = 'inbiul0'
image_directory = 'boot-image-url-list-repository'
@classmethod
def getInstanceParameterDict(cls):
return {
"nbd-host": cls.ipv6_address_pure,
"nbd-port": cls.nbd_1_port,
"boot-image-url-list": f"{cls.fake_image}#{cls.fake_image_md5sum}"
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.assertEqual(
[
f'nbd:{self.nbd_1_uri}',
f'${{inst}}/srv/{self.image_directory}/{self.fake_image_md5sum}',
'${shared}/debian-${ver}-amd64-netinst.iso',
],
self.getRunningImageList(kvm_partition)
)
@skipUnlessKvm
class TestInstanceNbdWithBootImageUrlSelect(
FakeImageServerMixin, TestInstanceNbd):
__partition_reference__ = 'inbius'
kvm_instance_partition_reference = 'inbius0'
image_directory = 'boot-image-url-select-repository'
@classmethod
def getInstanceParameterDict(cls):
return {
"nbd-host": cls.ipv6_address_pure,
"nbd-port": cls.nbd_1_port,
"boot-image-url-select": f'["{cls.fake_image}#{cls.fake_image_md5sum}"]'
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.assertEqual(
[
f'nbd:{self.nbd_1_uri}',
f'${{inst}}/srv/{self.image_directory}/{self.fake_image_md5sum}',
'${shared}/debian-${ver}-amd64-netinst.iso',
],
self.getRunningImageList(kvm_partition)
)
@skipUnlessKvm
class TestInstanceNbdBoth(TestInstanceNbd):
__partition_reference__ = 'inb'
kvm_instance_partition_reference = 'inb0'
@classmethod
def getInstanceParameterDict(cls):
return {
"nbd-host": cls.ipv6_address_pure,
"nbd-port": cls.nbd_1_port,
"nbd2-host": cls.ipv6_address_pure,
"nbd2-port": cls.nbd_2_port
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.assertEqual(
[f'nbd:{self.nbd_1_uri}', f'nbd:{self.nbd_2_uri}',
'${shared}/debian-${ver}-amd64-netinst.iso'],
self.getRunningImageList(kvm_partition)
)
@skipUnlessKvm
class TestVirtualHardDriveUrl(FakeImageServerMixin, KVMTestCase):
__partition_reference__ = 'vhdu'
kvm_instance_partition_reference = 'vhdu0'
@classmethod
def getInstanceParameterDict(cls):
return {
"virtual-hard-drive-url": cls.real_image,
"virtual-hard-drive-md5sum": cls.real_image_md5sum
}
def test(self):
kvm_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
image_repository = os.path.join(
kvm_partition,
'srv', 'virtual-hard-drive-url-repository')
self.assertEqual(
[self.getInstanceParameterDict()['virtual-hard-drive-md5sum']],
os.listdir(image_repository)
)
destination_image = os.path.join(kvm_partition, 'srv', 'virtual.qcow2')
# compare result of qemu-img info of repository and the one
qemu_img_list = [self.qemu_img, 'info', '-U', '--output', 'json']
source_image_info_json = json.loads(subprocess.check_output(
qemu_img_list + [
os.path.join(self.image_source_directory, self.real_image_md5sum)]))
destination_image_info_json = json.loads(subprocess.check_output(
qemu_img_list + [destination_image]))
source_image_info_json.pop('filename')
destination_image_info_json.pop('filename')
# the best possible way to assure that provided image is used is by
# comparing the result of qemu-img info for both
self.assertEqual(
source_image_info_json,
destination_image_info_json
)
@skipUnlessKvm
class TestVirtualHardDriveUrlGzipped(TestVirtualHardDriveUrl):
__partition_reference__ = 'vhdug'
kvm_instance_partition_reference = 'vhdug0'
@classmethod
def getInstanceParameterDict(cls):
return {
"virtual-hard-drive-url": cls.real_gzip,
"virtual-hard-drive-md5sum": cls.real_gzip_md5sum,
"virtual-hard-drive-gzipped": True
}
@skipUnlessKvm
class TestBootImageUrlList(FakeImageServerMixin, KVMTestCase):
__partition_reference__ = 'biul'
kvm_instance_partition_reference = 'biul0'
......@@ -976,20 +1298,6 @@ class TestBootImageUrlList(KVMTestCase, FakeImageServerMixin):
cls.fake_image2_md5sum)
}
@classmethod
def setUpClass(cls):
try:
cls.startImageHttpServer()
super().setUpClass()
except BaseException:
cls.stopImageHttpServer()
raise
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.stopImageHttpServer()
def tearDown(self):
# clean up the instance for other tests
# 1st remove all images...
......@@ -1000,30 +1308,6 @@ class TestBootImageUrlList(KVMTestCase, FakeImageServerMixin):
self.slap.waitForInstance(max_retry=10)
super().tearDown()
def getRunningImageList(
self, kvm_instance_partition,
_match_cdrom=re.compile('file=(.+),media=cdrom$').match,
_sub_iso=re.compile(r'(/debian)(-[^-/]+)(-[^/]+-netinst\.iso)$').sub,
):
with self.slap.instance_supervisor_rpc as instance_supervisor:
kvm_pid = next(q for q in instance_supervisor.getAllProcessInfo()
if 'kvm-' in q['name'])['pid']
sub_shared = re.compile(r'^%s/[^/]+/[0-9a-f]{32}/'
% re.escape(self.slap.shared_directory)).sub
image_list = []
for entry in psutil.Process(kvm_pid).cmdline():
m = _match_cdrom(entry)
if m:
path = m.group(1)
image_list.append(
_sub_iso(
r'\1-${ver}\3',
sub_shared(
r'${shared}/',
path.replace(kvm_instance_partition, '${inst}')
)))
return image_list
def test(self):
# check that image is correctly downloaded
kvm_instance_partition = os.path.join(
......@@ -1297,7 +1581,7 @@ class TestBootImageUrlSelectResilientJson(
@skipUnlessKvm
class TestBootImageUrlListKvmCluster(KVMTestCase, FakeImageServerMixin):
class TestBootImageUrlListKvmCluster(FakeImageServerMixin, KVMTestCase):
__partition_reference__ = 'biulkc'
@classmethod
......@@ -1308,14 +1592,6 @@ class TestBootImageUrlListKvmCluster(KVMTestCase, FakeImageServerMixin):
key = 'boot-image-url-list'
config_file_name = 'boot-image-url-list.conf'
def setUp(self):
super().setUp()
self.startImageHttpServer()
def tearDown(self):
self.stopImageHttpServer()
super().tearDown()
@classmethod
def getInstanceParameterDict(cls):
return {'_': json.dumps({
......@@ -1619,7 +1895,7 @@ class TestDiskDevicePathWipeDiskOndestroyJson(
@skipUnlessKvm
class TestImageDownloadController(KVMTestCase, FakeImageServerMixin):
class TestImageDownloadController(FakeImageServerMixin, KVMTestCase):
__partition_reference__ = 'idc'
maxDiff = None
......@@ -1637,14 +1913,12 @@ class TestImageDownloadController(KVMTestCase, FakeImageServerMixin):
self.working_directory, 'error_state_file')
self.processed_md5sum = os.path.join(
self.working_directory, 'processed_md5sum')
self.startImageHttpServer()
self.image_download_controller = os.path.join(
self.slap.instance_directory, self.__partition_reference__ + '0',
'software_release', 'parts', 'image-download-controller',
'image-download-controller.py')
def tearDown(self):
self.stopImageHttpServer()
shutil.rmtree(self.working_directory)
super().tearDown()
......
......@@ -14,7 +14,7 @@
# not need these here).
[instance-common]
filename = instance-common.cfg.in
md5sum = e000e7134113b9d1c63d40861eaf0489
md5sum = ecc98da90cd446ea224ddeece1374190
[root-common]
filename = root-common.cfg.in
......@@ -30,7 +30,7 @@ md5sum = 9f27195d770b2f57461c60a82c851ab9
[instance-neo]
filename = instance-neo.cfg.in
md5sum = fda911d5ef9efee365f1b0ff9843a50b
md5sum = 200ae55715cb735b0f97f8c835a3071f
[template-neo-my-cnf]
filename = my.cnf.in
......
......@@ -41,6 +41,7 @@ url = {{ neo_master }}
<= jinja2-template-base
url = {{ neo }}
extra-context =
import urllib urllib
key master_cfg neo-master:output
key admin_cfg neo-admin:output
{%- if mariadb_location is defined %}
......
......@@ -110,14 +110,6 @@
"default": false,
"type": "boolean"
},
"storage-type": {
"description": "Storage type. Defaults to MySQL if available, else SQLite.",
"enum": [
"MySQL",
"SQLite"
],
"type": "string"
},
"private-tmpfs": {
"description": "Size of private tmpfs mount to store the database. See filesystems/tmpfs.txt in Linux documentation. Use only for testing.",
"type": "string"
......@@ -126,24 +118,56 @@
"description": "List of bindings to test when running the test suite.",
"type": "array"
},
"storage-type": {
"description": "Storage type. Required when several types are configured and you select which one to use via 'node!' parameter. Defaults to whatever is configured ('sqlite' or 'mysql'), else MySQL if available, else SQLite.",
"enum": [
"MySQL",
"SQLite"
],
"type": "string"
},
"sqlite": {
"description": "Storage backend configuration.",
"properties": {
"relaxed-writes": {
"description": "When enabled, sets synchronous = OFF and journal_mode = MEMORY - RTFM, those options are dangerous.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": {
"description": "See NEO documentation for the list of supported settings.",
"type": [
"number",
"string"
]
},
"type": "object"
},
"mysql": {
"description": "Dictionary containing parameters for MySQL.",
"default": {},
"description": "MariaDB server configuration.",
"properties": {
"relaxed-writes": {
"description": "When enabled, sets innodb_flush_log_at_trx_commit = 0, innodb_flush_method = nosync, innodb_doublewrite = 0 and sync_frm = 0 - RTFM, those options are dangerous",
"description": "When enabled, sets innodb_flush_log_at_trx_commit = 0, innodb_flush_method = nosync, innodb_doublewrite = 0 and sync_frm = 0 - RTFM, those options are dangerous.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": {
"description": "To configure important parameters like innodb_buffer_pool_size, rocksdb_block_cache_size, etc.",
"type": "string"
"description": "To configure parameters like innodb_buffer_pool_size, rocksdb_block_cache_size, etc.",
"type": [
"number",
"string"
]
},
"type": "object"
},
"engine": {
"description": "Configures storage engine, currently only InnoDB and RocksDB are supported. Defaults to NEO's default.",
"description": "[MySQL only] For NEO, this is a creation-time parameter and it defaults to NEO's default. For mysqld, this sets plugins to load and it defaults to load all.",
"enum": [
"InnoDB",
"RocksDB"
],
"type": "string"
}
},
......
{% macro section(name) %}{% do part_list.append(name) %}{{ name }}{% endmacro -%}
{% set part_list = [] -%}
{% set init_list = [] -%}
{% set directory_dict = {} -%}
{% set private_tmpfs = slapparameter_dict.get('private-tmpfs') -%}
{% set storage_count = slapparameter_dict.get('storage-count', 1) -%}
{% set storage_type = slapparameter_dict.get('storage-type') or (
'MySQL' if mariadb_location is defined else 'SQLite') -%}
{% set mysql = storage_count and storage_type != 'SQLite' -%}
{# When mixing different storage types via node specialisation
('node!' parameter), it can be convenient to configure all types in the
common 'node' parameter and then switch between the 2 with 'storage-type'.
So we must be quite tolerant. -#}
{% if storage_count -%}
{% if 'mysql' in slapparameter_dict -%}
{% if 'sqlite' in slapparameter_dict -%}
{% set storage_type = slapparameter_dict['storage-type'] -%}
{% else -%}
{% set storage_type = 'MySQL' -%}
{% endif -%}
{% elif 'sqlite' in slapparameter_dict -%}
{% set storage_type = 'SQLite' -%}
{% else -%}
{% set storage_type = slapparameter_dict.get('storage-type') or (
'MySQL' if mariadb_location is defined else 'SQLite') -%}
{% endif -%}
{% do assert(slapparameter_dict.get('storage-type', storage_type) == storage_type) -%}
{% else -%}
{% set storage_type = '' -%}
{% endif -%}
{% set mysql = storage_type == 'MySQL' -%}
{% if mysql -%}
{% set extra_dict = slapparameter_dict.get('mysql') or {} -%}
[{{ section('mysqld') }}]
{% if private_tmpfs -%}
......@@ -37,7 +59,7 @@ tmp-directory = ${directory:tmp}
pid-file = ${directory:var_run}/mariadb.pid
error-log = ${directory:log}/mariadb_error.log
slow-query-log = ${directory:log}/mariadb_slowquery.log
extra-dict = {{ dumps(slapparameter_dict.get('mysql', {})) }}
extra-dict = {{ dumps(extra_dict) }}
init-file = ${init-script:output}
engine = {{ slapparameter_dict.get('engine', '') }}
......@@ -54,6 +76,18 @@ command-line = '{{ mariadb_location }}/bin/${:command}' --defaults-file="${my-cn
wrapper-path = ${directory:bin}/${:command}
command = mysql
{% elif storage_type == 'SQLite' -%}
{% set extra_dict = slapparameter_dict.get('sqlite') or {} -%}
{% if extra_dict.pop('relaxed-writes', False) -%}
{% do extra_dict.setdefault('synchronous', 'OFF') -%}
{% do extra_dict.setdefault('journal_mode', 'MEMORY') -%}
{% endif -%}
{% set query_string = urllib.urlencode(extra_dict) -%}
{% else -%}
{% do assert(not storage_count) -%}
{% endif -%}
[{{ section('binary-neolog') }}]
......@@ -123,14 +157,19 @@ logfile = ${directory:log}/{{ 'neostorage-' ~ i }}.log
{%- if mysql %}
{%- do init_list.append('CREATE DATABASE IF NOT EXISTS neo' ~ i ~ ';') %}
database-parameters = root@neo{{ i }}${my-cnf-parameters:socket}
{%- elif private_tmpfs %}
private-tmpfs = {{ private_tmpfs }} ${directory:tmp}
database-parameters = ${directory:tmp}/db.sqlite
{%- else %}
database-parameters = ${directory:db-{{i}}}/db.sqlite
[directory]
db-{{i}} = ${:srv}/{{ storage_id }}
{%- if private_tmpfs %}
private-tmpfs = {{ private_tmpfs }} ${directory:tmp}
{%- set path = '${directory:tmp}/db.sqlite' %}
{%- else %}
{%- set path = '${directory:db-' ~ i ~ '}/db.sqlite' %}
{%- do directory_dict.__setitem__('db-' ~ i, '${:srv}/' + storage_id) %}
{%- endif %}
{%- if query_string %}
database-parameters = file:{{ path }}?{{ query_string }}
{%- else %}
database-parameters = {{ path }}
{%- endif -%}
{%- endif %}
[{{ section('logrotate-storage-' ~ i) }}]
......@@ -141,17 +180,6 @@ post = {{ bin_directory }}/slapos-kill -s RTMIN+1 -- {{ bin_directory }}/neostor
{% endfor -%}
[directory]
recipe = slapos.cookbook:mkdirectory
bin = ${buildout:directory}/bin
etc = ${buildout:directory}/etc
var = ${buildout:directory}/var
etc_run = ${:etc}/run
var_run = ${:var}/run
log = ${buildout:directory}/var/log
tmp = ${buildout:directory}/tmp
srv = ${buildout:directory}/srv
{% if mysql -%}
[init-script]
recipe = slapos.recipe.template
......@@ -196,13 +224,26 @@ context =
key datadir my-cnf-parameters:data-directory
key results_directory directory:results
[directory]
results = ${directory:srv}/tests
{%- do directory_dict.__setitem__('results', '${directory:srv}/tests') %}
{%- endif %}
{%- endif %}
{%- endif %}
[directory]
recipe = slapos.cookbook:mkdirectory
bin = ${buildout:directory}/bin
etc = ${buildout:directory}/etc
var = ${buildout:directory}/var
etc_run = ${:etc}/run
var_run = ${:var}/run
log = ${buildout:directory}/var/log
tmp = ${buildout:directory}/tmp
srv = ${buildout:directory}/srv
{%- for k, v in directory_dict.iteritems() %}
{{ k }} = {{ v }}
{%- endfor %}
[buildout]
extends =
{{ logrotate_cfg }}
......
......@@ -18,6 +18,7 @@ extends =
../../component/python-mysqlclient/buildout.cfg
../../component/python-cryptography/buildout.cfg
../../component/pycurl/buildout.cfg
../../component/yappi/buildout.cfg
../../component/ZODB/buildout.cfg
../../component/ZEO/buildout.cfg
../../component/zodbtools/buildout.cfg
......@@ -55,6 +56,7 @@ eggs = neoppod[admin, ctl, master]
${cython-zstd:egg}
${msgpack-python:egg}
${:adapter-egg}
${yappi:egg}
${ZODB:egg}
${zodbtools:egg}
psutil
......
......@@ -16,7 +16,7 @@
[template]
filename = instance.cfg
md5sum = acd9dd8dbe613e7101e62930a8380ef0
md5sum = 43f02b7b3552d0d657fa7dbf43ce20a5
[template-ors]
filename = instance-ors.cfg
......@@ -44,7 +44,7 @@ md5sum = b7906ca3a6b17963f78f680fc0842b74
[ru_lopcomm_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/lopcomm/libinstance.jinja2.cfg
md5sum = 7d05f6a3980a79bfd35677dbb8b988ee
md5sum = d1a724be968a2d5ea1432c0f4a34f199
[ru_sunwave_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/sunwave/libinstance.jinja2.cfg
......@@ -154,10 +154,6 @@ md5sum = e435990eb0a0d4be41efa9bd16dce09b
_update_hash_filename_ = ru/lopcomm/cu_config.jinja2.xml
md5sum = 346c911e1ac5e5001a39c8926b44c91e
[ru_lopcomm_cu_inactive_config.jinja2.xml]
_update_hash_filename_ = ru/lopcomm/cu_inactive_config.jinja2.xml
md5sum = 9d48c35f9939446ce75ae9f85e44c26a
[software.cfg.html]
_update_hash_filename_ = gadget/software.cfg.html
md5sum = 61a2f783fbf683a34aed3d13e00baca2
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Cell. Common properties",
"type": "object",
"required": [
"cell_type",
"rf_mode",
......@@ -12,21 +10,22 @@
"bandwidth",
"ru"
],
"properties": {
"cell_type": {
"type": "string"
},
"cell_kind": {
"type": "string",
"const": "enb"
"const": "enb"
},
"rf_mode": {
"title": "RF mode",
"description": "Mode for TX/RX radio multiplexing: Frequency- or Time- Domain Division",
"type": "string",
"enum": ["fdd", "tdd"],
"enum": [
"fdd",
"tdd"
],
"propertyOrder": 101
},
"pci": {
......@@ -59,7 +58,6 @@
"propertyOrder": 9999
}
},
"$defs": {
"ru-of-cell": {
"title": "Radio Unit",
......@@ -68,11 +66,14 @@
"title": "Shared Radio Unit",
"description": "Use radio unit defined in separate shared instance",
"type": "object",
"required": ["ru_type", "ru_ref"],
"required": [
"ru_type",
"ru_ref"
],
"properties": {
"ru_type": {
"type": "string",
"const": "ru_ref"
"type": "string",
"const": "ru_ref"
},
"ru_ref": {
"title": "RU Reference",
......@@ -85,11 +86,14 @@
"title": "Shared Radio Unit of a Cell",
"description": "Use the same radio unit as referenced cell instance does",
"type": "object",
"required": ["ru_type", "ruincell_ref"],
"required": [
"ru_type",
"ruincell_ref"
],
"properties": {
"ru_type": {
"type": "string",
"const": "ruincell_ref"
"type": "string",
"const": "ruincell_ref"
},
"ruincell_ref": {
"title": "Cell Reference",
......@@ -98,9 +102,15 @@
}
}
},
{ "$ref": "../ru/sdr/input-schema.json" },
{ "$ref": "../ru/lopcomm/input-schema.json" },
{ "$ref": "../ru/sunwave/input-schema.json" }
{
"$ref": "../ru/sdr/input-schema.json"
},
{
"$ref": "../ru/lopcomm/input-schema.json"
},
{
"$ref": "../ru/sunwave/input-schema.json"
}
]
}
}
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Cell",
"type": "object",
"oneOf": [
{ "$ref": "../cell/lte/input-schema.json" },
{ "$ref": "../cell/nr/input-schema.json" }
{
"$ref": "../cell/lte/input-schema.json"
},
{
"$ref": "../cell/nr/input-schema.json"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "LTE Cell",
"type": "object",
"required": [
"cell_type",
"rf_mode",
......@@ -11,18 +9,15 @@
"cell_id",
"bandwidth",
"ru",
"dl_earfcn",
"tac"
],
"properties": {
"$ref": "../../cell/common.json#/properties",
"cell_type": {
"$ref": "#/properties/cell_type",
"const": "lte"
"const": "lte"
},
"tdd_ul_dl_config": {
"title": "TDD Configuration",
"type": "string",
......@@ -37,7 +32,6 @@
}
}
},
"bandwidth": {
"$ref": "#/properties/bandwidth",
"enum": [
......@@ -49,7 +43,6 @@
20
]
},
"dl_earfcn": {
"title": "DL EARFCN",
"description": "Downlink E-UTRA Absolute Radio Frequency Channel Number of the cell",
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NR Cell",
"type": "object",
"required": [
"cell_type",
"rf_mode",
......@@ -11,18 +9,15 @@
"cell_id",
"bandwidth",
"ru",
"dl_nr_arfcn",
"nr_band"
],
"properties": {
"$ref": "../../cell/common.json#/properties",
"cell_type": {
"$ref": "#/properties/cell_type",
"const": "nr"
"const": "nr"
},
"tdd_ul_dl_config": {
"title": "TDD Configuration",
"type": "string",
......@@ -38,11 +33,9 @@
}
}
},
"bandwidth": {
"$ref": "#/properties/bandwidth"
},
"dl_nr_arfcn": {
"title": "DL NR ARFCN",
"description": "Downlink NR Absolute Radio Frequency Channel Number of the cell",
......@@ -75,7 +68,6 @@
"default": 1
}
},
"$defs": {
"tac": {
"title": "Tracking Area Code",
......
......@@ -165,7 +165,7 @@ extra-context =
raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target}
raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target}
raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_inactive_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target}
raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename}
raw ru_tapsplit ${ru_tapsplit:target}
......@@ -221,7 +221,7 @@ extra-context =
raw ru_lopcomm_reset_template ${ru_lopcomm_reset.jinja2.py:target}
raw ru_lopcomm_CreateProcessingEle_template ${ru_lopcomm_CreateProcessingEle.jinja2.xml:target}
raw ru_lopcomm_cu_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_inactive_config.jinja2.xml:target}
raw ru_lopcomm_cu_inactive_config_template ${ru_lopcomm_cu_config.jinja2.xml:target}
raw ru_lopcomm_firmware_path ${ru_lopcomm_firmware-dl:target}
raw ru_lopcomm_firmware_filename ${ru_lopcomm_firmware-dl:filename}
raw ru_tapsplit ${ru_tapsplit:target}
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer Cell. Common properties",
"type": "object",
"required": [
......@@ -9,14 +8,13 @@
"pci",
"tac"
],
"properties": {
"cell_type": {
"type": "string"
},
"cell_kind": {
"type": "string",
"const": "enb_peer"
"const": "enb_peer"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer Cell",
"type": "object",
"oneOf": [
{ "$ref": "../../peer/cell/lte/input-schema.json" },
{ "$ref": "../../peer/cell/nr/input-schema.json" }
{
"$ref": "../../peer/cell/lte/input-schema.json"
},
{
"$ref": "../../peer/cell/nr/input-schema.json"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "LTE Peer Cell",
"type": "object",
"required": [
"cell_type",
"cell_kind",
"pci",
"tac",
"e_cell_id",
"dl_earfcn"
],
"properties": {
"cell_type": {
"$ref": "../../../peer/cell/common.json#/properties/cell_type",
"const": "lte"
"const": "lte"
},
"e_cell_id": {
"title": "E-UTRAN Cell ID",
"description": "28 bit E-UTRAN cell identity. Concatenation of enb_id and cell_id of the neighbour cell.",
"type": "string"
},
"dl_earfcn": { "$ref": "../../../cell/lte/input-schema.json#/properties/dl_earfcn" },
"pci": { "$ref": "../../../cell/lte/input-schema.json#/properties/pci" },
"tac": { "$ref": "../../../cell/lte/input-schema.json#/properties/tac" }
"dl_earfcn": {
"$ref": "../../../cell/lte/input-schema.json#/properties/dl_earfcn"
},
"pci": {
"$ref": "../../../cell/lte/input-schema.json#/properties/pci"
},
"tac": {
"$ref": "../../../cell/lte/input-schema.json#/properties/tac"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NR Peer Cell",
"type": "object",
"required": [
"cell_type",
"cell_kind",
"pci",
"tac",
"nr_cell_id",
"gnb_id_bits",
"dl_nr_arfcn",
"nr_band"
],
"properties": {
"cell_type": {
"$ref": "../../../peer/cell/common.json#/properties/cell_type",
"const": "nr"
"const": "nr"
},
"nr_cell_id": {
"title": "NR Cell ID",
"description": "Concatenation of gnb_id and cell_id of the neighbour cell",
......@@ -32,11 +27,23 @@
"description": "Number of bits for the gNodeB global identifier. (range 22 to 32)",
"type": "integer"
},
"dl_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/dl_nr_arfcn" },
"nr_band": { "$ref": "../../../cell/nr/input-schema.json#/properties/nr_band" },
"ssb_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/ssb_nr_arfcn" },
"ul_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/ul_nr_arfcn" },
"pci": { "$ref": "../../../cell/nr/input-schema.json#/properties/pci" },
"tac": { "$ref": "../../../cell/nr/input-schema.json#/$defs/tac" }
"dl_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/dl_nr_arfcn"
},
"nr_band": {
"$ref": "../../../cell/nr/input-schema.json#/properties/nr_band"
},
"ssb_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/ssb_nr_arfcn"
},
"ul_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/ul_nr_arfcn"
},
"pci": {
"$ref": "../../../cell/nr/input-schema.json#/properties/pci"
},
"tac": {
"$ref": "../../../cell/nr/input-schema.json#/$defs/tac"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer. Common properties",
"type": "object",
"required": [
"peer_type"
],
"properties": {
"peer_type": {
"type": "string"
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer eNB/gNB",
"type": "object",
"oneOf": [
{ "$ref": "../peer/lte/input-schema.json" },
{ "$ref": "../peer/nr/input-schema.json" }
{
"$ref": "../peer/lte/input-schema.json"
},
{
"$ref": "../peer/nr/input-schema.json"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer eNB",
"type": "object",
"required": [
"peer_type",
"x2_addr"
],
"properties": {
"peer_type": {
"$ref": "../../peer/common.json#/properties/peer_type",
"const": "lte"
"const": "lte"
},
"x2_addr": {
"title": "X2 Address",
"description": "X2 Address of the neighbour node (eNB Address)",
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Peer gNB",
"type": "object",
"required": [
"peer_type",
"xn_addr"
],
"properties": {
"peer_type": {
"$ref": "../../peer/common.json#/properties/peer_type",
"const": "nr"
"const": "nr"
},
"xn_addr": {
"title": "XN Address",
"description": "XN Address of the neighbour node (gNB Address)",
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Radio Unit. Common properties",
"type": "object",
"required": [
"ru_type",
"ru_link_type",
......@@ -12,7 +10,6 @@
"tx_gain",
"rx_gain"
],
"properties": {
"ru_type": {
"type": "string"
......@@ -20,46 +17,41 @@
"ru_link_type": {
"type": "string"
},
"n_antenna_dl": {
"title": "Number of DL antennas",
"type": "integer"
},
"n_antenna_ul": {
"title": "Number of UL antennas",
"type": "integer"
},
"tx_gain": {
"title": "Tx gain",
"description": "Tx gain (in dB)",
"type": "number"
},
"rx_gain": {
"title": "Rx gain",
"description": "Rx gain (in dB)",
"type": "number"
},
"txrx_active": {
"title": "Activate Tx/Rx",
"description": "Activate or inactivate Tx transmission and Rx reception. When inactive RU does no radio.",
"type": "string",
"enum": ["ACTIVE", "INACTIVE"],
"enum": [
"ACTIVE",
"INACTIVE"
],
"default": "INACTIVE"
},
"cpri_link": {
"title": "CPRI link settings",
"options": {
"dependencies": {
"ru_link_type": "cpri"
}
},
"type": "object",
"required": [
"sdr_dev",
......@@ -78,13 +70,23 @@
"mapping": {
"title": "Mapping method of AxCs on the CPRI",
"type": "string",
"enum": ["standard", "hw", "spread", "bf1"]
"enum": [
"standard",
"hw",
"spread",
"bf1"
]
},
"mult": {
"title": "CPRI line bit rate multipler",
"description": "Select the CPRI line bit rate in terms of multiple of option 1 (614.4 Mbps). E.g set 4 for option 3, 8 for option 5 and 16 for option 7",
"type": "integer",
"enum": [4, 5, 8, 16],
"enum": [
4,
5,
8,
16
],
"default": 16
},
"rx_delay": {
......@@ -107,18 +109,15 @@
}
}
},
"mac_addr": {
"title": "RU MAC address",
"description": "RU MAC address used for NETCONF",
"type": "string",
"options": {
"dependencies": {
"ru_link_type": "cpri"
}
}
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Radio Unit",
"type": "object",
"oneOf": [
{ "$ref": "sdr/input-schema.json" },
{ "$ref": "lopcomm/input-schema.json" },
{ "$ref": "sunwave/input-schema.json" }
{
"$ref": "sdr/input-schema.json"
},
{
"$ref": "lopcomm/input-schema.json"
},
{
"$ref": "sunwave/input-schema.json"
}
]
}
......@@ -32,9 +32,6 @@ destination = ${buildout:directory}/ncclient_common.py
[ru_lopcomm_cu_config.jinja2.xml]
<= download-base
[ru_lopcomm_cu_inactive_config.jinja2.xml]
<= download-base
[ru_lopcomm_firmware-dl]
recipe = slapos.recipe.build:download
url = https://lab.nexedi.com/nexedi/ors-utils/raw/master/lopcomm-firmware/${:filename}
......
<xc:config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<user-plane-configuration xc:operation="replace" xmlns="urn:o-ran:uplane-conf-option8:1.0">
<!-- TX path: eaxcid → TxEndpoint
mod → static TxEndpoint → TxArray
TxCarrier
(static TxEndpoint, TxArray and their association are defined by RU itself)
-->
{%- set TxCarrier = 'TXA0CC00' %}
{%- for ant in range(ru.n_antenna_dl) %}
{%- set port = ant // 2 %}
{%- set chan = ant % 2 %}
{%- set txep = 'TXA0P%02dC%02d' % (port, chan) %}
<!-- TxAntenna{{ ant }} -->
<tx-endpoints>
<name>{{ txep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ ant }}</eaxc-id>
</e-axcid>
</tx-endpoints>
<tx-links>
<name>{{ txep }}</name>
<processing-element>PE0</processing-element>
<tx-array-carrier>{{ TxCarrier }}</tx-array-carrier>
<tx-endpoint>{{ txep }}</tx-endpoint>
</tx-links>
{%- endfor %}
<!--
RX path: eaxcid ← RxEndpoint
(data ∪ prach)
demod ← static RxEndpoint ← RxArray
RxCarrier
(static RxEndpoint, RxArray and their association are defined by RU itself)
-->
{%- set RxCarrier = 'RXA0CC00' %}
{%- for ant in range(ru.n_antenna_ul) %}
{%- set port = ant // 2 %}
{%- set chan = ant % 2 %}
{%- set rxep = 'RXA0P%02dC%02d' % (port, chan) %}
{%- set prachep = 'PRACH0P%02dC%02d' % (port, chan) %}
<!-- RxAntenna{{ ant }} -->
<rx-endpoints>
<name>{{ rxep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ ant }}</eaxc-id>
</e-axcid>
</rx-endpoints>
<rx-endpoints>
<name>{{ prachep }}</name>
<e-axcid>
<o-du-port-bitmask>61440</o-du-port-bitmask>
<band-sector-bitmask>3968</band-sector-bitmask>
<ccid-bitmask>112</ccid-bitmask>
<ru-port-bitmask>15</ru-port-bitmask>
<eaxc-id>{{ 16*chan + 8 + port }}</eaxc-id>
</e-axcid>
</rx-endpoints>
<rx-links>
<name>{{ rxep }}</name>
<processing-element>PE0</processing-element>
<rx-array-carrier>{{ RxCarrier }}</rx-array-carrier>
<rx-endpoint>{{ rxep }}</rx-endpoint>
</rx-links>
<rx-links>
<name>{{ prachep }}</name>
<processing-element>PE0</processing-element>
<rx-array-carrier>{{ RxCarrier }}</rx-array-carrier>
<rx-endpoint>{{ prachep }}</rx-endpoint>
</rx-links>
{%- endfor %}
<!-- TX/RX carriers -->
<!-- TODO support multiple cells over 1 RU -->
{%- if cell.cell_type == 'lte' %}
{%- set dl_arfcn = cell.dl_earfcn %}
{%- set ul_arfcn = cell.ul_earfcn %}
{%- set dl_freq = int(xearfcn_module.frequency(dl_arfcn) * 1e6) %}
{%- set ul_freq = int(xearfcn_module.frequency(ul_arfcn) * 1e6) %}
{%- elif cell.cell_type == 'nr' %}
{%- set dl_arfcn = cell.dl_nr_arfcn %}
{%- set ul_arfcn = cell.ul_nr_arfcn %}
{%- set dl_freq = int(xnrarfcn_module.frequency(dl_arfcn) * 1e6) %}
{%- set ul_freq = int(xnrarfcn_module.frequency(ul_arfcn) * 1e6) %}
{%- else %}
{%- do bug('unreachable') %}
{%- endif %}
{%- set bw = int(cell.bandwidth * 1e6) %}
<tx-array-carriers>
<name>{{ TxCarrier }}</name>
<absolute-frequency-center>{{ dl_arfcn }}</absolute-frequency-center>
<center-of-channel-bandwidth>{{ dl_freq }}</center-of-channel-bandwidth>
<channel-bandwidth>{{ bw }}</channel-bandwidth>
<active>INACTIVE</active>
<rw-type>{{ cell.cell_type | upper }}</rw-type>
<rw-duplex-scheme>{{ cell.rf_mode | upper }}</rw-duplex-scheme>
<gain>{{ ru.tx_gain }}</gain>
<downlink-radio-frame-offset>0</downlink-radio-frame-offset>
<downlink-sfn-offset>0</downlink-sfn-offset>
</tx-array-carriers>
<rx-array-carriers>
<name>{{ RxCarrier }}</name>
<absolute-frequency-center>{{ ul_arfcn }}</absolute-frequency-center>
<center-of-channel-bandwidth>{{ ul_freq }}</center-of-channel-bandwidth>
<channel-bandwidth>{{ bw }}</channel-bandwidth>
<active>INACTIVE</active>
<downlink-radio-frame-offset>0</downlink-radio-frame-offset>
<downlink-sfn-offset>0</downlink-sfn-offset>
<!-- <gain>{{ ru.rx_gain }}</gain> -->
<!-- TODO(lu.xu): clarify with Lopcomm regaring rx gain -->
<gain-correction>0.0</gain-correction>
<n-ta-offset>0</n-ta-offset>
</rx-array-carriers>
</user-plane-configuration>
</xc:config>
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Lopcomm ORAN",
"type": "object",
"required": [
"ru_type",
"ru_link_type",
......@@ -11,23 +9,19 @@
"n_antenna_ul",
"tx_gain",
"rx_gain",
"cpri_link",
"mac_addr"
],
"properties": {
"$ref": "../../ru/common.json#/properties",
"ru_type": {
"$ref": "#/properties/ru_type",
"const": "lopcomm"
"const": "lopcomm"
},
"ru_link_type": {
"$ref": "#/properties/ru_link_type",
"const": "cpri"
"const": "cpri"
},
"n_antenna_dl": {
"$ref": "#/properties/n_antenna_dl",
"default": 2
......@@ -42,8 +36,10 @@
"$ref": "#/properties/cpri_link/properties",
"mapping": {
"$ref": "#/properties/cpri_link/properties/mapping",
"const": "hw",
"enum": ["hw"]
"const": "hw",
"enum": [
"hw"
]
},
"rx_delay": {
"$ref": "#/properties/cpri_link/properties/rx_delay",
......@@ -59,7 +55,6 @@
}
}
},
"reset_schedule": {
"title": "Cron schedule for RRH reset",
"description": "Refer https://crontab.guru/ to make a reset schedule for RRH, for example, '0 1 * * *' means the RRH will reset every day at 1 am",
......
......@@ -3,6 +3,8 @@
{%- macro buildout_iru(iru, icell_list) %}
{%- set ru_ref = J(jref_of_shared(iru)) %}
{%- set ru = iru['_'] %}
{%- set ns = namespace(inactive_ru=ru.copy()) %}
{%- do ns.inactive_ru.update({'txrx_active': 'INACTIVE'}) %}
{%- if len(icell_list) != 1 %}
{%- do ierror(iru, 'ru/lopcomm supports only 1 cell ; requested %d' % len(icell_list)) %}
......@@ -66,11 +68,7 @@ offline = false
[{{ B('%s-cu-config' % ru_ref) }}]
<= config-base
{% if ru.get("cu_config_link", None) %}
url = ${ {{-B('%s-cu-config-dl' % ru_ref)}}:target}
{% else %}
url = {{ ru_lopcomm_cu_config_template }}
{% endif %}
output = ${directory:etc}/{{B('%s-cu_config.xml' % ru_ref)}}
extra-context =
import xearfcn_module xlte.earfcn
......@@ -82,14 +80,14 @@ cell = {{ dumps(cell) }}
[{{ B('%s-cu-inactive-config' % ru_ref) }}]
<= config-base
url = {{ ru_lopcomm_cu_inactive_config_template }}
url = {{ ru_lopcomm_cu_config_template }}
output = ${directory:etc}/{{B('%s-cu_inactive_config.xml' % ru_ref)}}
extra-context =
import xearfcn_module xlte.earfcn
import xnrarfcn_module xlte.nrarfcn
key ru :ru
key cell :cell
ru = {{ dumps(ru) }}
ru = {{ dumps(ns.inactive_ru) }}
cell = {{ dumps(cell) }}
[{{ B('%s-config-template' % ru_ref) }}]
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SDR transiever",
"description": "Radio Unit constituted of several SDR boards",
"type": "object",
"required": [
"ru_type",
"ru_link_type",
......@@ -12,22 +10,18 @@
"n_antenna_ul",
"tx_gain",
"rx_gain",
"sdr_dev_list"
],
"properties": {
"$ref": "../../ru/common.json#/properties",
"ru_type": {
"$ref": "#/properties/ru_type",
"const": "sdr"
"const": "sdr"
},
"ru_link_type": {
"$ref": "#/properties/ru_link_type",
"const": "sdr"
"const": "sdr"
},
"sdr_dev_list": {
"title": "SDR boards",
"description": "Which SDR boards to use as combined RF port",
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Sunwave M2RU",
"type": "object",
"required": [
"ru_type",
"ru_link_type",
......@@ -11,23 +9,19 @@
"n_antenna_ul",
"tx_gain",
"rx_gain",
"cpri_link",
"mac_addr"
],
"properties": {
"$ref": "../../ru/common.json#/properties",
"ru_type": {
"$ref": "#/properties/ru_type",
"const": "sunwave"
"const": "sunwave"
},
"ru_link_type": {
"$ref": "#/properties/ru_link_type",
"const": "cpri"
"const": "cpri"
},
"n_antenna_dl": {
"$ref": "#/properties/n_antenna_dl",
"default": 2
......@@ -42,8 +36,10 @@
"$ref": "#/properties/cpri_link/properties",
"mapping": {
"$ref": "#/properties/cpri_link/properties/mapping",
"const": "bf1",
"enum": ["bf1"]
"const": "bf1",
"enum": [
"bf1"
]
},
"rx_delay": {
"$ref": "#/properties/cpri_link/properties/rx_delay",
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "UE Cell. Common properties",
"type": "object",
"required": [
"cell_type",
"cell_kind",
"rf_mode",
"ru"
],
"properties": {
"cell_type": {
"type": "string"
},
"cell_kind": {
"type": "string",
"const": "ue"
"const": "ue"
},
"rf_mode": { "$ref": "../../cell/common.json#/properties/rf_mode" },
"ru": { "$ref": "../../cell/common.json#/$defs/ru-of-cell" }
"rf_mode": {
"$ref": "../../cell/common.json#/properties/rf_mode"
},
"ru": {
"$ref": "../../cell/common.json#/$defs/ru-of-cell"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "UE Cell",
"type": "object",
"oneOf": [
{ "$ref": "../../ue/cell/lte/input-schema.json" },
{ "$ref": "../../ue/cell/nr/input-schema.json" }
{
"$ref": "../../ue/cell/lte/input-schema.json"
},
{
"$ref": "../../ue/cell/nr/input-schema.json"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NR Cell",
"type": "object",
"required": [
"cell_type",
"cell_kind",
"rf_mode",
"ru",
"dl_nr_arfcn",
"bandwidth",
"nr_band"
],
"properties": {
"cell_type": {
"$ref": "../../../ue/cell/common.json#/properties/cell_type",
"const": "nr"
},
"cell_kind": { "$ref": "../../../ue/cell/common.json#/properties/cell_kind" },
"rf_mode": { "$ref": "../../../ue/cell/common.json#/properties/rf_mode" },
"ru": { "$ref": "../../../ue/cell/common.json#/properties/ru",
"propertyOrder": 9999
},
"dl_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/dl_nr_arfcn" },
"bandwidth": { "$ref": "../../../cell/common.json#/properties/bandwidth" },
"nr_band": { "$ref": "../../../cell/nr/input-schema.json#/properties/nr_band" },
"ul_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/ul_nr_arfcn" },
"ssb_nr_arfcn": { "$ref": "../../../cell/nr/input-schema.json#/properties/ssb_nr_arfcn" }
"const": "nr"
},
"cell_kind": {
"$ref": "../../../ue/cell/common.json#/properties/cell_kind"
},
"rf_mode": {
"$ref": "../../../ue/cell/common.json#/properties/rf_mode"
},
"ru": {
"$ref": "../../../ue/cell/common.json#/properties/ru",
"propertyOrder": 9999
},
"dl_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/dl_nr_arfcn"
},
"bandwidth": {
"$ref": "../../../cell/common.json#/properties/bandwidth"
},
"nr_band": {
"$ref": "../../../cell/nr/input-schema.json#/properties/nr_band"
},
"ul_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/ul_nr_arfcn"
},
"ssb_nr_arfcn": {
"$ref": "../../../cell/nr/input-schema.json#/properties/ssb_nr_arfcn"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "UE. Common properties",
"type": "object",
"required": [
"ue_type",
"rue_addr"
],
"properties": {
"$ref": "../sim/input-schema.json#/properties",
"ue_type": {
"type": "string"
},
"rue_addr": {
"title": "[Required] Remote UE address",
"description": "[Required] Address of remote UE server. Default port is 2152.",
"type": "string",
"default": ""
},
"imsi": {
"$ref": "../sim/input-schema.json#/properties/imsi",
"default": "001010123456789"
......
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "UE",
"type": "object",
"oneOf": [
{ "$ref": "../ue/lte/input-schema.json" },
{ "$ref": "../ue/nr/input-schema.json" }
{
"$ref": "../ue/lte/input-schema.json"
},
{
"$ref": "../ue/nr/input-schema.json"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "LTE UE",
"type": "object",
"properties": {
"$ref": "../../ue/common.json#/properties",
"ue_type": {
"$ref": "#/properties/ue_type",
"const": "lte"
"const": "lte"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "NR UE",
"type": "object",
"properties": {
"$ref": "../../ue/common.json#/properties",
"ue_type": {
"$ref": "#/properties/ue_type",
"const": "nr"
"const": "nr"
}
}
}
......@@ -14,7 +14,7 @@
# not need these here).
[template-erp5]
filename = instance-erp5.cfg.in
md5sum = 38eab3283d175230231c998fa4a3416e
md5sum = d0009cd600d341935679a7fb193f34d1
[template-balancer]
filename = instance-balancer.cfg.in
......
......@@ -306,6 +306,7 @@ config-port-base = {{ dumps(zope_parameter_dict.get('port-base', 2200)) }}
config-with-max-rlimit-nofile = {{ dumps(slapparameter_dict.get('with-max-rlimit-nofile', false)) }}
{# BBB: zope_parameter_dict used to contain 'webdav', so fallback to it -#}
config-webdav = {{ dumps(current_zope_family_override_dict.get('webdav', zope_parameter_dict.get('webdav', False))) }}
config-xml-rpc = {{ dumps(current_zope_family_override_dict.get('xml-rpc', False)) }}
config-publisher-timeout = {{ dumps(current_zope_family_override_dict.get('publisher-timeout', global_publisher_timeout)) }}
config-activity-timeout = {{ dumps(current_zope_family_override_dict.get('activity-timeout', global_activity_timeout)) }}
{% if test_runner_enabled -%}
......
......@@ -73,7 +73,6 @@ parts +=
slapos-cookbook
mroonga-mariadb
tesseract
.coveragerc
# Buildoutish
eggs-all-scripts
......@@ -432,52 +431,15 @@ initialization =
if coverage_process:
coverage_process.stop()
coverage_process.save()
# upload the coverage so that they can be combined from another machine
# upload the coverage so that they can be combined
upload_url = test_runner_configuration['coverage'].get('upload-url')
if upload_url:
import requests
import time
import uritemplate
from six.moves.urllib.parse import urlparse
auth_list = (None, )
parsed_url = urlparse(upload_url)
if parsed_url.username:
# try Digest and Basic authentication and retry 5 times to tolerate transiant errors
auth_list = (
requests.auth.HTTPDigestAuth(parsed_url.username, parsed_url.password),
requests.auth.HTTPBasicAuth(parsed_url.username, parsed_url.password),
) * 5
url = uritemplate.URITemplate(upload_url).expand(
test_name=test_name,
# Environment variables are set in parts/erp5/product/ERP5Type/tests/runTestSuite.py
test_result_id=os.environ.get('ERP5_TEST_RESULT_ID', 'unknown_test_result_id'),
test_result_revision=os.environ.get('ERP5_TEST_RESULT_REVISION', 'unknown_test_result_revision'),
)
for auth in auth_list:
with open(coverage_data_file, 'rb') as f:
resp = requests.put(url, data=f, auth=auth)
if resp.ok:
# print just the hostname, not to include the auth part
print('Uploaded coverage data to {parsed_url.hostname}'.format(parsed_url=parsed_url))
break
print('Error {resp.status_code} uploading coverage data to {parsed_url.hostname} with {auth.__class__.__name__}'.format(
resp=resp, parsed_url=parsed_url, auth=auth))
time.sleep(1)
else:
sys.stderr.write('Error uploading coverage data to {parsed_url.hostname}\n'.format(parsed_url=parsed_url))
[.coveragerc]
recipe = slapos.recipe.template
output = ${buildout:directory}/${:_buildout_section_name_}
inline =
# coverage configuration file, useful when making html report
[run]
plugins =
erp5_coverage_plugin
relative_files = true
import Products.ERP5Type.tests.coverage_report
Products.ERP5Type.tests.coverage_report.upload(
coverage_data_file,
upload_url,
test_name)
[test-suite-runner]
# XXX: Workaround for fact ERP5Type is not an distribution and does not
......
......@@ -74,7 +74,7 @@ md5sum = ca0cb83950dd9079cc289891cce08e76
[template-erp5]
filename = instance-erp5.cfg.in
md5sum = 6f57c834eb3f774d265c3fd6661429d8
md5sum = edce1c63c13f0d8ec477711ea646444f
[template-zeo]
filename = instance-zeo.cfg.in
......@@ -86,7 +86,7 @@ md5sum = 0ac4b74436f554cd677f19275d18d880
[template-zope]
filename = instance-zope.cfg.in
md5sum = 8725a6b42de735b64b51d9bac598f94b
md5sum = 34da5e6d80b2992689825bb00bcd911d
[template-balancer]
filename = instance-balancer.cfg.in
......
......@@ -302,6 +302,7 @@ config-port-base = {{ dumps(zope_parameter_dict.get('port-base', 2200)) }}
config-with-max-rlimit-nofile = {{ dumps(with_max_rlimit_nofile_enable) }}
{# BBB: zope_parameter_dict used to contain 'webdav', so fallback to it -#}
config-webdav = {{ dumps(current_zope_family_override_dict.get('webdav', zope_parameter_dict.get('webdav', False))) }}
config-xml-rpc = {{ dumps(current_zope_family_override_dict.get('xml-rpc', False)) }}
config-publisher-timeout = {{ dumps(current_zope_family_override_dict.get('publisher-timeout', global_publisher_timeout)) }}
config-activity-timeout = {{ dumps(current_zope_family_override_dict.get('activity-timeout', global_activity_timeout)) }}
{% if test_runner_enabled -%}
......
......@@ -349,6 +349,7 @@ wrapped-command-line =
--access-log-file={{ '${' ~ conf_parameter_name ~ ':z2-log}' }}
{% if longrequest_logger_interval > 0 %} --long-request-log-file={{ '${' ~ conf_parameter_name ~ ':longrequest-logger-file}' }} {% endif %}
{% if webdav %}-w{% endif %}
{% if slapparameter_dict['xml-rpc'] %}--enable-xml-rpc{% endif %}
{% if with_max_rlimit_nofile %}--with-max-rlimit-nofile{% endif %}
{{ ipv4 }}:${:port}
{% if timerserver_interval %}--timerserver-interval={{ timerserver_interval }}{% endif %}
......
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