Skip to content

Commit 529e8b8

Browse files
author
Adrian Huber
committed
Add placement_groups
1 parent a8a19de commit 529e8b8

10 files changed

Lines changed: 517 additions & 7 deletions

File tree

hcloud/hcloud.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from hcloud.datacenters.client import DatacentersClient
1919
from hcloud.load_balancers.client import LoadBalancersClient
2020
from hcloud.load_balancer_types.client import LoadBalancerTypesClient
21+
from hcloud.placement_groups.client import PlacementGroupsClient
2122

2223
from .__version__ import VERSION
2324
from .firewalls.client import FirewallsClient
@@ -149,6 +150,12 @@ def __init__(
149150
:type: :class:`FirewallsClient <hcloud.firewalls.client.FirewallsClient>`
150151
"""
151152

153+
self.placement_groups = PlacementGroupsClient(self)
154+
"""PlacementGroupsClient Instance
155+
156+
:type: :class:`PlacementGroupsClient <hcloud.placement_groups.client.PlacementGroupsClient>`
157+
"""
158+
152159
def _get_user_agent(self):
153160
"""Get the user agent of the hcloud-python instance with the user application name (if specified)
154161
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

hcloud/placement_groups/client.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# -*- coding: utf-8 -*-
2+
from hcloud.core.client import BoundModelBase, ClientEntityBase, GetEntityByNameMixin
3+
4+
from hcloud.placement_groups.domain import PlacementGroup
5+
6+
7+
class BoundPlacementGroup(BoundModelBase):
8+
model = PlacementGroup
9+
10+
def update(self, labels=None, name=None):
11+
# type: (Optional[str], Optional[Dict[str, str]], Optional[str]) -> BoundPlacementGroup
12+
"""Updates the name or labels of a Placement Group
13+
14+
:param labels: Dict[str, str] (optional)
15+
User-defined labels (key-value pairs)
16+
:param name: str, (optional)
17+
New Name to set
18+
:return: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`
19+
"""
20+
return self._client.update(self, labels, name)
21+
22+
def delete(self):
23+
# type: () -> bool
24+
"""Deletes a Placement Group
25+
26+
:return: boolean
27+
"""
28+
return self._client.delete(self)
29+
30+
31+
class PlacementGroupsClient(ClientEntityBase, GetEntityByNameMixin):
32+
results_list_attribute_name = "placement_groups"
33+
34+
def get_by_id(self, id):
35+
# type: (int) -> BoundPlacementGroup
36+
"""Returns a specific Placement Group object
37+
38+
:param id: int
39+
:return: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`
40+
"""
41+
response = self._client.request(url="/placement_groups/{placement_group_id}".format(placement_group_id=id), method="GET")
42+
return BoundPlacementGroup(self, response['placement_group'])
43+
44+
def get_list(self,
45+
label_selector=None, # type: Optional[str]
46+
page=None, # type: Optional[int]
47+
per_page=None, # type: Optional[int]
48+
name=None, # type: Optional[str]
49+
sort=None, # type: Optional[List[str]]
50+
type=None, # type: Optional[str]
51+
):
52+
# type: (...) -> PageResults[List[BoundPlacementGroup]]
53+
"""Get a list of Placement Groups
54+
55+
:param label_selector: str (optional)
56+
Can be used to filter Placement Groups by labels. The response will only contain Placement Groups matching the label selector values.
57+
:param page: int (optional)
58+
Specifies the page to fetch
59+
:param per_page: int (optional)
60+
Specifies how many results are returned by page
61+
:param name: str (optional)
62+
Can be used to filter Placement Groups by their name.
63+
:param sort: List[str] (optional)
64+
Choices: id name created (You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default))
65+
:return: (List[:class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`], :class:`Meta <hcloud.core.domain.Meta>`)
66+
"""
67+
68+
params = {}
69+
70+
if label_selector is not None:
71+
params['label_selector'] = label_selector
72+
if page is not None:
73+
params['page'] = page
74+
if per_page is not None:
75+
params['per_page'] = per_page
76+
if name is not None:
77+
params['name'] = name
78+
if sort is not None:
79+
params['sort'] = sort
80+
if type is not None:
81+
params['type'] = type
82+
response = self._client.request(url="/placement_groups", method="GET", params=params)
83+
placement_groups = [BoundPlacementGroup(self, placement_group_data) for placement_group_data in response['placement_groups']]
84+
85+
return self._add_meta_to_result(placement_groups, response)
86+
87+
def get_all(self, label_selector=None, name=None, sort=None):
88+
# type: (Optional[str], Optional[str], Optional[List[str]]) -> List[BoundPlacementGroup]
89+
"""Get all Placement Groups
90+
91+
:param label_selector: str (optional)
92+
Can be used to filter Placement Groups by labels. The response will only contain Placement Groups matching the label selector values.
93+
:param name: str (optional)
94+
Can be used to filter Placement Groups by their name.
95+
:param sort: List[str] (optional)
96+
Choices: id name created (You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default))
97+
:return: List[:class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`]
98+
"""
99+
return super(PlacementGroupsClient, self).get_all(label_selector=label_selector, name=name, sort=sort)
100+
101+
def get_by_name(self, name):
102+
# type: (str) -> BoundPlacementGroup
103+
"""Get Placement Group by name
104+
105+
:param name: str
106+
Used to get Placement Group by name
107+
:return: class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`
108+
"""
109+
return super(PlacementGroupsClient, self).get_by_name(name)
110+
111+
def update(self, placement_group, labels=None, name=None):
112+
# type: (PlacementGroup, Optional[Dict[str, str]], Optional[str]) -> BoundPlacementGroup
113+
"""Updates the description or labels of a Placement Group.
114+
115+
:param placement_group: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>` or :class:`PlacementGroup <hcloud.placement_groups.domain.PlacementGroup>`
116+
:param labels: Dict[str, str] (optional)
117+
User-defined labels (key-value pairs)
118+
:param name: str (optional)
119+
New name to set
120+
:return: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>`
121+
"""
122+
123+
data = {}
124+
if labels is not None:
125+
data['labels'] = labels
126+
if name is not None:
127+
data['name'] = name
128+
129+
response = self._client.request(url="/placement_groups/{placement_group_id}".format(placement_group_id=placement_group.id),
130+
method="PUT", json=data)
131+
return BoundPlacementGroup(self, response['placement_group'])
132+
133+
def delete(self, placement_group):
134+
# type: (PlacementGroup) -> bool
135+
"""Deletes a Placement Group.
136+
137+
:param placement_group: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>` or :class:`PlacementGroup <hcloud.placement_groups.domain.PlacementGroup>`
138+
:return: boolean
139+
"""
140+
self._client.request(url="/placement_groups/{placement_group_id}".format(placement_group_id=placement_group.id),
141+
method="DELETE")
142+
return True

hcloud/placement_groups/domain.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
from dateutil.parser import isoparse
3+
4+
from hcloud.core.domain import BaseDomain
5+
6+
7+
class PlacementGroup(BaseDomain):
8+
"""Placement Group Domain
9+
10+
:param id: int
11+
ID of the Placement Group
12+
:param name: str
13+
Name of the Placement Group
14+
:param labels: dict
15+
User-defined labels (key-value pairs)
16+
:param servers: List[ int ]
17+
List of server IDs assigned to the Placement Group
18+
:param type: str
19+
Type of the Placement Group
20+
:param created: datetime
21+
Point in time when the image was created
22+
"""
23+
24+
__slots__ = (
25+
"id",
26+
"name",
27+
"labels",
28+
"servers",
29+
"type",
30+
"created"
31+
)
32+
33+
"""Placement Group type spread
34+
spreads all servers in the group on different vhosts
35+
"""
36+
TYPE_SPREAD = "spread"
37+
38+
def __init__(
39+
self,
40+
id=None,
41+
name=None,
42+
labels=None,
43+
servers=None,
44+
type=None,
45+
created=None
46+
):
47+
self.id = id
48+
self.name = name
49+
self.labels = labels
50+
self.servers = servers
51+
self.type = type
52+
self.created = isoparse(created) if created else None

hcloud/servers/client.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,23 @@ def change_alias_ips(self, network, alias_ips):
355355
"""
356356
return self._client.change_alias_ips(self, network, alias_ips)
357357

358+
def add_to_placement_group(self, placement_group):
359+
# type: (Union[PlacementGroup,BoundPlacementGroup]) -> BoundAction
360+
"""Adds a server to a placement group.
361+
362+
:param placement_group: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>` or :class:`Network <hcloud.placement_groups.domain.PlacementGroup>`
363+
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
364+
"""
365+
return self._client.add_to_placement_group(self, placement_group)
366+
367+
def remove_from_placement_group(self):
368+
# type: () -> BoundAction
369+
"""Removes a server from a placement group.
370+
371+
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
372+
"""
373+
return self._client.remove_from_placement_group(self)
374+
358375

359376
class ServersClient(ClientEntityBase, GetEntityByNameMixin):
360377
results_list_attribute_name = "servers"
@@ -987,10 +1004,33 @@ def change_alias_ips(self, server, network, alias_ips):
9871004
"""
9881005
data = {"network": network.id, "alias_ips": alias_ips}
9891006
response = self._client.request(
990-
url="/servers/{server_id}/actions/change_alias_ips".format(
991-
server_id=server.id
992-
),
993-
method="POST",
994-
json=data,
995-
)
996-
return BoundAction(self._client.actions, response["action"])
1007+
url="/servers/{server_id}/actions/change_alias_ips".format(server_id=server.id), method="POST",
1008+
json=data)
1009+
return BoundAction(self._client.actions, response['action'])
1010+
1011+
def add_to_placement_group(self, server, placement_group):
1012+
# type: (Union[Server,BoundServer], Union[PlacementGroup,BoundPlacementGroup]) -> BoundAction
1013+
"""Adds a server to a placement group.
1014+
1015+
:param server: :class:`BoundServer <hcloud.servers.client.BoundServer>` or :class:`Server <hcloud.servers.domain.Server>`
1016+
:param placement_group: :class:`BoundPlacementGroup <hcloud.placement_groups.client.BoundPlacementGroup>` or :class:`Network <hcloud.placement_groups.domain.PlacementGroup>`
1017+
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
1018+
"""
1019+
data = {
1020+
"placement_group": str(placement_group.id),
1021+
}
1022+
response = self._client.request(
1023+
url="/servers/{server_id}/actions/add_to_placement_group".format(server_id=server.id), method="POST",
1024+
json=data)
1025+
return BoundAction(self._client.actions, response['action'])
1026+
1027+
def remove_from_placement_group(self, server):
1028+
# type: (Union[Server,BoundServer]) -> BoundAction
1029+
"""Removes a server from a placement group.
1030+
1031+
:param server: :class:`BoundServer <hcloud.servers.client.BoundServer>` or :class:`Server <hcloud.servers.domain.Server>`
1032+
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
1033+
"""
1034+
response = self._client.request(
1035+
url="/servers/{server_id}/actions/remove_from_placement_group".format(server_id=server.id), method="POST")
1036+
return BoundAction(self._client.actions, response['action'])

tests/unit/placement_groups/__init__.py

Whitespace-only changes.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import pytest
2+
3+
4+
@pytest.fixture()
5+
def one_placement_group_response():
6+
return {
7+
"placement_groups": [
8+
{
9+
"created": "2019-01-08T12:10:00+00:00",
10+
"id": 897,
11+
"labels": {
12+
"key": "value"
13+
},
14+
"name": "my Placement Group",
15+
"servers": [
16+
4711,
17+
4712
18+
],
19+
"type": "spread"
20+
}
21+
]
22+
}
23+
24+
25+
@pytest.fixture()
26+
def two_placement_groups_response():
27+
return {
28+
"placement_groups": [
29+
{
30+
"created": "2019-01-08T12:10:00+00:00",
31+
"id": 897,
32+
"labels": {
33+
"key": "value"
34+
},
35+
"name": "my Placement Group",
36+
"servers": [
37+
4711,
38+
4712
39+
],
40+
"type": "spread"
41+
},
42+
{
43+
"created": "2019-01-08T12:10:00+00:00",
44+
"id": 898,
45+
"labels": {
46+
"key": "value"
47+
},
48+
"name": "my Placement Group",
49+
"servers": [
50+
4713,
51+
4714,
52+
4715
53+
],
54+
"type": "spread"
55+
}
56+
]
57+
}
58+
59+
60+
@pytest.fixture()
61+
def placement_group_response():
62+
return {
63+
"placement_group": {
64+
"created": "2019-01-08T12:10:00+00:00",
65+
"id": 897,
66+
"labels": {
67+
"key": "value"
68+
},
69+
"name": "my Placement Group",
70+
"servers": [
71+
4711,
72+
4712
73+
],
74+
"type": "spread"
75+
}
76+
}

0 commit comments

Comments
 (0)