Colony
my most ambitious (and needlessly convoluted) project yet
Colony is a project that I started to challenge myself. Its goal is to work as a
self-sustainable network of nodes with some tasks to do. It's supposed to be managing itself in
the most effective and resourceful way possible.
Basic Flow
1. boot
starts its base roles
2. look around
pings the IP range and old peers
3. say hello
swaps member lists with whoever answered
refused
backs off for a bit
first node
starts a new colony and takes every role
4. gossip
keeps chatting with peers, forgets silent ones
5. fill gaps
finds roles nobody holds, asks the best node
take the role?
that node accepts or declines
- 1. boot to 2. look around
- 2. look around to 3. say hello
- 3. say hello to refused
- refused to 2. look around (retry)
- 2. look around to first node (no answer)
- 3. say hello to 4. gossip
- first node to 4. gossip
- 4. gossip to 5. fill gaps
- 5. fill gaps and take the role?, both directions
- take the role? to 4. gossip (accepted)
class Discover:
DISCOVER_PORT = 28149
MAGIC = b"OK"
@staticmethod
def listen(real_port, host="0.0.0.0"):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, Discover.DISCOVER_PORT))
s.listen()
while True:
conn, _ = s.accept()
try:
data = conn.recv(2)
if data == Discover.MAGIC:
conn.sendall(struct.pack("!H", real_port))
finally:
conn.close()
@staticmethod
def request(target_host, timeout=3):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
s.connect((target_host, Discover.DISCOVER_PORT))
s.sendall(Discover.MAGIC)
data = s.recv(2)
s.close()
return struct.unpack("!H", data)[0]
The device being already in the network sends a request to a potential node. All the potential
nodes listen on port 28149 and if they hear the message "OK" (bytes) then they send back the
port on which the actual colony stuff happens.
class Foreman(Role):
CONTINUOUS = True
LOOP_SECONDS_WAIT = 60
class COMMANDS(Enum):
pass
...
def tick(self):
membership = self.context.membership()
if membership.denied:
return None
now = membership.now()
records = membership.alive_records()
roles = self.context.get_role_registry().roles
unmet = advice.unmet_roles(records, roles)
self._needed_since = {name: self._needed_since.get(name, now) for name in unmet}
self._proposals = {
key: (at, accepted) for key, (at, accepted) in self._proposals.items()
if now - at < self.proposal_cooldown
}
alone = len(records) == 1
rank = advice.rank_of(membership.key, records)
wait = 0 if alone else self.acquire_delay + min(rank, 3) * self.proposal_stagger
for role_name in unmet:
if now - self._needed_since[role_name] < wait:
continue
recent = {key: accepted for (name, key), (_, accepted) in self._proposals.items() if name == role_name}
if any(recent.values()):
continue
target = advice.pick_candidate(role_name, roles[role_name], records, exclude=set(recent))
if target is None:
if not recent:
self.logger.warning(f"No node is willing to take role {role_name}")
continue
result = self.propose(role_name, target)
self._proposals[(role_name, target.key)] = (now, result[2] is True)
return result
return None
Then it initializes the basic roles, either the basic ones that it needs for network to work
properly or roles given to it by the network's already existing Foreman role.
All roles
There are several roles that nodes take
available_roles = [
RoleStats(
cls=Unit,
priority=RolePriority.GENERAL,
),
RoleStats(
cls=Gossip,
priority=RolePriority.GENERAL,
),
RoleStats(
cls=Foreman,
priority=RolePriority.GENERAL,
),
RoleStats(
cls=VaultWarden,
priority=RolePriority.CRITICAL,
hardware_preferences=HardwarePreferences(
{
HardwareType.DISK_SPACE: 1,
HardwareType.LONGEVITY: 3,
}
)
),
...
RoleStats(
cls=Chatter,
priority=RolePriority.LOW,
on_demand=True,
)
]
- Unit - general utilities for a node to communicate
- Gossip - informs other nodes about it's peers
- Foreman - decides who should take what role
- VaultWarden - keeps files in order
- Explorer - pings all the devices to see if they host colony
- Gateway - acts as a bridge between nodes that can not communicate directly
- Replicator - replicates VaultWarden files
- WebView - shows stats in a web view hosted on configurable port
- Chatter - lets user message other nodes (either through cli or WebView)
What still needs to be done
The project right now works as a proof of concept. It lacks some security features (each node
blindly trusts each node that has a cert) and lacks some worker role that will actually benefit
from being a network. The most useful features are replicating data and chatting but while
functional it does not use the infrastructure to the full potential. For a full release please
wait maybe 15 years more and I might finish it by then.
Last updated: