-
Notifications
You must be signed in to change notification settings - Fork 452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New resource: vsphere_vmfs_datastore #142
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f5e8c1c
New resource: vsphere_vmfs_datastore
vancluever b2c0f0f
r/vmfs_datastore: Better error handling
vancluever f4c1da1
r/vmfs_datastore: Flatten disk space denominations to MB
vancluever c118a4d
r/vmfs_datastore: Support datastore renaming
vancluever 15c9b9a
r/vmfs_datastore: Better error messages
vancluever c617982
r/vmfs_datastore: Fix some error strings, refine retry waiter
vancluever a5b4034
r/vmfs_datastore: Add import feature
vancluever 923bab2
r/vmfs_datastore: Documentation
vancluever 418b574
r/vmfs_datastore: Add folder support
vancluever fc772b4
r/vmfs_datastore: Fix expected folder to env var
vancluever a61ebf3
r/vmfs_datastore: Make constants more specific, remove update state save
vancluever 2aab458
r/vmfs_datastore: Fix import
vancluever File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package vsphere | ||
|
||
import ( | ||
"context" | ||
|
||
"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, 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 | ||
} | ||
|
||
// moveDatastoreToFolder is a complex method that moves a datastore to a given | ||
// relative datastore folder path. "Relative" here means relative to a | ||
// datacenter, which is discovered from the current datastore path. | ||
func moveDatastoreToFolder(client *govmomi.Client, ds *object.Datastore, relative string) error { | ||
folder, err := datastoreFolderFromObject(client, ds, relative) | ||
if err != nil { | ||
return err | ||
} | ||
return moveObjectToFolder(ds.Reference(), folder) | ||
} | ||
|
||
// moveDatastoreToFolderRelativeHostSystemID is a complex method that moves a | ||
// datastore to a given datastore path, similar to moveDatastoreToFolder, | ||
// except the path is relative to a HostSystem supplied by ID instead of the | ||
// datastore. | ||
func moveDatastoreToFolderRelativeHostSystemID(client *govmomi.Client, ds *object.Datastore, hsID, relative string) error { | ||
hs, err := hostSystemFromID(client, hsID) | ||
if err != nil { | ||
return err | ||
} | ||
folder, err := datastoreFolderFromObject(client, hs, relative) | ||
if err != nil { | ||
return err | ||
} | ||
return moveObjectToFolder(ds.Reference(), folder) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 MB.", | ||
Computed: true, | ||
}, | ||
"free_space": &schema.Schema{ | ||
Type: schema.TypeInt, | ||
Description: "Available space of this datastore, in MB.", | ||
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 MB, 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", byteToMB(obj.Capacity)) | ||
d.Set("free_space", byteToMB(obj.FreeSpace)) | ||
d.Set("maintenance_mode", obj.MaintenanceMode) | ||
d.Set("multiple_host_access", obj.MultipleHostAccess) | ||
d.Set("uncommitted_space", byteToMB(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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package vsphere | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"path" | ||
"reflect" | ||
"strings" | ||
|
||
"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" | ||
) | ||
|
||
// rootPathParticle is the section of a vSphere inventory path that denotes a | ||
// specific kind of inventory item. | ||
type rootPathParticle string | ||
|
||
// String implements Stringer for rootPathParticle. | ||
func (p rootPathParticle) String() string { | ||
return string(p) | ||
} | ||
|
||
// Delimeter returns the path delimiter for the particle, which is basically | ||
// just a particle with a leading slash. | ||
func (p rootPathParticle) Delimeter() string { | ||
return string("/" + p) | ||
} | ||
|
||
// SplitDatacenter is a convenience method that splits out the datacenter path | ||
// from the supplied path for the particle. | ||
func (p rootPathParticle) SplitDatacenter(inventoryPath string) (string, error) { | ||
s := strings.SplitN(inventoryPath, p.Delimeter(), 2) | ||
if len(s) != 2 { | ||
return inventoryPath, fmt.Errorf("could not split path %q on %q", inventoryPath, p.Delimeter()) | ||
} | ||
return s[0], nil | ||
} | ||
|
||
// SplitRelativeFolder is a convenience method that splits out the relative | ||
// folder from the supplied path for the particle. | ||
func (p rootPathParticle) SplitRelativeFolder(inventoryPath string) (string, error) { | ||
s := strings.SplitN(inventoryPath, p.Delimeter(), 2) | ||
if len(s) != 2 { | ||
return inventoryPath, fmt.Errorf("could not split path %q on %q", inventoryPath, p.Delimeter()) | ||
} | ||
return path.Dir(s[1]), nil | ||
} | ||
|
||
// NewRootFromPath takes the datacenter path for a specific entity, and then | ||
// appends the new particle supplied. | ||
func (p rootPathParticle) NewRootFromPath(inventoryPath string, newParticle rootPathParticle) (string, error) { | ||
dcPath, err := p.SplitDatacenter(inventoryPath) | ||
if err != nil { | ||
return inventoryPath, err | ||
} | ||
return fmt.Sprintf("%s/%s", dcPath, newParticle), nil | ||
} | ||
|
||
// PathFromNewRoot takes the datacenter path for a specific entity, and then | ||
// appends the new particle supplied with the new relative path. | ||
// | ||
// As an example, consider a supplied host path "/dc1/host/cluster1/esxi1", and | ||
// a supplied datastore folder relative path of "/foo/bar". This function will | ||
// split off the datacenter section of the path (/dc1) and combine it with the | ||
// datastore folder with the proper delimiter. The resulting path will be | ||
// "/dc1/datastore/foo/bar". | ||
func (p rootPathParticle) PathFromNewRoot(inventoryPath string, newParticle rootPathParticle, relative string) (string, error) { | ||
rootPath, err := p.NewRootFromPath(inventoryPath, newParticle) | ||
if err != nil { | ||
return inventoryPath, err | ||
} | ||
return path.Clean(fmt.Sprintf("%s/%s", rootPath, relative)), nil | ||
} | ||
|
||
const ( | ||
rootPathParticleVM = rootPathParticle("vm") | ||
rootPathParticleNetwork = rootPathParticle("network") | ||
rootPathParticleHost = rootPathParticle("host") | ||
rootPathParticleDatastore = rootPathParticle("datastore") | ||
) | ||
|
||
// datacenterPathFromHostSystemID returns the datacenter section of a | ||
// HostSystem's inventory path. | ||
func datacenterPathFromHostSystemID(client *govmomi.Client, hsID string) (string, error) { | ||
hs, err := hostSystemFromID(client, hsID) | ||
if err != nil { | ||
return "", err | ||
} | ||
return rootPathParticleHost.SplitDatacenter(hs.InventoryPath) | ||
} | ||
|
||
// datastoreRootPathFromHostSystemID returns the root datastore folder path | ||
// for a specific host system ID. | ||
func datastoreRootPathFromHostSystemID(client *govmomi.Client, hsID string) (string, error) { | ||
hs, err := hostSystemFromID(client, hsID) | ||
if err != nil { | ||
return "", err | ||
} | ||
return rootPathParticleHost.NewRootFromPath(hs.InventoryPath, rootPathParticleDatastore) | ||
} | ||
|
||
// folderFromObject returns an *object.Folder from a given object of specific | ||
// types, and relative path of a type defined in folderType. If no such folder | ||
// is found, an appropriate error will be returned. | ||
// | ||
// The list of supported object types will grow as the provider supports more | ||
// resources. | ||
func folderFromObject(client *govmomi.Client, obj interface{}, folderType rootPathParticle, relative string) (*object.Folder, error) { | ||
if err := validateVirtualCenter(client); err != nil { | ||
return nil, err | ||
} | ||
var p string | ||
var err error | ||
switch o := obj.(type) { | ||
case (*object.Datastore): | ||
p, err = rootPathParticleDatastore.PathFromNewRoot(o.InventoryPath, folderType, relative) | ||
case (*object.HostSystem): | ||
p, err = rootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative) | ||
default: | ||
return nil, fmt.Errorf("unsupported object type %T", o) | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Set up a finder. Don't set datacenter here as we are looking for full | ||
// path, should not be necessary. | ||
finder := find.NewFinder(client.Client, false) | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) | ||
defer cancel() | ||
folder, err := finder.Folder(ctx, p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return folder, nil | ||
} | ||
|
||
// datastoreFolderFromObject returns an *object.Folder from a given object, | ||
// and relative datastore folder path. If no such folder is found, of if it is | ||
// not a datastore folder, an appropriate error will be returned. | ||
func datastoreFolderFromObject(client *govmomi.Client, obj interface{}, relative string) (*object.Folder, error) { | ||
folder, err := folderFromObject(client, obj, rootPathParticleDatastore, relative) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return validateDatastoreFolder(folder) | ||
} | ||
|
||
// validateDatastoreFolder checks to make sure the folder is a datastore | ||
// folder, and returns it if it is not, or an error if it isn't. | ||
func validateDatastoreFolder(folder *object.Folder) (*object.Folder, error) { | ||
var props mo.Folder | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) | ||
defer cancel() | ||
if err := folder.Properties(ctx, folder.Reference(), nil, &props); err != nil { | ||
return nil, err | ||
} | ||
if !reflect.DeepEqual(props.ChildType, []string{"Folder", "Datastore", "StoragePod"}) { | ||
return nil, fmt.Errorf("%q is not a datastore folder", folder.InventoryPath) | ||
} | ||
return folder, nil | ||
} | ||
|
||
// pathIsEmpty checks a folder path to see if it's "empty" (ie: would resolve | ||
// to the root inventory path for a given type in a datacenter - "" or "/"). | ||
func pathIsEmpty(path string) bool { | ||
return path == "" || path == "/" | ||
} | ||
|
||
// normalizeFolderPath is a SchemaStateFunc that normalizes a folder path. | ||
func normalizeFolderPath(v interface{}) string { | ||
p := v.(string) | ||
if pathIsEmpty(p) { | ||
return "" | ||
} | ||
return strings.TrimPrefix(path.Clean(p), "/") | ||
} | ||
|
||
// moveObjectToFolder moves a object by reference into a folder. | ||
func moveObjectToFolder(ref types.ManagedObjectReference, folder *object.Folder) error { | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) | ||
defer cancel() | ||
task, err := folder.MoveInto(ctx, []types.ManagedObjectReference{ref}) | ||
if err != nil { | ||
return err | ||
} | ||
tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) | ||
defer tcancel() | ||
return task.Wait(tctx) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the need for only this field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because
name
is not set in the accompanyingschemaDatastoreSummary
, as it's an attribute that may be controlled in different ways and go through different workflows depending on the resource. At the same time though, we want to flatten it from the summary data as it's one of the easiest ways to fetch it from the datastore. So rather than just drop the result on the floor if by any chance it's not in the schema, I want to check for errors so that any edge cases get caught (more than likely in testing, so the user will never see it, but it has value nonetheless).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This review item was helpful though as now that we use MoRefs for the IDs here on the datastore, we can support renaming of the datastore and also remove a ForceNew case if someone has changed it manually :) I just added that support.