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
...@@ -1464,9 +1464,15 @@ def parse_computer_definition(conf, definition_path): ...@@ -1464,9 +1464,15 @@ def parse_computer_definition(conf, definition_path):
address_list.append(dict(addr=address, netmask=netmask)) address_list.append(dict(addr=address, netmask=netmask))
if computer_definition.has_option(section, 'ipv6_range'): if computer_definition.has_option(section, 'ipv6_range'):
ipv6_range_network = computer_definition.get(section, 'ipv6_range') ipv6_range_network = computer_definition.get(section, 'ipv6_range')
addr, netmask = ipv6_range_network.split('/') ipv6_range_addr, ipv6_range_prefixlen = ipv6_range_network.split('/')
netmask = netmaskFromLenIPv6(int(netmask)) ipv6_range_prefixlen = int(ipv6_range_prefixlen)
ipv6_range = {'addr' : address, 'netmask' : netmask, 'network' : ipv6_range_network} 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: else:
ipv6_range = {} ipv6_range = {}
tap = Tap(computer_definition.get(section, 'network_interface')) tap = Tap(computer_definition.get(section, 'network_interface'))
......
...@@ -493,11 +493,7 @@ class GenericPromise(with_metaclass(ABCMeta, object)): ...@@ -493,11 +493,7 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
)) ))
elif (not self.__is_tested and not check_anomaly) or \ elif (not self.__is_tested and not check_anomaly) or \
(not self.__is_anomaly_detected and check_anomaly): (not self.__is_anomaly_detected and check_anomaly):
# Anomaly or Test is disabled on this promise, send empty result # Anomaly or Test is disabled on this promise, send empty
if self.getConfig('slapgrid-version', '') <= '1.4.17':
# old version cannot send EmptyResult
self.__sendResult(PromiseQueueResult(item=TestResult()))
else:
self.__sendResult(PromiseQueueResult()) self.__sendResult(PromiseQueueResult())
else: else:
try: try:
......
...@@ -59,12 +59,25 @@ from .interface.slap import IRequester ...@@ -59,12 +59,25 @@ from .interface.slap import IRequester
from ..grid.slapgrid import SLAPGRID_PROMISE_FAIL from ..grid.slapgrid import SLAPGRID_PROMISE_FAIL
from .slap import slap 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 getSupervisorRPC
from ..grid.svcbackend import _getSupervisordSocketPath 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) @zope.interface.implementer(IException)
class SlapOSNodeCommandError(Exception): class SlapOSNodeCommandError(Exception):
"""Exception raised when running a SlapOS Node command failed. """Exception raised when running a SlapOS Node command failed.
...@@ -205,6 +218,7 @@ class SlapOSConfigWriter(ConfigWriter): ...@@ -205,6 +218,7 @@ class SlapOSConfigWriter(ConfigWriter):
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()) partition_forward_configuration = '\n'.join(self._getPartitionForwardConfiguration())
has_ipv6_range = ('false', 'true')[standalone_slapos._partitions_have_ipv6_range]
with open(path, 'w') as f: with open(path, 'w') as f:
f.write( f.write(
textwrap.dedent( textwrap.dedent(
...@@ -232,6 +246,7 @@ class SlapOSConfigWriter(ConfigWriter): ...@@ -232,6 +246,7 @@ class SlapOSConfigWriter(ConfigWriter):
create_tap = false create_tap = false
create_tun = false create_tun = false
computer_xml = {standalone_slapos._slapos_xml} computer_xml = {standalone_slapos._slapos_xml}
partition_has_ipv6_range = {has_ipv6_range}
[slapproxy] [slapproxy]
host = {standalone_slapos._server_ip} host = {standalone_slapos._server_ip}
...@@ -287,9 +302,7 @@ class SlapformatDefinitionWriter(ConfigWriter): ...@@ -287,9 +302,7 @@ class SlapformatDefinitionWriter(ConfigWriter):
""" """
def writeConfig(self, path): def writeConfig(self, path):
ipv4 = self._standalone_slapos._ipv4_address ipv4 = self._standalone_slapos._ipv4_address
ipv6 = self._standalone_slapos._ipv6_address ipv4_cidr = '%s/%s' % (ipv4, NETMASK_IPV4_FULL) if ipv4 else ''
ipv4_cidr = ipv4 + '/255.255.255.255' if ipv4 else ''
ipv6_cidr = ipv6 + '/64' if ipv6 else ''
user = pwd.getpwuid(os.getuid()).pw_name user = pwd.getpwuid(os.getuid()).pw_name
partition_base_name = self._standalone_slapos._partition_base_name partition_base_name = self._standalone_slapos._partition_base_name
with open(path, 'w') as f: with open(path, 'w') as f:
...@@ -299,12 +312,24 @@ class SlapformatDefinitionWriter(ConfigWriter): ...@@ -299,12 +312,24 @@ class SlapformatDefinitionWriter(ConfigWriter):
[computer] [computer]
address = {ipv4_cidr}\n address = {ipv4_cidr}\n
""").format(**locals())) """).format(**locals()))
ipv6 = self._standalone_slapos._ipv6_address
for i in range(self._standalone_slapos._partition_count): 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( f.write(
textwrap.dedent( textwrap.dedent(
""" """
[partition_{i}] [partition_{i}]
address = {ipv6_cidr} {ipv4_cidr} address = {ipv6_single_cidr} {ipv4_cidr}
{ipv6_range_config_line}
pathname = {partition_base_name}{i} pathname = {partition_base_name}{i}
user = {user} user = {user}
network_interface =\n network_interface =\n
...@@ -415,7 +440,13 @@ class StandaloneSlapOS(object): ...@@ -415,7 +440,13 @@ class StandaloneSlapOS(object):
self._partition_base_name = 'slappart' self._partition_base_name = 'slappart'
self._ipv4_address = None self._ipv4_address = None
self._ipv6_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_bin = slapos_bin
self._slapos_commands = { self._slapos_commands = {
...@@ -594,8 +625,12 @@ class StandaloneSlapOS(object): ...@@ -594,8 +625,12 @@ class StandaloneSlapOS(object):
partition_base_name="slappart"): partition_base_name="slappart"):
"""Creates `partition_count` partitions. """Creates `partition_count` partitions.
All partitions have the same `ipv4_address` and `ipv6_address` and All partitions have the same `ipv4_address` and use the current system
use the current system user. 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 When calling this a second time with a lower `partition_count` or with
different `partition_base_name` will delete existing partitions. different `partition_base_name` will delete existing partitions.
...@@ -628,17 +663,19 @@ class StandaloneSlapOS(object): ...@@ -628,17 +663,19 @@ class StandaloneSlapOS(object):
if not (os.path.exists(partition_path)): if not (os.path.exists(partition_path)):
os.mkdir(partition_path) os.mkdir(partition_path)
os.chmod(partition_path, 0o750) os.chmod(partition_path, 0o750)
ipv6_addr, ipv6_range = self._getPartitionIpv6(i)
partition_list.append({ partition_list.append({
'address_list': [ 'address_list': [
{ {
'addr': ipv4_address, 'addr': ipv4_address,
'netmask': '255.255.255.255' 'netmask': NETMASK_IPV4_FULL
}, },
{ {
'addr': ipv6_address, 'addr': ipv6_addr,
'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' 'netmask': NETMASK_IPV6_FULL
}, }
], ],
'ipv6_range' : ipv6_range,
'path': partition_path, 'path': partition_path,
'reference': partition_reference, 'reference': partition_reference,
'tap': { 'tap': {
...@@ -665,7 +702,7 @@ class StandaloneSlapOS(object): ...@@ -665,7 +702,7 @@ class StandaloneSlapOS(object):
self.computer.updateConfiguration( self.computer.updateConfiguration(
dumps({ dumps({
'address': ipv4_address, 'address': ipv4_address,
'netmask': '255.255.255.255', 'netmask': NETMASK_IPV4_FULL,
'partition_list': partition_list, 'partition_list': partition_list,
'reference': self._computer_id, 'reference': self._computer_id,
'instance_root': self._instance_root, 'instance_root': self._instance_root,
...@@ -694,11 +731,33 @@ class StandaloneSlapOS(object): ...@@ -694,11 +731,33 @@ class StandaloneSlapOS(object):
self._partition_count = partition_count self._partition_count = partition_count
self._partition_base_name = partition_base_name self._partition_base_name = partition_base_name
self._ipv4_address = ipv4_address 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: if old_partition_count != partition_count:
SlapOSConfigWriter(self).writeConfig(self._slapos_config) SlapOSConfigWriter(self).writeConfig(self._slapos_config)
SlapformatDefinitionWriter(self).writeConfig(self._slapformat_definition) 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"): def supply(self, software_url, computer_guid=None, state="available"):
"""Supply a software, see ISupply.supply """Supply a software, see ISupply.supply
...@@ -949,3 +1008,17 @@ class StandaloneSlapOS(object): ...@@ -949,3 +1008,17 @@ class StandaloneSlapOS(object):
return return
time.sleep(i * .01) time.sleep(i * .01)
raise RuntimeError("SlapOS not started") 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): ...@@ -442,16 +442,13 @@ class TestCliBoot(CliMixin):
patch('slapos.cli.boot.ConfigCommand.config_path', return_value=slapos_conf.name), \ patch('slapos.cli.boot.ConfigCommand.config_path', return_value=slapos_conf.name), \
patch( patch(
'slapos.cli.boot.netifaces.ifaddresses', 'slapos.cli.boot.netifaces.ifaddresses',
return_value={socket.AF_INET6: ({'addr': '2000::1'},),},) as ifaddresses,\ return_value={socket.AF_INET6: ({'addr': '2000::1'},),},) as ifaddresses:
patch('slapos.cli.boot._ping_hostname', return_value=1) as _ping_hostname:
app.run(('node', 'boot')) app.run(('node', 'boot'))
# boot command runs as root # boot command runs as root
check_root_user.assert_called_once() check_root_user.assert_called_once()
# it waits for interface to have an IPv6 address # it waits for interface to have an IPv6 address
ifaddresses.assert_called_once_with('interface_name_from_config') 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 # then format and bang
SlapOSApp().run.assert_any_call(['node', 'format', '--now', '--verbose']) SlapOSApp().run.assert_any_call(['node', 'format', '--now', '--verbose'])
SlapOSApp().run.assert_any_call(['node', 'bang', '-m', 'Reboot']) SlapOSApp().run.assert_any_call(['node', 'bang', '-m', 'Reboot'])
......
This diff is collapsed.
...@@ -38,6 +38,7 @@ import socket ...@@ -38,6 +38,7 @@ import socket
import sqlite3 import sqlite3
import struct import struct
import subprocess import subprocess
import sys
import warnings import warnings
import jsonschema import jsonschema
...@@ -201,7 +202,7 @@ def ipv6FromBin(ip, suffix=''): ...@@ -201,7 +202,7 @@ def ipv6FromBin(ip, suffix=''):
if suffix_len > 0: if suffix_len > 0:
ip += suffix.rjust(suffix_len, '0') ip += suffix.rjust(suffix_len, '0')
elif suffix_len: 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, return socket.inet_ntop(socket.AF_INET6,
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2))) struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
...@@ -214,11 +215,23 @@ def getPartitionIpv6Addr(ipv6_range, partition_index): ...@@ -214,11 +215,23 @@ def getPartitionIpv6Addr(ipv6_range, partition_index):
} }
returns the IPv6 addr returns the IPv6 addr
addr::(partition_index+2) (address 1 is is used by re6st) addr::(partition_index+2) (address 1 is is used by re6st)
If the range is too small, wrap around
""" """
addr = ipv6_range['addr'] addr = ipv6_range['addr']
prefixlen = ipv6_range['prefixlen'] prefixlen = ipv6_range['prefixlen']
prefix = binFromIpv6(addr)[: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): 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