Esempio n. 1
0
    def test_user_cap_no(self):
        size_byte = bitmath.GiB(1).to_Byte().value
        new_size = supplement_filesystem(size_byte, False)
        assert_that(new_size, equal_to(bitmath.GiB(1).to_Byte().value))

        size_byte = bitmath.GiB(3).to_Byte().value
        new_size = supplement_filesystem(size_byte, False)
        assert_that(new_size, equal_to(bitmath.GiB(3).to_Byte().value))
class EMCVNXBlockAPIInterfaceTests(
    test_blockdevice.make_iblockdeviceapi_tests(
        blockdevice_api_factory=(
            lambda test_case: emcblockdeviceapi_for_test(
                # XXX A hack to work around the LUN name length limit. We
                # need a better way to store the cluster_id.
                unicode(uuid4()).split('-')[0],
                test_case)
        ),
        minimum_allocatable_size=int(bitmath.GiB(8).to_Byte().value),
        device_allocation_unit=int(bitmath.GiB(8).to_Byte().value),
        unknown_blockdevice_id_factory=lambda test: unicode(uuid4())
    )
):
    """
Esempio n. 3
0
 def create_lun(self,
                lun_name=None,
                size_gb=1,
                sp=None,
                host_access=None,
                is_thin=None,
                description=None,
                tiering_policy=None,
                is_repl_dst=None,
                snap_schedule=None,
                io_limit_policy=None,
                is_compression=None):
     size = int(bitmath.GiB(size_gb).to_Byte().value)
     return UnityLun.create(self._cli,
                            lun_name,
                            self,
                            size,
                            sp=sp,
                            host_access=host_access,
                            is_thin=is_thin,
                            description=description,
                            is_repl_dst=is_repl_dst,
                            tiering_policy=tiering_policy,
                            snap_schedule=snap_schedule,
                            io_limit_policy=io_limit_policy,
                            is_compression=is_compression)
def test_scheduler_deployment_script():
    config = ClusterConfigImpl(
        host='host',
        port=1234,
        user='******',
        auth=AuthMethod.ASK,
        setup_actions=SetupActionsConfigImpl(dask=['echo ABC',
                                                   'echo DEF']))
    script = get_worker_deployment_script(
        scheduler_address='tcp://localhost:1111/',
        bokeh_port=2222,
        scratch_subdir='/scratch',
        cores=12,
        memory_limit=bitmath.GiB(10),
        log_file='/home/user/log',
        config=config)
    expected = (
        '#!/usr/bin/env bash\n'
        'echo ABC\n'
        'echo DEF\n'
        'export PATH="$PATH:$(python -m site --user-base)/bin"\n'
        'dask-worker tcp://localhost:1111/ --host 0.0.0.0 --bokeh'
        ' --bokeh-port 2222 --nanny --reconnect --nprocs 1 --nthreads 12'
        ' --memory-limit 10737418240 --local-directory /scratch'
        ' > /home/user/log 2>&1\n'
        'exit $?')
    print()
    print(repr(script))
    print(repr(expected))
    assert script == expected
    def _gib_to_bytes(size):
        """Convert size in bytes to GiB.

        :param size: The number of bytes.
        :returns: The size in gigabytes.
        """
        return int(bitmath.GiB(size).bytes)
Esempio n. 6
0
    def allocate_nodes(self,
                       nodes: int = 1,
                       cores: int = 1,
                       memory_per_node: Union[str, bitmath.Byte] = None,
                       walltime: Union[str, Walltime] = None,
                       native_args: Optional[
                           Dict[str, Optional[
                               str]]] = None) -> Nodes:  # noqa, pylint: disable=bad-whitespace,line-too-long
        if memory_per_node is None:
            memory_per_node = bitmath.GiB(1)
        elif isinstance(memory_per_node, str):
            memory_per_node = bitmath.parse_string(memory_per_node)

        if walltime is None:
            walltime = Walltime(minutes=10)
        elif isinstance(walltime, str):
            walltime = Walltime.from_string(walltime)

        parameters = AllocationParameters(nodes=nodes,
                                          cores=cores,
                                          memory_per_node=memory_per_node,
                                          walltime=walltime,
                                          native_args=native_args)

        return allocate_slurm_nodes(parameters=parameters,
                                    config=self._config)
Esempio n. 7
0
    def test_with_format(self):
        """bitmath.format context mgr sets and restores formatting"""
        to_print = [
            bitmath.Byte(101),
            bitmath.KiB(202),
            bitmath.MB(303),
            bitmath.GiB(404),
            bitmath.TB(505),
            bitmath.PiB(606),
            bitmath.EB(707)
        ]

        str_reps = [
            "101.00-Byte", "202.00-KiB", "303.00-MB", "404.00-GiB",
            "505.00-TB", "606.00-PiB", "707.00-EB"
        ]

        # Make sure formatting looks right BEFORE the context manager
        self.assertEqual(str(bitmath.KiB(1.337)), "1.337 KiB")

        with bitmath.format("{value:.2f}-{unit}"):
            for (inst, inst_str) in zip(to_print, str_reps):
                self.assertEqual(str(inst), inst_str)

        # Make sure formatting looks right AFTER the context manager
        self.assertEqual(str(bitmath.KiB(1.337)), "1.337 KiB")
Esempio n. 8
0
    def test_parse_unsafe_NIST(self):
        """parse_string_unsafe can parse all accepted NIST inputs"""
        # Begin with the kilo unit because it's the most tricky (SI
        # defines the unit as a lower-case 'k')
        kilo_inputs = [
            '100ki',
            '100Ki',
            '100kib',
            '100KiB',
            '100kiB'
        ]
        expected_kilo_result = bitmath.KiB(100)

        for ki in kilo_inputs:
            _parsed = bitmath.parse_string_unsafe(ki)
            self.assertEqual(_parsed, expected_kilo_result)
            self.assertIs(type(_parsed), type(expected_kilo_result))

        # Now check for other easier to parse prefixes
        other_inputs = [
            '100gi',
            '100Gi',
            '100gib',
            '100giB',
            '100GiB'
        ]

        expected_gig_result = bitmath.GiB(100)

        for gi in other_inputs:
            _parsed = bitmath.parse_string_unsafe(gi)
            self.assertEqual(_parsed, expected_gig_result)
            self.assertIs(type(_parsed), type(expected_gig_result))
    def test_best_prefix_prefer_NIST_from_NIST(self):
        """NIST: Best prefix honors a NIST preference when starting with an NIST unit

