From 4a22605b6e9097b56ed461d5c49317b066aeff64 Mon Sep 17 00:00:00 2001 From: imjoey Date: Wed, 10 Jul 2019 09:12:00 +0800 Subject: [PATCH] Add support for resource ovirt_snapshot Signed-off-by: imjoey --- README.md | 1 + ovirt/provider.go | 1 + ovirt/resource_ovirt_snapshot.go | 195 ++++++++++++++++++++++++++ ovirt/resource_ovirt_snapshot_test.go | 114 +++++++++++++++ website/docs/r/snapshot.html.markdown | 46 ++++++ 5 files changed, 357 insertions(+) create mode 100644 ovirt/resource_ovirt_snapshot.go create mode 100644 ovirt/resource_ovirt_snapshot_test.go create mode 100644 website/docs/r/snapshot.html.markdown diff --git a/README.md b/README.md index f9b4a4f9..00f8f2b1 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ provider "ovirt" { * ovirt_host * ovirt_mac_pool * ovirt_network + * ovirt_snapshot * ovirt_storage_domain * ovirt_tag * ovirt_user diff --git a/ovirt/provider.go b/ovirt/provider.go index 4fcbdde4..49a63dae 100644 --- a/ovirt/provider.go +++ b/ovirt/provider.go @@ -54,6 +54,7 @@ func Provider() terraform.ResourceProvider { "ovirt_network": resourceOvirtNetwork(), "ovirt_vnic": resourceOvirtVnic(), "ovirt_vnic_profile": resourceOvirtVnicProfile(), + "ovirt_snapshot": resourceOvirtSnapshot(), "ovirt_storage_domain": resourceOvirtStorageDomain(), "ovirt_tag": resourceOvirtTag(), "ovirt_user": resourceOvirtUser(), diff --git a/ovirt/resource_ovirt_snapshot.go b/ovirt/resource_ovirt_snapshot.go new file mode 100644 index 00000000..5a5d7d80 --- /dev/null +++ b/ovirt/resource_ovirt_snapshot.go @@ -0,0 +1,195 @@ +package ovirt + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + ovirtsdk4 "github.com/ovirt/go-ovirt" +) + +func resourceOvirtSnapshot() *schema.Resource { + return &schema.Resource{ + Create: resourceOvirtSnapshotCreate, + Read: resourceOvirtSnapshotRead, + Delete: resourceOvirtSnapshotDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "vm_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "save_memory": { + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + "description": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + // Computed + "status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "date": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceOvirtSnapshotCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*ovirtsdk4.Connection) + builder := ovirtsdk4.NewSnapshotBuilder() + + builder.Description(d.Get("description").(string)). + PersistMemorystate(d.Get("save_memory").(bool)) + + vmID := d.Get("vm_id").(string) + snapshotsService := conn.SystemService().VmsService().VmService(vmID).SnapshotsService() + + resp, err := snapshotsService. + Add(). + Snapshot(builder.MustBuild()). + Send() + if err != nil { + log.Printf("[DEBUG] Error creating Snapshot for VM (%s): %s", vmID, err) + return nil + } + + snapshotID := resp.MustSnapshot().MustId() + d.SetId(vmID + ":" + snapshotID) + + // Wait for snapshot is OK + log.Printf("[DEBUG] Snapshot (%s) is created and wait for ready (status is OK)", d.Id()) + okStateConf := &resource.StateChangeConf{ + Pending: []string{string(ovirtsdk4.SNAPSHOTSTATUS_LOCKED)}, + Target: []string{string(ovirtsdk4.SNAPSHOTSTATUS_OK)}, + Refresh: SnapshotStateRefreshFunc(conn, vmID, snapshotID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err = okStateConf.WaitForState() + if err != nil { + log.Printf("[DEBUG] Failed to wait for Snapshot (%s) to become OK: %s", d.Id(), err) + return err + } + + return resourceOvirtSnapshotRead(d, meta) +} + +func resourceOvirtSnapshotRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*ovirtsdk4.Connection) + vmID, snapshotID, err := getVMIDAndSnapshotID(d.Id()) + if err != nil { + return err + } + + d.Set("vm_id", vmID) + + snapshotService := conn.SystemService(). + VmsService(). + VmService(vmID). + SnapshotsService(). + SnapshotService(snapshotID) + + snapshotResp, err := snapshotService.Get().Send() + if err != nil { + if _, ok := err.(*ovirtsdk4.NotFoundError); ok { + d.SetId("") + return nil + } + log.Printf("[DEBUG] Failed to get Snapshot (%s): %s", d.Id(), err) + return err + } + snapshot := snapshotResp.MustSnapshot() + + d.Set("description", snapshot.MustDescription()) + d.Set("save_memory", snapshot.MustPersistMemorystate()) + d.Set("status", string(snapshot.MustSnapshotStatus())) + d.Set("type", string(snapshot.MustSnapshotType())) + d.Set("date", snapshot.MustDate().Format(time.RFC3339)) + + return nil +} + +func resourceOvirtSnapshotDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*ovirtsdk4.Connection) + vmID, snapshotID, err := getVMIDAndSnapshotID(d.Id()) + if err != nil { + return err + } + snapshotService := conn.SystemService(). + VmsService(). + VmService(vmID). + SnapshotsService(). + SnapshotService(snapshotID) + + return resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { + log.Printf("[DEBUG] Now to remove Snapshot (%s)", d.Id()) + _, err := snapshotService.Remove().Send() + if err != nil { + if _, ok := err.(*ovirtsdk4.NotFoundError); ok { + // Wait until NotFoundError raises + log.Printf("[DEBUG] Snapshot (%s) has been removed", d.Id()) + return nil + } + return resource.RetryableError(fmt.Errorf("Error removing Snapshot (%s): %s", d.Id(), err)) + } + return resource.RetryableError(fmt.Errorf("Snapshot (%s) is still being removed", d.Id())) + }) +} + +// SnapshotStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an oVirt Snapshot. +func SnapshotStateRefreshFunc(conn *ovirtsdk4.Connection, vmID, snapshotID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + r, err := conn.SystemService(). + VmsService(). + VmService(vmID). + SnapshotsService(). + SnapshotService(snapshotID). + Get(). + Send() + + if err != nil { + if _, ok := err.(*ovirtsdk4.NotFoundError); ok { + return nil, "", nil + } + return nil, "", err + } + + return r.MustSnapshot, string(r.MustSnapshot().MustSnapshotStatus()), nil + } +} + +func getVMIDAndSnapshotID(rsID string) (string, string, error) { + parts := strings.Split(rsID, ":") + if len(parts) != 2 { + return "", "", fmt.Errorf("Invalid Snapshot ID: %s", rsID) + } + return parts[0], parts[1], nil +} diff --git a/ovirt/resource_ovirt_snapshot_test.go b/ovirt/resource_ovirt_snapshot_test.go new file mode 100644 index 00000000..72b3255e --- /dev/null +++ b/ovirt/resource_ovirt_snapshot_test.go @@ -0,0 +1,114 @@ +package ovirt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + ovirtsdk4 "github.com/ovirt/go-ovirt" +) + +func TestAccOvirtSnapshot_basic(t *testing.T) { + description := "description for snapshot" + vmID := "53000b15-82ad-4ed4-9f86-bffb95e3c28b" + saveMemory := true + + var snapshot ovirtsdk4.Snapshot + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + IDRefreshName: "ovirt_snapshot.snapshot", + CheckDestroy: testAccCheckSnapshotDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSnapshotBasic(description, vmID, saveMemory), + Check: resource.ComposeTestCheckFunc( + testAccCheckOvirtSnapshotExists("ovirt_snapshot.snapshot", &snapshot), + resource.TestCheckResourceAttr("ovirt_snapshot.snapshot", "description", description), + resource.TestCheckResourceAttr("ovirt_snapshot.snapshot", "vm_id", vmID), + resource.TestCheckResourceAttr("ovirt_snapshot.snapshot", "save_memory", fmt.Sprintf("%t", saveMemory)), + resource.TestCheckResourceAttr("ovirt_snapshot.snapshot", "status", string(ovirtsdk4.SNAPSHOTSTATUS_OK)), + ), + }, + }, + }) +} + +func testAccCheckSnapshotDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ovirtsdk4.Connection) + for _, rs := range s.RootModule().Resources { + if rs.Type != "ovirt_snapshot" { + continue + } + + vmID, snapshotID, err := getVMIDAndSnapshotID(rs.Primary.ID) + if err != nil { + return err + } + + getResp, err := conn.SystemService(). + VmsService(). + VmService(vmID). + SnapshotsService(). + SnapshotService(snapshotID). + Get(). + Send() + + if err != nil { + if _, ok := err.(*ovirtsdk4.NotFoundError); ok { + continue + } + return err + } + if _, ok := getResp.Snapshot(); ok { + return fmt.Errorf("Snapshot %s still exist", rs.Primary.ID) + } + } + return nil +} + +func testAccCheckOvirtSnapshotExists(n string, v *ovirtsdk4.Snapshot) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Snapshot ID is set") + } + + vmID, snapshotID, err := getVMIDAndSnapshotID(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*ovirtsdk4.Connection) + getResp, err := conn.SystemService(). + VmsService(). + VmService(vmID). + SnapshotsService(). + SnapshotService(snapshotID). + Get(). + Send() + if err != nil { + return err + } + snapshot, ok := getResp.Snapshot() + if ok { + *v = *snapshot + return nil + } + return fmt.Errorf("Snapshot %s not exist", rs.Primary.ID) + } +} + +func testAccSnapshotBasic(description, vmID string, saveMemory bool) string { + return fmt.Sprintf(` +resource "ovirt_snapshot" "snapshot" { + description = "%s" + vm_id = "%s" + save_memory = %t +} + `, description, vmID, saveMemory) +} diff --git a/website/docs/r/snapshot.html.markdown b/website/docs/r/snapshot.html.markdown new file mode 100644 index 00000000..249a608d --- /dev/null +++ b/website/docs/r/snapshot.html.markdown @@ -0,0 +1,46 @@ +--- +layout: "ovirt" +page_title: "oVirt: ovirt_snapshot" +sidebar_current: "docs-ovirt-resource-snapshot" +description: |- + Manages a Snapshot of vm resource within oVirt. +--- + +# ovirt\_snapshot + +Manages a Snapshot of VM resource within oVirt. + +## Example Usage + +```hcl +resource "ovirt_snapshot" "snapshot" { + description = "description-of-snasphot" + vm_id = "53000b15-82ad-4ed4-9f86-bffb95e3c28b" + save_memory = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Required) A description of the snapshot. Changing this creates a new snapshot. +* `vm_id` - (Required) The ID of vm the snapshot taken from. Changing this creates a new snapshot. +* `save_memory` - (Optional) The flag to indicate whether the content of the memory of the vm is included in the snapshot. Default is `true`. Changing this creates a new snapshot. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The composite ID of the snapshot which is constituted by the ID of the vm the snapshot taken from and the ID of the snapshot within oVirt. +* `status` - The status of the snapshot. Can be "in_preview", "locked" or "ok". +* `type` - The type of the snapshot. Can be "active", "preview", "regular" or "stateless". +* `date` - The string representation of the creation time of the snapshot in RFC3339 format. + +## Import + +Snapshots can be imported using the composite `id`, e.g. + +``` +$ terraform import ovirt_snapshot.snapshot 53000b15-82ad-4ed4-9f86-bffb95e3c28b:df736600-b8be-4029-be98-4b0611be6be4 +``` \ No newline at end of file