From a47ad103e2320a40071a54400be46cf24071091b Mon Sep 17 00:00:00 2001 From: dkalleg Date: Wed, 25 May 2016 03:21:16 -0700 Subject: [PATCH] Adding volume resource creation to vSphere provider (#6273) Allows the user to create a vmdk in vSphere to the given path. In the future this could support updates for rename and move. --- builtin/providers/vsphere/provider.go | 1 + .../vsphere/resource_vsphere_virtual_disk.go | 259 ++++++++++++++++++ .../resource_vsphere_virtual_disk_test.go | 129 +++++++++ .../providers/vsphere/index.html.markdown | 8 + .../vsphere/r/virtual_disk.html.markdown | 33 +++ 5 files changed, 430 insertions(+) create mode 100644 builtin/providers/vsphere/resource_vsphere_virtual_disk.go create mode 100644 builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go create mode 100644 website/source/docs/providers/vsphere/r/virtual_disk.html.markdown diff --git a/builtin/providers/vsphere/provider.go b/builtin/providers/vsphere/provider.go index cbe9782ff..36ef814a6 100644 --- a/builtin/providers/vsphere/provider.go +++ b/builtin/providers/vsphere/provider.go @@ -48,6 +48,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "vsphere_file": resourceVSphereFile(), "vsphere_folder": resourceVSphereFolder(), + "vsphere_virtual_disk": resourceVSphereVirtualDisk(), "vsphere_virtual_machine": resourceVSphereVirtualMachine(), }, diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_disk.go b/builtin/providers/vsphere/resource_vsphere_virtual_disk.go new file mode 100644 index 000000000..0370f14e1 --- /dev/null +++ b/builtin/providers/vsphere/resource_vsphere_virtual_disk.go @@ -0,0 +1,259 @@ +package vsphere + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" + "golang.org/x/net/context" +) + +type virtualDisk struct { + size int + vmdkPath string + initType string + adapterType string + datacenter string + datastore string +} + +// Define VirtualDisk args +func resourceVSphereVirtualDisk() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereVirtualDiskCreate, + Read: resourceVSphereVirtualDiskRead, + Delete: resourceVSphereVirtualDiskDelete, + + Schema: map[string]*schema.Schema{ + // Size in GB + "size": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, //TODO Can this be optional (resize)? + }, + + "vmdk_path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, //TODO Can this be optional (move)? + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "eagerZeroedThick", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "thin" && value != "eagerZeroedThick" { + errors = append(errors, fmt.Errorf( + "only 'thin' and 'eagerZeroedThick' are supported values for 'type'")) + } + return + }, + }, + + "adapter_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "ide", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "ide" && value != "busLogic" && value != "lsiLogic" { + errors = append(errors, fmt.Errorf( + "only 'ide', 'busLogic', and 'lsiLogic' are supported values for 'adapter_type'")) + } + return + }, + }, + + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "datastore": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceVSphereVirtualDiskCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Creating Virtual Disk") + client := meta.(*govmomi.Client) + + vDisk := virtualDisk{ + size: d.Get("size").(int), + } + + if v, ok := d.GetOk("vmdk_path"); ok { + vDisk.vmdkPath = v.(string) + } + + if v, ok := d.GetOk("type"); ok { + vDisk.initType = v.(string) + } + + if v, ok := d.GetOk("adapter_type"); ok { + vDisk.adapterType = v.(string) + } + + if v, ok := d.GetOk("datacenter"); ok { + vDisk.datacenter = v.(string) + } + + if v, ok := d.GetOk("datastore"); ok { + vDisk.datastore = v.(string) + } + + diskPath := fmt.Sprintf("[%v] %v", vDisk.datastore, vDisk.vmdkPath) + + err := createHardDisk(client, vDisk.size, diskPath, vDisk.initType, vDisk.adapterType, vDisk.datacenter) + if err != nil { + return err + } + + d.SetId(diskPath) + log.Printf("[DEBUG] Virtual Disk id: %v", diskPath) + + return resourceVSphereVirtualDiskRead(d, meta) +} + +func resourceVSphereVirtualDiskRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Reading virtual disk.") + client := meta.(*govmomi.Client) + + vDisk := virtualDisk{ + size: d.Get("size").(int), + } + + if v, ok := d.GetOk("vmdk_path"); ok { + vDisk.vmdkPath = v.(string) + } + + if v, ok := d.GetOk("type"); ok { + vDisk.initType = v.(string) + } + + if v, ok := d.GetOk("adapter_type"); ok { + vDisk.adapterType = v.(string) + } + + if v, ok := d.GetOk("datacenter"); ok { + vDisk.datacenter = v.(string) + } + + if v, ok := d.GetOk("datastore"); ok { + vDisk.datastore = v.(string) + } + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + ds, err := finder.Datastore(context.TODO(), d.Get("datastore").(string)) + if err != nil { + return err + } + + fileInfo, err := ds.Stat(context.TODO(), vDisk.vmdkPath) + if err != nil { + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - stat failed on: %v", vDisk.vmdkPath) + d.SetId("") + return err + } + fileInfo = fileInfo.GetFileInfo() + log.Printf("[DEBUG] resourceVSphereVirtualDiskRead - fileinfo: %#v", fileInfo) + size := fileInfo.(*types.FileInfo).FileSize / 1024 / 1024 / 1024 + + d.SetId(vDisk.vmdkPath) + + d.Set("size", size) + d.Set("vmdk_path", vDisk.vmdkPath) + d.Set("datacenter", d.Get("datacenter")) + d.Set("datastore", d.Get("datastore")) + // Todo collect and write type info + + return nil + +} + +func resourceVSphereVirtualDiskDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + + vDisk := virtualDisk{} + + if v, ok := d.GetOk("vmdk_path"); ok { + vDisk.vmdkPath = v.(string) + } + if v, ok := d.GetOk("datastore"); ok { + vDisk.datastore = v.(string) + } + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return err + } + diskPath := fmt.Sprintf("[%v] %v", vDisk.datastore, vDisk.vmdkPath) + + virtualDiskManager := object.NewVirtualDiskManager(client.Client) + + task, err := virtualDiskManager.DeleteVirtualDisk(context.TODO(), diskPath, dc) + if err != nil { + return err + } + + _, err = task.WaitForResult(context.TODO(), nil) + if err != nil { + log.Printf("[INFO] Failed to delete disk: %v", err) + return err + } + + log.Printf("[INFO] Deleted disk: %v", diskPath) + d.SetId("") + return nil +} + +// createHardDisk creates a new Hard Disk. +func createHardDisk(client *govmomi.Client, size int, diskPath string, diskType string, adapterType string, dc string) error { + virtualDiskManager := object.NewVirtualDiskManager(client.Client) + spec := &types.FileBackedVirtualDiskSpec{ + VirtualDiskSpec: types.VirtualDiskSpec{ + AdapterType: adapterType, + DiskType: diskType, + }, + CapacityKb: int64(1024 * 1024 * size), + } + datacenter, err := getDatacenter(client, dc) + if err != nil { + return err + } + log.Printf("[DEBUG] Disk spec: %v", spec) + + task, err := virtualDiskManager.CreateVirtualDisk(context.TODO(), diskPath, datacenter, spec) + if err != nil { + return err + } + + _, err = task.WaitForResult(context.TODO(), nil) + if err != nil { + log.Printf("[INFO] Failed to create disk: %v", err) + return err + } + log.Printf("[INFO] Created disk.") + + return nil +} diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go new file mode 100644 index 000000000..c27426ad7 --- /dev/null +++ b/builtin/providers/vsphere/resource_vsphere_virtual_disk_test.go @@ -0,0 +1,129 @@ +package vsphere + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "golang.org/x/net/context" +) + +func TestAccVSphereVirtualDisk_basic(t *testing.T) { + var datacenterOpt string + var datastoreOpt string + var initTypeOpt string + var adapterTypeOpt string + + if v := os.Getenv("VSPHERE_DATACENTER"); v != "" { + datacenterOpt = v + } + if v := os.Getenv("VSPHERE_DATASTORE"); v != "" { + datastoreOpt = v + } + if v := os.Getenv("VSPHERE_INIT_TYPE"); v != "" { + initTypeOpt += fmt.Sprintf(" type = \"%s\"\n", v) + } + if v := os.Getenv("VSPHERE_ADAPTER_TYPE"); v != "" { + adapterTypeOpt += fmt.Sprintf(" adapter_type = \"%s\"\n", v) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereVirtualDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf( + testAccCheckVSphereVirtuaDiskConfig_basic, + initTypeOpt, + adapterTypeOpt, + datacenterOpt, + datastoreOpt, + ), + Check: resource.ComposeTestCheckFunc( + testAccVSphereVirtualDiskExists("vsphere_virtual_disk.foo"), + ), + }, + }, + }) +} + +func testAccVSphereVirtualDiskExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + client := testAccProvider.Meta().(*govmomi.Client) + finder := find.NewFinder(client.Client, true) + + dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + finder = finder.SetDatacenter(dc) + + ds, err := finder.Datastore(context.TODO(), rs.Primary.Attributes["datastore"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = ds.Stat(context.TODO(), rs.Primary.Attributes["vmdk_path"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + return nil + } +} + +func testAccCheckVSphereVirtualDiskDestroy(s *terraform.State) error { + log.Printf("[FINDME] test Destroy") + client := testAccProvider.Meta().(*govmomi.Client) + finder := find.NewFinder(client.Client, true) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vsphere_virtual_disk" { + continue + } + + dc, err := finder.Datacenter(context.TODO(), rs.Primary.Attributes["datacenter"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + finder = finder.SetDatacenter(dc) + + ds, err := finder.Datastore(context.TODO(), rs.Primary.Attributes["datastore"]) + if err != nil { + return fmt.Errorf("error %s", err) + } + + _, err = ds.Stat(context.TODO(), rs.Primary.Attributes["vmdk_path"]) + if err == nil { + return fmt.Errorf("error %s", err) + } + } + + return nil +} + +const testAccCheckVSphereVirtuaDiskConfig_basic = ` +resource "vsphere_virtual_disk" "foo" { + size = 1 + vmdk_path = "tfTestDisk.vmdk" +%s +%s + datacenter = "%s" + datastore = "%s" +} +` diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index b016a682f..3b1300e02 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -42,6 +42,14 @@ resource "vsphere_file" "ubuntu_disk" { destination_file = "/my_path/disks/custom_ubuntu.vmdk" } +# Create a disk image +resource "vsphere_virtual_disk" "extraStorage" { + size = 2 + vmdk_path = "myDisk.vmdk" + datacenter = "Datacenter" + datastore = "local" +} + # Create a virtual machine within the folder resource "vsphere_virtual_machine" "web" { name = "terraform-web" diff --git a/website/source/docs/providers/vsphere/r/virtual_disk.html.markdown b/website/source/docs/providers/vsphere/r/virtual_disk.html.markdown new file mode 100644 index 000000000..1542fd8d9 --- /dev/null +++ b/website/source/docs/providers/vsphere/r/virtual_disk.html.markdown @@ -0,0 +1,33 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_virtual_disk" +sidebar_current: "docs-vsphere-resource-virtual-disk" +description: |- + Provides a VMware virtual disk resource. This can be used to create and delete virtual disks. +--- + +# vsphere\_virtual\_disk + +Provides a VMware virtual disk resource. This can be used to create and delete virtual disks. + +## Example Usage + +``` +resource "vsphere_virtual_disk" "myDisk" { + size = 2 + vmdk_path = "myDisk.vmdk" + datacenter = "Datacenter" + datastore = "local" + init_type = "thin" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `size` - (Required) Size of the disk (in GB). +* `vmdk_path` - (Required) The path, including filename, of the virtual disk to be created. This should end with '.vmdk'. +* `type` - (Optional) 'eagerZeroedThick' (the default), or 'thin' are supported options. +* `datacenter` - (Optional) The name of a Datacenter in which to create the disk. +* `datastore` - (Required) The name of the Datastore in which to create the disk. \ No newline at end of file