Commit 1bc59b59 authored by Xavier Thompson's avatar Xavier Thompson

slap/standalone: Use IPv6 range when available

See merge request nexedi/slapos.core!538
parents 8f9a4ffd 3da9c477
Pipeline #28935 failed with stage
in 0 seconds
......@@ -1464,9 +1464,15 @@ def parse_computer_definition(conf, definition_path):
address_list.append(dict(addr=address, netmask=netmask))
if computer_definition.has_option(section, 'ipv6_range'):
ipv6_range_network = computer_definition.get(section, 'ipv6_range')
addr, netmask = ipv6_range_network.split('/')
netmask = netmaskFromLenIPv6(int(netmask))
ipv6_range = {'addr' : address, 'netmask' : netmask, 'network' : ipv6_range_network}
ipv6_range_addr, ipv6_range_prefixlen = ipv6_range_network.split('/')
ipv6_range_prefixlen = int(ipv6_range_prefixlen)
ipv6_range_netmask = netmaskFromLenIPv6(ipv6_range_prefixlen)
ipv6_range = {
'addr' : ipv6_range_addr,
'netmask' : ipv6_range_netmask,
'network' : ipv6_range_network,
'prefixlen': ipv6_range_prefixlen,
}
else:
ipv6_range = {}
tap = Tap(computer_definition.get(section, 'network_interface'))
......
......@@ -493,12 +493,8 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
))
elif (not self.__is_tested and not check_anomaly) or \
(not self.__is_anomaly_detected and check_anomaly):
# Anomaly or Test is disabled on this promise, send empty result
if self.getConfig('slapgrid-version', '') <= '1.4.17':
# old version cannot send EmptyResult
self.__sendResult(PromiseQueueResult(item=TestResult()))
else:
self.__sendResult(PromiseQueueResult())
# Anomaly or Test is disabled on this promise, send empty
self.__sendResult(PromiseQueueResult())
else:
try:
self.sense()
......
......@@ -59,12 +59,25 @@ from .interface.slap import IRequester
from ..grid.slapgrid import SLAPGRID_PROMISE_FAIL
from .slap import slap
from ..util import dumps, rmtree
from ..util import dumps, rmtree, getPartitionIpv6Addr, getPartitionIpv6Range
from ..grid.svcbackend import getSupervisorRPC
from ..grid.svcbackend import _getSupervisordSocketPath
NETMASK_IPV6_FULL = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
NETMASK_IPV4_FULL = '255.255.255.255'
def _parseIPv6(ipv6):
try:
addr, prefixlen = ipv6.split('/')
prefixlen = int(prefixlen)
except ValueError:
addr, prefixlen = ipv6, None
return addr, prefixlen
@zope.interface.implementer(IException)
class SlapOSNodeCommandError(Exception):
"""Exception raised when running a SlapOS Node command failed.
......@@ -205,6 +218,7 @@ class SlapOSConfigWriter(ConfigWriter):
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())
has_ipv6_range = ('false', 'true')[standalone_slapos._partitions_have_ipv6_range]
with open(path, 'w') as f:
f.write(
textwrap.dedent(
......@@ -232,6 +246,7 @@ class SlapOSConfigWriter(ConfigWriter):
create_tap = false
create_tun = false
computer_xml = {standalone_slapos._slapos_xml}
partition_has_ipv6_range = {has_ipv6_range}
[slapproxy]
host = {standalone_slapos._server_ip}
......@@ -287,9 +302,7 @@ class SlapformatDefinitionWriter(ConfigWriter):
"""
def writeConfig(self, path):
ipv4 = self._standalone_slapos._ipv4_address
ipv6 = self._standalone_slapos._ipv6_address
ipv4_cidr = ipv4 + '/255.255.255.255' if ipv4 else ''
ipv6_cidr = ipv6 + '/64' if ipv6 else ''
ipv4_cidr = '%s/%s' % (ipv4, NETMASK_IPV4_FULL) if ipv4 else ''
user = pwd.getpwuid(os.getuid()).pw_name
partition_base_name = self._standalone_slapos._partition_base_name
with open(path, 'w') as f:
......@@ -299,12 +312,24 @@ class SlapformatDefinitionWriter(ConfigWriter):
[computer]
address = {ipv4_cidr}\n
""").format(**locals()))
ipv6 = self._standalone_slapos._ipv6_address
for i in range(self._standalone_slapos._partition_count):
ipv6_single, ipv6_range = self._standalone_slapos._getPartitionIpv6(i)
if ipv6_single:
ipv6_single_cidr = '%s/%s' % (ipv6_single, NETMASK_IPV6_FULL)
else:
ipv6_single_cidr = ''
if ipv6_range:
ipv6_range_cidr = '%(addr)s/%(prefixlen)s' % ipv6_range
ipv6_range_config_line = 'ipv6_range = ' + ipv6_range_cidr
else:
ipv6_range_config_line = ''
f.write(
textwrap.dedent(
"""
[partition_{i}]
address = {ipv6_cidr} {ipv4_cidr}
address = {ipv6_single_cidr} {ipv4_cidr}
{ipv6_range_config_line}
pathname = {partition_base_name}{i}
user = {user}
network_interface =\n
......@@ -415,7 +440,13 @@ class StandaloneSlapOS(object):
self._partition_base_name = 'slappart'
self._ipv4_address = None
self._ipv6_address = None
self._ipv6_range_prefixlen = None
self._partitions_have_ipv6_range = False
# NOTE: Using Standalone's own slapos (slapos.cli.entry) instead
# is not that easy because in test nodes standalone is often run
# with gpython (pygolang), and gpython currently doesn't support
# buildout
self._slapos_bin = slapos_bin
self._slapos_commands = {
......@@ -594,8 +625,12 @@ class StandaloneSlapOS(object):
partition_base_name="slappart"):
"""Creates `partition_count` partitions.
All partitions have the same `ipv4_address` and `ipv6_address` and
use the current system user.
All partitions have the same `ipv4_address` and use the current system
user.
`ipv6_address` can be a single address (in this case all partitions have
the same address) or a range in the form IPV6/CIDR (in this case each
partition has a subrange).
When calling this a second time with a lower `partition_count` or with
different `partition_base_name` will delete existing partitions.
......@@ -628,17 +663,19 @@ class StandaloneSlapOS(object):
if not (os.path.exists(partition_path)):
os.mkdir(partition_path)
os.chmod(partition_path, 0o750)
ipv6_addr, ipv6_range = self._getPartitionIpv6(i)
partition_list.append({
'address_list': [
{
'addr': ipv4_address,
'netmask': '255.255.255.255'
'netmask': NETMASK_IPV4_FULL
},
{
'addr': ipv6_address,
'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
},
'addr': ipv6_addr,
'netmask': NETMASK_IPV6_FULL
}
],
'ipv6_range' : ipv6_range,
'path': partition_path,
'reference': partition_reference,
'tap': {
......@@ -665,7 +702,7 @@ class StandaloneSlapOS(object):
self.computer.updateConfiguration(
dumps({
'address': ipv4_address,
'netmask': '255.255.255.255',
'netmask': NETMASK_IPV4_FULL,
'partition_list': partition_list,
'reference': self._computer_id,
'instance_root': self._instance_root,
......@@ -694,11 +731,33 @@ class StandaloneSlapOS(object):
self._partition_count = partition_count
self._partition_base_name = partition_base_name
self._ipv4_address = ipv4_address
self._ipv6_address = ipv6_address
self._ipv6_address, prefixlen = _parseIPv6(ipv6_address)
self._ipv6_range_prefixlen = prefixlen
self._partitions_have_ipv6_range = bool(prefixlen) and prefixlen < 112
if old_partition_count != partition_count:
SlapOSConfigWriter(self).writeConfig(self._slapos_config)
SlapformatDefinitionWriter(self).writeConfig(self._slapformat_definition)
# remove slapos xml configuration in case of ip changes
try:
os.unlink(self._slapos_xml)
except OSError as e:
if e.errno != errno.ENOENT:
raise
# run slapos format --now
command = (
self._slapos_bin, 'node', 'format',
'--now',
'--cfg', self._slapos_config)
self._logger.debug("Running %s", command)
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
self._logger.info(output)
except subprocess.CalledProcessError as e:
self._logger.error(e.output)
raise
def supply(self, software_url, computer_guid=None, state="available"):
"""Supply a software, see ISupply.supply
......@@ -949,3 +1008,17 @@ class StandaloneSlapOS(object):
return
time.sleep(i * .01)
raise RuntimeError("SlapOS not started")
def _getPartitionIpv6(self, i):
# returns (single_ipv6_address, ipv6_range) for a partition
# ipv6_address can be either a range or a single IPv6 address (with no /)
prefixlen = self._ipv6_range_prefixlen
if prefixlen is None:
return self._ipv6_address, None
ipv6_range = {'addr': self._ipv6_address, 'prefixlen': prefixlen}
ipv6_single = getPartitionIpv6Addr(ipv6_range, i)['addr']
if self._partitions_have_ipv6_range:
ipv6_partition_range = getPartitionIpv6Range(ipv6_range, i, 16)
return ipv6_single, ipv6_partition_range
else:
return ipv6_single, None
......@@ -442,16 +442,13 @@ class TestCliBoot(CliMixin):
patch('slapos.cli.boot.ConfigCommand.config_path', return_value=slapos_conf.name), \
patch(
'slapos.cli.boot.netifaces.ifaddresses',
return_value={socket.AF_INET6: ({'addr': '2000::1'},),},) as ifaddresses,\
patch('slapos.cli.boot._ping_hostname', return_value=1) as _ping_hostname:
return_value={socket.AF_INET6: ({'addr': '2000::1'},),},) as ifaddresses:
app.run(('node', 'boot'))
# boot command runs as root
check_root_user.assert_called_once()
# it waits for interface to have an IPv6 address
ifaddresses.assert_called_once_with('interface_name_from_config')
# then ping master hostname to wait for connectivity
_ping_hostname.assert_called_once_with('slap.vifib.com')
# then format and bang
SlapOSApp().run.assert_any_call(['node', 'format', '--now', '--verbose'])
SlapOSApp().run.assert_any_call(['node', 'bang', '-m', 'Reboot'])
......
This diff is collapsed.
......@@ -38,6 +38,7 @@ import socket
import sqlite3
import struct
import subprocess
import sys
import warnings
import jsonschema
......@@ -201,7 +202,7 @@ def ipv6FromBin(ip, suffix=''):
if suffix_len > 0:
ip += suffix.rjust(suffix_len, '0')
elif suffix_len:
sys.exit("Prefix exceeds 128 bits")
sys.exit("Prefix %s exceeds 128 bits by %d bit" % (ip, -suffix_len))
return socket.inet_ntop(socket.AF_INET6,
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
......@@ -214,11 +215,23 @@ def getPartitionIpv6Addr(ipv6_range, partition_index):
}
returns the IPv6 addr
addr::(partition_index+2) (address 1 is is used by re6st)
If the range is too small, wrap around
"""
addr = ipv6_range['addr']
prefixlen = ipv6_range['prefixlen']
prefix = binFromIpv6(addr)[:prefixlen]
return dict(addr=ipv6FromBin(prefix + bin(partition_index+2)[2:].zfill(128 - prefixlen)), prefixlen=prefixlen)
remaining = 128 - prefixlen
suffix = bin(partition_index+2)[2:]
if len(suffix) > remaining:
if remaining >= 2:
# skip reserved addresses 0 and 1
suffix = bin((partition_index % ((1 << remaining) - 2)) + 2)[2:]
else:
# truncate, we have no other addresses than 0 and 1
suffix = suffix[len(suffix) - remaining:]
suffix = suffix.zfill(remaining)
bits = prefix + suffix
return dict(addr=ipv6FromBin(bits), prefixlen=prefixlen)
def getIpv6RangeFactory(k, s):
"""
......
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