Skip to content

Commit

Permalink
New resource: vsphere_vmfs_datastore
Browse files Browse the repository at this point in the history
This commit introduces the vsphere_vmfs_datastore resource, which can be
used to manage VMFS-based datastores.

Currently only whole disk support is included, however import
functionality is planned so that one can import existing datastores that
don't have full-disk extents, although whole disks will only be allowed
to be added from there.

Support for disk expansion may also be planned, depending on business
case.
  • Loading branch information
vancluever committed Sep 1, 2017
1 parent 1eac679 commit f5e8c1c
Show file tree
Hide file tree
Showing 7 changed files with 849 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tf-vsphere-devrc.mk.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ export VSPHERE_HOST_NIC0 ?= vmnic0 # NIC0 for host net tests
export VSPHERE_HOST_NIC1 ?= vmnic1 # NIC1 for host net tests
export VSPHERE_VMFS_EXPECTED ?= scsi-name # Name of expected SCSI disk
export VSPHERE_VMFS_REGEXP ?= expr # Regexp for SCSI disk search
export VSPHERE_DS_VMFS_DISK0 ?= scsi-name0 # 1st disk for vmfs_datastore
export VSPHERE_DS_VMFS_DISK1 ?= scsi-name1 # 2nd disk for vmfs_datastore
export VSPHERE_DS_VMFS_DISK2 ?= scsi-name2 # 3rd disk for vmfs_datastore

# vi: filetype=make
57 changes: 57 additions & 0 deletions vsphere/datastore_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package vsphere

