Fix for Support selection of datastore for dynamic provisioning in vSphere

This commit is contained in:
Balu Dontu
2017-02-16 16:12:01 -08:00
committed by Ritesh H Shukla
parent b201ac2f8f
commit 12f75f0b86
83 changed files with 8640 additions and 504 deletions

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type AuthorizationManager struct {
@@ -106,3 +107,68 @@ func (m AuthorizationManager) SetEntityPermissions(ctx context.Context, entity t
_, err := methods.SetEntityPermissions(ctx, m.Client(), &req)
return err
}
func (m AuthorizationManager) RetrieveRolePermissions(ctx context.Context, id int32) ([]types.Permission, error) {
req := types.RetrieveRolePermissions{
This: m.Reference(),
RoleId: id,
}
res, err := methods.RetrieveRolePermissions(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
func (m AuthorizationManager) RetrieveAllPermissions(ctx context.Context) ([]types.Permission, error) {
req := types.RetrieveAllPermissions{
This: m.Reference(),
}
res, err := methods.RetrieveAllPermissions(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
func (m AuthorizationManager) AddRole(ctx context.Context, name string, ids []string) (int32, error) {
req := types.AddAuthorizationRole{
This: m.Reference(),
Name: name,
PrivIds: ids,
}
res, err := methods.AddAuthorizationRole(ctx, m.Client(), &req)
if err != nil {
return -1, err
}
return res.Returnval, nil
}
func (m AuthorizationManager) RemoveRole(ctx context.Context, id int32, failIfUsed bool) error {
req := types.RemoveAuthorizationRole{
This: m.Reference(),
RoleId: id,
FailIfUsed: failIfUsed,
}
_, err := methods.RemoveAuthorizationRole(ctx, m.Client(), &req)
return err
}
func (m AuthorizationManager) UpdateRole(ctx context.Context, id int32, name string, ids []string) error {
req := types.UpdateAuthorizationRole{
This: m.Reference(),
RoleId: id,
NewName: name,
PrivIds: ids,
}
_, err := methods.UpdateAuthorizationRole(ctx, m.Client(), &req)
return err
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type ClusterComputeResource struct {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"errors"
"fmt"
"path"
@@ -26,11 +27,10 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
var (
ErrNotSupported = errors.New("not supported (vCenter only)")
ErrNotSupported = errors.New("product/version specific feature not supported by target")
)
// Common contains the fields and functions common to all objects.

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"path"
"github.com/vmware/govmomi/property"
@@ -24,7 +25,6 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type ComputeResource struct {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"errors"
"strconv"
@@ -24,7 +25,6 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
var (

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type CustomizationSpecManager struct {

View File

@@ -17,13 +17,13 @@ limitations under the License.
package object
import (
"context"
"fmt"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type DatacenterFolders struct {

View File

@@ -24,6 +24,7 @@ import (
"path"
"strings"
"context"
"net/http"
"net/url"
@@ -33,7 +34,6 @@ import (
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
// DatastoreNoSuchDirectoryError is returned when a directory could not be found.
@@ -58,6 +58,8 @@ func (e DatastoreNoSuchFileError) Error() string {
type Datastore struct {
Common
DatacenterPath string
}
func NewDatastore(c *vim25.Client, ref types.ManagedObjectReference) *Datastore {
@@ -67,26 +69,14 @@ func NewDatastore(c *vim25.Client, ref types.ManagedObjectReference) *Datastore
}
func (d Datastore) Path(path string) string {
name := d.Name()
if name == "" {
panic("expected non-empty name")
}
return fmt.Sprintf("[%s] %s", name, path)
return (&DatastorePath{
Datastore: d.Name(),
Path: path,
}).String()
}
// URL for datastore access over HTTP
func (d Datastore) URL(ctx context.Context, dc *Datacenter, path string) (*url.URL, error) {
var mdc mo.Datacenter
if err := dc.Properties(ctx, dc.Reference(), []string{"name"}, &mdc); err != nil {
return nil, err
}
var mds mo.Datastore
if err := d.Properties(ctx, d.Reference(), []string{"name"}, &mds); err != nil {
return nil, err
}
// NewURL constructs a url.URL with the given file path for datastore access over HTTP.
func (d Datastore) NewURL(path string) *url.URL {
u := d.c.URL()
return &url.URL{
@@ -94,10 +84,15 @@ func (d Datastore) URL(ctx context.Context, dc *Datacenter, path string) (*url.U
Host: u.Host,
Path: fmt.Sprintf("/folder/%s", path),
RawQuery: url.Values{
"dcPath": []string{mdc.Name},
"dsName": []string{mds.Name},
"dcPath": []string{d.DatacenterPath},
"dsName": []string{d.Name()},
}.Encode(),
}, nil
}
}
// URL is deprecated, use NewURL instead.
func (d Datastore) URL(ctx context.Context, dc *Datacenter, path string) (*url.URL, error) {
return d.NewURL(path), nil
}
func (d Datastore) Browser(ctx context.Context) (*HostDatastoreBrowser, error) {
@@ -111,6 +106,27 @@ func (d Datastore) Browser(ctx context.Context) (*HostDatastoreBrowser, error) {
return NewHostDatastoreBrowser(d.c, do.Browser), nil
}
func (d Datastore) useServiceTicket() bool {
// If connected to workstation, service ticketing not supported
// If connected to ESX, service ticketing not needed
if !d.c.IsVC() {
return false
}
key := "GOVMOMI_USE_SERVICE_TICKET"
val := d.c.URL().Query().Get(key)
if val == "" {
val = os.Getenv(key)
}
if val == "1" || val == "true" {
return true
}
return false
}
func (d Datastore) useServiceTicketHostName(name string) bool {
// No need if talking directly to ESX.
if !d.c.IsVC() {
@@ -147,39 +163,62 @@ func (d Datastore) useServiceTicketHostName(name string) bool {
return false
}
// ServiceTicket obtains a ticket via AcquireGenericServiceTicket and returns it an http.Cookie with the url.URL
// that can be used along with the ticket cookie to access the given path.
func (d Datastore) ServiceTicket(ctx context.Context, path string, method string) (*url.URL, *http.Cookie, error) {
// We are uploading to an ESX host
u := &url.URL{
Scheme: d.c.URL().Scheme,
Host: d.c.URL().Host,
Path: fmt.Sprintf("/folder/%s", path),
RawQuery: url.Values{
"dsName": []string{d.Name()},
}.Encode(),
}
type datastoreServiceTicketHostKey struct{}
// HostContext returns a Context where the given host will be used for datastore HTTP access
// via the ServiceTicket method.
func (d Datastore) HostContext(ctx context.Context, host *HostSystem) context.Context {
return context.WithValue(ctx, datastoreServiceTicketHostKey{}, host)
}
// ServiceTicket obtains a ticket via AcquireGenericServiceTicket and returns it an http.Cookie with the url.URL
// that can be used along with the ticket cookie to access the given path. An host is chosen at random unless the
// the given Context was created with a specific host via the HostContext method.
func (d Datastore) ServiceTicket(ctx context.Context, path string, method string) (*url.URL, *http.Cookie, error) {
u := d.NewURL(path)
host, ok := ctx.Value(datastoreServiceTicketHostKey{}).(*HostSystem)
if !ok {
if !d.useServiceTicket() {
return u, nil, nil
}
// If connected to VC, the ticket request must be for an ESX host.
if d.c.IsVC() {
hosts, err := d.AttachedHosts(ctx)
if err != nil {
return nil, nil, err
}
if len(hosts) == 0 {
return nil, nil, fmt.Errorf("no hosts attached to datastore %#v", d.Reference())
// Fallback to letting vCenter choose a host
return u, nil, nil
}
// Pick a random attached host
host := hosts[rand.Intn(len(hosts))]
name, err := host.ObjectName(ctx)
host = hosts[rand.Intn(len(hosts))]
}
ips, err := host.ManagementIPs(ctx)
if err != nil {
return nil, nil, err
}
if len(ips) > 0 {
// prefer a ManagementIP
u.Host = ips[0].String()
} else {
// fallback to inventory name
u.Host, err = host.ObjectName(ctx)
if err != nil {
return nil, nil, err
}
u.Host = name
}
// VC datacenter path will not be valid against ESX
q := u.Query()
delete(q, "dcPath")
u.RawQuery = q.Encode()
spec := types.SessionManagerHttpServiceRequestSpec{
Url: u.String(),
// See SessionManagerHttpServiceRequestSpecMethod enum
@@ -202,6 +241,8 @@ func (d Datastore) ServiceTicket(ctx context.Context, path string, method string
u.Host = ticket.HostName
}
d.Client().SetThumbprint(u.Host, ticket.SslThumbprint)
return u, cookie, nil
}
@@ -313,7 +354,7 @@ func (d Datastore) AttachedHosts(ctx context.Context) ([]*HostSystem, error) {
return hosts, nil
}
// AttachedHosts returns hosts that have this Datastore attached, accessible and writable and are members of the given cluster.
// AttachedClusterHosts returns hosts that have this Datastore attached, accessible and writable and are members of the given cluster.
func (d Datastore) AttachedClusterHosts(ctx context.Context, cluster *ComputeResource) ([]*HostSystem, error) {
var hosts []*HostSystem
@@ -358,12 +399,12 @@ func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, e
}
dsPath := d.Path(path.Dir(file))
task, err := b.SearchDatastore(context.TODO(), dsPath, &spec)
task, err := b.SearchDatastore(ctx, dsPath, &spec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(context.TODO(), nil)
info, err := task.WaitForResult(ctx, nil)
if err != nil {
if info == nil || info.Error != nil {
_, ok := info.Error.Fault.(*types.FileNotFound)

View File

@@ -0,0 +1,399 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"time"
"github.com/vmware/govmomi/vim25/soap"
)
// DatastoreFile implements io.Reader, io.Seeker and io.Closer interfaces for datastore file access.
type DatastoreFile struct {
d Datastore
ctx context.Context
name string
buf io.Reader
body io.ReadCloser
length int64
offset struct {
read, seek int64
}
}
// Open opens the named file relative to the Datastore.
func (d Datastore) Open(ctx context.Context, name string) (*DatastoreFile, error) {
return &DatastoreFile{
d: d,
name: name,
length: -1,
ctx: ctx,
}, nil
}
// Read reads up to len(b) bytes from the DatastoreFile.
func (f *DatastoreFile) Read(b []byte) (int, error) {
if f.offset.read != f.offset.seek {
// A Seek() call changed the offset, we need to issue a new GET
_ = f.Close()
f.offset.read = f.offset.seek
} else if f.buf != nil {
// f.buf + f behaves like an io.MultiReader
n, err := f.buf.Read(b)
if err == io.EOF {
f.buf = nil // buffer has been drained
}
if n > 0 {
return n, nil
}
}
body, err := f.get()
if err != nil {
return 0, err
}
n, err := body.Read(b)
f.offset.read += int64(n)
f.offset.seek += int64(n)
return n, err
}
// Close closes the DatastoreFile.
func (f *DatastoreFile) Close() error {
var err error
if f.body != nil {
err = f.body.Close()
f.body = nil
}
f.buf = nil
return err
}
// Seek sets the offset for the next Read on the DatastoreFile.
func (f *DatastoreFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
case io.SeekCurrent:
offset += f.offset.seek
case io.SeekEnd:
if f.length < 0 {
_, err := f.Stat()
if err != nil {
return 0, err
}
}
offset += f.length
default:
return 0, errors.New("Seek: invalid whence")
}
// allow negative SeekStart for initial Range request
if offset < 0 {
return 0, errors.New("Seek: invalid offset")
}
f.offset.seek = offset
return offset, nil
}
type fileStat struct {
file *DatastoreFile
header http.Header
}
func (s *fileStat) Name() string {
return path.Base(s.file.name)
}
func (s *fileStat) Size() int64 {
return s.file.length
}
func (s *fileStat) Mode() os.FileMode {
return 0
}
func (s *fileStat) ModTime() time.Time {
return time.Now() // no Last-Modified
}
func (s *fileStat) IsDir() bool {
return false
}
func (s *fileStat) Sys() interface{} {
return s.header
}
func statusError(res *http.Response) error {
if res.StatusCode == http.StatusNotFound {
return os.ErrNotExist
}
return errors.New(res.Status)
}
// Stat returns the os.FileInfo interface describing file.
func (f *DatastoreFile) Stat() (os.FileInfo, error) {
// TODO: consider using Datastore.Stat() instead
u, p, err := f.d.downloadTicket(f.ctx, f.name, &soap.Download{Method: "HEAD"})
if err != nil {
return nil, err
}
res, err := f.d.Client().DownloadRequest(u, p)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, statusError(res)
}
f.length = res.ContentLength
return &fileStat{f, res.Header}, nil
}
func (f *DatastoreFile) get() (io.Reader, error) {
if f.body != nil {
return f.body, nil
}
u, p, err := f.d.downloadTicket(f.ctx, f.name, nil)
if err != nil {
return nil, err
}
if f.offset.read != 0 {
p.Headers = map[string]string{
"Range": fmt.Sprintf("bytes=%d-", f.offset.read),
}
}
res, err := f.d.Client().DownloadRequest(u, p)
if err != nil {
return nil, err
}
switch res.StatusCode {
case http.StatusOK:
f.length = res.ContentLength
case http.StatusPartialContent:
var start, end int
cr := res.Header.Get("Content-Range")
_, err = fmt.Sscanf(cr, "bytes %d-%d/%d", &start, &end, &f.length)
if err != nil {
f.length = -1
}
case http.StatusRequestedRangeNotSatisfiable:
// ok: Read() will return io.EOF
default:
return nil, statusError(res)
}
if f.length < 0 {
_ = res.Body.Close()
return nil, errors.New("unable to determine file size")
}
f.body = res.Body
return f.body, nil
}
func lastIndexLines(s []byte, n *int) int64 {
i := len(s) - 1
for i > 0 {
o := bytes.LastIndexByte(s[:i], '\n')
if o < 0 {
break
}
i = o
*n--
if *n == 0 {
break
}
}
return int64(i)
}
// Tail seeks to the position of the last N lines of the file.
func (f *DatastoreFile) Tail(n int) error {
// Read the file in reverse using bsize chunks
const bsize = int64(1024 * 16)
fsize, err := f.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if n == 0 {
return nil
}
chunk := int64(-1)
buf := bytes.NewBuffer(make([]byte, 0, bsize))
for {
var eof bool
var pos int64
nread := bsize
offset := chunk * bsize
remain := fsize + offset
if remain < 0 {
if pos, err = f.Seek(0, io.SeekStart); err != nil {
return err
}
nread = bsize + remain
eof = true
} else {
if pos, err = f.Seek(offset, io.SeekEnd); err != nil {
return err
}
}
if _, err = io.CopyN(buf, f, nread); err != nil {
if err != io.EOF {
return err
}
}
b := buf.Bytes()
idx := lastIndexLines(b, &n) + 1
if n == 0 {
if chunk == -1 {
// We found all N lines in the last chunk of the file.
// The seek offset is also now at the current end of file.
// Save this buffer to avoid another GET request when Read() is called.
buf.Next(int(idx))
f.buf = buf
return nil
}
if _, err = f.Seek(pos+idx, io.SeekStart); err != nil {
return err
}
break
}
if eof {
if remain < 0 {
// We found < N lines in the entire file, so seek to the start.
_, _ = f.Seek(0, io.SeekStart)
}
break
}
chunk--
buf.Reset()
}
return nil
}
type followDatastoreFile struct {
r *DatastoreFile
c chan struct{}
i time.Duration
}
// Read reads up to len(b) bytes from the DatastoreFile being followed.
// This method will block until data is read, an error other than io.EOF is returned or Close() is called.
func (f *followDatastoreFile) Read(p []byte) (int, error) {
offset := f.r.offset.seek
stop := false
defer f.r.Close()
for {
n, err := f.r.Read(p)
if err != nil && err == io.EOF {
_ = f.r.Close() // GET request body has been drained.
if stop {
return n, err
}
err = nil
}
if n > 0 {
return n, err
}
select {
case <-f.c:
// Wake up and stop polling once the body has been drained
stop = true
case <-time.After(f.i):
}
info, serr := f.r.Stat()
if serr != nil {
// Return EOF rather than 404 if the file goes away
if serr == os.ErrNotExist {
_ = f.r.Close()
return 0, io.EOF
}
return 0, serr
}
if info.Size() < offset {
// assume file has be truncated
offset, err = f.r.Seek(0, io.SeekStart)
if err != nil {
return 0, err
}
}
}
}
// Close will stop Follow polling and close the underlying DatastoreFile.
func (f *followDatastoreFile) Close() error {
close(f.c)
return nil
}
// Follow returns an io.ReadCloser to stream the file contents as data is appended.
func (f *DatastoreFile) Follow(interval time.Duration) io.ReadCloser {
return &followDatastoreFile{f, make(chan struct{}), interval}
}

View File

@@ -0,0 +1,65 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"fmt"
"strings"
)
// DatastorePath contains the components of a datastore path.
type DatastorePath struct {
Datastore string
Path string
}
// FromString parses a datastore path.
// Returns true if the path could be parsed, false otherwise.
func (p *DatastorePath) FromString(s string) bool {
if len(s) == 0 {
return false
}
s = strings.TrimSpace(s)
if !strings.HasPrefix(s, "[") {
return false
}
s = s[1:]
ix := strings.Index(s, "]")
if ix < 0 {
return false
}
p.Datastore = s[:ix]
p.Path = strings.TrimSpace(s[ix+1:])
return true
}
// String formats a datastore path.
func (p *DatastorePath) String() string {
s := fmt.Sprintf("[%s]", p.Datastore)
if p.Path == "" {
return s
}
return strings.Join([]string{s, p.Path}, " ")
}

View File

@@ -0,0 +1,76 @@
/*
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"context"
"fmt"
"io"
"math"
)
// DiagnosticLog wraps DiagnosticManager.BrowseLog
type DiagnosticLog struct {
m DiagnosticManager
Key string
Host *HostSystem
Start int32
}
// Seek to log position starting at the last nlines of the log
func (l *DiagnosticLog) Seek(ctx context.Context, nlines int32) error {
h, err := l.m.BrowseLog(ctx, l.Host, l.Key, math.MaxInt32, 0)
if err != nil {
return err
}
l.Start = h.LineEnd - nlines
return nil
}
// Copy log starting from l.Start to the given io.Writer
// Returns on error or when end of log is reached.
func (l *DiagnosticLog) Copy(ctx context.Context, w io.Writer) (int, error) {
const max = 500 // VC max == 500, ESX max == 1000
written := 0
for {
h, err := l.m.BrowseLog(ctx, l.Host, l.Key, l.Start, max)
if err != nil {
return 0, err
}
for _, line := range h.LineText {
n, err := fmt.Fprintln(w, line)
written += n
if err != nil {
return written, err
}
}
l.Start += int32(len(h.LineText))
if l.Start >= h.LineEnd {
break
}
}
return written, nil
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type DiagnosticManager struct {
@@ -35,6 +36,14 @@ func NewDiagnosticManager(c *vim25.Client) *DiagnosticManager {
return &m
}
func (m DiagnosticManager) Log(ctx context.Context, host *HostSystem, key string) *DiagnosticLog {
return &DiagnosticLog{
m: m,
Key: key,
Host: host,
}
}
func (m DiagnosticManager) BrowseLog(ctx context.Context, host *HostSystem, key string, start, lines int32) (*types.DiagnosticManagerLogHeader, error) {
req := types.BrowseDiagnosticLog{
This: m.Reference(),

View File

@@ -17,10 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type DistributedVirtualPortgroup struct {
@@ -55,3 +57,17 @@ func (p DistributedVirtualPortgroup) EthernetCardBackingInfo(ctx context.Context
return backing, nil
}
func (p DistributedVirtualPortgroup) Reconfigure(ctx context.Context, spec types.DVPortgroupConfigSpec) (*Task, error) {
req := types.ReconfigureDVPortgroup_Task{
This: p.Reference(),
Spec: spec,
}
res, err := methods.ReconfigureDVPortgroup_Task(ctx, p.Client(), &req)
if err != nil {
return nil, err
}
return NewTask(p.Client(), res.Returnval), nil
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type DistributedVirtualSwitch struct {

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type ExtensionManager struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type FileManager struct {

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type Folder struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HistoryCollector struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostAccountManager struct {

View File

@@ -0,0 +1,250 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"io"
"net/url"
"strings"
"text/tabwriter"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
// HostCertificateInfo provides helpers for types.HostCertificateManagerCertificateInfo
type HostCertificateInfo struct {
types.HostCertificateManagerCertificateInfo
ThumbprintSHA1 string
ThumbprintSHA256 string
Err error
Certificate *x509.Certificate `json:"-"`
subjectName *pkix.Name
issuerName *pkix.Name
}
// FromCertificate converts x509.Certificate to HostCertificateInfo
func (info *HostCertificateInfo) FromCertificate(cert *x509.Certificate) *HostCertificateInfo {
info.Certificate = cert
info.subjectName = &cert.Subject
info.issuerName = &cert.Issuer
info.Issuer = info.fromName(info.issuerName)
info.NotBefore = &cert.NotBefore
info.NotAfter = &cert.NotAfter
info.Subject = info.fromName(info.subjectName)
info.ThumbprintSHA1 = soap.ThumbprintSHA1(cert)
// SHA-256 for info purposes only, API fields all use SHA-1
sum := sha256.Sum256(cert.Raw)
hex := make([]string, len(sum))
for i, b := range sum {
hex[i] = fmt.Sprintf("%02X", b)
}
info.ThumbprintSHA256 = strings.Join(hex, ":")
if info.Status == "" {
info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusUnknown)
}
return info
}
// FromURL connects to the given URL.Host via tls.Dial with the given tls.Config and populates the HostCertificateInfo
// via tls.ConnectionState. If the certificate was verified with the given tls.Config, the Err field will be nil.
// Otherwise, Err will be set to the x509.UnknownAuthorityError or x509.HostnameError.
// If tls.Dial returns an error of any other type, that error is returned.
func (info *HostCertificateInfo) FromURL(u *url.URL, config *tls.Config) error {
addr := u.Host
if !(strings.LastIndex(addr, ":") > strings.LastIndex(addr, "]")) {
addr += ":443"
}
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
switch err.(type) {
case x509.UnknownAuthorityError:
case x509.HostnameError:
default:
return err
}
info.Err = err
conn, err = tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return err
}
} else {
info.Status = string(types.HostCertificateManagerCertificateInfoCertificateStatusGood)
}
state := conn.ConnectionState()
_ = conn.Close()
info.FromCertificate(state.PeerCertificates[0])
return nil
}
var emailAddressOID = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
func (info *HostCertificateInfo) fromName(name *pkix.Name) string {
var attrs []string
oids := map[string]string{
emailAddressOID.String(): "emailAddress",
}
for _, attr := range name.Names {
if key, ok := oids[attr.Type.String()]; ok {
attrs = append(attrs, fmt.Sprintf("%s=%s", key, attr.Value))
}
}
attrs = append(attrs, fmt.Sprintf("CN=%s", name.CommonName))
add := func(key string, vals []string) {
for _, val := range vals {
attrs = append(attrs, fmt.Sprintf("%s=%s", key, val))
}
}
elts := []struct {
key string
val []string
}{
{"OU", name.OrganizationalUnit},
{"O", name.Organization},
{"L", name.Locality},
{"ST", name.Province},
{"C", name.Country},
}
for _, elt := range elts {
add(elt.key, elt.val)
}
return strings.Join(attrs, ",")
}
func (info *HostCertificateInfo) toName(s string) *pkix.Name {
var name pkix.Name
for _, pair := range strings.Split(s, ",") {
attr := strings.SplitN(pair, "=", 2)
if len(attr) != 2 {
continue
}
v := attr[1]
switch strings.ToLower(attr[0]) {
case "cn":
name.CommonName = v
case "ou":
name.OrganizationalUnit = append(name.OrganizationalUnit, v)
case "o":
name.Organization = append(name.Organization, v)
case "l":
name.Locality = append(name.Locality, v)
case "st":
name.Province = append(name.Province, v)
case "c":
name.Country = append(name.Country, v)
case "emailaddress":
name.Names = append(name.Names, pkix.AttributeTypeAndValue{Type: emailAddressOID, Value: v})
}
}
return &name
}
// SubjectName parses Subject into a pkix.Name
func (info *HostCertificateInfo) SubjectName() *pkix.Name {
if info.subjectName != nil {
return info.subjectName
}
return info.toName(info.Subject)
}
// IssuerName parses Issuer into a pkix.Name
func (info *HostCertificateInfo) IssuerName() *pkix.Name {
if info.issuerName != nil {
return info.issuerName
}
return info.toName(info.Issuer)
}
// Write outputs info similar to the Chrome Certificate Viewer.
func (info *HostCertificateInfo) Write(w io.Writer) error {
tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
s := func(val string) string {
if val != "" {
return val
}
return "<Not Part Of Certificate>"
}
ss := func(val []string) string {
return s(strings.Join(val, ","))
}
name := func(n *pkix.Name) {
fmt.Fprintf(tw, " Common Name (CN):\t%s\n", s(n.CommonName))
fmt.Fprintf(tw, " Organization (O):\t%s\n", ss(n.Organization))
fmt.Fprintf(tw, " Organizational Unit (OU):\t%s\n", ss(n.OrganizationalUnit))
}
status := info.Status
if info.Err != nil {
status = fmt.Sprintf("ERROR %s", info.Err)
}
fmt.Fprintf(tw, "Certificate Status:\t%s\n", status)
fmt.Fprintln(tw, "Issued To:\t")
name(info.SubjectName())
fmt.Fprintln(tw, "Issued By:\t")
name(info.IssuerName())
fmt.Fprintln(tw, "Validity Period:\t")
fmt.Fprintf(tw, " Issued On:\t%s\n", info.NotBefore)
fmt.Fprintf(tw, " Expires On:\t%s\n", info.NotAfter)
if info.ThumbprintSHA1 != "" {
fmt.Fprintln(tw, "Thumbprints:\t")
if info.ThumbprintSHA256 != "" {
fmt.Fprintf(tw, " SHA-256 Thumbprint:\t%s\n", info.ThumbprintSHA256)
}
fmt.Fprintf(tw, " SHA-1 Thumbprint:\t%s\n", info.ThumbprintSHA1)
}
return tw.Flush()
}

View File

@@ -0,0 +1,162 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"context"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
// HostCertificateManager provides helper methods around the HostSystem.ConfigManager.CertificateManager
type HostCertificateManager struct {
Common
Host *HostSystem
}
// NewHostCertificateManager creates a new HostCertificateManager helper
func NewHostCertificateManager(c *vim25.Client, ref types.ManagedObjectReference, host types.ManagedObjectReference) *HostCertificateManager {
return &HostCertificateManager{
Common: NewCommon(c, ref),
Host: NewHostSystem(c, host),
}
}
// CertificateInfo wraps the host CertificateManager certificateInfo property with the HostCertificateInfo helper.
// The ThumbprintSHA1 field is set to HostSystem.Summary.Config.SslThumbprint if the host system is managed by a vCenter.
func (m HostCertificateManager) CertificateInfo(ctx context.Context) (*HostCertificateInfo, error) {
var hs mo.HostSystem
var cm mo.HostCertificateManager
pc := property.DefaultCollector(m.Client())
err := pc.RetrieveOne(ctx, m.Reference(), []string{"certificateInfo"}, &cm)
if err != nil {
return nil, err
}
_ = pc.RetrieveOne(ctx, m.Host.Reference(), []string{"summary.config.sslThumbprint"}, &hs)
return &HostCertificateInfo{
HostCertificateManagerCertificateInfo: cm.CertificateInfo,
ThumbprintSHA1: hs.Summary.Config.SslThumbprint,
}, nil
}
// GenerateCertificateSigningRequest requests the host system to generate a certificate-signing request (CSR) for itself.
// The CSR is then typically provided to a Certificate Authority to sign and issue the SSL certificate for the host system.
// Use InstallServerCertificate to import this certificate.
func (m HostCertificateManager) GenerateCertificateSigningRequest(ctx context.Context, useIPAddressAsCommonName bool) (string, error) {
req := types.GenerateCertificateSigningRequest{
This: m.Reference(),
UseIpAddressAsCommonName: useIPAddressAsCommonName,
}
res, err := methods.GenerateCertificateSigningRequest(ctx, m.Client(), &req)
if err != nil {
return "", err
}
return res.Returnval, nil
}
// GenerateCertificateSigningRequestByDn requests the host system to generate a certificate-signing request (CSR) for itself.
// Alternative version similar to GenerateCertificateSigningRequest but takes a Distinguished Name (DN) as a parameter.
func (m HostCertificateManager) GenerateCertificateSigningRequestByDn(ctx context.Context, distinguishedName string) (string, error) {
req := types.GenerateCertificateSigningRequestByDn{
This: m.Reference(),
DistinguishedName: distinguishedName,
}
res, err := methods.GenerateCertificateSigningRequestByDn(ctx, m.Client(), &req)
if err != nil {
return "", err
}
return res.Returnval, nil
}
// InstallServerCertificate imports the given SSL certificate to the host system.
func (m HostCertificateManager) InstallServerCertificate(ctx context.Context, cert string) error {
req := types.InstallServerCertificate{
This: m.Reference(),
Cert: cert,
}
_, err := methods.InstallServerCertificate(ctx, m.Client(), &req)
if err != nil {
return err
}
// NotifyAffectedService is internal, not exposing as we don't have a use case other than with InstallServerCertificate
// Without this call, hostd needs to be restarted to use the updated certificate
// Note: using Refresh as it has the same struct/signature, we just need to use different xml name tags
body := struct {
Req *types.Refresh `xml:"urn:vim25 NotifyAffectedServices,omitempty"`
Res *types.RefreshResponse `xml:"urn:vim25 NotifyAffectedServicesResponse,omitempty"`
methods.RefreshBody
}{
Req: &types.Refresh{This: m.Reference()},
}
return m.Client().RoundTrip(ctx, &body, &body)
}
// ListCACertificateRevocationLists returns the SSL CRLs of Certificate Authorities that are trusted by the host system.
func (m HostCertificateManager) ListCACertificateRevocationLists(ctx context.Context) ([]string, error) {
req := types.ListCACertificateRevocationLists{
This: m.Reference(),
}
res, err := methods.ListCACertificateRevocationLists(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
// ListCACertificates returns the SSL certificates of Certificate Authorities that are trusted by the host system.
func (m HostCertificateManager) ListCACertificates(ctx context.Context) ([]string, error) {
req := types.ListCACertificates{
This: m.Reference(),
}
res, err := methods.ListCACertificates(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
// ReplaceCACertificatesAndCRLs replaces the trusted CA certificates and CRL used by the host system.
// These determine whether the server can verify the identity of an external entity.
func (m HostCertificateManager) ReplaceCACertificatesAndCRLs(ctx context.Context, caCert []string, caCrl []string) error {
req := types.ReplaceCACertificatesAndCRLs{
This: m.Reference(),
CaCert: caCert,
CaCrl: caCrl,
}
_, err := methods.ReplaceCACertificatesAndCRLs(ctx, m.Client(), &req)
return err
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostConfigManager struct {
@@ -96,6 +97,11 @@ func (m HostConfigManager) VsanSystem(ctx context.Context) (*HostVsanSystem, err
return nil, err
}
// Added in 5.5
if h.ConfigManager.VsanSystem == nil {
return nil, ErrNotSupported
}
return NewHostVsanSystem(m.c, *h.ConfigManager.VsanSystem), nil
}
@@ -107,7 +113,21 @@ func (m HostConfigManager) AccountManager(ctx context.Context) (*HostAccountMana
return nil, err
}
return NewHostAccountManager(m.c, *h.ConfigManager.AccountManager), nil
ref := h.ConfigManager.AccountManager // Added in 6.0
if ref == nil {
// Versions < 5.5 can use the ServiceContent ref,
// but we can only use it when connected directly to ESX.
c := m.Client()
if !c.IsVC() {
ref = c.ServiceContent.AccountManager
}
if ref == nil {
return nil, ErrNotSupported
}
}
return NewHostAccountManager(m.c, *ref), nil
}
func (m HostConfigManager) OptionManager(ctx context.Context) (*OptionManager, error) {
@@ -131,3 +151,30 @@ func (m HostConfigManager) ServiceSystem(ctx context.Context) (*HostServiceSyste
return NewHostServiceSystem(m.c, *h.ConfigManager.ServiceSystem), nil
}
func (m HostConfigManager) CertificateManager(ctx context.Context) (*HostCertificateManager, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.certificateManager"}, &h)
if err != nil {
return nil, err
}
// Added in 6.0
if h.ConfigManager.CertificateManager == nil {
return nil, ErrNotSupported
}
return NewHostCertificateManager(m.c, *h.ConfigManager.CertificateManager, m.Reference()), nil
}
func (m HostConfigManager) DateTimeSystem(ctx context.Context) (*HostDateTimeSystem, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.dateTimeSystem"}, &h)
if err != nil {
return nil, err
}
return NewHostDateTimeSystem(m.c, *h.ConfigManager.DateTimeSystem), nil
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostDatastoreBrowser struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostDatastoreSystem struct {

View File

@@ -0,0 +1,69 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"context"
"time"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
)
type HostDateTimeSystem struct {
Common
}
func NewHostDateTimeSystem(c *vim25.Client, ref types.ManagedObjectReference) *HostDateTimeSystem {
return &HostDateTimeSystem{
Common: NewCommon(c, ref),
}
}
func (s HostDateTimeSystem) UpdateConfig(ctx context.Context, config types.HostDateTimeConfig) error {
req := types.UpdateDateTimeConfig{
This: s.Reference(),
Config: config,
}
_, err := methods.UpdateDateTimeConfig(ctx, s.c, &req)
return err
}
func (s HostDateTimeSystem) Update(ctx context.Context, date time.Time) error {
req := types.UpdateDateTime{
This: s.Reference(),
DateTime: date,
}
_, err := methods.UpdateDateTime(ctx, s.c, &req)
return err
}
func (s HostDateTimeSystem) Query(ctx context.Context) (*time.Time, error) {
req := types.QueryDateTime{
This: s.Reference(),
}
res, err := methods.QueryDateTime(ctx, s.c, &req)
if err != nil {
return nil, err
}
return &res.Returnval, nil
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"errors"
"fmt"
"strings"
@@ -25,7 +26,6 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostFirewallSystem struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostNetworkSystem struct {

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostServiceSystem struct {

View File

@@ -17,12 +17,12 @@ limitations under the License.
package object
import (
"context"
"errors"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostStorageSystem struct {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"fmt"
"net"
@@ -24,7 +25,6 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostSystem struct {

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostVirtualNicManager struct {

View File

@@ -17,11 +17,12 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostVsanSystem struct {

View File

@@ -17,6 +17,7 @@ limitations under the License.
package object
import (
"context"
"errors"
"fmt"
@@ -25,7 +26,6 @@ import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HttpNfcLease struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type ListView struct {
@@ -37,7 +38,33 @@ func (v ListView) Destroy(ctx context.Context) error {
req := types.DestroyView{
This: v.Reference(),
}
_, err := methods.DestroyView(ctx, v.c, &req)
return err
}
func (v ListView) Add(ctx context.Context, refs []types.ManagedObjectReference) error {
req := types.ModifyListView{
This: v.Reference(),
Add: refs,
}
_, err := methods.ModifyListView(ctx, v.c, &req)
return err
}
func (v ListView) Remove(ctx context.Context, refs []types.ManagedObjectReference) error {
req := types.ModifyListView{
This: v.Reference(),
Remove: refs,
}
_, err := methods.ModifyListView(ctx, v.c, &req)
return err
}
func (v ListView) Reset(ctx context.Context, refs []types.ManagedObjectReference) error {
req := types.ResetListView{
This: v.Reference(),
Obj: refs,
}
_, err := methods.ResetListView(ctx, v.c, &req)
return err
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type DatastoreNamespaceManager struct {

View File

@@ -17,9 +17,10 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type Network struct {

View File

@@ -17,8 +17,9 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
// The NetworkReference interface is implemented by managed objects

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type OptionManager struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type OvfManager struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type ResourcePool struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type SearchIndex struct {

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type StorageResourceManager struct {

View File

@@ -17,12 +17,13 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/task"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/progress"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
// Task is a convenience wrapper around task.Task that keeps a reference to

View File

@@ -47,7 +47,7 @@ func NewReference(c *vim25.Client, e types.ManagedObjectReference) Reference {
return NewClusterComputeResource(c, e)
case "HostSystem":
return NewHostSystem(c, e)
case "Network":
case "Network", "OpaqueNetwork":
return NewNetwork(c, e)
case "ResourcePool":
return NewResourcePool(c, e)

View File

@@ -17,7 +17,7 @@ limitations under the License.
package object
import (
"golang.org/x/net/context"
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
@@ -34,7 +34,7 @@ func NewVirtualApp(c *vim25.Client, ref types.ManagedObjectReference) *VirtualAp
}
}
func (p VirtualApp) CreateChildVM_Task(ctx context.Context, config types.VirtualMachineConfigSpec, host *HostSystem) (*Task, error) {
func (p VirtualApp) CreateChildVM(ctx context.Context, config types.VirtualMachineConfigSpec, host *HostSystem) (*Task, error) {
req := types.CreateChildVM_Task{
This: p.Reference(),
Config: config,
@@ -53,7 +53,7 @@ func (p VirtualApp) CreateChildVM_Task(ctx context.Context, config types.Virtual
return NewTask(p.c, res.Returnval), nil
}
func (p VirtualApp) UpdateVAppConfig(ctx context.Context, spec types.VAppConfigSpec) error {
func (p VirtualApp) UpdateConfig(ctx context.Context, spec types.VAppConfigSpec) error {
req := types.UpdateVAppConfig{
This: p.Reference(),
Spec: spec,
@@ -63,7 +63,7 @@ func (p VirtualApp) UpdateVAppConfig(ctx context.Context, spec types.VAppConfigS
return err
}
func (p VirtualApp) PowerOnVApp_Task(ctx context.Context) (*Task, error) {
func (p VirtualApp) PowerOn(ctx context.Context) (*Task, error) {
req := types.PowerOnVApp_Task{
This: p.Reference(),
}
@@ -76,7 +76,7 @@ func (p VirtualApp) PowerOnVApp_Task(ctx context.Context) (*Task, error) {
return NewTask(p.c, res.Returnval), nil
}
func (p VirtualApp) PowerOffVApp_Task(ctx context.Context, force bool) (*Task, error) {
func (p VirtualApp) PowerOff(ctx context.Context, force bool) (*Task, error) {
req := types.PowerOffVApp_Task{
This: p.Reference(),
Force: force,
@@ -91,7 +91,7 @@ func (p VirtualApp) PowerOffVApp_Task(ctx context.Context, force bool) (*Task, e
}
func (p VirtualApp) SuspendVApp_Task(ctx context.Context) (*Task, error) {
func (p VirtualApp) Suspend(ctx context.Context) (*Task, error) {
req := types.SuspendVApp_Task{
This: p.Reference(),
}

View File

@@ -84,6 +84,9 @@ func (l VirtualDeviceList) Select(f func(device types.BaseVirtualDevice) bool) V
// SelectByType returns a new list with devices that are equal to or extend the given type.
func (l VirtualDeviceList) SelectByType(deviceType types.BaseVirtualDevice) VirtualDeviceList {
dtype := reflect.TypeOf(deviceType)
if dtype == nil {
return nil
}
dname := dtype.Elem().Name()
return l.Select(func(device types.BaseVirtualDevice) bool {
@@ -242,6 +245,7 @@ func (l VirtualDeviceList) CreateSCSIController(name string) (types.BaseVirtualD
scsi := c.GetVirtualSCSIController()
scsi.BusNumber = l.newSCSIBusNumber()
scsi.Key = l.NewKey()
scsi.ScsiCtlrUnitNumber = 7
return c.(types.BaseVirtualDevice), nil
}
@@ -270,6 +274,63 @@ func (l VirtualDeviceList) newSCSIBusNumber() int32 {
return -1
}
// FindNVMEController will find the named NVME controller if given, otherwise will pick an available controller.
// An error is returned if the named controller is not found or not an NVME controller. Or, if name is not
// given and no available controller can be found.
func (l VirtualDeviceList) FindNVMEController(name string) (*types.VirtualNVMEController, error) {
if name != "" {
d := l.Find(name)
if d == nil {
return nil, fmt.Errorf("device '%s' not found", name)
}
if c, ok := d.(*types.VirtualNVMEController); ok {
return c, nil
}
return nil, fmt.Errorf("%s is not an NVME controller", name)
}
c := l.PickController((*types.VirtualNVMEController)(nil))
if c == nil {
return nil, errors.New("no available NVME controller")
}
return c.(*types.VirtualNVMEController), nil
}
// CreateNVMEController creates a new NVMWE controller.
func (l VirtualDeviceList) CreateNVMEController() (types.BaseVirtualDevice, error) {
nvme := &types.VirtualNVMEController{}
nvme.BusNumber = l.newNVMEBusNumber()
nvme.Key = l.NewKey()
return nvme, nil
}
var nvmeBusNumbers = []int{0, 1, 2, 3}
// newNVMEBusNumber returns the bus number to use for adding a new NVME bus device.
// -1 is returned if there are no bus numbers available.
func (l VirtualDeviceList) newNVMEBusNumber() int32 {
var used []int
for _, d := range l.SelectByType((*types.VirtualNVMEController)(nil)) {
num := d.(types.BaseVirtualController).GetVirtualController().BusNumber
if num >= 0 {
used = append(used, int(num))
} // else caller is creating a new vm using NVMEControllerTypes
}
sort.Ints(used)
for i, n := range nvmeBusNumbers {
if i == len(used) || n != used[i] {
return int32(n)
}
}
return -1
}
// FindDiskController will find an existing ide or scsi disk controller.
func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualController, error) {
switch {
@@ -277,6 +338,8 @@ func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualCon
return l.FindIDEController("")
case name == "scsi" || name == "":
return l.FindSCSIController("")
case name == "nvme":
return l.FindNVMEController("")
default:
if c, ok := l.Find(name).(types.BaseVirtualController); ok {
return c, nil
@@ -296,6 +359,8 @@ func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) type
return num < 15
case *types.VirtualIDEController:
return num < 2
case *types.VirtualNVMEController:
return num < 8
default:
return true
}
@@ -310,20 +375,31 @@ func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) type
// newUnitNumber returns the unit number to use for attaching a new device to the given controller.
func (l VirtualDeviceList) newUnitNumber(c types.BaseVirtualController) int32 {
units := make([]bool, 30)
switch sc := c.(type) {
case types.BaseVirtualSCSIController:
// The SCSI controller sits on its own bus
units[sc.GetVirtualSCSIController().ScsiCtlrUnitNumber] = true
}
key := c.GetVirtualController().Key
var max int32 = -1
for _, device := range l {
d := device.GetVirtualDevice()
if d.ControllerKey == key {
if d.UnitNumber != nil && *d.UnitNumber > max {
max = *d.UnitNumber
}
if d.ControllerKey == key && d.UnitNumber != nil {
units[int(*d.UnitNumber)] = true
}
}
return max + 1
for unit, used := range units {
if !used {
return int32(unit)
}
}
return -1
}
// NewKey returns the key to use for adding a new device to the device list.
@@ -384,12 +460,14 @@ func (l VirtualDeviceList) CreateDisk(c types.BaseVirtualController, ds types.Ma
func (l VirtualDeviceList) ChildDisk(parent *types.VirtualDisk) *types.VirtualDisk {
disk := *parent
backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
ds := strings.SplitN(backing.FileName[1:], "]", 2)
p := new(DatastorePath)
p.FromString(backing.FileName)
p.Path = ""
// Use specified disk as parent backing to a new disk.
disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: fmt.Sprintf("[%s]", ds[0]),
FileName: p.String(),
Datastore: backing.Datastore,
},
Parent: backing,
@@ -595,7 +673,17 @@ func (l VirtualDeviceList) CreateSerialPort() (*types.VirtualSerialPort, error)
}
// ConnectSerialPort connects a serial port to a server or client uri.
func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, uri string, client bool) *types.VirtualSerialPort {
func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, uri string, client bool, proxyuri string) *types.VirtualSerialPort {
if strings.HasPrefix(uri, "[") {
device.Backing = &types.VirtualSerialPortFileBackingInfo{
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: uri,
},
}
return device
}
direction := types.VirtualDeviceURIBackingOptionDirectionServer
if client {
direction = types.VirtualDeviceURIBackingOptionDirectionClient
@@ -605,6 +693,7 @@ func (l VirtualDeviceList) ConnectSerialPort(device *types.VirtualSerialPort, ur
VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
Direction: string(direction),
ServiceURI: uri,
ProxyURI: proxyuri,
},
}
@@ -728,7 +817,11 @@ func (l VirtualDeviceList) SelectBootOrder(order []types.BaseVirtualMachineBootO
// TypeName returns the vmodl type name of the device
func (l VirtualDeviceList) TypeName(device types.BaseVirtualDevice) string {
return reflect.TypeOf(device).Elem().Name()
dtype := reflect.TypeOf(device)
if dtype == nil {
return ""
}
return dtype.Elem().Name()
}
var deviceNameRegexp = regexp.MustCompile(`(?:Virtual)?(?:Machine)?(\w+?)(?:Card|Device|Controller)?$`)
@@ -754,6 +847,8 @@ func (l VirtualDeviceList) Type(device types.BaseVirtualDevice) string {
return "pvscsi"
case *types.VirtualLsiLogicSASController:
return "lsilogic-sas"
case *types.VirtualNVMEController:
return "nvme"
default:
return l.deviceName(device)
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package object
import (
"context"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type VirtualDiskManager struct {

View File

@@ -17,15 +17,17 @@ limitations under the License.
package object
import (
"context"
"errors"
"fmt"
"net"
"path"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
const (
@@ -227,9 +229,12 @@ func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
// WaitForNetIP waits for the VM guest.net property to report an IP address for all VM NICs.
// Only consider IPv4 addresses if the v4 param is true.
// By default, wait for all NICs to get an IP address, unless 1 or more device is given.
// A device can be specified by the MAC address or the device name, e.g. "ethernet-0".
// Returns a map with MAC address as the key and IP address list as the value.
func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][]string, error) {
func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool, device ...string) (map[string][]string, error) {
macs := make(map[string][]string)
eths := make(map[string]string)
p := property.DefaultCollector(v.c)
@@ -240,14 +245,15 @@ func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][
continue
}
devices := c.Val.(types.ArrayOfVirtualDevice).VirtualDevice
for _, device := range devices {
if nic, ok := device.(types.BaseVirtualEthernetCard); ok {
devices := VirtualDeviceList(c.Val.(types.ArrayOfVirtualDevice).VirtualDevice)
for _, d := range devices {
if nic, ok := d.(types.BaseVirtualEthernetCard); ok {
mac := nic.GetVirtualEthernetCard().MacAddress
if mac == "" {
return false
}
macs[mac] = nil
eths[devices.Name(d)] = mac
}
}
}
@@ -255,6 +261,17 @@ func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][
return true
})
if len(device) != 0 {
// Only wait for specific NIC(s)
macs = make(map[string][]string)
for _, mac := range device {
if eth, ok := eths[mac]; ok {
mac = eth // device name, e.g. "ethernet-0"
}
macs[mac] = nil
}
}
err = property.Wait(ctx, p, v.Reference(), []string{"guest.net"}, func(pc []types.PropertyChange) bool {
for _, c := range pc {
if c.Op != types.PropertyChangeOpAssign {
@@ -300,18 +317,28 @@ func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][
func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"config.hardware.device"}, &o)
err := v.Properties(ctx, v.Reference(), []string{"config.hardware.device", "summary.runtime.connectionState"}, &o)
if err != nil {
return nil, err
}
// Quoting the SDK doc:
// The virtual machine configuration is not guaranteed to be available.
// For example, the configuration information would be unavailable if the server
// is unable to access the virtual machine files on disk, and is often also unavailable
// during the initial phases of virtual machine creation.
if o.Config == nil {
return nil, fmt.Errorf("%s Config is not available, connectionState=%s",
v.Reference(), o.Summary.Runtime.ConnectionState)
}
return VirtualDeviceList(o.Config.Hardware.Device), nil
}
func (v VirtualMachine) HostSystem(ctx context.Context) (*HostSystem, error) {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"summary"}, &o)
err := v.Properties(ctx, v.Reference(), []string{"summary.runtime.host"}, &o)
if err != nil {
return nil, err
}
@@ -470,24 +497,102 @@ func (v VirtualMachine) RemoveAllSnapshot(ctx context.Context, consolidate *bool
return NewTask(v.c, res.Returnval), nil
}
// RevertToSnapshot reverts to a named snapshot
func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) {
type snapshotMap map[string][]Reference
func (m snapshotMap) add(parent string, tree []types.VirtualMachineSnapshotTree) {
for i, st := range tree {
sname := st.Name
names := []string{sname, st.Snapshot.Value}
if parent != "" {
sname = path.Join(parent, sname)
// Add full path as an option to resolve duplicate names
names = append(names, sname)
}
for _, name := range names {
m[name] = append(m[name], &tree[i].Snapshot)
}
m.add(sname, st.ChildSnapshotList)
}
}
// findSnapshot supports snapshot lookup by name, where name can be:
// 1) snapshot ManagedObjectReference.Value (unique)
// 2) snapshot name (may not be unique)
// 3) snapshot tree path (may not be unique)
func (v VirtualMachine) findSnapshot(ctx context.Context, name string) (Reference, error) {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o)
if err != nil {
return nil, err
}
snapshotTree := o.Snapshot.RootSnapshotList
if len(snapshotTree) < 1 {
if o.Snapshot == nil || len(o.Snapshot.RootSnapshotList) == 0 {
return nil, errors.New("No snapshots for this VM")
}
snapshot, err := traverseSnapshotInTree(snapshotTree, name)
m := make(snapshotMap)
m.add("", o.Snapshot.RootSnapshotList)
s := m[name]
switch len(s) {
case 0:
return nil, fmt.Errorf("snapshot %q not found", name)
case 1:
return s[0], nil
default:
return nil, fmt.Errorf("%q resolves to %d snapshots", name, len(s))
}
}
// RemoveSnapshot removes a named snapshot
func (v VirtualMachine) RemoveSnapshot(ctx context.Context, name string, removeChildren bool, consolidate *bool) (*Task, error) {
snapshot, err := v.findSnapshot(ctx, name)
if err != nil {
return nil, err
}
req := types.RemoveSnapshot_Task{
This: snapshot.Reference(),
RemoveChildren: removeChildren,
Consolidate: consolidate,
}
res, err := methods.RemoveSnapshot_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// RevertToCurrentSnapshot reverts to the current snapshot
func (v VirtualMachine) RevertToCurrentSnapshot(ctx context.Context, suppressPowerOn bool) (*Task, error) {
req := types.RevertToCurrentSnapshot_Task{
This: v.Reference(),
SuppressPowerOn: types.NewBool(suppressPowerOn),
}
res, err := methods.RevertToCurrentSnapshot_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// RevertToSnapshot reverts to a named snapshot
func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) {
snapshot, err := v.findSnapshot(ctx, name)
if err != nil {
return nil, err
}
req := types.RevertToSnapshot_Task{
This: snapshot,
This: snapshot.Reference(),
SuppressPowerOn: types.NewBool(suppressPowerOn),
}
@@ -499,32 +604,6 @@ func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppr
return NewTask(v.c, res.Returnval), nil
}
// traverseSnapshotInTree is a recursive function that will traverse a snapshot tree to find a given snapshot
func traverseSnapshotInTree(tree []types.VirtualMachineSnapshotTree, name string) (types.ManagedObjectReference, error) {
var o types.ManagedObjectReference
if tree == nil {
return o, errors.New("Snapshot tree is empty")
}
for _, s := range tree {
if s.Name == name {
o = s.Snapshot
break
} else {
childTree := s.ChildSnapshotList
var err error
o, err = traverseSnapshotInTree(childTree, name)
if err != nil {
return o, err
}
}
}
if o.Value == "" {
return o, errors.New("Snapshot not found")
}
return o, nil
}
// IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise.
func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) {
var o mo.VirtualMachine
@@ -591,3 +670,58 @@ func (v VirtualMachine) MarkAsVirtualMachine(ctx context.Context, pool ResourceP
return nil
}
func (v VirtualMachine) Migrate(ctx context.Context, pool *ResourcePool, host *HostSystem, priority types.VirtualMachineMovePriority, state types.VirtualMachinePowerState) (*Task, error) {
req := types.MigrateVM_Task{
This: v.Reference(),
Priority: priority,
State: state,
}
if pool != nil {
ref := pool.Reference()
req.Pool = &ref
}
if host != nil {
ref := host.Reference()
req.Host = &ref
}
res, err := methods.MigrateVM_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
func (v VirtualMachine) Unregister(ctx context.Context) error {
req := types.UnregisterVM{
This: v.Reference(),
}
_, err := methods.UnregisterVM(ctx, v.Client(), &req)
return err
}
// QueryEnvironmentBrowser is a helper to get the environmentBrowser property.
func (v VirtualMachine) QueryConfigTarget(ctx context.Context) (*types.ConfigTarget, error) {
var vm mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"environmentBrowser"}, &vm)
if err != nil {
return nil, err
}
req := types.QueryConfigTarget{
This: vm.EnvironmentBrowser,
}
res, err := methods.QueryConfigTarget(ctx, v.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}