Skip to content
Merged
65 changes: 65 additions & 0 deletions examples/vm_snapshot_removal_based_on_timestamp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
- name: Example - delete all snapshots that are older than X days.
hosts: localhost
connection: local
gather_facts: false
vars:
# Format: 'YYYY-MM-DD hh:mm:ss'
# All snapshots older than this date will be deleted.
# use_date timezone should match the Scale cluster timezone
use_date: '1999-05-03 12:52:00'

tasks:
# ------------------------------------------------------
- name: List all snapshots
scale_computing.hypercore.vm_snapshot_info:
register: snapshot_results

- name: Convert date to unix timestamp 'epoch'
ansible.builtin.set_fact:
epoch_timestamp: "{{ (use_date | to_datetime).strftime('%s') }}"

- name: Show epoch_timestamp
ansible.builtin.debug:
var: epoch_timestamp

- name: Create filtered_snapshots list
ansible.builtin.set_fact:
filtered_snapshots: []

- name: Loop through snapshots and add snapshots that are older than 'use_date'
ansible.builtin.set_fact:
filtered_snapshots: "{{ filtered_snapshots + [item] }}"
when: item.timestamp < epoch_timestamp | int
loop: "{{ snapshot_results.records }}"
no_log: true

- name: Show only snapshots that are older than 'use_date'
ansible.builtin.debug:
var: filtered_snapshots

# We could reuse "filtered_snapshots" here instead of "snapshot_results" and avoid the "when" statement.
# But leaving it as is for example purposes.
# Since this is the only mandatory task of the playbook, can be copy-pasted and reused as standalone task.
- name: Loop through list of snapshots and delete all older than the 'use_date'
scale_computing.hypercore.vm_snapshot:
vm_name: "{{ item.vm.name }}"
uuid: "{{ item.snapshot_uuid }}"
state: absent
when: item.timestamp < epoch_timestamp | int
loop: "{{ snapshot_results.records }}"

- name: Create filtered_snapshots list - second time
ansible.builtin.set_fact:
filtered_snapshots: []

- name: Loop through snapshots and add snapshots that are older than 'use_date' - second time
ansible.builtin.set_fact:
filtered_snapshots: "{{ filtered_snapshots + [item] }}"
when: item.timestamp < epoch_timestamp | int
loop: "{{ snapshot_results.records }}"
no_log: true

- name: Show only snapshots that are older than 'use_date' - second time
ansible.builtin.debug:
var: filtered_snapshots
60 changes: 60 additions & 0 deletions examples/vm_snapshot_special_cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
- name: Example - delete all snapshots with label "TEST" and type "USER".
hosts: localhost
connection: local
gather_facts: false
vars:
# This variable is used to filter and delete snapshots.
# All snapshots with 'use_label' will be DELETED.
use_label: 'TEST'

tasks:
# ------------------------------------------------------
- name: List all snapshots
scale_computing.hypercore.vm_snapshot_info:
register: snapshot_results

- name: Create filtered_snapshots list
ansible.builtin.set_fact:
filtered_snapshots: []

- name: Loop through snapshots and add snapshots with use_label and type 'USER' to filtered_snapshots
ansible.builtin.set_fact:
filtered_snapshots: "{{ filtered_snapshots + [item] }}"
when: item.label == use_label and item.type == 'USER'
loop: "{{ snapshot_results.records }}"
no_log: true

- name: Show only snapshots with use_label and type "USER"
ansible.builtin.debug:
var: filtered_snapshots

# We could reuse "filtered_snapshots" here instead of "snapshot_results" and avoid the "when" statement.
# But leaving it as is for example purposes.
# Since this is the only mandatory task of the playbook, can be copy-pasted and reused as standalone task.
- name: Loop through list of snapshots delete if label is use_label and type is 'USER'
scale_computing.hypercore.vm_snapshot:
vm_name: "{{ item.vm.name }}"
uuid: "{{ item.snapshot_uuid }}"
state: absent
when: item.label == use_label and item.type == 'USER'
loop: "{{ snapshot_results.records }}"

- name: List all snapshots - second time
scale_computing.hypercore.vm_snapshot_info:
register: snapshot_results

- name: Create filtered_snapshots list - second time
ansible.builtin.set_fact:
filtered_snapshots: []