Start with a NIST (GiB) unit and prefer a NIST unit as the result (MiB)"""
        # This should be MiB(512.0)
        should_be_MiB = bitmath.GiB(0.5).best_prefix(system=bitmath.NIST)
        self.assertIs(type(should_be_MiB), bitmath.MiB)
Esempio n. 10
0
def test_serialize_deserialize():
    value = AllocationParameters(nodes=20,
                                 cores=10,
                                 memory_per_node=bitmath.GiB(20),
                                 walltime=Walltime(days=1,
                                                   hours=12,
                                                   minutes=5,
                                                   seconds=32),
                                 native_args={
                                     '--arg1': 'value 1',
                                     '--arg2': None,
                                     '--arg3': '65'
                                 })
    serialized = value.serialize()
    assert serialized == {
        'type': 'SerializableTypes.ALLOCATION_PARAMETERS',
        'nodes': 20,
        'cores': 10,
        'memory_per_node': '20.0 GiB',
        'walltime': '1-12:05:32',
        'native_args': {
            '--arg1': 'value 1',
            '--arg2': None,
            '--arg3': '65'
        }
    }

    deserialized = AllocationParameters.deserialize(serialized=serialized)
    assert deserialized == value
Esempio n. 11
0
 def test_BitmathType_good_two_args(self):
     """Argparse: BitmathType - Works when given two correct parameters"""
     args = "--two-args 1337B 0.001GiB"
     result = self._parse_two_args(args)
     self.assertEqual(len(result.two_args), 2)
     self.assertIn(bitmath.Byte(1337), result.two_args)
     self.assertIn(bitmath.GiB(0.001), result.two_args)
Esempio n. 12
0
 def create_lun(self,
                lun_name=None,
                size_gb=1,
                sp=None,
                host_access=None,
                is_thin=None,
                description=None,
                tiering_policy=None,
                is_repl_dst=None,
                snap_schedule=None,
                is_snap_schedule_paused=None,
                skip_sync_to_remote_system=None,
                io_limit_policy=None,
                is_compression=None,
                is_advanced_dedup_enabled=None):
     size = int(bitmath.GiB(size_gb).to_Byte().value)
     return UnityLun.create(
         self._cli,
         lun_name,
         self,
         size,
         sp=sp,
         host_access=host_access,
         is_thin=is_thin,
         description=description,
         is_repl_dst=is_repl_dst,
         tiering_policy=tiering_policy,
         snap_schedule=snap_schedule,
         is_snap_schedule_paused=is_snap_schedule_paused,
         skip_sync_to_remote_system=skip_sync_to_remote_system,
         io_limit_policy=io_limit_policy,
         is_compression=is_compression,
         is_advanced_dedup_enabled=is_advanced_dedup_enabled)
Esempio n. 13
0
    def test_best_prefix_prefer_SI_from_NIST(self):
        """SI: Best prefix honors a SI preference when starting with a NIST unit

