Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 linode_api4/groups/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .lke import *
from .lke_tier import *
from .longview import *
from .maintenance import *
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed
from .monitor import *
from .monitor_api import *
from .networking import *
Expand Down
2 changes: 1 addition & 1 deletion 2 linode_api4/groups/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def maintenance(self):
"""
Returns a collection of Maintenance objects for any entity a user has permissions to view. Cancelled Maintenance objects are not returned.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-account-logins
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-maintenance

:returns: A list of Maintenance objects on this account.
:rtype: List of Maintenance objects as MappedObjects
Expand Down
7 changes: 7 additions & 0 deletions 7 linode_api4/groups/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def instance_create(
int,
]
] = None,
maintenance_policy: Optional[str] = None,
**kwargs,
):
"""
Expand Down Expand Up @@ -296,6 +297,11 @@ def instance_create(
:type interfaces: list[ConfigInterface] or list[dict[str, Any]]
:param placement_group: A Placement Group to create this Linode under.
:type placement_group: Union[InstancePlacementGroupAssignment, PlacementGroup, Dict[str, Any], int]
:param maintenance_policy: The slug of the maintenance policy to apply during maintenance.
If not provided, the default policy (linode/migrate) will be applied.
NOTE: This field is in beta and may only
function if base_url is set to `https://api.linode.com/v4beta`.
:type maintenance_policy: str

:returns: A new Instance object, or a tuple containing the new Instance and
the generated password.
Expand Down Expand Up @@ -327,6 +333,7 @@ def instance_create(
"firewall_id": firewall,
"backup_id": backup,
"stackscript_id": stackscript,
"maintenance_policy": maintenance_policy,
# Special cases
"disk_encryption": (
str(disk_encryption) if disk_encryption else None
Expand Down
25 changes: 25 additions & 0 deletions 25 linode_api4/groups/maintenance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from linode_api4.groups import Group
from linode_api4.objects import MappedObject


class MaintenanceGroup(Group):
"""
Collections related to Maintenance.
"""

def maintenance_policies(self):
"""
.. note:: This endpoint is in beta. This will only function if base_url is set to `https://api.linode.com/v4beta`.

Returns a collection of MaintenancePolicy objects representing
available maintenance policies that can be applied to Linodes

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-policies

:returns: A list of Maintenance Policies that can be applied to Linodes
:rtype: List of MaintenancePolicy objects as MappedObjects
"""

result = self.client.get("/maintenance/policies", model=self)

return [MappedObject(**r) for r in result["data"]]
5 changes: 5 additions & 0 deletions 5 linode_api4/linode_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
LinodeGroup,
LKEGroup,
LongviewGroup,
MaintenanceGroup,
MetricsGroup,
MonitorGroup,
NetworkingGroup,
Expand Down Expand Up @@ -399,6 +400,10 @@ def __init__(
#: :any:`NetworkingGroup` for more information
self.networking = NetworkingGroup(self)

#: Access methods related to maintenance on your account - see
#: :any:`MaintenanceGroup` for more information
self.maintenance = MaintenanceGroup(self)

#: Access methods related to support - see :any:`SupportGroup` for more
#: information
self.support = SupportGroup(self)
Expand Down
11 changes: 10 additions & 1 deletion 11 linode_api4/objects/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ class AccountSettings(Base):
),
"object_storage": Property(),
"backups_enabled": Property(mutable=True),
"maintenance_policy": Property(
mutable=True
), # Note: This field is only available when using v4beta.
}


Expand All @@ -220,12 +223,18 @@ class Event(Base):
"user_id": Property(),
"username": Property(),
"entity": Property(),
"time_remaining": Property(),
"time_remaining": Property(), # Deprecated
"rate": Property(),
"status": Property(),
"duration": Property(),
"secondary_entity": Property(),
"message": Property(),
"maintenance_policy_set": Property(), # Note: This field is only available when using v4beta.
"description": Property(),
"source": Property(),
"not_before": Property(is_datetime=True),
"start_time": Property(is_datetime=True),
"complete_time": Property(is_datetime=True),
}

@property
Expand Down
3 changes: 3 additions & 0 deletions 3 linode_api4/objects/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@ class Instance(Base):
"disk_encryption": Property(),
"lke_cluster_id": Property(),
"capabilities": Property(unordered=True),
"maintenance_policy": Property(
mutable=True
), # Note: This field is only available when using v4beta.
}

