From d02adbe3750fc2c17dbb0f23c0e97861bd195b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Myyr=C3=A4?= Date: Thu, 27 Jul 2023 23:29:16 +0000 Subject: [PATCH] feat(api): Labels support for servers & storages, server group support --- upcloud_api/__init__.py | 2 + upcloud_api/cloud_manager/server_mixin.py | 23 ++++++++- upcloud_api/label.py | 35 ++++++++++++++ upcloud_api/server.py | 38 ++++++++++----- upcloud_api/server_group.py | 57 +++++++++++++++++++++++ upcloud_api/storage.py | 15 ++++-- 6 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 upcloud_api/label.py create mode 100644 upcloud_api/server_group.py diff --git a/upcloud_api/__init__.py b/upcloud_api/__init__.py index 84828ca..70c91a1 100644 --- a/upcloud_api/__init__.py +++ b/upcloud_api/__init__.py @@ -17,10 +17,12 @@ from upcloud_api.interface import Interface from upcloud_api.ip_address import IPAddress from upcloud_api.ip_network import IpNetwork +from upcloud_api.label import Label from upcloud_api.network import Network from upcloud_api.object_storage import ObjectStorage from upcloud_api.router import Router from upcloud_api.server import Server, login_user_block +from upcloud_api.server_group import ServerGroup, ServerGroupAffinityPolicy from upcloud_api.storage import Storage from upcloud_api.storage_import import StorageImport from upcloud_api.tag import Tag diff --git a/upcloud_api/cloud_manager/server_mixin.py b/upcloud_api/cloud_manager/server_mixin.py index d840b5c..2bf1411 100644 --- a/upcloud_api/cloud_manager/server_mixin.py +++ b/upcloud_api/cloud_manager/server_mixin.py @@ -1,6 +1,7 @@ from upcloud_api.api import API from upcloud_api.ip_address import IPAddress from upcloud_api.server import Server +from upcloud_api.server_group import ServerGroup from upcloud_api.storage import BackupDeletionPolicy, Storage @@ -91,9 +92,10 @@ def create_server(self, server: Server) -> Server: memory_amount = 1024, hostname = "my.example.1", zone = "uk-lon1", + labels = [Label('role', 'example')], storage_devices = [ Storage(os = "01000000-0000-4000-8000-000030200200", size=10, tier=maxiops, title='Example OS disk'), - Storage(size=10), + Storage(size=10, labels=[Label('usage', 'data_disk')]), Storage() title = "My Example Server" ]) @@ -191,3 +193,22 @@ def get_server_data(self, uuid: str): storages = Storage._create_storage_objs(server.pop('storage_devices'), cloud_manager=self) return server, IPAddresses, storages + + def create_server_group(self, server_group: ServerGroup) -> ServerGroup: + """ + Creates a new server group. Allows including servers and defining labels. + """ + if not isinstance(server_group, ServerGroup): + server_group = ServerGroup(server_group) + body = server_group.to_dict() + + res = self.api.post_request('/server-group', body) + return ServerGroup(cloud_manager=self, **res['server_group']) + + def delete_server_group(self, uuid: str): + """ + DELETE '/server-group/UUID'. Destroys the server group, but not attached servers. + + Returns an empty object. + """ + return self.api.delete_request(f'/server-group/{uuid}') diff --git a/upcloud_api/label.py b/upcloud_api/label.py new file mode 100644 index 0000000..b159001 --- /dev/null +++ b/upcloud_api/label.py @@ -0,0 +1,35 @@ +from upcloud_api.upcloud_resource import UpCloudResource + + +class Label(UpCloudResource): + """ + Class representation of UpCloud resource label + """ + + ATTRIBUTES = { + 'key': "", + 'value': "", + } + + def __init__(self, key="", value="") -> None: + """ + Initialize label. + + Set both values for label if given + """ + self.key = key + self.value = value + + def __str__(self) -> str: + return f"{self.key}={self.value}" + + def to_dict(self): + """ + Return a dict that can be serialised to JSON and sent to UpCloud's API. + """ + body = { + 'key': self.key, + 'value': self.value, + } + + return body diff --git a/upcloud_api/server.py b/upcloud_api/server.py index d26f4ac..1b01946 100644 --- a/upcloud_api/server.py +++ b/upcloud_api/server.py @@ -3,6 +3,7 @@ from upcloud_api.firewall import FirewallRule from upcloud_api.ip_address import IPAddress +from upcloud_api.server_group import ServerGroup from upcloud_api.storage import Storage from upcloud_api.utils import try_it_n_times @@ -47,31 +48,34 @@ class Server: 'core_number', 'firewall', 'hostname', + 'labels', 'memory_amount', 'nic_model', + 'plan', 'title', 'timezone', 'video_model', 'vnc', 'vnc_password', - 'plan', ] optional_fields = [ - 'plan', - 'core_number', - 'memory_amount', + 'avoid_host', 'boot_order', + 'core_number', 'firewall', + 'labels', + 'login_user', + 'memory_amount', 'nic_model', + 'password_delivery', + 'plan', + 'server_group', 'timezone', + 'metadata', + 'user_data', 'video_model', 'vnc_password', - 'password_delivery', - 'avoid_host', - 'login_user', - 'user_data', - 'metadata', ] def __init__(self, server=None, **kwargs) -> None: @@ -149,11 +153,11 @@ def save(self) -> None: self.cloud_manager.modify_server(self.uuid, **kwargs) self._reset(kwargs) - def destroy(self): + def destroy(self, delete_storages=False): """ Destroy the server. """ - self.cloud_manager.delete_server(self.uuid) + self.cloud_manager.delete_server(self.uuid, delete_storages=delete_storages) def shutdown(self, hard: bool = False, timeout: int = 30) -> None: """ @@ -335,6 +339,18 @@ def prepare_post_body(self): if hasattr(self, optional_field): body['server'][optional_field] = getattr(self, optional_field) + if hasattr(self, 'labels'): + dict_labels = {'label': []} + for label in self.labels: + dict_labels['label'].append(label.to_dict()) + body['server']['labels'] = dict_labels + + if hasattr(self, 'metadata') and isinstance(self.metadata, bool): + body['server']['metadata'] = "yes" if self.metadata else "no" + + if hasattr(self, 'server_group') and isinstance(self.server_group, ServerGroup): + body['server']['server_group'] = f"{self.server_group.uuid}" + # set password_delivery default as 'none' to prevent API from sending # emails (with credentials) about each created server if not hasattr(self, 'password_delivery'): diff --git a/upcloud_api/server_group.py b/upcloud_api/server_group.py new file mode 100644 index 0000000..91b4e07 --- /dev/null +++ b/upcloud_api/server_group.py @@ -0,0 +1,57 @@ +from enum import Enum + +from upcloud_api.upcloud_resource import UpCloudResource + + +class ServerGroupAffinityPolicy(str, Enum): + """ + Enum representation of affinity policy for a server group + """ + + STRICT_ANTI_AFFINITY = 'strict' + ANTI_AFFINITY_PREFERRED = 'yes' + NO_ANTI_AFFINITY = 'no' + + +class ServerGroup(UpCloudResource): + """ + Class representation of UpCloud server group resource + """ + + ATTRIBUTES = { + 'anti_affinity': ServerGroupAffinityPolicy.NO_ANTI_AFFINITY, + 'labels': None, + 'servers': None, + 'title': None, + 'uuid': None, + } + + def __str__(self) -> str: + return self.title + + def to_dict(self): + """ + Return a dict that can be serialised to JSON and sent to UpCloud's API. + """ + body = { + 'title': self.title, + } + + if hasattr(self, 'anti_affinity'): + body['anti_affinity'] = f"{self.anti_affinity}" + + if hasattr(self, 'servers'): + servers = [] + for server in self.servers: + if isinstance(server, server.Server) and hasattr(server, 'uuid'): + servers.append(server.uuid) + else: + servers.append(server) + + if hasattr(self, 'labels'): + dict_labels = {'label': []} + for label in self.labels: + dict_labels['label'].append(label.to_dict()) + body['labels'] = dict_labels + + return {'server_group': body} diff --git a/upcloud_api/storage.py b/upcloud_api/storage.py index dadec1a..9771ef2 100644 --- a/upcloud_api/storage.py +++ b/upcloud_api/storage.py @@ -19,15 +19,16 @@ class Storage(UpCloudResource): """ ATTRIBUTES = { - 'uuid': None, - 'tier': 'maxiops', - 'size': 10, 'access': None, + 'address': None, + 'labels': None, 'license': None, 'state': None, + 'size': 10, + 'tier': 'maxiops', 'title': '', 'type': None, - 'address': None, + 'uuid': None, 'zone': None, } @@ -146,6 +147,12 @@ def to_dict(self): if hasattr(self, 'zone') and self.zone: body['zone'] = self.zone + if hasattr(self, 'labels'): + dict_labels = [] + for label in self.labels: + dict_labels.append(label.to_dict()) + body['labels'] = dict_labels + return body @staticmethod