Start with a NIST (GiB) unit and prefer a SI unit as the result (MB)"""
        # Start with GiB, an NIST unit
        should_be_MB = bitmath.GiB(0.5).best_prefix(system=bitmath.SI)
        self.assertIs(type(should_be_MB), bitmath.MB)
Esempio n. 14
0
    def test_parse_unsafe_NIST_units(self):
        """parse_string_unsafe can parse abbreviated NIST units (Gi, Ki, ...)"""
        nist_input = "100 Gi"
        expected_result = bitmath.GiB(100)

        self.assertEqual(
            bitmath.parse_string_unsafe(nist_input),
            expected_result)
Esempio n. 15
0
def test_able_to_sync_nodes_before_and_after_wait():
    user = USER_43
    with ExitStack() as stack:
        stack.enter_context(disable_pytest_stdin())
        stack.enter_context(set_up_key_location(user))
        stack.enter_context(reset_environment(user))
        stack.enter_context(set_password(get_test_user_password(user)))
        stack.enter_context(clear_deployment_sync_data(user))

        cluster = show_cluster(name=TEST_CLUSTER)

        nodes = cluster.allocate_nodes()
        nodes_2 = None
        nodes_3 = None
        try:
            deployments = cluster.pull_deployments()
            assert not deployments.nodes

            cluster.push_deployment(deployment=nodes)
            nodes = None
            deployments = cluster.pull_deployments()
            print(deployments)

            assert len(deployments.nodes) == 1
            nodes_2 = deployments.nodes[0]
            assert len(nodes_2) == 1
            nodes_2.wait(timeout=SLURM_WAIT_TIMEOUT)
            assert nodes_2.running()
            node_2 = nodes_2[0]

            cluster.push_deployment(deployment=nodes_2)
            nodes_2 = None
            deployments = cluster.pull_deployments()
            print(deployments)

            assert len(deployments.nodes) == 1
            nodes_3 = deployments.nodes[0]

            assert nodes_3.running()
            with pytest.raises(RuntimeError):
                nodes_3.wait()
            assert len(nodes_3) == 1
            node_3 = nodes_3[0]

            assert node_2.host == node_3.host
            assert node_2.port == node_3.port
            assert node_3.resources.cpu_cores == 1
            assert node_3.resources.memory_total == bitmath.GiB(1)
            print(node_3)

            assert node_3.run('whoami') == user
        finally:
            if nodes is not None:
                nodes.cancel()
            if nodes_2 is not None:
                nodes_2.cancel()
            if nodes_3 is not None:
                nodes_3.cancel()
Esempio n. 16
0
def test_can_read_node_resources():
    user = USER_39
    with ExitStack() as stack:
        stack.enter_context(disable_pytest_stdin())
        stack.enter_context(set_up_key_location(user))
        stack.enter_context(reset_environment(user))
        stack.enter_context(set_password(get_test_user_password(user)))

        cluster = show_cluster(name=TEST_CLUSTER)

        access_node = cluster.get_access_node()
        print(str(access_node))
        assert str(access_node) == repr(access_node)

        assert access_node.resources.cpu_cores is None
        assert access_node.resources.memory_total is None
        start_stress_cpu(user=user, timeout=10)
        try:
            check_resources_in_believable_range(access_node.resources)
        finally:
            stop_stress_cpu(user=user)

        nodes = cluster.allocate_nodes(cores=1,
                                       memory_per_node=bitmath.GiB(0.8))

        assert len(nodes) == 1
        node = nodes[0]

        stack.enter_context(cancel_on_exit(nodes))

        nodes.wait(timeout=SLURM_WAIT_TIMEOUT)
        assert nodes.running()

        assert node.resources.cpu_cores == 1
        assert node.resources.memory_total == bitmath.GiB(0.8)
        start_stress_cpu(user=user, timeout=10)
        try:
            check_resources_in_believable_range(access_node.resources)
        finally:
            stop_stress_cpu(user=user)

        assert node.run('whoami') == user
Esempio n. 17
0
def to_MiB(n):
    if "K" in n[1]:
        return int(round(bitmath.KiB(n[0]).to_MiB()))
    elif "M" in n[1]:
        return int(round(bitmath.MiB(n[0]).to_MiB()))
    elif "G" in n[1]:
        return int(round(bitmath.GiB(n[0]).to_MiB()))
    elif "T" in n[1]:
        return int(round(bitmath.TiB(n[0]).to_MiB()))
    else:
        return int(round(float(n[0])))
Esempio n. 18
0
    def test_click_BitmathType_good_two_args(self):
        @click.command()
        @click.argument('arg1', type=BitmathType())
        @click.argument('arg2', type=BitmathType())
        def func(arg1, arg2):
            click.echo(arg1)
            click.echo(arg2)

        result = self.runner.invoke(func, ['1337B', '0.001GiB'])
        self.assertFalse(result.exception)
        self.assertEqual(result.output.splitlines(),
                         [str(bitmath.Byte(1337)),
                          str(bitmath.GiB(0.001))])
Esempio n. 19
0
    def test_sort_heterogeneous_list(self):
        """Different types in a list can be sorted properly

