Skip to content

Commit 7c0c14f

Browse files
committed
Resolve "Implement client for FloatingIP endpoint"
1 parent f1dc33a commit 7c0c14f

9 files changed

Lines changed: 869 additions & 0 deletions

File tree

hcloud/floating_ips/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

hcloud/floating_ips/client.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# -*- coding: utf-8 -*-
2+
from hcloud.actions.client import BoundAction
3+
from hcloud.core.client import BoundModelBase, ClientEntityBase
4+
from hcloud.core.domain import add_meta_to_result
5+
6+
from hcloud.floating_ips.domain import FloatingIP, CreateFloatingIPResponse
7+
from hcloud.locations.client import BoundLocation
8+
9+
10+
class BoundFloatingIP(BoundModelBase):
11+
model = FloatingIP
12+
13+
def __init__(self, client, data):
14+
from hcloud.servers.client import BoundServer
15+
server = data.get("server")
16+
if server is not None:
17+
data['server'] = BoundServer(client._client.servers, {"id": server}, complete=False)
18+
19+
home_location = data.get("home_location")
20+
if home_location is not None:
21+
data['home_location'] = BoundLocation(client._client.locations, home_location)
22+
23+
super(BoundFloatingIP, self).__init__(client, data)
24+
25+
def get_actions_list(self, sort=None, page=None, per_page=None):
26+
# type: (Optional[List[str]], Optional[int], Optional[int]) -> PageResult[BoundAction, Meta]
27+
return self._client.get_actions_list(self, sort, page, per_page)
28+
29+
def get_actions(self, sort=None):
30+
# type: (Optional[List[str]]) -> List[BoundAction]
31+
return self._client.get_actions(self, sort)
32+
33+
def update(self, description=None, labels=None):
34+
# type: (Optional[str], Optional[Dict[str, str]]) -> BoundFloatingIP
35+
return self._client.update(self, description, labels)
36+
37+
def delete(self):
38+
# type: () -> bool
39+
return self._client.delete(self)
40+
41+
def change_protection(self, delete=None):
42+
# type: (Optional[bool]) -> BoundAction
43+
return self._client.change_protection(self, delete)
44+
45+
def assign(self, server):
46+
# type: (Server) -> BoundAction
47+
return self._client.assign(self, server)
48+
49+
def unassign(self):
50+
# type: () -> BoundAction
51+
return self._client.unassign(self)
52+
53+
def change_dns_ptr(self, ip, dns_ptr):
54+
# type: (str, str) -> BoundAction
55+
return self._client.change_dns_ptr(self, ip, dns_ptr)
56+
57+
58+
class FloatingIPsClient(ClientEntityBase):
59+
results_list_attribute_name = 'floating_ips'
60+
61+
def get_actions_list(self,
62+
floating_ip, # type: FloatingIP
63+
sort=None, # type: Optional[List[str]]
64+
page=None, # type: Optional[int]
65+
per_page=None # type: Optional[int]
66+
):
67+
# type: (...) -> PageResults[List[BoundAction], Meta]
68+
params = {}
69+
if sort is not None:
70+
params["sort"] = sort
71+
if page is not None:
72+
params["page"] = page
73+
if per_page is not None:
74+
params["per_page"] = per_page
75+
response = self._client.request(url="/floating_ips/{floating_ip_id}/actions".format(floating_ip_id=floating_ip.id), method="GET", params=params)
76+
actions = [BoundAction(self._client.actions, action_data) for action_data in response['actions']]
77+
return add_meta_to_result(actions, response, 'actions')
78+
79+
def get_actions(self,
80+
floating_ip, # type: FloatingIP
81+
sort=None, # type: Optional[List[str]]
82+
):
83+
# type: (...) -> List[BoundAction]
84+
return super(FloatingIPsClient, self).get_actions(floating_ip, sort=sort)
85+
86+
def get_by_id(self, id):
87+
# type: (int) -> BoundFloatingIP
88+
response = self._client.request(url="/floating_ips/{floating_ip_id}".format(floating_ip_id=id), method="GET")
89+
return BoundFloatingIP(self, response['floating_ip'])
90+
91+
def get_list(self,
92+
label_selector=None, # type: Optional[str]
93+
page=None, # type: Optional[int]
94+
per_page=None # type: Optional[int]
95+
):
96+
# type: (...) -> PageResults[List[BoundFloatingIP]]
97+
params = {}
98+
99+
if label_selector:
100+
params['label_selector'] = label_selector
101+
if page:
102+
params['page'] = page
103+
if per_page:
104+
params['per_page'] = per_page
105+
106+
response = self._client.request(url="/floating_ips", method="GET", params=params)
107+
floating_ips = [BoundFloatingIP(self, floating_ip_data) for floating_ip_data in response['floating_ips']]
108+
109+
return self.add_meta_to_result(floating_ips, response)
110+
111+
def get_all(self, label_selector=None):
112+
# type: (Optional[str]) -> List[BoundFloatingIP]
113+
return super(FloatingIPsClient, self).get_all(label_selector=label_selector)
114+
115+
def create(self,
116+
type, # type: str
117+
description=None, # type: Optional[str]
118+
labels=None, # type: Optional[str]
119+
home_location=None, # type: Optional[Location]
120+
server=None, # type: Optional[Server]
121+
):
122+
# type: (...) -> CreateFloatingIPResponse
123+
124+
data = {
125+
'type': type
126+
}
127+
if description is not None:
128+
data['description'] = description
129+
if labels is not None:
130+
data['labels'] = labels
131+
if home_location is not None:
132+
data['home_location'] = home_location.id_or_name
133+
if server is not None:
134+
data['server'] = server.id
135+
136+
response = self._client.request(url="/floating_ips", json=data, method="POST")
137+
138+
result = CreateFloatingIPResponse(
139+
floating_ip=BoundFloatingIP(self, response['floating_ip']),
140+
action=BoundAction(self._client.actions, response['action'])
141+
)
142+
return result
143+
144+
def update(self, floating_ip, description=None, labels=None):
145+
# type: (FloatingIP, Optional[str], Optional[Dict[str, str]]) -> BoundFloatingIP
146+
data = {}
147+
if description is not None:
148+
data['description'] = description
149+
if labels is not None:
150+
data['labels'] = labels
151+
152+
response = self._client.request(url="/floating_ips/{floating_ip_id}".format(floating_ip_id=floating_ip.id), method="PUT", json=data)
153+
return BoundFloatingIP(self, response['floating_ip'])
154+
155+
def delete(self, floating_ip):
156+
# type: (FloatingIP) -> bool
157+
self._client.request(url="/floating_ips/{floating_ip_id}".format(floating_ip_id=floating_ip.id), method="DELETE")
158+
# Return allays true, because the API does not return an action for it. When an error occurs a HcloudAPIException will be raised
159+
return True
160+
161+
def change_protection(self, floating_ip, delete=None):
162+
# type: (FloatingIP, Optional[bool], Optional[bool]) -> BoundAction
163+
data = {}
164+
if delete is not None:
165+
data.update({"delete": delete})
166+
167+
response = self._client.request(url="/floating_ips/{floating_ip_id}/actions/change_protection".format(floating_ip_id=floating_ip.id), method="POST", json=data)
168+
return BoundAction(self._client.actions, response['action'])
169+
170+
def assign(self, floating_ip, server):
171+
# type: (FloatingIP, Server) -> BoundAction
172+
response = self._client.request(url="/floating_ips/{floating_ip_id}/actions/assign".format(floating_ip_id=floating_ip.id), method="POST", json={"server": server.id})
173+
return BoundAction(self._client.actions, response['action'])
174+
175+
def unassign(self, floating_ip):
176+
# type: (FloatingIP) -> BoundAction
177+
response = self._client.request(url="/floating_ips/{floating_ip_id}/actions/unassign".format(floating_ip_id=floating_ip.id), method="POST")
178+
return BoundAction(self._client.actions, response['action'])
179+
180+
def change_dns_ptr(self, floating_ip, ip, dns_ptr):
181+
# type: (FloatingIP, str, str) -> BoundAction
182+
response = self._client.request(url="/floating_ips/{floating_ip_id}/actions/change_dns_ptr".format(floating_ip_id=floating_ip.id), method="POST", json={"ip": ip, "dns_ptr": dns_ptr})
183+
return BoundAction(self._client.actions, response['action'])