@property
Expand Down
56 changes: 30 additions & 26 deletions 56 test/fixtures/account_events_123.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
{
"action": "ticket_create",
"created": "2018-01-01T00:01:01",
"duration": 300.56,
"entity": {
"id": 11111,
"label": "Problem booting my Linode",
"type": "ticket",
"url": "/v4/support/tickets/11111"
},
"id": 123,
"message": "None",
"percent_complete": null,
"rate": null,
"read": true,
"secondary_entity": {
"id": "linode/debian9",
"label": "linode1234",
"type": "linode",
"url": "/v4/linode/instances/1234"
},
"seen": true,
"status": null,
"time_remaining": null,
"username": "exampleUser"
}

"action": "ticket_create",
"created": "2025-03-25T12:00:00",
"duration": 300.56,
"entity": {
"id": 11111,
"label": "Problem booting my Linode",
"type": "ticket",
"url": "/v4/support/tickets/11111"
},
"id": 123,
"message": "Ticket created for user issue.",
"percent_complete": null,
"rate": null,
"read": true,
"secondary_entity": {
"id": "linode/debian9",
"label": "linode1234",
"type": "linode",
"url": "/v4/linode/instances/1234"
},
"seen": true,
"status": "completed",
"username": "exampleUser",
"maintenance_policy_set": "Tentative",
"description": "Scheduled maintenance",
"source": "user",
"not_before": "2025-03-25T12:00:00",
"start_time": "2025-03-25T12:30:00",
"complete_time": "2025-03-25T13:00:00"
}
58 changes: 40 additions & 18 deletions 58 test/fixtures/account_maintenance.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
{
"data": [
{
"entity": {
"id": 123,
"label": "demo-linode",
"type": "Linode",
"url": "https://api.linode.com/v4/linode/instances/{linodeId}"
},
"reason": "This maintenance will allow us to update the BIOS on the host's motherboard.",
"status": "started",
"type": "reboot",
"when": "2020-07-09T00:01:01"
}
],
"page": 1,
"pages": 1,
"results": 1
}
"pages": 1,
"page": 1,
"results": 2,
"data": [
{
"entity": {
"id": 1234,
"label": "Linode #1234",
"type": "linode",
"url": "/linodes/1234"
},
"reason": "Scheduled upgrade to faster NVMe hardware.",
"type": "linode_migrate",
"maintenance_policy_set": "linode/power_off_on",
"description": "Scheduled Maintenance",
"source": "platform",
"not_before": "2025-03-25T10:00:00Z",
"start_time": "2025-03-25T12:00:00Z",
"complete_time": "2025-03-25T14:00:00Z",
"status": "scheduled"
},
{
"entity": {
"id": 1234,
"label": "Linode #1234",
"type": "linode",
"url": "/linodes/1234"
},
"reason": "Pending migration of Linode #1234 to a new host.",
"type": "linode_migrate",
"maintenance_policy_set": "linode/migrate",
"description": "Emergency Maintenance",
"source": "user",
"not_before": "2025-03-26T15:00:00Z",
"start_time": "2025-03-26T15:00:00Z",
"complete_time": "2025-03-26T17:00:00Z",
"status": "in-progress"
}
]
}
3 changes: 2 additions & 1 deletion 3 test/fixtures/account_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"managed": false,
"network_helper": false,
"object_storage": "active",
"backups_enabled": true
"backups_enabled": true,
"maintenance_policy": "linode/migrate"
}
6 changes: 4 additions & 2 deletions 6 test/fixtures/linode_instances.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"label": "test",
"placement_group_type": "anti_affinity:local",
"placement_group_policy": "strict"
}
},
"maintenance_policy" : "linode/migrate"
},
{
"group": "test",
Expand Down Expand Up @@ -90,7 +91,8 @@
"watchdog_enabled": false,
"disk_encryption": "enabled",
"lke_cluster_id": 18881,
"placement_group": null
"placement_group": null,
"maintenance_policy" : "linode/power_off_on"
}
]
}
28 changes: 28 additions & 0 deletions 28 test/fixtures/maintenance_policies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": [
{
"slug": "linode/migrate",
"label": "Migrate",
"description": "Migrates the Linode to a new host while it remains fully operational. Recommended for maximizing availability.",
"type": "migrate",
"notification_period_sec": 3600,
"is_default": true
},
{
"slug": "linode/power_off_on",
"label": "Power Off/Power On",
"description": "Powers off the Linode at the start of the maintenance event and reboots it once the maintenance finishes. Recommended for maximizing performance.",
"type": "power_off_on",
"notification_period_sec": 1800,
"is_default": false
},
{
"slug": "private/12345",
"label": "Critical Workload - Avoid Migration",
"description": "Custom policy designed to power off and perform maintenance during user-defined windows only.",
"type": "power_off_on",
"notification_period_sec": 7200,
"is_default": false
}
]
}
25 changes: 25 additions & 0 deletions 25 test/integration/models/account/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,31 @@ def test_get_account_settings(test_linode_client):
assert "longview_subscription" in str(account_settings._raw_json)
assert "backups_enabled" in str(account_settings._raw_json)
assert "object_storage" in str(account_settings._raw_json)
assert "maintenance_policy" in str(account_settings._raw_json)