import (
"context"
"fmt"

"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)

// datastoreFromID locates a Datastore by its managed object reference ID.
func datastoreFromID(client *govmomi.Client, id string) (*object.Datastore, error) {
finder := find.NewFinder(client.Client, false)

ref := types.ManagedObjectReference{
Type: "Datastore",
Value: id,
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
ds, err := finder.ObjectReference(ctx, ref)
if err != nil {
return nil, fmt.Errorf("could not find datastore with id: %s: %s", id, err)
}
// Should be safe to return here. If our reference returned here and is not a
// datastore, then we have bigger problems and to be honest we should be
// panicking anyway.
return ds.(*object.Datastore), nil
}

// datastoreProperties is a convenience method that wraps fetching the
// Datastore MO from its higher-level object.
func datastoreProperties(ds *object.Datastore) (*mo.Datastore, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
var props mo.Datastore
if err := ds.Properties(ctx, ds.Reference(), nil, &props); err != nil {
return nil, err
}
return &props, nil
}

// datastoreIsMissing checks if the error messages returned from
// datastoreFromID indicates that the datastore is missing. This is used in
// various deletion checks.
func datastoreIsMissing(id string, err error) bool {
msg := fmt.Sprintf("could not find datastore with id: %s: ServerFaultCode: The object 'vim.Datastore:%s' has already been deleted or has not been completely created", id, id)

if err.Error() == msg {
return true
}
return false
}
72 changes: 72 additions & 0 deletions vsphere/datastore_summary_structure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package vsphere

import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/vmware/govmomi/vim25/types"
)

// schemaDatastoreSummary returns schema items for resources that
// need to work with a DatastoreSummary.
func schemaDatastoreSummary() map[string]*schema.Schema {
return map[string]*schema.Schema{
// Note that the following fields are not represented in the schema here:
// * Name (more than likely the ID attribute and will be represented in
// resource schema)
// * Type (redundant attribute as the datastore type will be represented by
// the resource)
"accessible": &schema.Schema{
Type: schema.TypeBool,
Description: "The connectivity status of the datastore. If this is false, some other computed attributes may be out of date.",
Computed: true,
},
"capacity": &schema.Schema{
Type: schema.TypeInt,
Description: "Maximum capacity of the datastore, in bytes.",
Computed: true,
},
"free_space": &schema.Schema{
Type: schema.TypeInt,
Description: "Available space of this datastore, in bytes.",
Computed: true,
},
"maintenance_mode": &schema.Schema{
Type: schema.TypeString,
Description: "The current maintenance mode state of the datastore.",
Computed: true,
},
"multiple_host_access": &schema.Schema{
Type: schema.TypeBool,
Description: "If true, more than one host in the datacenter has been configured with access to the datastore",
Computed: true,
},
"uncommitted_space": &schema.Schema{
Type: schema.TypeInt,
Description: "Total additional storage space, in bytes, potentially used by all virtual machines on this datastore.",
Computed: true,
},
"url": &schema.Schema{
Type: schema.TypeString,
Description: "The unique locator for the datastore.",
Computed: true,
},
}
}

// flattenDatastoreSummary reads various fields from a DatastoreSummary into
// the passed in ResourceData.
func flattenDatastoreSummary(d *schema.ResourceData, obj *types.DatastoreSummary) error {
d.Set("accessible", obj.Accessible)
d.Set("capacity", obj.Capacity)
d.Set("free_space", obj.FreeSpace)
d.Set("maintenance_mode", obj.MaintenanceMode)
d.Set("multiple_host_access", obj.MultipleHostAccess)
d.Set("uncommitted_space", obj.Uncommitted)
d.Set("url", obj.Url)

// Set the name attribute off of the name here - since we do not track this
// here we check for errors
if err := d.Set("name", obj.Name); err != nil {
return err
}
return nil
}
147 changes: 147 additions & 0 deletions vsphere/host_data_store_system_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package vsphere

import (
"context"
"fmt"

"github.com/vmware/govmomi"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
)

// hostDatastoreSystemFromHostSystemID locates a HostDatastoreSystem from a
// specified HostSystem managed object ID.
func hostDatastoreSystemFromHostSystemID(client *govmomi.Client, hsID string) (*object.HostDatastoreSystem, error) {
hs, err := hostSystemFromID(client, hsID)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
return hs.ConfigManager().DatastoreSystem(ctx)
}

// availableScsiDisk checks to make sure that a disk is available for use in a
// VMFS datastore, and returns the ScsiDisk.
func availableScsiDisk(dss *object.HostDatastoreSystem, name string) (*types.HostScsiDisk, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
disks, err := dss.QueryAvailableDisksForVmfs(ctx)
if err != nil {
return nil, fmt.Errorf("cannot query available disks: %s", err)
}

var disk *types.HostScsiDisk
for _, d := range disks {
if d.CanonicalName == name {
disk = &d
break
}
}
if disk == nil {
return nil, fmt.Errorf("%s does not seem to be a disk available for VMFS", name)
}
return disk, nil
}

// diskSpecForCreate checks to make sure that a disk is available to be used to
// create a VMFS datastore, specifically in its entirety, and returns a
// respective VmfsDatastoreCreateSpec.
func diskSpecForCreate(dss *object.HostDatastoreSystem, name string) (*types.VmfsDatastoreCreateSpec, error) {
disk, err := availableScsiDisk(dss, name)
if err != nil {
return nil, err
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
options, err := dss.QueryVmfsDatastoreCreateOptions(ctx, disk.DevicePath)
if err != nil {
return nil, fmt.Errorf("could not get disk creation options for %s: %s", name, err)
}
var option *types.VmfsDatastoreOption
for _, o := range options {
if _, ok := o.Info.(*types.VmfsDatastoreAllExtentOption); ok {
option = &o
break
}
}
if option == nil {
return nil, fmt.Errorf("cannot use entire disk on device %s for datastore", name)
}
return option.Spec.(*types.VmfsDatastoreCreateSpec), nil
}

// diskSpecForExtend checks to make sure that a disk is available to be
// used to extend a VMFS datastore, specifically in its entirety, and returns a
// respective VmfsDatastoreExtendSpec if it is. An error is returned if it's
// not.
func diskSpecForExtend(dss *object.HostDatastoreSystem, ds *object.Datastore, name string) (*types.VmfsDatastoreExtendSpec, error) {
disk, err := availableScsiDisk(dss, name)
if err != nil {
return nil, err
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
options, err := queryVmfsDatastoreExtendOptions(ctx, dss, ds, disk.DevicePath, true)
if err != nil {
return nil, fmt.Errorf("could not get disk extension options for %s: %s", name, err)
}
var option *types.VmfsDatastoreOption
for _, o := range options {
if _, ok := o.Info.(*types.VmfsDatastoreAllExtentOption); ok {
option = &o
break
}
}
if option == nil {
return nil, fmt.Errorf("cannot use entire disk on device %s for datastore", name)
}
return option.Spec.(*types.VmfsDatastoreExtendSpec), nil
}

// removeDatastore is a convenience method for removing a referenced datastore.
func removeDatastore(s *object.HostDatastoreSystem, ds *object.Datastore) error {
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
return s.Remove(ctx, ds)
}

// queryVmfsDatastoreExtendOptions is a stop-gap method that implements
// QueryVmfsDatastoreExtendOptions. It will be removed once the higher level
// HostDatastoreSystem object supports this method.
func queryVmfsDatastoreExtendOptions(ctx context.Context, s *object.HostDatastoreSystem, ds *object.Datastore, devicePath string, suppressExpandCandidates bool) ([]types.VmfsDatastoreOption, error) {
req := types.QueryVmfsDatastoreExtendOptions{
This: s.Reference(),
Datastore: ds.Reference(),
DevicePath: devicePath,
SuppressExpandCandidates: &suppressExpandCandidates,
}

res, err := methods.QueryVmfsDatastoreExtendOptions(ctx, s.Client(), &req)
if err != nil {
return nil, err
}

return res.Returnval, nil
}

// extendVmfsDatastore is a stop-gap method that implements
// ExtendVmfsDatastore. It will be removed once the higher level
// HostDatastoreSystem object supports this method.
func extendVmfsDatastore(ctx context.Context, s *object.HostDatastoreSystem, ds *object.Datastore, spec types.VmfsDatastoreExtendSpec) (*object.Datastore, error) {
req := types.ExtendVmfsDatastore{
This: s.Reference(),
Datastore: ds.Reference(),
Spec: spec,
}

res, err := methods.ExtendVmfsDatastore(ctx, s.Client(), &req)
if err != nil {
return nil, err
}

return object.NewDatastore(s.Client(), res.Returnval), nil
}
1 change: 1 addition & 0 deletions vsphere/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func Provider() terraform.ResourceProvider {
"vsphere_license": resourceVSphereLicense(),
"vsphere_virtual_disk": resourceVSphereVirtualDisk(),
"vsphere_virtual_machine": resourceVSphereVirtualMachine(),
"vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
Loading

0 comments on commit f5e8c1c

Please sign in to comment.