hcloud/floating_ips/domain.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# -*- coding: utf-8 -*-
2+
from hcloud.core.domain import BaseDomain
3+
4+
5+
class FloatingIP(BaseDomain):
6+
7+
__slots__ = (
8+
"id",
9+
"type",
10+
"description",
11+
"ip",
12+
"server",
13+
"dns_ptr",
14+
"home_location",
15+
"blocked",
16+
"protection",
17+
"labels"
18+
)
19+
20+
def __init__(
21+
self,
22+
id=None,
23+
type=None,
24+
description=None,
25+
ip=None,
26+
server=None,
27+
dns_ptr=None,
28+
home_location=None,
29+
blocked=None,
30+
protection=None,
31+
labels=None
32+
33+
):
34+
self.id = id
35+
self.type = type
36+
self.description = description
37+
self.ip = ip
38+
self.server = server
39+
self.dns_ptr = dns_ptr
40+
self.home_location = home_location
41+
self.blocked = blocked
42+
self.protection = protection
43+
self.labels = labels
44+
45+
46+
class CreateFloatingIPResponse(BaseDomain):
47+
__slots__ = (
48+
"floating_ip",
49+
"action"
50+
)
51+
52+
def __init__(
53+
self,
54+
floating_ip, # type: BoundFloatingIP
55+
action, # type: BoundAction
56+
):
57+
self.floating_ip = floating_ip
58+
self.action = action

