mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			435 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright (c) 2015-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"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"math/rand"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/vmware/govmomi/property"
 | 
						|
	"github.com/vmware/govmomi/session"
 | 
						|
	"github.com/vmware/govmomi/vim25"
 | 
						|
	"github.com/vmware/govmomi/vim25/mo"
 | 
						|
	"github.com/vmware/govmomi/vim25/soap"
 | 
						|
	"github.com/vmware/govmomi/vim25/types"
 | 
						|
)
 | 
						|
 | 
						|
// DatastoreNoSuchDirectoryError is returned when a directory could not be found.
 | 
						|
type DatastoreNoSuchDirectoryError struct {
 | 
						|
	verb    string
 | 
						|
	subject string
 | 
						|
}
 | 
						|
 | 
						|
func (e DatastoreNoSuchDirectoryError) Error() string {
 | 
						|
	return fmt.Sprintf("cannot %s '%s': No such directory", e.verb, e.subject)
 | 
						|
}
 | 
						|
 | 
						|
// DatastoreNoSuchFileError is returned when a file could not be found.
 | 
						|
type DatastoreNoSuchFileError struct {
 | 
						|
	verb    string
 | 
						|
	subject string
 | 
						|
}
 | 
						|
 | 
						|
func (e DatastoreNoSuchFileError) Error() string {
 | 
						|
	return fmt.Sprintf("cannot %s '%s': No such file", e.verb, e.subject)
 | 
						|
}
 | 
						|
 | 
						|
type Datastore struct {
 | 
						|
	Common
 | 
						|
 | 
						|
	DatacenterPath string
 | 
						|
}
 | 
						|
 | 
						|
