-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Network.remove and auto cleanup #677
Changes from 3 commits
2d87784
de30ba9
0f6edeb
ef159a3
c5d425c
b319b73
c6b32db
2bbb7cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
import asyncio | ||||||
from dataclasses import dataclass | ||||||
from ipaddress import ip_address, ip_network, IPv4Address, IPv6Address, IPv4Network, IPv6Network | ||||||
from statemachine import State, StateMachine # type: ignore | ||||||
from typing import Dict, Optional, Union | ||||||
from urllib.parse import urlparse | ||||||
import yapapi | ||||||
|
@@ -54,6 +55,26 @@ def get_websocket_uri(self, port: int) -> str: | |||||
return f"{net_api_ws}/net/{self.network.network_id}/tcp/{self.ip}/{port}" | ||||||
|
||||||
|
||||||
class NetworkStateMachine(StateMachine): | ||||||
"""State machine describing the states and lifecycle of a :class:`Network` instance.""" | ||||||
|
||||||
# states | ||||||
initialized = State("initialized", initial=True) | ||||||
creating = State("creating") | ||||||
ready = State("ready") | ||||||
removing = State("removing") | ||||||
removed = State("removed") | ||||||
|
||||||
# transitions | ||||||
add_address = creating.to.itself() | ready.to.itself() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm... I'm unsure I like how the state transitions are used to control when the methods can be run... I'd expect that logic to be part of those methods and not declared on the state machine ... is this some kind of a well-understood pattern? ... asking since I might not realize that this "transition" is used to control method's usage and rather think that it indeed somehow changes the state of the network... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is an established pattern, I just found this more elegant.
I think that expressing this using possible transitions is cleaner and scales better if we need to handle different situations or new states in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nah, maybe it's okay ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed in b319b73 |
||||||
add_node = ready.to.itself() | ||||||
get_id = creating.to.itself() | ready.to.itself() | removing.to.itself() | ||||||
|
||||||
# lifecycle | ||||||
create = initialized.to(creating) | creating.to(ready) | ||||||
remove = ready.to(removing) | removing.to(removed) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and actually, I'd do the same with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in b319b73 |
||||||
|
||||||
|
||||||
class Network: | ||||||
""" | ||||||
Describes a VPN created between the requestor and the provider nodes within Golem Network. | ||||||
|
@@ -80,6 +101,7 @@ async def create( | |||||
""" | ||||||
|
||||||
network = cls(net_api, ip, owner_id, owner_ip, mask, gateway) | ||||||
network._state_machine.create() | ||||||
|
||||||
# create the network in yagna and set the id | ||||||
network._network_id = await net_api.create_network( | ||||||
|
@@ -89,6 +111,7 @@ async def create( | |||||
# add requestor's own address to the network | ||||||
await network.add_owner_address(network.owner_ip) | ||||||
|
||||||
network._state_machine.create() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it here twice? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This follows the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be clearer to have two separate, explicit transitions here then -> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in b319b73 |
||||||
return network | ||||||
|
||||||
def __init__( | ||||||
|
@@ -121,6 +144,7 @@ def __init__( | |||||
self._gateway = gateway | ||||||
self._owner_id = owner_id | ||||||
self._owner_ip: IpAddress = ip_address(owner_ip) if owner_ip else self._next_address() | ||||||
self._state_machine: NetworkStateMachine = NetworkStateMachine() | ||||||
|
||||||
self._nodes: Dict[str, Node] = dict() | ||||||
"""the mapping between a Golem node id and a Node in this VPN.""" | ||||||
|
@@ -137,11 +161,22 @@ def __str__(self) -> str: | |||||
nodes: {self.nodes_dict} | ||||||
}}""" | ||||||
|
||||||
async def __aenter__(self) -> "Network": | ||||||
return self | ||||||
|
||||||
async def __aexit__(self, *exc_info) -> None: | ||||||
await self.remove() | ||||||
|
||||||
@property | ||||||
def owner_ip(self) -> str: | ||||||
"""the IP address of the requestor node within the network""" | ||||||
"""The IP address of the requestor node within the network.""" | ||||||
return str(self._owner_ip) | ||||||
|
||||||
@property | ||||||
def state(self) -> State: | ||||||
"""Current state in this network's lifecycle.""" | ||||||
return self._state_machine.current_state | ||||||
|
||||||
@property | ||||||
def network_address(self) -> str: | ||||||
"""The network address of this network, without a netmask.""" | ||||||
|
@@ -165,8 +200,10 @@ def nodes_dict(self) -> Dict[str, str]: | |||||
return {str(v.ip): k for k, v in self._nodes.items()} | ||||||
|
||||||
@property | ||||||
def network_id(self) -> Optional[str]: | ||||||
def network_id(self) -> str: | ||||||
"""The automatically-generated, unique ID of this VPN.""" | ||||||
self._state_machine.get_id() | ||||||
assert self._network_id | ||||||
return self._network_id | ||||||
|
||||||
def _ensure_ip_in_network(self, ip: str): | ||||||
|
@@ -186,8 +223,7 @@ async def add_owner_address(self, ip: str): | |||||
|
||||||
:param ip: the IP address to assign to the requestor node. | ||||||
""" | ||||||
assert self.network_id, "Network not initialized correctly" | ||||||
|
||||||
self._state_machine.add_address() | ||||||
self._ensure_ip_in_network(ip) | ||||||
|
||||||
async with self._nodes_lock: | ||||||
|
@@ -202,7 +238,7 @@ async def add_node(self, node_id: str, ip: Optional[str] = None) -> Node: | |||||
:param node_id: Node ID within the Golem network of this VPN node. | ||||||
:param ip: IP address to assign to this node. | ||||||
""" | ||||||
assert self.network_id, "Network not initialized correctly" | ||||||
self._state_machine.add_node() | ||||||
|
||||||
async with self._nodes_lock: | ||||||
if ip: | ||||||
|
@@ -221,6 +257,12 @@ async def add_node(self, node_id: str, ip: Optional[str] = None) -> Node: | |||||
|
||||||
return node | ||||||
|
||||||
async def remove(self) -> None: | ||||||
"""Remove this network, terminating any connections it provides.""" | ||||||
self._state_machine.remove() | ||||||
await self._net_api.remove_network(self.network_id) | ||||||
self._state_machine.remove() | ||||||
|
||||||
def _next_address(self) -> IpAddress: | ||||||
"""Provide the next available IP address within this Network. | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call this class
NetworkState
for a bit more brevity and to make it consistent withServiceState
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in c5d425c