Define these with the bytes keyword so we don't lose our minds trying
to figure out if the results are correct."""
        first = bitmath.KiB(bytes=0)
        second = bitmath.GiB(bytes=1337)
        third = bitmath.Eb(bytes=2048)
        fourth = bitmath.Byte(bytes=96783)
        unsorted_list = [fourth, second, first, third]
        sorted_list = sorted(unsorted_list)
        self.assertIs(sorted_list[0], first)
        self.assertIs(sorted_list[1], second)
        self.assertIs(sorted_list[2], third)
        self.assertIs(sorted_list[3], fourth)
Esempio n. 20
0
    def setUp(self):
        self.bit = bitmath.Bit(1)
        self.byte = bitmath.Byte(1)
        # NIST units
        self.kib = bitmath.KiB(1)
        self.mib = bitmath.MiB(1)
        self.gib = bitmath.GiB(1)
        self.tib = bitmath.TiB(1)
        self.pib = bitmath.PiB(1)
        self.eib = bitmath.EiB(1)

        # SI units
        self.kb = bitmath.kB(1)
        self.mb = bitmath.MB(1)
        self.gb = bitmath.GB(1)
        self.tb = bitmath.TB(1)
        self.pb = bitmath.PB(1)
        self.eb = bitmath.EB(1)
Esempio n. 21
0
def size_to_bytes(human_size):
    PARSE_REGEXP = r"(\d+)([MGTPE]i)"
    parse = re.compile(PARSE_REGEXP)
    try:
        size, unit = re.match(parse, human_size).group(1, 2)
        size = int(size)
        assert size > 0

        if unit == 'Mi':
            return int(bitmath.MiB(size).to_Byte())
        elif unit == 'Gi':
            return int(bitmath.GiB(size).to_Byte())
        elif unit == 'Ti':
            return int(bitmath.TiB(size).to_Byte())
        elif unit == 'Pi':
            return int(bitmath.PiB(size).to_Byte())
        elif unit == 'Ei':
            return int(bitmath.EiB(size).to_Byte())
        else:
            return 0
    except Exception as e:
        return 0
Esempio n. 22
0
def test_allocate_defaults():
    user = USER_22
    with ExitStack() as stack:
        stack.enter_context(disable_pytest_stdin())
        stack.enter_context(set_up_key_location(user))
        stack.enter_context(reset_environment(user))
        stack.enter_context(set_password(get_test_user_password(user)))

        cluster = show_cluster(name=TEST_CLUSTER)

        nodes = cluster.allocate_nodes()
        stack.enter_context(cancel_on_exit(nodes))
        assert len(nodes) == 1
        node = nodes[0]

        nodes.wait(timeout=SLURM_WAIT_TIMEOUT)
        assert nodes.running()

        assert node.resources.cpu_cores == 1
        assert node.resources.memory_total == bitmath.GiB(1)
        print(node)

        assert node.run('whoami') == user
Esempio n. 23
0
def test_serialize_deserialize():
    config = get_config_for_test()

    value = NodeImpl(config=config)
    value.make_allocated(host='node1',
                         port=12323,
                         cores=30,
                         memory=bitmath.GiB(60),
                         allocated_until=(datetime.datetime(
                             2018, 11, 12, 13,
                             14).replace(tzinfo=dateutil.tz.tzutc())))

    serialized = value.serialize()
    assert serialized == {
        'type': 'SerializableTypes.NODE_IMPL',
        'host': 'node1',
        'port': 12323,
        'cores': 30,
        'memory': '60.0 GiB',
        'allocated_until': '2018-11-12T13:14:00+00:00'
    }

    deserialized = NodeImpl.deserialize(config=config, serialized=serialized)
    assert deserialized == value
Esempio n. 24
0
 def create_vmfs(self,
                 vmfs_name=None,
                 size_gb=1,
                 sp=None,
                 host_access=None,
                 is_thin=None,
                 description=None,
                 tiering_policy=None,
                 is_repl_dst=None,
                 snap_schedule=None,
                 is_snap_schedule_paused=None,
                 skip_sync_to_remote_system=None,
                 io_limit_policy=None,
                 is_compression=None,
                 major_version=None,
                 block_size=None):
     size = int(bitmath.GiB(size_gb).to_Byte().value)
     return UnityLun.create(
         self._cli,
         vmfs_name,
         self,
         size,
         sp=sp,
         host_access=host_access,
         is_thin=is_thin,
         description=description,
         is_repl_dst=is_repl_dst,
         tiering_policy=tiering_policy,
         snap_schedule=snap_schedule,
         is_snap_schedule_paused=is_snap_schedule_paused,
         skip_sync_to_remote_system=skip_sync_to_remote_system,
         io_limit_policy=io_limit_policy,
         is_compression=is_compression,
         create_vmfs=True,
         major_version=major_version,
         block_size=block_size)
Esempio n. 25
0
import os
from uuid import uuid4
import yaml

from flocker.node.agents import blockdevice
from flocker.node.agents.test.test_blockdevice import (
    make_iblockdeviceapi_tests)
from flocker.node.agents.test.test_blockdevice import (
    make_iprofiledblockdeviceapi_tests)
from twisted.python.components import proxyForInterface
from zope.interface import implementer

from dell_storagecenter_driver.dell_storagecenter_blockdevice import (
    create_driver_instance)

MIN_ALLOCATION_SIZE = bitmath.GiB(1).bytes
MIN_ALLOCATION_UNIT = MIN_ALLOCATION_SIZE

LOG = logging.getLogger(__name__)


@implementer(blockdevice.IBlockDeviceAPI, blockdevice.IProfiledBlockDeviceAPI)
class TestDriver(proxyForInterface(blockdevice.IBlockDeviceAPI, 'original')):
    """Wrapper around driver class to provide test cleanup."""
    def __init__(self, original):
        self.original = original
        self.volumes = {}

    def _cleanup(self):
        """Clean up testing artifacts."""
        with self.original._client.open_connection() as api:
import bitmath
from eliot import Logger
from eliot import Message
from eliot import startTask
from flocker.node.agents import blockdevice
from twisted.python import filepath
from zope.interface import implementer

from flocker.node.agents.blockdevice import (
    AlreadyAttachedVolume, BlockDeviceVolume,
    IBlockDeviceAPI, UnknownVolume,
    UnattachedVolume, IProfiledBlockDeviceAPI)
from solidfire_flocker_driver import sfapi
from solidfire_flocker_driver import utils

ALLOCATION_UNIT = bitmath.GiB(1).bytes
logger = Logger()


def initialize_driver(cluster_id, **kwargs):
    """Initialize a new instance of the SolidFire driver.

    :param kwargs['endpoint', 'vag_name', 'account_name']
    :return: SolidFireBolockDeviceAPI object
    """
    return SolidFireBlockDeviceAPI(str(cluster_id), **kwargs)


@implementer(IBlockDeviceAPI)
@implementer(IProfiledBlockDeviceAPI)
class SolidFireBlockDeviceAPI(object):
 def test_simple_round_down(self):
     """NIST: 1 MiB (as a GiB()) rounds down into a MiB()"""
     # Represent one MiB as a small GiB
     MiB_in_GiB = bitmath.GiB(bytes=1048576)
     # This should turn into a MiB
     self.assertIs(type(MiB_in_GiB.best_prefix()), bitmath.MiB)
Esempio n. 28
0
def _GiB_to_Byte(size_gb):
    return bitmath.GiB(size_gb).to_Byte().value
Esempio n. 29
0
 def test_parse_string_unicode(self):
     """parse_string can handle a unicode string"""
     self.assertEqual(
         bitmath.parse_string(u"750 GiB"),
         bitmath.GiB(750))
Esempio n. 30
0
class Config:
    """
    The central configuration hub for the operator.

    To access the config from another module, import
    :data:`crate.operator.config.config` and access its attributes.
    """

    #: Time in seconds for which the operator will continue and wait to
    #: bootstrap a cluster. Once this threshold has passed, a bootstrapping is
    #: considered failed
    BOOTSTRAP_TIMEOUT: Optional[int] = 1800

    #: When set, enable special handling for the defind cloud provider, e.g. on
    #: AWS pass the availability zone as a CrateDB node attribute.
    CLOUD_PROVIDER: Optional[CloudProvider] = None

    #: The Docker image that contains scripts to run cluster backups.
    CLUSTER_BACKUP_IMAGE: Optional[str] = None

    #: The volume size for the ``PersistentVolume`` that is used as a storage
    #: location for Java heap dumps.
    DEBUG_VOLUME_SIZE: bitmath.Byte = bitmath.GiB(256)

    #: The Kubernetes storage class name for the ``PersistentVolume`` that is
    #: used as a storage location for Java heap dumps.
    DEBUG_VOLUME_STORAGE_CLASS: str = "crate-local"

    #: A list of image pull secrets. Separate names by ``,``.
    IMAGE_PULL_SECRETS: Optional[List[str]] = None

    #: JMX exporter version
    JMX_EXPORTER_VERSION: str

    #: The path the Kubernetes configuration to use.
    KUBECONFIG: Optional[str] = None

    #: The log level to use for all CrateDB operator related log messages.
    LOG_LEVEL: str = "INFO"

    #: Time in seconds for which the operator will continue and wait to perform
    #: a rolling restart of a cluster. Once this threshold has passed, a
    #: restart is considered failed.
    ROLLING_RESTART_TIMEOUT = 3600

    #: Time in seconds for which the operator will continue and wait to scale a
    #: cluster up or down, including deallocating nodes before turning them
    #: off. Once the threshold has passed, a scaling operation is considered
    #: failed.
    SCALING_TIMEOUT = 3600

    #: Enable several testing behaviors, such as relaxed pod anti-affinity to
    #: allow for easier testing in smaller Kubernetes clusters.
    TESTING: bool = False

    #: HTTP Basic Auth password for web requests made to :attr:`WEBHOOK_URL`.
    WEBHOOK_PASSWORD: Optional[str] = None

    #: Full URL where the operator will send HTTP POST requests to when certain
    #: events occured.
    WEBHOOK_URL: Optional[str] = None

    #: HTTP Basic Auth username for web requests made to :attr:`WEBHOOK_URL`.
    WEBHOOK_USERNAME: Optional[str] = None

    #: Which table are the running jobs stored in. This is only changed in tests.
    JOBS_TABLE: str = "sys.jobs"

    def __init__(self, *, prefix: str):
        self._prefix = prefix

    def load(self):
        bootstrap_timeout = self.env("BOOTSTRAP_TIMEOUT",
                                     default=str(self.BOOTSTRAP_TIMEOUT))
        try:
            self.BOOTSTRAP_TIMEOUT = int(bootstrap_timeout)
        except ValueError:
            raise ConfigurationError(
                f"Invalid {self._prefix}BOOTSTRAP_TIMEOUT="
                f"'{bootstrap_timeout}'. Needs to be a positive integer or 0.")
        if self.BOOTSTRAP_TIMEOUT < 0:
            raise ConfigurationError(
                f"Invalid {self._prefix}BOOTSTRAP_TIMEOUT="
                f"'{bootstrap_timeout}'. Needs to be a positive integer or 0.")
        if self.BOOTSTRAP_TIMEOUT == 0:
            self.BOOTSTRAP_TIMEOUT = None

        cloud_provider = self.env("CLOUD_PROVIDER",
                                  default=self.CLOUD_PROVIDER)
        if cloud_provider is not None:
            try:
                self.CLOUD_PROVIDER = CloudProvider(cloud_provider)
            except ValueError:
                allowed = ", ".join(CloudProvider.__members__.values())
                raise ConfigurationError(
                    f"Invalid {self._prefix}CLOUD_PROVIDER="
                    f"'{cloud_provider}'. Needs to be of {allowed}.")

        self.CLUSTER_BACKUP_IMAGE = self.env("CLUSTER_BACKUP_IMAGE",
                                             default=self.CLUSTER_BACKUP_IMAGE)

        debug_volume_size = self.env("DEBUG_VOLUME_SIZE",
                                     default=str(self.DEBUG_VOLUME_SIZE))
        try:
            self.DEBUG_VOLUME_SIZE = bitmath.parse_string(debug_volume_size)
        except ValueError:
            raise ConfigurationError(
                f"Invalid {self._prefix}DEBUG_VOLUME_SIZE='{debug_volume_size}'."
            )

        self.DEBUG_VOLUME_STORAGE_CLASS = self.env(
            "DEBUG_VOLUME_STORAGE_CLASS",
            default=self.DEBUG_VOLUME_STORAGE_CLASS)

        secrets = self.env("IMAGE_PULL_SECRETS",
                           default=self.IMAGE_PULL_SECRETS)
        if secrets is not None:
            self.IMAGE_PULL_SECRETS = [
                s for s in (secret.strip() for secret in secrets.split(","))
                if s
            ]

        self.JMX_EXPORTER_VERSION = self.env("JMX_EXPORTER_VERSION")

        self.KUBECONFIG = self.env("KUBECONFIG", default=self.KUBECONFIG)
        if self.KUBECONFIG is not None:
            # When the CRATEDB_OPERATOR_KUBECONFIG env var is set we need to
            # ensure that KUBECONFIG env var is set to the same value for
            # PyKube login of the Kopf framework to work correctly.
            os.environ["KUBECONFIG"] = self.KUBECONFIG
        else:
            self.KUBECONFIG = os.getenv("KUBECONFIG")
        if self.KUBECONFIG is not None:
            for path in self.KUBECONFIG.split(ENV_KUBECONFIG_PATH_SEPARATOR):
                if not os.path.exists(path):
                    raise ConfigurationError(
                        "The KUBECONFIG environment variable contains a path "
                        f"'{path}' that does not exist.")

        self.LOG_LEVEL = self.env("LOG_LEVEL", default=self.LOG_LEVEL)
        level = logging.getLevelName(self.LOG_LEVEL)
        for logger_name in ("", "crate", "kopf", "kubernetes_asyncio"):
            logger = logging.getLogger(logger_name)
            logger.setLevel(level)

        rolling_restart_timeout = self.env("ROLLING_RESTART_TIMEOUT",
                                           default=str(
                                               self.ROLLING_RESTART_TIMEOUT))
        try:
            self.ROLLING_RESTART_TIMEOUT = int(rolling_restart_timeout)
        except ValueError:
            raise ConfigurationError(
                f"Invalid {self._prefix}ROLLING_RESTART_TIMEOUT="
                f"'{rolling_restart_timeout}'. Needs to be a positive integer or 0."
            )
        if self.ROLLING_RESTART_TIMEOUT < 0:
            raise ConfigurationError(
                f"Invalid {self._prefix}ROLLING_RESTART_TIMEOUT="
                f"'{rolling_restart_timeout}'. Needs to be a positive integer or 0."
            )

        scaling_timeout = self.env("SCALING_TIMEOUT",
                                   default=str(self.SCALING_TIMEOUT))
        try:
            self.SCALING_TIMEOUT = int(scaling_timeout)
        except ValueError:
            raise ConfigurationError(
                f"Invalid {self._prefix}SCALING_TIMEOUT="
                f"'{scaling_timeout}'. Needs to be a positive integer or 0.")
        if self.SCALING_TIMEOUT < 0:
            raise ConfigurationError(
                f"Invalid {self._prefix}SCALING_TIMEOUT="
                f"'{scaling_timeout}'. Needs to be a positive integer or 0.")

        testing = self.env("TESTING", default=str(self.TESTING))
        self.TESTING = testing.lower() == "true"

        self.WEBHOOK_PASSWORD = self.env("WEBHOOK_PASSWORD",
                                         default=self.WEBHOOK_PASSWORD)
        self.WEBHOOK_URL = self.env("WEBHOOK_URL", default=self.WEBHOOK_URL)
        self.WEBHOOK_USERNAME = self.env("WEBHOOK_USERNAME",
                                         default=self.WEBHOOK_USERNAME)
        self.JOBS_TABLE = self.env("JOBS_TABLE", default=self.JOBS_TABLE)

    def env(self, name: str, *, default=UNDEFINED) -> str:
        """
        Retrieve the environment variable ``name`` or fall-back to its default
        if provided. If no default is provided, a :exc:`~.ConfigurationError` is
        raised.
        """
        full_name = f"{self._prefix}{name}"
        try:
            return os.environ[full_name]
        except KeyError:
            if default is UNDEFINED:
                # raise from None - so that the traceback of the original
                # exception (KeyError) is not printed
                # https://docs.python.org/3.8/reference/simple_stmts.html#the-raise-statement
                raise ConfigurationError(
                    f"Required environment variable '{full_name}' is not set."
                ) from None
            return default