Skip to content
17 changes: 15 additions & 2 deletions linode_api4/objects/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@
from linode_api4.objects.serializable import JSONObject, StrEnum
from linode_api4.objects.vpc import VPC, VPCSubnet
from linode_api4.paginated_list import PaginatedList
from linode_api4.util import drop_null_keys
from linode_api4.util import drop_null_keys, generate_device_suffixes

PASSWORD_CHARS = string.ascii_letters + string.digits + string.punctuation
MIN_DEVICE_LIMIT = 8
MB_PER_GB = 1024
MAX_DEVICE_LIMIT = 64


class InstanceDiskEncryptionType(StrEnum):
Expand Down Expand Up @@ -1257,9 +1260,19 @@ def config_create(
from .volume import Volume # pylint: disable=import-outside-toplevel

hypervisor_prefix = "sd" if self.hypervisor == "kvm" else "xvd"

device_limit = int(
max(
MIN_DEVICE_LIMIT,
min(self.specs.memory // MB_PER_GB, MAX_DEVICE_LIMIT),
)
)

device_names = [
hypervisor_prefix + string.ascii_lowercase[i] for i in range(0, 8)
hypervisor_prefix + suffix
for suffix in generate_device_suffixes(device_limit)
]

device_map = {
device_names[i]: None for i in range(0, len(device_names))
}
Expand Down
26 changes: 26 additions & 0 deletions linode_api4/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Contains various utility functions.
"""

import string
from typing import Any, Dict


Expand All @@ -27,3 +28,28 @@ def recursive_helper(value: Any) -> Any:
return value

return recursive_helper(data)


def generate_device_suffixes(n: int) -> list[str]:
"""
Generate n alphabetical suffixes starting with a, b, c, etc.
After z, continue with aa, ab, ac, etc. followed by aaa, aab, etc.
Example:
generate_device_suffixes(30) ->
['a', 'b', 'c', ..., 'z', 'aa', 'ab', 'ac', 'ad']
"""
letters = string.ascii_lowercase
result = []
i = 0

while len(result) < n:
s = ""
x = i
while True:
s = letters[x % 26] + s
x = x // 26 - 1
if x < 0:
break
result.append(s)
i += 1
return result
40 changes: 40 additions & 0 deletions test/integration/models/volume/test_blockstorage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from test.integration.conftest import get_region
from test.integration.helpers import get_test_label, retry_sending_request


def test_config_create_with_extended_volume_limit(test_linode_client):
client = test_linode_client

region = get_region(client, {"Linodes", "Block Storage"}, site_type="core")
label = get_test_label()

linode, _ = client.linode.instance_create(
"g6-standard-6",
region,
image="linode/debian12",
label=label,
)

volumes = [
client.volume_create(
f"{label}-vol-{i}",
region=region,
size=10,
)
for i in range(12)
]

config = linode.config_create(volumes=volumes)

devices = config._raw_json["devices"]

assert len([d for d in devices.values() if d is not None]) == 12

assert "sdi" in devices
assert "sdj" in devices
assert "sdk" in devices
assert "sdl" in devices

linode.delete()
for v in volumes:
retry_sending_request(3, v.delete)
121 changes: 120 additions & 1 deletion test/unit/util_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from linode_api4.util import drop_null_keys
from linode_api4.util import drop_null_keys, generate_device_suffixes


class UtilTest(unittest.TestCase):
Expand Down Expand Up @@ -53,3 +53,122 @@ def test_drop_null_keys_recursive(self):
}

assert drop_null_keys(value) == expected_output

def test_generate_device_suffixes(self):
"""
Tests whether generate_device_suffixes works as expected.
"""

expected_output_12 = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
]
assert generate_device_suffixes(12) == expected_output_12

expected_output_30 = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"aa",
"ab",
"ac",
"ad",
]
assert generate_device_suffixes(30) == expected_output_30

expected_output_60 = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"aa",
"ab",
"ac",
"ad",
"ae",
"af",
"ag",
"ah",
"ai",
"aj",
"ak",
"al",
"am",
"an",
"ao",
"ap",
"aq",
"ar",
"as",
"at",
"au",
"av",
"aw",
"ax",
"ay",
"az",
"ba",
"bb",
"bc",
"bd",
"be",
"bf",
"bg",
"bh",
]
assert generate_device_suffixes(60) == expected_output_60