XCP-ng
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Register
    • Login

    Delete VM Snapshot over REST

    Scheduled Pinned Locked Moved Solved REST API
    7 Posts 4 Posters 173 Views 3 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • R Offline
      RaHu
      last edited by

      Hi Community

      I was able to create a bash script that uses the local machines uuid (from bios) in order to trigger a snapshot creation (of the vm itself) over xo rest api.

      Now, I would like to do some very basic kind of retention. I want my script to get all snapshots of the same VM and remove older snapshots that exceed a limit provided over command line. Otherwise, you'd have to care manually about removing older snapshots.

      However, I was unable to find a how to for deleting a snapshot over rest. Is this not possible yet?

      Thanks,
      Rainer

      lsouai-vatesL 2 Replies Last reply Reply Quote 0
      • olivierlambertO Offline
        olivierlambert Vates πŸͺ Co-Founder CEO
        last edited by

        Let me ask to @lsouai-vates πŸ™‚

        1 Reply Last reply Reply Quote 0
        • lsouai-vatesL Offline
          lsouai-vates Vates πŸͺ XO Team @RaHu
          last edited by

          Hello @RaHu !
          I am asking the XO team and keep you in touch πŸ™‚

          1 Reply Last reply Reply Quote 0
          • lsouai-vatesL Offline
            lsouai-vates Vates πŸͺ XO Team @RaHu
            last edited by

            @RaHu you can take a look there: https://github.com/vatesfr/xen-orchestra/blob/master/packages/xo-server/docs/rest-api.md#vm-destruction

            curl \
              -X DELETE \
              -b authenticationToken=KQxQdm2vMiv7jBIK0hgkmgxKzemd8wSJ7ugFGKFkTbs \
              'https://xo.company.lan/rest/v0/vms/770aa52a-fd42-8faf-f167-8c5c4a237cac'
            

            Hope it will help you πŸ™‚

            R 1 Reply Last reply Reply Quote 0
            • R Offline
              RaHu @lsouai-vates
              last edited by RaHu

              @lsouai-vates

              Thanks! Will try it out!

              //update: but if I see this correctly, this would delete the whole vm, not a snapshot - or is this just an example that could be applied to https://xo.company.lan/rest/v0/vm-snapshots/uuid ?

              DanpD 1 Reply Last reply Reply Quote 0
              • DanpD Offline
                Danp Pro Support Team @RaHu
                last edited by

                @RaHu Your are correct. To delete an individual snapshot, change /vms/ to /vm-snapshots/.

                R 1 Reply Last reply Reply Quote 0
                • R Offline
                  RaHu @Danp
                  last edited by RaHu

                  @Danp Thanks! Finally I was able to get my script working. Also some contribution to "GROK" who assisted quite well! πŸ˜‰

                  It allows a VM to Snapshot itself, before running automated maintenance over cron. And it allows minimalistic retention of the snapshots created this way.

                  Happy to share it with the community:

                  #!/bin/bash
                  
                  : <<'END'
                  Snapshot Management Script for Xen Orchestra
                  
                  Usage: ./snapshot-vm.sh [OPTIONS]
                  
                  Options:
                    -v          Enable verbose output (detailed logging)
                    -s          Silent mode (suppress all non-error output)
                    -r <number> Set retention limit (delete oldest snapshots beyond this number)
                    -n          No-snapshot mode (skip snapshot creation, only manage retention)
                  
                  Examples:
                    ./snapshot-vm.sh              # Create a snapshot, no retention
                    ./snapshot-vm.sh -v -r 3      # Create a snapshot, keep latest 3, verbose
                    ./snapshot-vm.sh -r 5 -n      # Skip snapshot, keep latest 5
                    ./snapshot-vm.sh -v -s -r 2   # Create a snapshot, keep latest 2, verbose but silent
                  
                  Configuration:
                    Edit XO_URL, TOKEN, and SNAPSHOT_PREFIX at the top of the script.
                  END
                  
                  # Configuration variables
                  XO_URL="http://your-xen-orchestra-server"
                  TOKEN="your-authentication-token"
                  VM_UUID=$(dmidecode -s system-uuid | tr '[:upper:]' '[:lower:]')
                  SNAPSHOT_PREFIX="AutoMaintenance"  # Prefix for snapshot names
                  
                  # Default retention (no deletion if not specified)
                  RETENTION=-1
                  
                  # Flags
                  VERBOSE=0
                  SILENT=0
                  NOSNAPSHOT=0
                  
                  # Parse command-line options
                  while getopts "vsr:n" opt; do
                      case $opt in
                          v) VERBOSE=1;;
                          s) SILENT=1;;
                          r) RETENTION="$OPTARG";;
                          n) NOSNAPSHOT=1;;
                      esac
                  done
                  
                  # Function to print messages based on mode
                  print_msg() {
                      if [ $SILENT -eq 0 ]; then
                          if [ $VERBOSE -eq 1 ]; then
                              echo "$1"
                          else
                              echo -e "$1"
                          fi
                      fi
                  }
                  
                  # Check if we got the UUID
                  if [ -z "$VM_UUID" ]; then
                      echo "Error: Could not retrieve system UUID"
                      exit 1
                  fi
                  
                  # Function to check if jq is installed
                  check_jq() {
                      if ! command -v jq &> /dev/null; then
                          echo "Error: jq is required but not installed"
                          exit 1
                      fi
                  }
                  
                  # Function to get VM name
                  get_vm_name() {
                      local vm_info=$(curl -s -X GET \
                          -b "authenticationToken=$TOKEN" \
                          -H "Accept: application/json" \
                          "$XO_URL/rest/v0/vms/$VM_UUID")
                      vm_name=$(echo "$vm_info" | jq -r '.name_label // "Unknown"')
                  }
                  
                  # Function to trigger snapshot
                  trigger_snapshot() {
                      local SNAPSHOT_NAME="${SNAPSHOT_PREFIX}-$(date +%Y%m%d-%H%M%S)"
                      
                      if [ $VERBOSE -eq 1 ]; then
                          local curl_cmd="curl -s -X POST \
                              -b \"authenticationToken=$TOKEN\" \
                              -H \"Content-Type: application/json\" \
                              -H \"Accept: application/json\" \
                              -d '{\"name_label\": \"$SNAPSHOT_NAME\"}' \
                              \"$XO_URL/rest/v0/vms/$VM_UUID/actions/snapshot\""
                          print_msg "Executing snapshot trigger command:"
                          print_msg "$curl_cmd"
                          print_msg "-----"
                      fi
                  
                      response=$(curl -s -X POST \
                          -b "authenticationToken=$TOKEN" \
                          -H "Content-Type: application/json" \
                          -H "Accept: application/json" \
                          -d "{\"name_label\": \"$SNAPSHOT_NAME\"}" \
                          "$XO_URL/rest/v0/vms/$VM_UUID/actions/snapshot")
                      
                      if [ $VERBOSE -eq 1 ]; then
                          print_msg "Snapshot trigger response: $response"
                          print_msg "-----"
                      fi
                  
                      if [[ "$response" =~ /rest/v0/tasks/([a-z0-9]+) ]]; then
                          task_id="${BASH_REMATCH[1]}"
                          if [ $VERBOSE -eq 1 ]; then
                              print_msg "Snapshot triggered (Task ID: $task_id)"
                          fi
                          return 0
                      elif [[ "$response" =~ ^[a-z0-9]+$ ]]; then
                          task_id="$response"
                          if [ $VERBOSE -eq 1 ]; then
                              print_msg "Snapshot triggered (Task ID: $task_id)"
                          fi
                          return 0
                      else
                          print_msg "Error: Failed to get task ID. Response: $response"
                          exit 1
                      fi
                  }
                  
                  # Function to monitor task status
                  monitor_task() {
                      local task_id=$1
                      local max_attempts=30
                      local attempt=1
                  
                      if [ $VERBOSE -eq 1 ]; then
                          print_msg "Monitoring task $task_id..."
                      fi
                  
                      while [ $attempt -le $max_attempts ]; do
                          status_response=$(curl -s -X GET \
                              -b "authenticationToken=$TOKEN" \
                              -H "Accept: application/json" \
                              "$XO_URL/rest/v0/tasks/$task_id")
                          
                          if [ $VERBOSE -eq 1 ]; then
                              print_msg "Attempt $attempt - Raw response: $status_response"
                          fi
                  
                          if [ -n "$status_response" ] && echo "$status_response" | jq -e . >/dev/null 2>&1; then
                              status=$(echo "$status_response" | jq -r '.status')
                              
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Status: $status"
                              else
                                  print_msg "Taking Snapshot of VM: $vm_name... Status: $status"
                              fi
                              
                              case "$status" in
                                  "pending"|"running")
                                      if [ $VERBOSE -eq 1 ]; then
                                          print_msg "Task still in progress"
                                      fi
                                      sleep 5
                                      ((attempt++))
                                      ;;
                                  "success")
                                      if [ $VERBOSE -eq 1 ]; then
                                          print_msg "Snapshot completed successfully"
                                      else
                                          print_msg "Taking Snapshot of VM: $vm_name... Status: Completed"
                                      fi
                                      return 0
                                      ;;
                                  "failure")
                                      error=$(echo "$status_response" | jq -r '.result.message // "Unknown error"')
                                      print_msg "Task failed: $error"
                                      exit 1
                                      ;;
                                  *)
                                      if [ $VERBOSE -eq 1 ]; then
                                          print_msg "Unknown status: $status"
                                      fi
                                      sleep 5
                                      ((attempt++))
                                      ;;
                              esac
                          else
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Invalid or empty response"
                              fi
                              sleep 5
                              ((attempt++))
                          fi
                      done
                  
                      print_msg "Timeout waiting for task completion"
                      exit 1
                  }
                  
                  # Function to manage snapshot retention
                  manage_retention() {
                      if [ $RETENTION -lt 0 ]; then
                          print_msg "Retention not specified, skipping cleanup"
                          return 0
                      fi
                  
                      # Get VM data with snapshot UUIDs
                      vm_data=$(curl -s -X GET \
                          -b "authenticationToken=$TOKEN" \
                          -H "Accept: application/json" \
                          "$XO_URL/rest/v0/vms/$VM_UUID")
                      
                      if [ $VERBOSE -eq 1 ]; then
                          print_msg "Raw VM data response: $vm_data"
                          print_msg "-----"
                      fi
                  
                      if ! echo "$vm_data" | jq -e . >/dev/null 2>&1; then
                          print_msg "Error: Invalid JSON response from VM endpoint"
                          print_msg "Response: $vm_data"
                          exit 1
                      fi
                  
                      # Extract snapshot UUIDs
                      snapshot_uuids=$(echo "$vm_data" | jq -r '.snapshots[]')
                  
                      # Fetch details for each snapshot, verify existence, and filter by prefix
                      auto_snapshots=""
                      for uuid in $snapshot_uuids; do
                          # Check existence with a HEAD request to avoid full body download
                          http_status=$(curl -s -o /dev/null -w "%{http_code}" -I \
                              -b "authenticationToken=$TOKEN" \
                              "$XO_URL/rest/v0/vm-snapshots/$uuid")
                          
                          if [ "$http_status" -eq 404 ]; then
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Snapshot $uuid does not exist (HTTP 404), skipping"
                                  print_msg "-----"
                              fi
                              continue
                          elif [ "$http_status" -ne 200 ]; then
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Unexpected HTTP status $http_status for snapshot $uuid, skipping"
                                  print_msg "-----"
                              fi
                              continue
                          fi
                  
                          # Fetch full snapshot data if it exists
                          snapshot_data=$(curl -s -X GET \
                              -b "authenticationToken=$TOKEN" \
                              -H "Accept: application/json" \
                              "$XO_URL/rest/v0/vm-snapshots/$uuid")
                          
                          if [ $VERBOSE -eq 1 ]; then
                              print_msg "Snapshot $uuid data: $snapshot_data"
                              print_msg "-----"
                          fi
                  
                          # Verify it’s a valid JSON response
                          if ! echo "$snapshot_data" | jq -e . >/dev/null 2>&1; then
                              print_msg "Error: Invalid JSON response for snapshot $uuid"
                              print_msg "Response: $snapshot_data"
                              continue
                          fi
                  
                          # Filter for snapshots with the specified prefix
                          if echo "$snapshot_data" | jq -e ".name_label | startswith(\"$SNAPSHOT_PREFIX\")" >/dev/null 2>&1; then
                              snapshot_line=$(echo "$snapshot_data" | jq -r '[.id, .name_label, .snapshot_time] | join("\t")')
                              auto_snapshots="$auto_snapshots$snapshot_line\n"
                          fi
                      done
                  
                      # Sort by snapshot_time
                      auto_snapshots=$(echo -e "$auto_snapshots" | sort -k3 -n | grep -v '^$')
                  
                      if [ $VERBOSE -eq 1 ]; then
                          print_msg "Filtered and sorted $SNAPSHOT_PREFIX snapshots:"
                          print_msg "$auto_snapshots"
                          print_msg "-----"
                      fi
                  
                      # Count current snapshots
                      snapshot_count=$(echo "$auto_snapshots" | wc -l)
                      print_msg "Current $SNAPSHOT_PREFIX snapshot count: $snapshot_count"
                  
                      # If over retention limit, delete oldest
                      if [ $snapshot_count -gt $RETENTION ]; then
                          excess=$((snapshot_count - RETENTION))
                          print_msg "Excess snapshots to delete: $excess"
                  
                          # Get IDs of oldest snapshots to delete
                          delete_ids=$(echo "$auto_snapshots" | head -n $excess | cut -f1)
                  
                          for id in $delete_ids; do
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Deleting snapshot $id"
                                  print_msg "Delete command: curl -s -X DELETE -b \"authenticationToken=$TOKEN\" \"$XO_URL/rest/v0/vm-snapshots/$id\""
                              fi
                              
                              delete_response=$(curl -s -X DELETE \
                                  -b "authenticationToken=$TOKEN" \
                                  "$XO_URL/rest/v0/vm-snapshots/$id")
                              
                              if [ $VERBOSE -eq 1 ]; then
                                  print_msg "Delete response: $delete_response"
                                  print_msg "-----"
                              fi
                              
                              # Treat empty response, {"status": "success"}, or "OK" as success
                              if [ -z "$delete_response" ] || echo "$delete_response" | jq -e '.status == "success"' >/dev/null 2>&1 || [ "$delete_response" = "OK" ]; then
                                  print_msg "Successfully deleted snapshot $id"
                              else
                                  print_msg "Warning: Failed to delete snapshot $id. Response: $delete_response"
                              fi
                          done
                      else
                          if [ $VERBOSE -eq 1 ]; then
                              print_msg "No excess snapshots to delete"
                          fi
                      fi
                  }
                  
                  # Main execution
                  check_jq
                  get_vm_name
                  
                  if [ $VERBOSE -eq 1 ]; then
                      print_msg "Using VM UUID: $VM_UUID"
                  fi
                  
                  if [ $NOSNAPSHOT -eq 0 ]; then
                      trigger_snapshot
                      monitor_task "$task_id"
                  else
                      if [ $VERBOSE -eq 1 ]; then
                          print_msg "Skipping snapshot creation due to --nosnapshot flag"
                      fi
                  fi
                  
                  manage_retention
                  
                  exit 0
                  

                  Cheers!

                  1 Reply Last reply Reply Quote 2
                  • olivierlambertO olivierlambert marked this topic as a question on
                  • olivierlambertO olivierlambert has marked this topic as solved on
                  • First post
                    Last post