Skip to content

Commit f07c1ce

Browse files
committed
feat: implement allocator
1 parent b1545be commit f07c1ce

File tree

6 files changed

+205
-0
lines changed

6 files changed

+205
-0
lines changed

allocator/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .base_allocator import Allocator
2+
from .silent_allocator import SilentAllocator
3+
from .non_silent_allocator import NonSilentAllocator

allocator/base_allocator.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from .name_validator import NameValidator
2+
3+
4+
class InvalidHostName(ValueError):
5+
def __init__(self, msg):
6+
super().__init__()
7+
self.message = msg
8+
9+
10+
class Allocator:
11+
def __init__(self):
12+
self.allocated_hosts = dict()
13+
self.deallocated_pool = dict()
14+
15+
def allocate(self, host_type):
16+
if not NameValidator.hasValidHostType(host_type):
17+
return False
18+
19+
from_pool = self.getHostFromPool(host_type)
20+
if from_pool:
21+
return from_pool
22+
23+
new_instance_num = self.allocated_hosts.get(host_type, 1)
24+
25+
self.allocated_hosts[host_type] = new_instance_num + 1
26+
return f"{host_type}{new_instance_num}"
27+
28+
def getHostFromPool(self, host_type):
29+
host_pool = self.deallocated_pool.get(host_type, None)
30+
if host_pool:
31+
return host_pool.pop(0)
32+
33+
def deallocate(self, host_name):
34+
if not NameValidator.hasValidHostName(host_name):
35+
raise InvalidHostName("Invalid host name")
36+
37+
host_type, host_id = NameValidator.parseTypeIdFrom(host_name)
38+
39+
total_allocations = self.allocated_hosts.get(host_type, 0)
40+
41+
if host_id <= total_allocations:
42+
pool = self.deallocated_pool.get(host_type, list())
43+
pool.append(host_name)
44+
self.deallocated_pool[host_type] = pool
45+
return True
46+
return False

allocator/name_validator.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import re
2+
3+
class NameValidator:
4+
@staticmethod
5+
def hasValidHostType(host_type):
6+
pattern = r"^[a-z]+$"
7+
return re.match(pattern, host_type) is not None
8+
9+
@staticmethod
10+
def hasValidHostName(host_name):
11+
pattern = r"^[a-z]+[1-9]+[0-9]*"
12+
return re.match(pattern, host_name) is not None
13+
14+
@staticmethod
15+
def parseTypeFrom(host_name):
16+
pattern = r"[a-z]+"
17+
return re.search(pattern, host_name).group()
18+
19+
@staticmethod
20+
def parseIdFrom(host_name):
21+
pattern = r"\d+"
22+
id = re.search(pattern, host_name).group()
23+
return int(id)
24+
25+
@staticmethod
26+
def parseTypeIdFrom(host_name):
27+
return NameValidator.parseTypeFrom(host_name), NameValidator.parseIdFrom(
28+
host_name
29+
)

allocator/non_silent_allocator.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from .base_allocator import Allocator
2+
3+
4+
class NonSilentAllocator(Allocator):
5+
def __ini__(self):
6+
super().__init__()
7+
8+
def allocate(self, host_type):
9+
allocated_intance = super().allocate(host_type)
10+
if allocated_intance:
11+
return allocated_intance
12+
return "Invalid Name"
13+
14+
def deallocate(self, host_name):
15+
try:
16+
return super().deallocate(host_name)
17+
except ValueError as e:
18+
return e.message

allocator/silent_allocator.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .base_allocator import Allocator
2+
3+
class SilentAllocator(Allocator):
4+
def __init__(self):
5+
super().__init__()
6+
7+
def allocate(self, host_type):
8+
allocated_intance = super().allocate(host_type)
9+
if allocated_intance:
10+
return allocated_intance
11+
12+
def deallocate(self, host_name):
13+
try:
14+
super().deallocate(host_name)
15+
except:
16+
pass

base_test.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import pytest
2+
3+
from allocator import Allocator, SilentAllocator, NonSilentAllocator
4+
5+
6+
@pytest.fixture
7+
def allocator():
8+
return Allocator()
9+
10+
11+
@pytest.fixture
12+
def silent_allocator():
13+
return SilentAllocator()
14+
15+
16+
@pytest.fixture
17+
def non_silent_allocator():
18+
return NonSilentAllocator()
19+
20+
21+
def test_should_not_allocate_bad_name(allocator):
22+
assert allocator.allocate("invalid_name") is False
23+
24+
25+
def test_should_allocate_good_name(allocator):
26+
assert allocator.allocate("api") == "api1"
27+
28+
def test_should_allocate_two_times(allocator):
29+
assert allocator.allocate("api") == "api1"
30+
assert allocator.allocate("api") == "api2"
31+
32+
33+
def test_should_allocate_different_hosts(allocator):
34+
assert allocator.allocate("api") == "api1"
35+
assert allocator.allocate("db") == "db1"
36+
assert allocator.allocate("api") == "api2"
37+
assert allocator.allocate("db") == "db2"
38+
39+
40+
def test_should_return_error_on_bad_name_deallocate(allocator):
41+
assert allocator.allocate("api") == "api1"
42+
with pytest.raises(ValueError):
43+
assert allocator.deallocate("invalid_name") is False
44+
45+
46+
def test_should_allocate_deallocate(allocator):
47+
assert allocator.allocate("api") == "api1"
48+
assert allocator.deallocate("api1") is True
49+
50+
51+
def test_should_not_deallocate_on_not_allocated(allocator):
52+
assert allocator.deallocate("db1") is False
53+
assert allocator.allocate("api") == "api1"
54+
assert allocator.deallocate("api1") is True
55+
56+
57+
def test_should_use_previous_deallocated_type(allocator):
58+
assert allocator.allocate("api") == "api1"
59+
assert allocator.deallocate("api1") is True
60+
assert allocator.allocate("api") == "api1"
61+
62+
63+
def test_should_allocate_and_deallocate_sequentially(allocator):
64+
assert allocator.allocate("api") == "api1"
65+
assert allocator.allocate("api") == "api2"
66+
assert allocator.allocate("api") == "api3"
67+
68+
assert allocator.allocate("db") == "db1"
69+
assert allocator.allocate("api") == "api4"
70+
assert allocator.allocate("db") == "db2"
71+
72+
assert allocator.deallocate("db1") == True
73+
assert allocator.deallocate("api2") == True
74+
assert allocator.deallocate("api1") == True
75+
76+
assert allocator.allocate("api") == "api2"
77+
assert allocator.allocate("db") == "db1"
78+
assert allocator.allocate("api") == "api1"
79+
assert allocator.allocate("api") == "api5"
80+
81+
82+
def test_silent_allocator(silent_allocator):
83+
assert silent_allocator.allocate("api") == "api1"
84+
assert silent_allocator.allocate("invalid_name") is None
85+
assert silent_allocator.deallocate("invalid_name") is None
86+
assert silent_allocator.deallocate("api1") is None
87+
88+
89+
def test_non_silent_allocator(non_silent_allocator):
90+
assert non_silent_allocator.allocate("api") == "api1"
91+
assert non_silent_allocator.allocate("invalid_name") == "Invalid Name"
92+
assert non_silent_allocator.deallocate("invalid_name") == "Invalid host name"
93+
assert non_silent_allocator.deallocate("api1") is True

0 commit comments

Comments
 (0)