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,25 +17,31 @@ limitations under the License.
package soap
import (
"bufio"
"bytes"
"context"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"github.com/vmware/govmomi/vim25/progress"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vim25/xml"
"golang.org/x/net/context"
)
type HasFault interface {
@@ -46,8 +52,11 @@ type RoundTripper interface {
RoundTrip(ctx context.Context, req, res HasFault) error
}
var DefaultVimNamespace = "urn:vim25"
var DefaultVimVersion = "6.0"
const (
DefaultVimNamespace = "urn:vim25"
DefaultVimVersion = "6.5"
DefaultMinVimVersion = "5.5"
)
type Client struct {
http.Client
@@ -58,8 +67,12 @@ type Client struct {
t *http.Transport
p *url.URL
hostsMu sync.Mutex
hosts map[string]string
Namespace string // Vim namespace
Version string // Vim version
UserAgent string
}
var schemeMatch = regexp.MustCompile(`^\w+://`)
@@ -101,17 +114,24 @@ func NewClient(u *url.URL, insecure bool) *Client {
}
// Initialize http.RoundTripper on client, so we can customize it below
c.t = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
if t, ok := http.DefaultTransport.(*http.Transport); ok {
c.t = &http.Transport{
Proxy: t.Proxy,
DialContext: t.DialContext,
MaxIdleConns: t.MaxIdleConns,
IdleConnTimeout: t.IdleConnTimeout,
TLSHandshakeTimeout: t.TLSHandshakeTimeout,
ExpectContinueTimeout: t.ExpectContinueTimeout,
}
} else {
c.t = new(http.Transport)
}
if c.u.Scheme == "https" {
c.t.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.k}
c.t.TLSHandshakeTimeout = 10 * time.Second
c.hosts = make(map[string]string)
c.t.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.k}
// Don't bother setting DialTLS if InsecureSkipVerify=true
if !c.k {
c.t.DialTLS = c.dialTLS
}
c.Client.Transport = c.t
@@ -127,6 +147,156 @@ func NewClient(u *url.URL, insecure bool) *Client {
return &c
}
// SetRootCAs defines the set of root certificate authorities
// that clients use when verifying server certificates.
// By default TLS uses the host's root CA set.
//
// See: http.Client.Transport.TLSClientConfig.RootCAs
func (c *Client) SetRootCAs(file string) error {
pool := x509.NewCertPool()
for _, name := range filepath.SplitList(file) {
pem, err := ioutil.ReadFile(name)
if err != nil {
return err
}
pool.AppendCertsFromPEM(pem)
}
c.t.TLSClientConfig.RootCAs = pool
return nil
}
// Add default https port if missing
func hostAddr(addr string) string {
_, port := splitHostPort(addr)
if port == "" {
return addr + ":443"
}
return addr
}
// SetThumbprint sets the known certificate thumbprint for the given host.
// A custom DialTLS function is used to support thumbprint based verification.
// We first try tls.Dial with the default tls.Config, only falling back to thumbprint verification
// if it fails with an x509.UnknownAuthorityError or x509.HostnameError
//
// See: http.Client.Transport.DialTLS
func (c *Client) SetThumbprint(host string, thumbprint string) {
host = hostAddr(host)
c.hostsMu.Lock()
if thumbprint == "" {
delete(c.hosts, host)
} else {
c.hosts[host] = thumbprint
}
c.hostsMu.Unlock()
}
// Thumbprint returns the certificate thumbprint for the given host if known to this client.
func (c *Client) Thumbprint(host string) string {
host = hostAddr(host)
c.hostsMu.Lock()
defer c.hostsMu.Unlock()
return c.hosts[host]
}
// LoadThumbprints from file with the give name.
// If name is empty or name does not exist this function will return nil.
func (c *Client) LoadThumbprints(file string) error {
if file == "" {
return nil
}
for _, name := range filepath.SplitList(file) {
err := c.loadThumbprints(name)
if err != nil {
return err
}
}
return nil
}
func (c *Client) loadThumbprints(name string) error {
f, err := os.Open(name)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
e := strings.SplitN(scanner.Text(), " ", 2)
if len(e) != 2 {
continue
}
c.SetThumbprint(e[0], e[1])
}
_ = f.Close()
return scanner.Err()
}
// ThumbprintSHA1 returns the thumbprint of the given cert in the same format used by the SDK and Client.SetThumbprint.
//
// See: SSLVerifyFault.Thumbprint, SessionManagerGenericServiceTicket.Thumbprint, HostConnectSpec.SslThumbprint
func ThumbprintSHA1(cert *x509.Certificate) string {
sum := sha1.Sum(cert.Raw)
hex := make([]string, len(sum))
for i, b := range sum {
hex[i] = fmt.Sprintf("%02X", b)
}
return strings.Join(hex, ":")
}
func (c *Client) dialTLS(network string, addr string) (net.Conn, error) {
// Would be nice if there was a tls.Config.Verify func,
// see tls.clientHandshakeState.doFullHandshake
conn, err := tls.Dial(network, addr, c.t.TLSClientConfig)
if err == nil {
return conn, nil
}
switch err.(type) {
case x509.UnknownAuthorityError:
case x509.HostnameError:
default:
return nil, err
}
thumbprint := c.Thumbprint(addr)
if thumbprint == "" {
return nil, err
}
config := &tls.Config{InsecureSkipVerify: true}
conn, err = tls.Dial(network, addr, config)
if err != nil {
return nil, err
}
cert := conn.ConnectionState().PeerCertificates[0]
peer := ThumbprintSHA1(cert)
if thumbprint != peer {
_ = conn.Close()
return nil, fmt.Errorf("Host %q thumbprint does not match %q", addr, thumbprint)
}
return conn, nil
}
// splitHostPort is similar to net.SplitHostPort,
// but rather than return error if there isn't a ':port',
// return an empty string for the port.
@@ -155,7 +325,13 @@ func (c *Client) SetCertificate(cert tls.Certificate) {
host, _ := splitHostPort(c.u.Host)
// Should be no reason to change the default port other than testing
port := os.Getenv("GOVC_TUNNEL_PROXY_PORT")
key := "GOVMOMI_TUNNEL_PROXY_PORT"
port := c.URL().Query().Get(key)
if port == "" {
port = os.Getenv(key)
}
if port != "" {
host += ":" + port
}
@@ -212,33 +388,11 @@ func (c *Client) UnmarshalJSON(b []byte) error {
}
func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) {
if nil == ctx || nil == ctx.Done() { // ctx.Done() is for context.TODO()
if nil == ctx || nil == ctx.Done() { // ctx.Done() is for ctx
return c.Client.Do(req)
}
var resc = make(chan *http.Response, 1)
var errc = make(chan error, 1)
// Perform request from separate routine.
go func() {
res, err := c.Client.Do(req)
if err != nil {
errc <- err
} else {
resc <- res
}
}()
// Wait for request completion of context expiry.
select {
case <-ctx.Done():
c.t.CancelRequest(req)
return nil, ctx.Err()
case err := <-errc:
return nil, err
case res := <-resc:
return res, nil
}
return c.Client.Do(req.WithContext(ctx))
}
func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error {
@@ -267,6 +421,9 @@ func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error
req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`)
soapAction := fmt.Sprintf("%s/%s", c.Namespace, c.Version)
req.Header.Set(`SOAPAction`, soapAction)
if c.UserAgent != "" {
req.Header.Set(`User-Agent`, c.UserAgent)
}
if d.enabled() {
d.debugRequest(req)
@@ -420,6 +577,7 @@ func (c *Client) UploadFile(file string, u *url.URL, param *Upload) error {
type Download struct {
Method string
Headers map[string]string
Ticket *http.Cookie
Progress progress.Sinker
}
@@ -428,19 +586,27 @@ var DefaultDownload = Download{
Method: "GET",
}
// Download GETs the remote file from the given URL
func (c *Client) Download(u *url.URL, param *Download) (io.ReadCloser, int64, error) {
// DownloadRequest wraps http.Client.Do, returning the http.Response without checking its StatusCode
func (c *Client) DownloadRequest(u *url.URL, param *Download) (*http.Response, error) {
req, err := http.NewRequest(param.Method, u.String(), nil)
if err != nil {
return nil, 0, err
return nil, err
}
for k, v := range param.Headers {
req.Header.Add(k, v)
}
if param.Ticket != nil {
req.AddCookie(param.Ticket)
}
res, err := c.Client.Do(req)
return c.Client.Do(req)
}
// Download GETs the remote file from the given URL
func (c *Client) Download(u *url.URL, param *Download) (io.ReadCloser, int64, error) {
res, err := c.DownloadRequest(u, param)
if err != nil {
return nil, 0, err
}
@@ -455,9 +621,7 @@ func (c *Client) Download(u *url.URL, param *Download) (io.ReadCloser, int64, er
return nil, 0, err
}
var r io.ReadCloser = res.Body
return r, res.ContentLength, nil
return res.Body, res.ContentLength, nil
}
// DownloadFile GETs the given URL to a local file