Skip to content

Commit

Permalink
feat(network): add field expose_routes_to_vswitch (#208)
Browse files Browse the repository at this point in the history
* fix(network): missing subnets vswitch_id in networks create

* feat(network): add field expose_routes_to_vswitch
  • Loading branch information
jooola authored Jun 22, 2023
1 parent 7a19add commit 5321182
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 11 deletions.
51 changes: 42 additions & 9 deletions hcloud/networks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,29 @@ def __init__(self, client, data, complete=True):

super().__init__(client, data, complete)

def update(self, name=None, labels=None):
# type: (Optional[str], Optional[Dict[str, str]]) -> BoundNetwork
def update(
self,
name=None, # type: Optional[str]
expose_routes_to_vswitch=None, # type: Optional[bool]
labels=None, # type: Optional[Dict[str, str]]
): # type: (...) -> BoundNetwork
"""Updates a network. You can update a network’s name and a networks’s labels.
:param name: str (optional)
New name to set
:param expose_routes_to_vswitch: Optional[bool]
Indicates if the routes from this network should be exposed to the vSwitch connection.
The exposing only takes effect if a vSwitch connection is active.
:param labels: Dict[str, str] (optional)
User-defined labels (key-value pairs)
:return: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>`
"""
return self._client.update(self, name, labels)
return self._client.update(
self,
name=name,
expose_routes_to_vswitch=expose_routes_to_vswitch,
labels=labels,
)

def delete(self):
# type: () -> BoundAction
Expand Down Expand Up @@ -217,6 +229,7 @@ def create(
ip_range, # type: str
subnets=None, # type: Optional[List[NetworkSubnet]]
routes=None, # type: Optional[List[NetworkRoute]]
expose_routes_to_vswitch=None, # type: Optional[bool]
labels=None, # type: Optional[Dict[str, str]]
):
"""Creates a network with range ip_range.
Expand All @@ -229,48 +242,68 @@ def create(
Array of subnets allocated
:param routes: List[:class:`NetworkRoute <hcloud.networks.domain.NetworkRoute>`]
Array of routes set in this network
:param expose_routes_to_vswitch: Optional[bool]
Indicates if the routes from this network should be exposed to the vSwitch connection.
The exposing only takes effect if a vSwitch connection is active.
:param labels: Dict[str, str] (optional)
User-defined labels (key-value pairs)
:return: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>`
"""
data = {"name": name, "ip_range": ip_range}
if subnets is not None:
data["subnets"] = [
{
data_subnets = []
for subnet in subnets:
data_subnet = {
"type": subnet.type,
"ip_range": subnet.ip_range,
"network_zone": subnet.network_zone,
}
for subnet in subnets
]
if subnet.vswitch_id is not None:
data_subnet["vswitch_id"] = subnet.vswitch_id

data_subnets.append(data_subnet)
data["subnets"] = data_subnets

if routes is not None:
data["routes"] = [
{"destination": route.destination, "gateway": route.gateway}
for route in routes
]

if expose_routes_to_vswitch is not None:
data["expose_routes_to_vswitch"] = expose_routes_to_vswitch

if labels is not None:
data["labels"] = labels

response = self._client.request(url="/networks", method="POST", json=data)

return BoundNetwork(self, response["network"])

def update(self, network, name=None, labels=None):
# type:(Network, Optional[str], Optional[Dict[str, str]]) -> BoundNetwork
def update(self, network, name=None, expose_routes_to_vswitch=None, labels=None):
# type:(Network, Optional[str], Optional[bool], Optional[Dict[str, str]]) -> BoundNetwork
"""Updates a network. You can update a network’s name and a network’s labels.
:param network: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>` or :class:`Network <hcloud.networks.domain.Network>`
:param name: str (optional)
New name to set
:param expose_routes_to_vswitch: Optional[bool]
Indicates if the routes from this network should be exposed to the vSwitch connection.
The exposing only takes effect if a vSwitch connection is active.
:param labels: Dict[str, str] (optional)
User-defined labels (key-value pairs)
:return: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>`
"""
data = {}
if name is not None:
data.update({"name": name})

if expose_routes_to_vswitch is not None:
data["expose_routes_to_vswitch"] = expose_routes_to_vswitch

if labels is not None:
data.update({"labels": labels})

response = self._client.request(
url=f"/networks/{network.id}",
method="PUT",
Expand Down
5 changes: 5 additions & 0 deletions hcloud/networks/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class Network(BaseDomain):
Subnets allocated in this network
:param routes: List[:class:`NetworkRoute <hcloud.networks.domain.NetworkRoute>`]
Routes set in this network
:param expose_routes_to_vswitch: bool
Indicates if the routes from this network should be exposed to the vSwitch connection.
:param servers: List[:class:`BoundServer <hcloud.servers.client.BoundServer>`]
Servers attached to this network
:param protection: dict
Expand All @@ -30,6 +32,7 @@ class Network(BaseDomain):
"ip_range",
"subnets",
"routes",
"expose_routes_to_vswitch",
"servers",
"protection",
"labels",
Expand All @@ -44,6 +47,7 @@ def __init__(
ip_range=None,
subnets=None,
routes=None,
expose_routes_to_vswitch=None,
servers=None,
protection=None,
labels=None,
Expand All @@ -54,6 +58,7 @@ def __init__(
self.ip_range = ip_range
self.subnets = subnets
self.routes = routes
self.expose_routes_to_vswitch = expose_routes_to_vswitch
self.servers = servers
self.protection = protection
self.labels = labels
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/networks/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def network_response():
},
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": False,
"servers": [42],
"protection": {"delete": False},
"labels": {},
Expand Down Expand Up @@ -55,6 +56,7 @@ def two_networks_response():
},
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": False,
"servers": [42],
"protection": {"delete": False},
"labels": {},
Expand All @@ -73,6 +75,7 @@ def two_networks_response():
}
],
"routes": [{"destination": "12.100.1.0/24", "gateway": "12.0.1.1"}],
"expose_routes_to_vswitch": False,
"servers": [45],
"protection": {"delete": False},
"labels": {},
Expand Down Expand Up @@ -105,6 +108,7 @@ def one_network_response():
},
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": False,
"servers": [42],
"protection": {"delete": False},
"labels": {},
Expand All @@ -129,6 +133,32 @@ def network_create_response():
}
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": False,
"servers": [42],
"protection": {"delete": False},
"labels": {},
"created": "2016-01-30T23:50:00+00:00",
}
}


