forked from platypush/platypush
Rewritten linode
integration.
- Support for cloud instances as native entities. - Using Marshmallow dataclasses+schemas instead of custom `Response` objects. - Merge `linode` backend into `linode` plugin.
This commit is contained in:
parent
4b9c5a0203
commit
bc2730c841
8 changed files with 654 additions and 261 deletions
|
@ -1,46 +0,0 @@
|
|||
from typing import Dict, Optional, List
|
||||
|
||||
from platypush.backend.sensor import SensorBackend
|
||||
from platypush.message.event.linode import LinodeInstanceStatusChanged
|
||||
|
||||
|
||||
class LinodeBackend(SensorBackend):
|
||||
"""
|
||||
This backend monitors the state of one or more Linode instances.
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.linode.LinodeInstanceStatusChanged` when the status of an instance changes.
|
||||
|
||||
Requires:
|
||||
|
||||
* The :class:`platypush.plugins.linode.LinodePlugin` plugin configured.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, instances: Optional[List[str]] = None, poll_seconds: float = 30.0, **kwargs):
|
||||
"""
|
||||
:param instances: List of instances to monitor, by label (default: monitor all the instances).
|
||||
"""
|
||||
super().__init__(plugin='linode', poll_seconds=poll_seconds, **kwargs)
|
||||
self.instances = set(instances or [])
|
||||
|
||||
def process_data(self, data: Dict[str, dict], new_data: Optional[Dict[str, dict]] = None, **kwargs):
|
||||
instances = data.get('instances', {})
|
||||
old_instances = (self.data or {}).get('instances', {})
|
||||
|
||||
if self.instances:
|
||||
instances = {label: instances[label] for label in self.instances if label in instances}
|
||||
|
||||
if not instances:
|
||||
return
|
||||
|
||||
for label, instance in instances.items():
|
||||
old_instance = old_instances.get(label, {})
|
||||
if 'status' in old_instance and old_instance['status'] != instance['status']:
|
||||
self.bus.post(LinodeInstanceStatusChanged(instance=label,
|
||||
status=instance['status'],
|
||||
old_status=old_instance['status']))
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -1,8 +0,0 @@
|
|||
manifest:
|
||||
events:
|
||||
platypush.message.event.linode.LinodeInstanceStatusChanged: when the status of
|
||||
an instance changes.
|
||||
install:
|
||||
pip: []
|
||||
package: platypush.backend.linode
|
||||
type: backend
|
43
platypush/entities/cloud.py
Normal file
43
platypush/entities/cloud.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from sqlalchemy import (
|
||||
Column,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
JSON,
|
||||
String,
|
||||
)
|
||||
|
||||
from platypush.common.db import Base
|
||||
|
||||
from .devices import Device
|
||||
|
||||
|
||||
if 'cloud_instance' not in Base.metadata:
|
||||
|
||||
class CloudInstance(Device):
|
||||
"""
|
||||
Entity that maps a cloud node - like a Linode or AWS instance.
|
||||
"""
|
||||
|
||||
__tablename__ = 'cloud_instance'
|
||||
|
||||
id = Column(
|
||||
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
|
||||
)
|
||||
|
||||
status = Column(String)
|
||||
instance_type = Column(String)
|
||||
ipv4_addresses = Column(JSON)
|
||||
ipv6_address = Column(String)
|
||||
group = Column(String)
|
||||
tags = Column(JSON)
|
||||
image = Column(String)
|
||||
region = Column(String)
|
||||
hypervisor = Column(String)
|
||||
uuid = Column(String)
|
||||
specs = Column(JSON)
|
||||
alerts = Column(JSON)
|
||||
backups = Column(JSON)
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': __tablename__,
|
||||
}
|
|
@ -4,15 +4,39 @@ from platypush.message.event import Event
|
|||
|
||||
|
||||
class LinodeEvent(Event):
|
||||
pass
|
||||
"""
|
||||
Base Linode event class.
|
||||
"""
|
||||
|
||||
|
||||
class LinodeInstanceStatusChanged(LinodeEvent):
|
||||
"""
|
||||
Event triggered when the status of a Linode instance changes.
|
||||
"""
|
||||
def __init__(self, instance: str, status: str, old_status: Optional[str] = None, *args, **kwargs):
|
||||
super().__init__(*args, instance=instance, status=status, old_status=old_status, **kwargs)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
instance_id: int,
|
||||
instance: str,
|
||||
status: str,
|
||||
old_status: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
:param instance_id: Linode instance ID.
|
||||
:param instance: Linode instance name.
|
||||
:param status: New status of the instance.
|
||||
:param old_status: Old status of the instance.
|
||||
"""
|
||||
super().__init__(
|
||||
*args,
|
||||
instance_id=instance_id,
|
||||
instance=instance,
|
||||
status=status,
|
||||
old_status=old_status,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
import datetime
|
||||
from typing import List
|
||||
|
||||
from linode_api4.objects.linode import Instance, Config, Disk, Backup, Image, Kernel, Type
|
||||
|
||||
from platypush.message import Mapping
|
||||
from platypush.message.response import Response
|
||||
|
||||
|
||||
class LinodeResponse(Response):
|
||||
pass
|
||||
|
||||
|
||||
class LinodeConfigModel(Mapping):
|
||||
def __init__(self, config: Config):
|
||||
super().__init__()
|
||||
self.comments = config.comments
|
||||
self.created = config.created
|
||||
self.helpers = config.helpers.dict
|
||||
self.id = config.id
|
||||
self.initrd = config.initrd
|
||||
self.kernel = dict(LinodeKernelModel(config.kernel))
|
||||
self.label = config.label
|
||||
self.linode_id = config.linode_id
|
||||
self.memory_limit = config.memory_limit
|
||||
self.parent_id_name = config.parent_id_name
|
||||
self.root_device = config.root_device
|
||||
self.run_level = config.run_level
|
||||
self.updated = datetime.datetime.fromisoformat(config.updated)
|
||||
self.virt_mode = config.virt_mode
|
||||
|
||||
|
||||
class LinodeKernelModel(Mapping):
|
||||
def __init__(self, kernel: Kernel):
|
||||
super().__init__()
|
||||
self.architecture = kernel.architecture
|
||||
self.created = kernel.created
|
||||
self.deprecated = kernel.deprecated
|
||||
self.description = kernel.description
|
||||
self.id = kernel.id
|
||||
self.kvm = kernel.kvm
|
||||
self.label = kernel.label
|
||||
self.version = kernel.version
|
||||
self.xen = kernel.xen
|
||||
|
||||
|
||||
class LinodeBackupModel(Mapping):
|
||||
def __init__(self, backup: Backup):
|
||||
super().__init__()
|
||||
self.created = backup.created
|
||||
self.disks = {
|
||||
disk.label: {
|
||||
'label': disk.label,
|
||||
'size': disk.size,
|
||||
'filesystem': disk.filesystem,
|
||||
}
|
||||
for disk in backup.disks
|
||||
}
|
||||
|
||||
self.duration = backup.duration
|
||||
self.finished = backup.finished
|
||||
self.id = backup.id
|
||||
self.label = backup.label
|
||||
self.linode_id = backup.linode_id
|
||||
self.message = backup.message
|
||||
self.parent_id_name = backup.parent_id_name
|
||||
self.country = backup.region.country
|
||||
self.status = backup.status
|
||||
self.type = backup.type
|
||||
self.updated = backup.updated
|
||||
|
||||
|
||||
class LinodeDiskModel(Mapping):
|
||||
def __init__(self, disk: Disk):
|
||||
super().__init__()
|
||||
self.created = disk.created
|
||||
self.filesystem = disk.filesystem
|
||||
self.id = disk.id
|
||||
self.label = disk.label
|
||||
self.linode_id = disk.linode_id
|
||||
self.parent_id_name = disk.parent_id_name
|
||||
self.size = disk.size
|
||||
self.status = disk.status
|
||||
self.updated = disk.updated
|
||||
|
||||
|
||||
class LinodeImageModel(Mapping):
|
||||
def __init__(self, image: Image):
|
||||
super().__init__()
|
||||
self.created = image.created
|
||||
self.created_by = image.created_by
|
||||
self.deprecated = image.deprecated
|
||||
self.description = image.description
|
||||
self.is_public = image.is_public
|
||||
self.label = image.label
|
||||
self.size = image.size
|
||||
self.status = image.status
|
||||
self.type = image.type
|
||||
self.vendor = image.vendor
|
||||
|
||||
|
||||
class LinodeTypeModel(Mapping):
|
||||
# noinspection PyShadowingBuiltins
|
||||
def __init__(self, type: Type):
|
||||
super().__init__()
|
||||
self.disk = type.disk
|
||||
self.id = type.id
|
||||
self.label = type.label
|
||||
self.memory = type.memory
|
||||
self.network_out = type.network_out
|
||||
self.price = type.price.dict
|
||||
self.transfer = type.transfer
|
||||
self.type_class = type.type_class
|
||||
self.vcpus = type.vcpus
|
||||
|
||||
|
||||
class LinodeInstanceModel(Mapping):
|
||||
def __init__(self, node: Instance):
|
||||
super().__init__()
|
||||
self.label = node.label
|
||||
self.status = node.status
|
||||
self.alerts = node.alerts.dict
|
||||
self.available_backups = [
|
||||
dict(LinodeBackupModel(backup))
|
||||
for backup in node.available_backups.automatic
|
||||
],
|
||||
|
||||
self.backups = {
|
||||
'enabled': node.backups.enabled,
|
||||
'schedule': node.backups.schedule.dict,
|
||||
'last_successful': datetime.datetime.fromisoformat(node.backups.last_successful),
|
||||
}
|
||||
|
||||
self.configs = {config.label: dict(LinodeConfigModel(config)) for config in node.configs}
|
||||
self.disks = {disk.label: dict(LinodeDiskModel(disk)) for disk in node.disks}
|
||||
self.group = node.group
|
||||
self.hypervisor = node.hypervisor
|
||||
self.id = node.id
|
||||
self.image = LinodeImageModel(node.image)
|
||||
self.country = node.region.country
|
||||
self.specs = node.specs.dict
|
||||
self.tags = node.tags
|
||||
self.transfer = node.transfer.dict
|
||||
self.type = dict(LinodeTypeModel(node.type))
|
||||
self.updated = node.updated
|
||||
|
||||
|
||||
class LinodeInstanceResponse(LinodeResponse):
|
||||
def __init__(self, instance: Instance, *args, **kwargs):
|
||||
super().__init__(*args, output={
|
||||
'instance': dict(LinodeInstanceModel(instance))
|
||||
}, **kwargs)
|
||||
|
||||
|
||||
class LinodeInstancesResponse(LinodeResponse):
|
||||
def __init__(self,
|
||||
instances: List[Instance],
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, output={
|
||||
'instances': {instance.label: dict(LinodeInstanceModel(instance)) for instance in instances},
|
||||
}, **kwargs)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -1,13 +1,22 @@
|
|||
from typing import Optional, Union
|
||||
from typing import Collection, Dict, List, Optional
|
||||
|
||||
from linode_api4 import LinodeClient, Instance
|
||||
from platypush.plugins.sensor import SensorPlugin
|
||||
from typing_extensions import override
|
||||
|
||||
from platypush.message.response.linode import LinodeInstancesResponse, LinodeInstanceResponse
|
||||
from platypush.plugins import action
|
||||
from linode_api4 import LinodeClient, Instance, objects
|
||||
|
||||
from platypush.context import get_bus
|
||||
from platypush.entities.cloud import CloudInstance
|
||||
from platypush.message.event.linode import LinodeInstanceStatusChanged
|
||||
from platypush.schemas.linode import (
|
||||
LinodeInstance,
|
||||
LinodeInstanceSchema,
|
||||
LinodeInstanceStatus,
|
||||
)
|
||||
from platypush.entities.managers.cloud import CloudInstanceEntityManager, InstanceId
|
||||
from platypush.plugins import RunnablePlugin, action
|
||||
|
||||
|
||||
class LinodePlugin(SensorPlugin):
|
||||
class LinodePlugin(RunnablePlugin, CloudInstanceEntityManager):
|
||||
"""
|
||||
This plugin can interact with a Linode account and manage node and volumes.
|
||||
|
||||
|
@ -21,79 +30,192 @@ class LinodePlugin(SensorPlugin):
|
|||
|
||||
* **linode_api4** (``pip install linode_api4``)
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.linode.LinodeInstanceStatusChanged` when the status of an instance changes.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, token: str, **kwargs):
|
||||
def __init__(self, token: str, poll_interval: float = 10.0, **kwargs):
|
||||
"""
|
||||
:param token: Your Linode token.
|
||||
:param token: Linode API token.
|
||||
:param poll_interval: How often to poll the Linode API
|
||||
(default: 60 seconds).
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
super().__init__(poll_interval=poll_interval, **kwargs)
|
||||
self._token = token
|
||||
self._instances: Dict[int, CloudInstance] = {}
|
||||
""" ``{instance_id: CloudInstance}`` mapping. """
|
||||
|
||||
def _get_client(self, token: Optional[str] = None) -> LinodeClient:
|
||||
"""
|
||||
Get a :class:`LinodeClient` instance.
|
||||
|
||||
:param token: Override the default token.
|
||||
"""
|
||||
return LinodeClient(token or self._token)
|
||||
|
||||
def _get_instance(self, label: str, token: Optional[str] = None) -> Instance:
|
||||
def _get_instance(
|
||||
self, instance: InstanceId, token: Optional[str] = None
|
||||
) -> Instance:
|
||||
"""
|
||||
Get an instance by name or ID.
|
||||
|
||||
:param instance: The label, ID or host UUID of the instance.
|
||||
:param token: Override the default token.
|
||||
"""
|
||||
client = self._get_client(token)
|
||||
instances = client.linode.instances(Instance.label == label)
|
||||
assert instances, 'No such Linode instance: ' + label
|
||||
if isinstance(instance, str):
|
||||
filters = Instance.label == instance
|
||||
elif isinstance(instance, int):
|
||||
filters = Instance.id == instance
|
||||
else:
|
||||
raise AssertionError(f'Invalid instance type: {type(instance)}')
|
||||
|
||||
instances = client.linode.instances(*filters)
|
||||
assert instances, f'No such Linode instance: {instance}'
|
||||
return instances[0]
|
||||
|
||||
def _linode_instance_to_dict(self, instance: Instance) -> dict:
|
||||
"""
|
||||
Convert an internal :class:`linode_api4.Instance` to a
|
||||
dictionary representation that can be used to create a
|
||||
:class:`platypush.entities.cloud.CloudInstance` object.
|
||||
"""
|
||||
return {
|
||||
key: (value.dict if isinstance(value, objects.MappedObject) else value)
|
||||
for key, value in instance.__dict__.items()
|
||||
if not key.startswith('_')
|
||||
}
|
||||
|
||||
@override
|
||||
def main(self):
|
||||
while not self.should_stop():
|
||||
status = self._instances.copy()
|
||||
new_status = self.status(publish_entities=False).output
|
||||
changed_instances = (
|
||||
[
|
||||
instance
|
||||
for instance in new_status
|
||||
if not (
|
||||
status.get(instance.id)
|
||||
and status[instance.id].status == instance.status
|
||||
)
|
||||
]
|
||||
if new_status
|
||||
else []
|
||||
)
|
||||
|
||||
if changed_instances:
|
||||
for instance in changed_instances:
|
||||
get_bus().post(
|
||||
LinodeInstanceStatusChanged(
|
||||
instance_id=instance.id,
|
||||
instance=instance.name,
|
||||
status=instance.status,
|
||||
old_status=(
|
||||
status[instance.id].status
|
||||
if status.get(instance.id)
|
||||
else None
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
self.publish_entities(changed_instances)
|
||||
|
||||
self._instances = new_status
|
||||
self.wait_stop(self.poll_interval)
|
||||
|
||||
@override
|
||||
def transform_entities(
|
||||
self, entities: Collection[LinodeInstance]
|
||||
) -> Collection[CloudInstance]:
|
||||
schema = LinodeInstanceSchema()
|
||||
return super().transform_entities(
|
||||
[
|
||||
CloudInstance(
|
||||
reachable=instance.status == LinodeInstanceStatus.RUNNING,
|
||||
**schema.dump(instance),
|
||||
)
|
||||
for instance in entities
|
||||
]
|
||||
)
|
||||
|
||||
@action
|
||||
def status(self, token: Optional[str] = None, instance: Optional[str] = None) \
|
||||
-> Union[LinodeInstanceResponse, LinodeInstancesResponse]:
|
||||
@override
|
||||
def status(
|
||||
self,
|
||||
*_,
|
||||
instance: Optional[InstanceId] = None,
|
||||
token: Optional[str] = None,
|
||||
publish_entities: bool = True,
|
||||
**__,
|
||||
) -> List[LinodeInstance]:
|
||||
"""
|
||||
Get the full status and info of the instances associated to a selected account.
|
||||
|
||||
:param token: Override the default access token if you want to query another account.
|
||||
:param instance: Select only one node by label.
|
||||
:return: :class:`platypush.message.response.linode.LinodeInstanceResponse` if ``label`` is specified,
|
||||
:class:`platypush.message.response.linode.LinodeInstancesResponse` otherwise.
|
||||
:param instance: Select only one instance, either by name, ID or host UUID.
|
||||
:param publish_entities: Whether
|
||||
:class:`platypush.message.event.entities.EntityUpdateEvent` should
|
||||
be published for all the instances, whether or not their status has
|
||||
changed (default: ``True``).
|
||||
:return: .. schema:: linode.LinodeInstanceSchema(many=True)
|
||||
"""
|
||||
if instance:
|
||||
instance = self._get_instance(label=instance)
|
||||
return LinodeInstanceResponse(instance=instance)
|
||||
instances = (
|
||||
[self._get_instance(instance=instance)]
|
||||
if instance
|
||||
else [
|
||||
instance
|
||||
for page in self._get_client(token).linode.instances().lists
|
||||
for instance in page
|
||||
]
|
||||
)
|
||||
|
||||
client = self._get_client(token)
|
||||
return LinodeInstancesResponse(instances=client.linode.instances())
|
||||
mapped_instances = LinodeInstanceSchema(many=True).load(
|
||||
map(self._linode_instance_to_dict, instances)
|
||||
)
|
||||
|
||||
if publish_entities:
|
||||
self.publish_entities(mapped_instances)
|
||||
|
||||
return mapped_instances
|
||||
|
||||
@override
|
||||
@action
|
||||
def reboot(self, instance: str, token: Optional[str] = None) -> None:
|
||||
def reboot(self, instance: InstanceId, token: Optional[str] = None, **_):
|
||||
"""
|
||||
Reboot an instance.
|
||||
|
||||
:param instance: Label of the instance to be rebooted.
|
||||
:param instance: Instance ID, label or host UUID.
|
||||
:param token: Default access token override.
|
||||
"""
|
||||
instance = self._get_instance(label=instance, token=token)
|
||||
assert instance.reboot(), 'Reboot failed'
|
||||
node = self._get_instance(instance=instance, token=token)
|
||||
assert node.reboot(), 'Reboot failed'
|
||||
|
||||
@override
|
||||
@action
|
||||
def boot(self, instance: str, token: Optional[str] = None) -> None:
|
||||
def boot(self, instance: InstanceId, token: Optional[str] = None, **_):
|
||||
"""
|
||||
Boot an instance.
|
||||
|
||||
:param instance: Label of the instance to be booted.
|
||||
:param instance: Instance ID, label or host UUID.
|
||||
:param token: Default access token override.
|
||||
"""
|
||||
instance = self._get_instance(label=instance, token=token)
|
||||
assert instance.boot(), 'Boot failed'
|
||||
node = self._get_instance(instance=instance, token=token)
|
||||
assert node.boot(), 'Boot failed'
|
||||
|
||||
@override
|
||||
@action
|
||||
def shutdown(self, instance: str, token: Optional[str] = None) -> None:
|
||||
def shutdown(self, instance: InstanceId, token: Optional[str] = None, **_):
|
||||
"""
|
||||
Shutdown an instance.
|
||||
|
||||
:param instance: Label of the instance to be shut down.
|
||||
:param instance: Instance ID, label or host UUID.
|
||||
:param token: Default access token override.
|
||||
"""
|
||||
instance = self._get_instance(label=instance, token=token)
|
||||
assert instance.shutdown(), 'Shutdown failed'
|
||||
|
||||
@action
|
||||
def get_measurement(self, *args, **kwargs):
|
||||
return self.status(*args, **kwargs)
|
||||
node = self._get_instance(instance=instance, token=token)
|
||||
assert node.shutdown(), 'Shutdown failed'
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
manifest:
|
||||
events: {}
|
||||
events:
|
||||
platypush.message.event.linode.LinodeInstanceStatusChanged:
|
||||
install:
|
||||
pip:
|
||||
- linode_api4
|
||||
|
|
421
platypush/schemas/linode.py
Normal file
421
platypush/schemas/linode.py
Normal file
|
@ -0,0 +1,421 @@
|
|||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from marshmallow import pre_load
|
||||
from marshmallow.fields import Function
|
||||
from marshmallow.validate import Range
|
||||
from marshmallow_dataclass import class_schema
|
||||
|
||||
from platypush.schemas import EnumField
|
||||
from platypush.schemas.dataclasses import DataClassSchema
|
||||
|
||||
|
||||
class LinodeInstanceStatus(Enum):
|
||||
"""
|
||||
Maps the possible states of an instance.
|
||||
"""
|
||||
|
||||
RUNNING = 'running'
|
||||
OFFLINE = 'offline'
|
||||
BOOTING = 'booting'
|
||||
REBOOTING = 'rebooting'
|
||||
SHUTTING_DOWN = 'shutting_down'
|
||||
PROVISIONING = 'provisioning'
|
||||
DELETING = 'deleting'
|
||||
MIGRATING = 'migrating'
|
||||
REBUILDING = 'rebuilding'
|
||||
CLONING = 'cloning'
|
||||
RESTORING = 'restoring'
|
||||
STOPPED = 'stopped'
|
||||
|
||||
|
||||
class LinodeInstanceBackupScheduleDay(Enum):
|
||||
"""
|
||||
Allowed values for ``backups.schedule.day``.
|
||||
"""
|
||||
|
||||
SCHEDULING = 'Scheduling'
|
||||
SUNDAY = 'Sunday'
|
||||
MONDAY = 'Monday'
|
||||
TUESDAY = 'Tuesday'
|
||||
WEDNESDAY = 'Wednesday'
|
||||
THURSDAY = 'Thursday'
|
||||
FRIDAY = 'Friday'
|
||||
SATURDAY = 'Saturday'
|
||||
|
||||
|
||||
class LinodeInstanceBackupScheduleWindow(Enum):
|
||||
"""
|
||||
Allowed values for ``backups.schedule.window``.
|
||||
|
||||
The window in which your backups will be taken, in UTC. A backups window is
|
||||
a two-hour span of time in which the backup may occur.
|
||||
|
||||
For example, W10 indicates that your backups should be taken between 10:00
|
||||
and 12:00.
|
||||
"""
|
||||
|
||||
SCHEDULING = 'Scheduling'
|
||||
W0 = 'W0'
|
||||
W2 = 'W2'
|
||||
W4 = 'W4'
|
||||
W6 = 'W6'
|
||||
W8 = 'W8'
|
||||
W10 = 'W10'
|
||||
W12 = 'W12'
|
||||
W14 = 'W14'
|
||||
W16 = 'W16'
|
||||
W18 = 'W18'
|
||||
W20 = 'W20'
|
||||
W22 = 'W22'
|
||||
|
||||
|
||||
class FieldWithId(Function):
|
||||
"""
|
||||
Field that handles values that are objects with an ``id`` attribute.
|
||||
"""
|
||||
|
||||
def _deserialize(self, value: Any, *_, **__) -> Optional[Any]:
|
||||
return value.id if value is not None else None
|
||||
|
||||
def _serialize(self, value: Any, *_, **__) -> Optional[Any]:
|
||||
return value
|
||||
|
||||
|
||||
class LinodeBaseSchema(DataClassSchema):
|
||||
"""
|
||||
Base schema for all Linode objects.
|
||||
"""
|
||||
|
||||
TYPE_MAPPING = {
|
||||
LinodeInstanceStatus: partial( # type: ignore
|
||||
EnumField, type=LinodeInstanceStatus
|
||||
),
|
||||
LinodeInstanceBackupScheduleDay: partial( # type: ignore
|
||||
EnumField, type=LinodeInstanceBackupScheduleDay
|
||||
),
|
||||
LinodeInstanceBackupScheduleWindow: partial( # type: ignore
|
||||
EnumField, type=LinodeInstanceBackupScheduleWindow
|
||||
),
|
||||
**DataClassSchema.TYPE_MAPPING,
|
||||
}
|
||||
|
||||
@pre_load
|
||||
def pre_load(self, data: dict, **_) -> dict:
|
||||
from linode_api4.objects.base import MappedObject
|
||||
|
||||
# Expand MappedObjects to dictionaries
|
||||
for key, value in data.items():
|
||||
if isinstance(value, MappedObject):
|
||||
data[key] = value.dict
|
||||
|
||||
# NOTE Workaround for type -> instance_type not being correctly mapped
|
||||
if 'type' in data:
|
||||
data['instance_type'] = data.pop('type')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinodeInstanceSpecs:
|
||||
"""
|
||||
Class that models the specifications of a Linode instance.
|
||||
"""
|
||||
|
||||
disk: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Allocated disk size, in MB',
|
||||
'example': 100000,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
memory: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Allocated RAM size, in MB',
|
||||
'example': 8192,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
cpus: int = field(
|
||||
metadata={
|
||||
'data_key': 'vcpus',
|
||||
'metadata': {
|
||||
'description': 'Number of virtual CPUs allocated to the instance',
|
||||
'example': 4,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
gpus: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Number of GPUs allocated to the instance',
|
||||
'example': 1,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
transfer: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': (
|
||||
'Number of network transfers this instance is allotted each month',
|
||||
),
|
||||
'example': 5000,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinodeInstanceAlerts:
|
||||
"""
|
||||
Class that models the alerts configuration of a Linode instance.
|
||||
"""
|
||||
|
||||
cpu: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'validate': Range(min=0, max=100),
|
||||
'description': (
|
||||
'The percentage of CPU average usage over the past two hours '
|
||||
'required to trigger an alert',
|
||||
),
|
||||
'example': 90,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
io: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': (
|
||||
'The amount of disk I/O operations per second required to '
|
||||
'trigger an alert'
|
||||
),
|
||||
'example': 5000,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
network_in: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': (
|
||||
'The amount of incoming network traffic, in Mbit/s, '
|
||||
'required to trigger an alert'
|
||||
),
|
||||
'example': 10,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
network_out: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': (
|
||||
'The amount of outgoing network traffic, in Mbit/s, '
|
||||
'required to trigger an alert'
|
||||
),
|
||||
'example': 10,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
transfer_quota: int = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'validate': Range(min=0, max=100),
|
||||
'description': (
|
||||
'The percentage of network transfer that may be used before '
|
||||
'an alert is triggered',
|
||||
),
|
||||
'example': 80,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinodeInstanceBackupSchedule:
|
||||
"""
|
||||
Class that models the backup schedule of a Linode instance.
|
||||
"""
|
||||
|
||||
day: Optional[LinodeInstanceBackupScheduleDay]
|
||||
window: Optional[LinodeInstanceBackupScheduleWindow]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinodeInstanceBackups:
|
||||
"""
|
||||
Class that models the backup status of a Linode instance.
|
||||
"""
|
||||
|
||||
available: bool
|
||||
enabled: bool = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Whether the backups are enabled on this instance',
|
||||
'example': True,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
schedule: LinodeInstanceBackupSchedule
|
||||
last_successful: Optional[datetime] = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'When the last backup was successful',
|
||||
'example': '2020-01-01T00:00:00Z',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinodeInstance:
|
||||
"""
|
||||
Class that models a Linode instance.
|
||||
"""
|
||||
|
||||
id: int = field(
|
||||
metadata={
|
||||
'required': True,
|
||||
'metadata': {
|
||||
'description': 'Instance ID',
|
||||
'example': 12345,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
name: str = field(
|
||||
metadata={
|
||||
'required': True,
|
||||
'data_key': 'label',
|
||||
'metadata': {
|
||||
'description': 'Instance name',
|
||||
'example': 'my-instance',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
instance_type: str = field(
|
||||
metadata={
|
||||
'marshmallow_field': FieldWithId(),
|
||||
'metadata': {
|
||||
'description': 'Instance type',
|
||||
'example': 'g6-standard-4',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ipv4_addresses: List[str] = field(
|
||||
metadata={
|
||||
'data_key': 'ipv4',
|
||||
'metadata': {
|
||||
'description': 'List of IPv4 addresses associated with this instance',
|
||||
'example': '["1.2.3.4"]',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ipv6_address: str = field(
|
||||
metadata={
|
||||
'data_key': 'ipv6',
|
||||
'metadata': {
|
||||
'description': 'IPv6 address associated with this instance',
|
||||
'example': '1234:5678::9abc:def0:1234:5678/128',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
group: str = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Group the instance belongs to',
|
||||
'example': 'my-group',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
status: LinodeInstanceStatus = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'Instance status',
|
||||
'example': 'running',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
tags: List[str] = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'List of tags associated with this instance',
|
||||
'example': '["tag1", "tag2"]',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
image: str = field(
|
||||
metadata={
|
||||
'marshmallow_field': FieldWithId(),
|
||||
'metadata': {
|
||||
'description': 'Image used to ',
|
||||
'example': 'linode/archlinux2014.04',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
region: str = field(
|
||||
metadata={
|
||||
'marshmallow_field': FieldWithId(),
|
||||
'metadata': {
|
||||
'description': 'Region where the instance is located',
|
||||
'example': 'eu-west',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
hypervisor: str = field(
|
||||
metadata={
|
||||
'metadata': {
|
||||
'description': 'The virtualization engine powering this instance',
|
||||
'example': 'kvm',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
specs: LinodeInstanceSpecs
|
||||
alerts: LinodeInstanceAlerts
|
||||
backups: LinodeInstanceBackups
|
||||
|
||||
created_at: datetime = field(
|
||||
metadata={
|
||||
'data_key': 'created',
|
||||
'metadata': {
|
||||
'description': 'Instance creation date',
|
||||
'example': '2020-01-01T00:00:00Z',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
updated_at: datetime = field(
|
||||
metadata={
|
||||
'data_key': 'updated',
|
||||
'metadata': {
|
||||
'description': 'When the instance was last polled/updated',
|
||||
'example': '2020-01-01T01:00:00Z',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
LinodeInstanceSchema = class_schema(LinodeInstance, base_schema=LinodeBaseSchema)
|
Loading…
Reference in a new issue