hcloud/hcloud.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import requests
55

66
from hcloud.actions.client import ActionsClient
7+
from hcloud.floating_ips.client import FloatingIPsClient
78
from hcloud.isos.client import IsosClient
89
from hcloud.servers.client import ServersClient
910
from hcloud.server_types.client import ServerTypesClient
@@ -39,6 +40,7 @@ def __init__(self, token):
3940
self.images = ImagesClient(self)
4041
self.isos = IsosClient(self)
4142
self.ssh_keys = SSHKeysClient(self)
43+
self.floating_ips = FloatingIPsClient(self)
4244

4345
def _get_user_agent(self):
4446
return "hcloud-python/" + self.version

tests/integration/floating_ips/__init__.py

Whitespace-only changes.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import pytest
2+
import mock
3+
4+
from hcloud.servers.client import BoundServer
5+
from hcloud.servers.domain import Server
6+
from hcloud.floating_ips.client import BoundFloatingIP
7+
from hcloud.floating_ips.domain import FloatingIP
8+
9+
10+
class TestBoundFloatingIPs(object):
11+
12+
@pytest.fixture()
13+
def bound_floating_ip(self, hetzner_client):
14+
return BoundFloatingIP(client=hetzner_client.floating_ips, data=dict(id=4711))
15+
16+
def test_get_actions(self, bound_floating_ip):
17+
actions = bound_floating_ip.get_actions()
18+
19+
assert len(actions) == 1
20+
assert actions[0].id == 13
21+
assert actions[0].command == "assign_floating_ip"
22+
23+
def test_update(self, bound_floating_ip):
24+
floating_ip = bound_floating_ip.update(description="New description", labels={})
25+
assert floating_ip.id == 4711
26+
assert floating_ip.description == "New description"
27+
28+
def test_delete(self, bound_floating_ip):
29+
delete_success = bound_floating_ip.delete()
30+
assert delete_success is True
31+
32+
@pytest.mark.parametrize("server",
33+
(Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))))
34+
def test_assign(self, hetzner_client, bound_floating_ip, server):
35+
action = bound_floating_ip.assign(server)
36+
assert action.id == 13
37+
assert action.progress == 0
38+
assert action.command == "assign_floating_ip"
39+
40+
def test_unassign(self, hetzner_client, bound_floating_ip):
41+
action = bound_floating_ip.unassign()
42+
assert action.id == 13
43+
assert action.progress == 0
44+
assert action.command == "unassign_floating_ip"
45+
46+
def test_change_dns_ptr(self, hetzner_client, bound_floating_ip):
47+
action = bound_floating_ip.change_dns_ptr("1.2.3.4", "server02.example.com")
48+
assert action.id == 13
49+
assert action.progress == 0
50+
assert action.command == "change_dns_ptr"
51+
52+
53+
class TestFloatingIPsClient(object):
54+
55+
def test_get_by_id(self, hetzner_client):
56+
bound_floating_ip = hetzner_client.floating_ips.get_by_id(4711)
57+
assert bound_floating_ip.id == 4711
58+
assert bound_floating_ip.description == "Web Frontend"
59+
assert bound_floating_ip.type == "ipv4"
60+
61+
def test_get_list(self, hetzner_client):
62+
result = hetzner_client.floating_ips.get_list()
63+
bound_floating_ips = result.floating_ips
64+
assert bound_floating_ips[0].id == 4711
65+
assert bound_floating_ips[0].description == "Web Frontend"
66+
assert bound_floating_ips[0].type == "ipv4"
67+
68+
@pytest.mark.parametrize("server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))])
69+
def test_create(self, hetzner_client, server):
70+
response = hetzner_client.floating_ips.create(
71+
type="ipv4",
72+
description="Web Frontend",
73+
# home_location=Location(description="fsn1"),
74+
server=server
75+
)
76+
77+
floating_ip = response.floating_ip
78+
action = response.action
79+
80+
assert floating_ip.id == 4711
81+
assert floating_ip.description == "Web Frontend"
82+
assert floating_ip.type == "ipv4"
83+
84+
assert action.id == 13
85+
assert action.command == "assign_floating_ip"
86+
87+
@pytest.mark.parametrize("floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))])
88+
def test_get_actions(self, hetzner_client, floating_ip):
89+
actions = hetzner_client.floating_ips.get_actions(floating_ip)
90+
91+
assert len(actions) == 1
92+
assert actions[0].id == 13
93+
assert actions[0].command == "assign_floating_ip"
94+
95+
@pytest.mark.parametrize("floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))])
96+
def test_update(self, hetzner_client, floating_ip):
97+
floating_ip = hetzner_client.floating_ips.update(floating_ip, description="New description", labels={})
98+
99+
assert floating_ip.id == 4711
100+
assert floating_ip.description == "New description"
101+
102+
@pytest.mark.parametrize("floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))])
103+
def test_delete(self, hetzner_client, floating_ip):
104+
delete_success = hetzner_client.floating_ips.delete(floating_ip)
105+
106+
assert delete_success is True
107+
108+
@pytest.mark.parametrize("server,floating_ip",
109+
[(Server(id=43), FloatingIP(id=4711)),
110+
(BoundServer(mock.MagicMock(), dict(id=43)), BoundFloatingIP(mock.MagicMock(), dict(id=4711)))])
111+
def test_assign(self, hetzner_client, server, floating_ip):
112+
action = hetzner_client.floating_ips.assign(floating_ip, server)
113+
assert action.id == 13
114+
assert action.progress == 0
115+
assert action.command == "assign_floating_ip"
116+
117+
@pytest.mark.parametrize("floating_ip", [FloatingIP(id=4711), BoundFloatingIP(mock.MagicMock(), dict(id=4711))])
118+
def test_unassign(self, hetzner_client, floating_ip):
119+
action = hetzner_client.floating_ips.unassign(floating_ip)
120+
assert action.id == 13
121+
assert action.progress == 0
122+
assert action.command == "unassign_floating_ip"
123+
124+
@pytest.mark.parametrize("floating_ip", [FloatingIP(id=4711), BoundFloatingIP(mock.MagicMock(), dict(id=4711))])
125+
def test_change_dns_ptr(self, hetzner_client, floating_ip):
126+
action = hetzner_client.floating_ips.change_dns_ptr(floating_ip, "1.2.3.4", "server02.example.com")
127+
assert action.id == 13
128+
assert action.progress == 0
129+
assert action.command == "change_dns_ptr"

tests/unit/floating_ips/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)