func NewDatastore(c *vim25.Client, ref types.ManagedObjectReference) *Datastore {
 | 
						|
	return &Datastore{
 | 
						|
		Common: NewCommon(c, ref),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d Datastore) Path(path string) string {
 | 
						|
	return (&DatastorePath{
 | 
						|
		Datastore: d.Name(),
 | 
						|
		Path:      path,
 | 
						|
	}).String()
 | 
						|
}
 | 
						|
 | 
						|
// 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{
 | 
						|
		Scheme: u.Scheme,
 | 
						|
		Host:   u.Host,
 | 
						|
		Path:   fmt.Sprintf("/folder/%s", path),
 | 
						|
		RawQuery: url.Values{
 | 
						|
			"dcPath": []string{d.DatacenterPath},
 | 
						|
			"dsName": []string{d.Name()},
 | 
						|
		}.Encode(),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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) {
 | 
						|
	var do mo.Datastore
 | 
						|
 | 
						|
	err := d.Properties(ctx, d.Reference(), []string{"browser"}, &do)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	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() {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	// If version happens to be < 5.1
 | 
						|
	if name == "" {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	// If the HostSystem is using DHCP on a network without dynamic DNS,
 | 
						|
	// HostSystem.Config.Network.DnsConfig.HostName is set to "localhost" by default.
 | 
						|
	// This resolves to "localhost.localdomain" by default via /etc/hosts on ESX.
 | 
						|
	// In that case, we will stick with the HostSystem.Name which is the IP address that
 | 
						|
	// was used to connect the host to VC.
 | 
						|
	if name == "localhost.localdomain" {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	// Still possible to have HostName that don't resolve via DNS,
 | 
						|
	// so we default to false.
 | 
						|
	key := "GOVMOMI_USE_SERVICE_TICKET_HOSTNAME"
 | 
						|
 | 
						|
	val := d.c.URL().Query().Get(key)
 | 
						|
	if val == "" {
 | 
						|
		val = os.Getenv(key)
 | 
						|
	}
 | 
						|
 | 
						|
	if val == "1" || val == "true" {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
		}
 | 
						|
 | 
						|
		hosts, err := d.AttachedHosts(ctx)
 | 
						|
		if err != nil {
 | 
						|
			return nil, nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		if len(hosts) == 0 {
 | 
						|
			// Fallback to letting vCenter choose a host
 | 
						|
			return u, nil, nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Pick a random attached host
 | 
						|
		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
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
		Method: fmt.Sprintf("http%s%s", method[0:1], strings.ToLower(method[1:])),
 | 
						|
	}
 | 
						|
 | 
						|
	sm := session.NewManager(d.Client())
 | 
						|
 | 
						|
	ticket, err := sm.AcquireGenericServiceTicket(ctx, &spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	cookie := &http.Cookie{
 | 
						|
		Name:  "vmware_cgi_ticket",
 | 
						|
		Value: ticket.Id,
 | 
						|
	}
 | 
						|
 | 
						|
	if d.useServiceTicketHostName(ticket.HostName) {
 | 
						|
		u.Host = ticket.HostName
 | 
						|
	}
 | 
						|
 | 
						|
	d.Client().SetThumbprint(u.Host, ticket.SslThumbprint)
 | 
						|
 | 
						|
	return u, cookie, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d Datastore) uploadTicket(ctx context.Context, path string, param *soap.Upload) (*url.URL, *soap.Upload, error) {
 | 
						|
	p := soap.DefaultUpload
 | 
						|
	if param != nil {
 | 
						|
		p = *param // copy
 | 
						|
	}
 | 
						|
 | 
						|
	u, ticket, err := d.ServiceTicket(ctx, path, p.Method)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	p.Ticket = ticket
 | 
						|
 | 
						|
	return u, &p, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d Datastore) downloadTicket(ctx context.Context, path string, param *soap.Download) (*url.URL, *soap.Download, error) {
 | 
						|
	p := soap.DefaultDownload
 | 
						|
	if param != nil {
 | 
						|
		p = *param // copy
 | 
						|
	}
 | 
						|
 | 
						|
	u, ticket, err := d.ServiceTicket(ctx, path, p.Method)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	p.Ticket = ticket
 | 
						|
 | 
						|
	return u, &p, nil
 | 
						|
}
 | 
						|
 | 
						|
// Upload via soap.Upload with an http service ticket
 | 
						|
func (d Datastore) Upload(ctx context.Context, f io.Reader, path string, param *soap.Upload) error {
 | 
						|
	u, p, err := d.uploadTicket(ctx, path, param)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return d.Client().Upload(ctx, f, u, p)
 | 
						|
}
 | 
						|
 | 
						|
// UploadFile via soap.Upload with an http service ticket
 | 
						|
func (d Datastore) UploadFile(ctx context.Context, file string, path string, param *soap.Upload) error {
 | 
						|
	u, p, err := d.uploadTicket(ctx, path, param)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return d.Client().UploadFile(ctx, file, u, p)
 | 
						|
}
 | 
						|
 | 
						|
// Download via soap.Download with an http service ticket
 | 
						|
func (d Datastore) Download(ctx context.Context, path string, param *soap.Download) (io.ReadCloser, int64, error) {
 | 
						|
	u, p, err := d.downloadTicket(ctx, path, param)
 | 
						|
	if err != nil {
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
	return d.Client().Download(ctx, u, p)
 | 
						|
}
 | 
						|
 | 
						|
// DownloadFile via soap.Download with an http service ticket
 | 
						|
func (d Datastore) DownloadFile(ctx context.Context, path string, file string, param *soap.Download) error {
 | 
						|
	u, p, err := d.downloadTicket(ctx, path, param)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return d.Client().DownloadFile(ctx, file, u, p)
 | 
						|
}
 | 
						|
 | 
						|
// AttachedHosts returns hosts that have this Datastore attached, accessible and writable.
 | 
						|
func (d Datastore) AttachedHosts(ctx context.Context) ([]*HostSystem, error) {
 | 
						|
	var ds mo.Datastore
 | 
						|
	var hosts []*HostSystem
 | 
						|
 | 
						|
	pc := property.DefaultCollector(d.Client())
 | 
						|
	err := pc.RetrieveOne(ctx, d.Reference(), []string{"host"}, &ds)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	mounts := make(map[types.ManagedObjectReference]types.DatastoreHostMount)
 | 
						|
	var refs []types.ManagedObjectReference
 | 
						|
	for _, host := range ds.Host {
 | 
						|
		refs = append(refs, host.Key)
 | 
						|
		mounts[host.Key] = host
 | 
						|
	}
 | 
						|
 | 
						|
	var hs []mo.HostSystem
 | 
						|
	err = pc.Retrieve(ctx, refs, []string{"runtime.connectionState", "runtime.powerState"}, &hs)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	for _, host := range hs {
 | 
						|
		if host.Runtime.ConnectionState == types.HostSystemConnectionStateConnected &&
 | 
						|
			host.Runtime.PowerState == types.HostSystemPowerStatePoweredOn {
 | 
						|
 | 
						|
			mount := mounts[host.Reference()]
 | 
						|
			info := mount.MountInfo
 | 
						|
 | 
						|
			if *info.Mounted && *info.Accessible && info.AccessMode == string(types.HostMountModeReadWrite) {
 | 
						|
				hosts = append(hosts, NewHostSystem(d.Client(), mount.Key))
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return hosts, nil
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
 | 
						|
	clusterHosts, err := cluster.Hosts(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	attachedHosts, err := d.AttachedHosts(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	refs := make(map[types.ManagedObjectReference]bool)
 | 
						|
	for _, host := range attachedHosts {
 | 
						|
		refs[host.Reference()] = true
 | 
						|
	}
 | 
						|
 | 
						|
	for _, host := range clusterHosts {
 | 
						|
		if refs[host.Reference()] {
 | 
						|
			hosts = append(hosts, host)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return hosts, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, error) {
 | 
						|
	b, err := d.Browser(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	spec := types.HostDatastoreBrowserSearchSpec{
 | 
						|
		Details: &types.FileQueryFlags{
 | 
						|
			FileType:     true,
 | 
						|
			FileSize:     true,
 | 
						|
			Modification: true,
 | 
						|
			FileOwner:    types.NewBool(true),
 | 
						|
		},
 | 
						|
		MatchPattern: []string{path.Base(file)},
 | 
						|
	}
 | 
						|
 | 
						|
	dsPath := d.Path(path.Dir(file))
 | 
						|
	task, err := b.SearchDatastore(ctx, dsPath, &spec)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	info, err := task.WaitForResult(ctx, nil)
 | 
						|
	if err != nil {
 | 
						|
		if types.IsFileNotFound(err) {
 | 
						|
			// FileNotFound means the base path doesn't exist.
 | 
						|
			return nil, DatastoreNoSuchDirectoryError{"stat", dsPath}
 | 
						|
		}
 | 
						|
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	res := info.Result.(types.HostDatastoreBrowserSearchResults)
 | 
						|
	if len(res.File) == 0 {
 | 
						|
		// File doesn't exist
 | 
						|
		return nil, DatastoreNoSuchFileError{"stat", d.Path(file)}
 | 
						|
	}
 | 
						|
 | 
						|
	return res.File[0], nil
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// Type returns the type of file system volume.
 | 
						|
func (d Datastore) Type(ctx context.Context) (types.HostFileSystemVolumeFileSystemType, error) {
 | 
						|
	var mds mo.Datastore
 | 
						|
 | 
						|
	if err := d.Properties(ctx, d.Reference(), []string{"summary.type"}, &mds); err != nil {
 | 
						|
		return types.HostFileSystemVolumeFileSystemType(""), err
 | 
						|
	}
 | 
						|
	return types.HostFileSystemVolumeFileSystemType(mds.Summary.Type), nil
 | 
						|
}
 |