def test_update_maintenance_policy(test_linode_client):
client = test_linode_client
settings = client.load(AccountSettings(client, ""), "")

original_policy = settings.maintenance_policy
new_policy = (
"linode/power_off_on"
if original_policy == "linode/migrate"
else "linode/migrate"
)

settings.maintenance_policy = new_policy
settings.save()

updated = client.load(AccountSettings(client, ""), "")
assert updated.maintenance_policy == new_policy
ezilber-akamai marked this conversation as resolved.
Show resolved Hide resolved

settings.maintenance_policy = original_policy
settings.save()

updated = client.load(AccountSettings(client, ""), "")
assert updated.maintenance_policy == original_policy


@pytest.mark.smoke
Expand Down
44 changes: 44 additions & 0 deletions 44 test/integration/models/linode/test_linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,3 +877,47 @@ def test_delete_interface_containing_vpc(

# returns true when delete successful
assert result


def test_create_linode_with_maintenance_policy(test_linode_client):
client = test_linode_client
region = get_region(client, {"Linodes"}, site_type="core")
label = get_test_label()

policies = client.maintenance.maintenance_policies()
assert policies, "No maintenance policies returned from API"

non_default_policy = next((p for p in policies if not p.is_default), None)
assert non_default_policy, "No non-default maintenance policy available"

linode_instance, password = client.linode.instance_create(
"g6-nanode-1",
region,
image="linode/debian12",
label=label + "_with_policy",
maintenance_policy_id=non_default_policy.slug,
)

assert linode_instance.id is not None
assert linode_instance.label.startswith(label)
assert linode_instance.maintenance_policy == non_default_policy.slug

linode_instance.delete()


def test_update_linode_maintenance_policy(create_linode, test_linode_client):
client = test_linode_client
linode = create_linode

policies = client.maintenance.maintenance_policies()
assert policies, "No maintenance policies returned from API"

non_default_policy = next((p for p in policies if not p.is_default), None)
assert non_default_policy, "No non-default maintenance policy found"

linode.maintenance_policy_id = non_default_policy.slug
result = linode.save()

linode.invalidate()
assert result
assert linode.maintenance_policy_id == non_default_policy.slug
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.