@pytest.fixture()
def network_create_response_with_expose_routes_to_vswitch():
return {
"network": {
"id": 4711,
"name": "mynet",
"ip_range": "10.0.0.0/16",
"subnets": [
{
"type": "cloud",
"ip_range": "10.0.1.0/24",
"network_zone": "eu-central",
"gateway": "10.0.0.1",
}
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": True,
"servers": [42],
"protection": {"delete": False},
"labels": {},
Expand All @@ -153,6 +183,7 @@ def response_update_network():
}
],
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": True,
"servers": [42],
"protection": {"delete": False},
"labels": {},
Expand Down
80 changes: 78 additions & 2 deletions tests/unit/networks/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,25 @@ def test_create(self, networks_client, network_create_response):
json={"name": "mynet", "ip_range": "10.0.0.0/8"},
)

def test_create_with_expose_routes_to_vswitch(
self, networks_client, network_create_response_with_expose_routes_to_vswitch
):
networks_client._client.request.return_value = (
network_create_response_with_expose_routes_to_vswitch
)
networks_client.create(
name="mynet", ip_range="10.0.0.0/8", expose_routes_to_vswitch=True
)
networks_client._client.request.assert_called_with(
url="/networks",
method="POST",
json={
"name": "mynet",
"ip_range": "10.0.0.0/8",
"expose_routes_to_vswitch": True,
},
)

def test_create_with_subnet(
self, networks_client, network_subnet, network_create_response
):
Expand All @@ -291,6 +310,32 @@ def test_create_with_subnet(
},
)

def test_create_with_subnet_vswitch(
self, networks_client, network_subnet, network_create_response
):
networks_client._client.request.return_value = network_create_response
network_subnet.type = NetworkSubnet.TYPE_VSWITCH
network_subnet.vswitch_id = 1000
networks_client.create(
name="mynet", ip_range="10.0.0.0/8", subnets=[network_subnet]
)
networks_client._client.request.assert_called_with(
url="/networks",
method="POST",
json={
"name": "mynet",
"ip_range": "10.0.0.0/8",
"subnets": [
{
"type": NetworkSubnet.TYPE_VSWITCH,
"ip_range": "10.0.1.0/24",
"network_zone": "eu-central",
"vswitch_id": 1000,
}
],
},
)

def test_create_with_route(
self, networks_client, network_route, network_create_response
):
Expand All @@ -308,6 +353,32 @@ def test_create_with_route(
},
)

def test_create_with_route_and_expose_routes_to_vswitch(
self,
networks_client,
network_route,
network_create_response_with_expose_routes_to_vswitch,
):
networks_client._client.request.return_value = (
network_create_response_with_expose_routes_to_vswitch
)
networks_client.create(
name="mynet",
ip_range="10.0.0.0/8",
routes=[network_route],
expose_routes_to_vswitch=True,
)
networks_client._client.request.assert_called_with(
url="/networks",
method="POST",
json={
"name": "mynet",
"ip_range": "10.0.0.0/8",
"routes": [{"destination": "10.100.1.0/24", "gateway": "10.0.1.1"}],
"expose_routes_to_vswitch": True,
},
)

def test_create_with_route_and_subnet(
self, networks_client, network_subnet, network_route, network_create_response
):
Expand Down Expand Up @@ -358,13 +429,18 @@ def test_get_actions_list(self, networks_client, network, response_get_actions):
)
def test_update(self, networks_client, network, response_update_network):
networks_client._client.request.return_value = response_update_network
network = networks_client.update(network, name="new-name")
network = networks_client.update(
network, name="new-name", expose_routes_to_vswitch=True
)
networks_client._client.request.assert_called_with(
url="/networks/1", method="PUT", json={"name": "new-name"}
url="/networks/1",
method="PUT",
json={"name": "new-name", "expose_routes_to_vswitch": True},
)

assert network.id == 4711
assert network.name == "new-name"
assert network.expose_routes_to_vswitch is True

@pytest.mark.parametrize(
"network", [Network(id=1), BoundNetwork(mock.MagicMock(), dict(id=1))]
Expand Down

0 comments on commit 5321182

Please sign in to comment.