- name: Loop through snapshots and add snapshots with use_label and type 'USER' to filtered_snapshots - second time
ansible.builtin.set_fact:
filtered_snapshots: "{{ filtered_snapshots + [item] }}"
when: item.label == use_label and item.type == 'USER'
loop: "{{ snapshot_results.records }}"
no_log: true

- name: Show only snapshots with use_label and type 'USER' - second time
ansible.builtin.debug:
var: filtered_snapshots
3 changes: 1 addition & 2 deletions plugins/module_utils/vm_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,7 @@ def get_snapshot_by_uuid(
cls, snapshot_uuid: str, rest_client: RestClient, must_exist: bool = False
) -> Optional[VMSnapshot]:
hypercore_dict = rest_client.get_record(
endpoint="/rest/v1/VirDomainSnapshot",
query={"uuid": snapshot_uuid},
endpoint=f"/rest/v1/VirDomainSnapshot/{snapshot_uuid}",
must_exist=must_exist,
)
vm_snapshot = cls.from_hypercore(hypercore_dict)
Expand Down
49 changes: 38 additions & 11 deletions plugins/modules/vm_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
description: source VM name.
label:
type: str
required: true
description:
- Snapshot label, used as identificator in combination with vm_name.
- Must be unique for a specific VM.
Expand All @@ -49,6 +48,12 @@
choices: [ present, absent]
type: str
required: True
uuid:
type: str
description:
- Snapshot uuid, used as identificator.
- Can be used instead of label.
- Must be unique.
"""


Expand Down Expand Up @@ -145,6 +150,29 @@
from ..module_utils.vm import VM


def get_snapshot(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance to get a snapshot with correct label, but from a different VM?

It seems to me: filter to domainUUID=vm_object.uuid part is lost. And integration tests should be extended too.

Copy link
Collaborator Author

@domendobnikar domendobnikar May 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pass VMobject as one of the parameters in this function.
It is then used with label for snapshot query if snapshot_uuid isn't passed as parameter to playbook.
The only added part was the if module.params["uuid"]: , everything else is the same. (code was taken from ensure_present function)
UUID is unique throughout the platform and use a different endpoint, it does not require any additional filtering by VM or label.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will extend integration tests to include the filtering by uuid part.
Good point 👍

module: AnsibleModule, rest_client: RestClient, vm_object: VM
) -> List[TypedVMSnapshotToAnsible]:
# Get snapshot by uuid first if parameter exists.
if module.params["uuid"]:
snapshot_list = VMSnapshot.get_snapshots_by_query(
dict(uuid=module.params["uuid"], domainUUID=vm_object.uuid), rest_client
)
# Otherwise get by label
else:
snapshot_list = VMSnapshot.get_snapshots_by_query(
dict(label=module.params["label"], domainUUID=vm_object.uuid), rest_client
)

# Snapshot should be unique by this point.
if len(snapshot_list) > 1:
raise errors.ScaleComputingError(
f"Virtual machine - {module.params['vm_name']} - has more than one snapshot with label - {module.params['label']}, specify uuid instead."
)

return snapshot_list


def ensure_present(
module: AnsibleModule,
rest_client: RestClient,
Expand Down Expand Up @@ -203,15 +231,7 @@ def run(
module: AnsibleModule, rest_client: RestClient
) -> Tuple[bool, Optional[TypedVMSnapshotToAnsible], TypedDiff]:
vm_object: VM = VM.get_by_name(module.params, rest_client, must_exist=True) # type: ignore
snapshot_list = VMSnapshot.get_snapshots_by_query(
dict(label=module.params["label"], domainUUID=vm_object.uuid), rest_client
)

# VM should only have one snapshot with a specific label, we use vm_name and label as snapshot identificator.
if len(snapshot_list) > 1:
raise errors.ScaleComputingError(
f"Virtual machine - {module.params['vm_name']} - has more than one snapshot with label - {module.params['label']}."
)
snapshot_list = get_snapshot(module, rest_client, vm_object)

if module.params["state"] == State.present:
return ensure_present(module, rest_client, vm_object, snapshot_list)
Expand All @@ -237,7 +257,6 @@ def main() -> None:
),
label=dict(
type="str",
required=True,
),
retain_for=dict(
type="int",
Expand All @@ -246,7 +265,15 @@ def main() -> None:
type="bool",
default=True,
),
uuid=dict(
type="str",
),
),
mutually_exclusive=[("label", "uuid")],
required_if=[
("state", "absent", ("label", "uuid"), True),
("state", "present", ["label"]),
],
)

try:
Expand Down
Loading