VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

View File

@@ -0,0 +1,359 @@
// Copyright 2016-2017 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 certificate
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"time"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
// Default certificate file names
const (
ClientCert = "cert.pem"
ClientKey = "key.pem"
ServerCert = "server-cert.pem"
ServerKey = "server-key.pem"
CACert = "ca.pem"
CAKey = "ca-key.pem"
)
func hashPublicKey(key *rsa.PublicKey) ([]byte, error) {
b, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
return nil, fmt.Errorf("Unable to hash key: %s", err)
}
h := sha1.New()
h.Write(b)
return h.Sum(nil), nil
}
func template(org []string) *x509.Certificate {
now := time.Now().UTC()
// help address issues with clock drift
notBefore := now.AddDate(0, 0, -1)
notAfter := now.AddDate(1, 0, 0) // 1 year
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
err = errors.Errorf("Failed to generate random number: %s", err)
return nil
}
// ensure that org is set to something
if len(org) == 0 {
org = []string{"default"}
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: org,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement,
BasicConstraintsValid: true,
}
return &template
}
func templateWithKey(template *x509.Certificate, size int) (*x509.Certificate, *rsa.PrivateKey, error) {
priv, err := rsa.GenerateKey(rand.Reader, size)
if err != nil {
return nil, nil, err
}
keyID, err := hashPublicKey(&priv.PublicKey)
if err != nil {
return nil, nil, err
}
template.SubjectKeyId = keyID
template.PublicKey = priv.Public()
return template, priv, nil
}
func templateWithCA(template *x509.Certificate) *x509.Certificate {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
template.KeyUsage |= x509.KeyUsageKeyEncipherment
template.KeyUsage |= x509.KeyUsageKeyAgreement
template.ExtKeyUsage = nil
return template
}
// templateAsClientOnly restricts the capabilities of the certificate to be only used for client auth
func templateAsClientOnly(template *x509.Certificate) *x509.Certificate {
template.KeyUsage = x509.KeyUsageDigitalSignature
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
return template
}
// templateWithServer adds the capabilities of the certificate to be only used for server auth
func templateWithServer(template *x509.Certificate, domain string) *x509.Certificate {
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
template.Subject.CommonName = domain
// abide by the spec - if CN is an IP, put it in the subjectAltName as well
ip := net.ParseIP(domain)
if ip == nil {
// see if CIDR works
// #nosec: Errors unhandled.
ip, _, _ = net.ParseCIDR(domain)
}
if ip != nil {
// use the normalized address
template.Subject.CommonName = ip.String()
template.IPAddresses = []net.IP{ip}
// try best guess at DNSNames entries
names, err := net.LookupAddr(domain)
if err == nil && len(names) > 0 {
template.DNSNames = names
}
return template
}
if domain != "" {
template.DNSNames = []string{domain}
// try best guess at IPAddresses entries
ips, err := net.LookupIP(domain)
if err == nil && len(ips) > 0 {
template.IPAddresses = ips
}
}
return template
}
// createCertificate creates a certificate from the supplied template:
// template: an x509 template describing the certificate to generate.
// parent: either a CA certificate, or template (for self-signed). If nil, will use template.
// templateKey: the private key for the certificate supplied as template
// parentKey: the private key for the certificate supplied as parent (whether CA or self-signed). If nil will use templateKey
//
// return PEM encoded certificate and key
func createCertificate(template, parent *x509.Certificate, templateKey, parentKey *rsa.PrivateKey) (cert bytes.Buffer, key bytes.Buffer, err error) {
defer trace.End(trace.Begin(""))
if parent == nil {
parent = template
}
if parentKey == nil {
parentKey = templateKey
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, parent, &templateKey.PublicKey, parentKey)
if err != nil {
err = errors.Errorf("Failed to generate x509 certificate: %s", err)
return cert, key, err
}
err = pem.Encode(&cert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
err = errors.Errorf("Failed to encode x509 certificate: %s", err)
return cert, key, err
}
err = pem.Encode(&key, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(templateKey)})
if err != nil {
err = errors.Errorf("Failed to encode tls key pairs: %s", err)
return cert, key, err
}
return cert, key, nil
}
// saveCertificate saves the certificate and key to files of the form basename-cert.pem and basename-key.pem
// cf and kf are the certificate file and key file respectively
func saveCertificate(cf, kf string, cert, key *bytes.Buffer) error {
defer trace.End(trace.Begin(""))
// #nosec: Expect file permissions to be 0600 or less
certFile, err := os.OpenFile(cf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
err = errors.Errorf("Failed to create certificate file %s: %s", cf, err)
return err
}
defer certFile.Close()
_, err = certFile.Write(cert.Bytes())
if err != nil {
err = errors.Errorf("Failed to write certificate: %s", err)
return err
}
keyFile, err := os.OpenFile(kf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
err = errors.Errorf("Failed to create key file %s: %s", kf, err)
return err
}
defer keyFile.Close()
_, err = keyFile.Write(key.Bytes())
if err != nil {
err = errors.Errorf("Failed to write key: %s", err)
return err
}
return nil
}
func loadCertificate(cf, kf string) (*x509.Certificate, *rsa.PrivateKey, error) {
defer trace.End(trace.Begin(""))
cb, err := ioutil.ReadFile(cf)
if err != nil {
err = errors.Errorf("Failed to read certificate file %s: %s", cf, err)
return nil, nil, err
}
kb, err := ioutil.ReadFile(kf)
if err != nil {
err = errors.Errorf("Failed to read key file %s: %s", kf, err)
return nil, nil, err
}
return ParseCertificate(cb, kb)
}
func ParseCertificate(cb, kb []byte) (*x509.Certificate, *rsa.PrivateKey, error) {
defer trace.End(trace.Begin(""))
block, _ := pem.Decode(cb)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
err = errors.Errorf("Failed to parse certificate data: %s", err)
return nil, nil, err
}
var key *rsa.PrivateKey
block, _ = pem.Decode(kb)
if block != nil {
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
err = errors.Errorf("Failed to parse key data: %s", err)
return nil, nil, err
}
}
return cert, key, nil
}
func CreateSelfSigned(domain string, org []string, size int) (cert bytes.Buffer, key bytes.Buffer, err error) {
defer trace.End(trace.Begin(""))
template, pkey, err := templateWithKey(templateWithServer(template(org), domain), size)
if err != nil {
return cert, key, err
}
return createCertificate(template, nil, pkey, nil)
}
func CreateRootCA(domain string, org []string, size int) (cert bytes.Buffer, key bytes.Buffer, err error) {
defer trace.End(trace.Begin(""))
template, pkey, err := templateWithKey(templateWithCA(template(org)), size)
if err != nil {
return cert, key, err
}
return createCertificate(template, nil, pkey, nil)
}
func CreateServerCertificate(domain string, org []string, size int, cb, kb []byte) (cert bytes.Buffer, key bytes.Buffer, err error) {
defer trace.End(trace.Begin(""))
// Load up the CA
cacert, cakey, err := ParseCertificate(cb, kb)
if err != nil {
return cert, key, err
}
// Generate the new cert
template, pkey, err := templateWithKey(templateWithServer(template(org), domain), size)
if err != nil {
return cert, key, err
}
return createCertificate(template, cacert, pkey, cakey)
}
func CreateClientCertificate(domain string, org []string, size int, cb, kb []byte) (cert bytes.Buffer, key bytes.Buffer, err error) {
defer trace.End(trace.Begin(""))
// Load up the CA
cacert, cakey, err := ParseCertificate(cb, kb)
// Generate the new cert
template, pkey, err := templateWithKey(templateAsClientOnly(template(org)), size)
if err != nil {
return cert, key, err
}
return createCertificate(template, cacert, pkey, cakey)
}
// VerifyClientCert verifies the loaded client cert keypair against the input CA and
// returns the certificate on success.
func VerifyClientCert(ca []byte, ckp *KeyPair) (*tls.Certificate, error) {
var err error
cert, err := ckp.Certificate()
if err != nil || cert.Leaf == nil {
return nil, CertParseError{msg: err.Error()}
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(ca) {
return nil, CreateCAPoolError{}
}
opts := x509.VerifyOptions{
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
if _, err = cert.Leaf.Verify(opts); err != nil {
return nil, CertVerifyError{}
}
return cert, nil
}

View File

@@ -0,0 +1,122 @@
// Copyright 2016-2017 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 certificate
import (
"crypto/x509"
"os"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestCreateCA(t *testing.T) {
log.SetLevel(log.DebugLevel)
cacert, cakey, err := CreateRootCA("somewhere.com", []string{"MyOrg"}, 2048)
assert.NoError(t, err, "Failed generating CA certificate")
_, _, err = ParseCertificate(cacert.Bytes(), cakey.Bytes())
assert.NoError(t, err, "Failed reparsing CA certificate")
}
func TestSignedCertificate(t *testing.T) {
log.SetLevel(log.DebugLevel)
cacert, cakey, err := CreateRootCA("somewhere.com", []string{"MyOrg"}, 2048)
assert.NoError(t, err, "Failed generating ca certificate")
cert, key, err := CreateServerCertificate("somewere.com", []string{"MyOrg"}, 2048, cacert.Bytes(), cakey.Bytes())
assert.NoError(t, err, "Failed generating signed certificate")
// validate
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(cacert.Bytes())
assert.Equal(t, true, ok, "Failed to append CA to roots")
opts := x509.VerifyOptions{
Roots: roots,
}
tlsCert, _, err := ParseCertificate(cert.Bytes(), key.Bytes())
assert.NoError(t, err, "Failed loading signed certificate")
_, err = tlsCert.Verify(opts)
assert.NoError(t, err, "Failed loading signed certificate")
}
func TestFailedValidation(t *testing.T) {
log.SetLevel(log.DebugLevel)
cacert, cakey, err := CreateRootCA("somewhere.com", []string{"MyOrg"}, 2048)
assert.NoError(t, err, "Failed generating ca certificate")
cert, key, err := CreateServerCertificate("somewere.com", []string{"MyOrg"}, 2048, cacert.Bytes(), cakey.Bytes())
assert.NoError(t, err, "Failed generating signed certificate")
// validate
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(cacert.Bytes())
assert.Equal(t, true, ok, "Failed to append CA to roots")
tlsCert, _, err := ParseCertificate(cert.Bytes(), key.Bytes())
assert.NoError(t, err, "Failed loading signed certificate")
opts := x509.VerifyOptions{
Roots: roots,
DNSName: "somewhereELSE.com",
}
_, err = tlsCert.Verify(opts)
assert.Error(t, err, "Expected to fail initial verify")
opts = x509.VerifyOptions{
Roots: roots,
DNSName: "somewhere.com",
}
_, err = tlsCert.Verify(opts)
assert.Error(t, err, "Expected to pass second verify")
}
func TestVerifyClientCert(t *testing.T) {
cacert, cakey, err := CreateRootCA("foo.com", []string{"FooOrg"}, 2048)
assert.NoError(t, err)
cert, key, err := CreateClientCertificate("foo.com", []string{"FooOrg"}, 2048, cacert.Bytes(), cakey.Bytes())
assert.NoError(t, err)
kp := NewKeyPair(ClientCert, ClientKey, cert.Bytes(), key.Bytes())
err = kp.SaveCertificate()
assert.NoError(t, err)
defer func() {
os.Remove(ClientCert)
os.Remove(ClientKey)
}()
// Validate client certificate keypair created with the right CA
_, err = VerifyClientCert(cacert.Bytes(), kp)
assert.NoError(t, err)
cacert, cakey, err = CreateRootCA("bar.com", []string{"BarOrg"}, 2048)
assert.NoError(t, err)
// Attempt to validate client certificate keypair created with a different CA
_, err = VerifyClientCert(cacert.Bytes(), kp)
assert.NotNil(t, err)
}

40
vendor/github.com/vmware/vic/pkg/certificate/errors.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2017 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 certificate
import "fmt"
// CertParseError is returned when there's an error parsing a cert.
type CertParseError struct {
msg string
}
func (e CertParseError) Error() string {
return fmt.Sprintf("Unable to parse client certificate: %s", e.msg)
}
// CreateCAPoolError is returned when there's an error creating a CA cert pool.
type CreateCAPoolError struct{}
func (e CreateCAPoolError) Error() string {
return "Unable to create CA pool to check client certificate"
}
// CertVerifyError is returned when the client cert cannot be validated against the CA.
type CertVerifyError struct{}
func (e CertVerifyError) Error() string {
return "Client certificate in certificate path does not validate with provided CA"
}

127
vendor/github.com/vmware/vic/pkg/certificate/keypair.go generated vendored Normal file
View File

@@ -0,0 +1,127 @@
// Copyright 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 certificate
import (
"bytes"
"crypto/tls"
"io/ioutil"
"github.com/vmware/vic/pkg/errors"
)
type KeyPair struct {
KeyPEM []byte
CertPEM []byte
KeyFile string
CertFile string
}
func NewKeyPair(certFile, keyFile string, certPEM, keyPEM []byte) *KeyPair {
return &KeyPair{
KeyPEM: keyPEM,
CertPEM: certPEM,
KeyFile: keyFile,
CertFile: certFile,
}
}
func (kp *KeyPair) LoadCertificate() error {
c, err := ioutil.ReadFile(kp.CertFile)
if err != nil {
return err
}
k, err := ioutil.ReadFile(kp.KeyFile)
if err != nil {
return err
}
kp.CertPEM = c
kp.KeyPEM = k
return nil
}
func (kp *KeyPair) SaveCertificate() error {
return saveCertificate(kp.CertFile, kp.KeyFile, bytes.NewBuffer(kp.CertPEM), bytes.NewBuffer(kp.KeyPEM))
}
func (kp *KeyPair) CreateSelfSigned(domain string, org []string, size int) error {
c, k, err := CreateSelfSigned(domain, org, size)
if err != nil {
return err
}
kp.CertPEM = c.Bytes()
kp.KeyPEM = k.Bytes()
return nil
}
func (kp *KeyPair) CreateRootCA(domain string, org []string, size int) error {
c, k, err := CreateRootCA(domain, org, size)
if err != nil {
return err
}
kp.CertPEM = c.Bytes()
kp.KeyPEM = k.Bytes()
return nil
}
func (kp *KeyPair) CreateServerCertificate(domain string, org []string, size int, ca *KeyPair) error {
c, k, err := CreateServerCertificate(domain, org, size, ca.CertPEM, ca.KeyPEM)
if err != nil {
return err
}
kp.CertPEM = c.Bytes()
kp.KeyPEM = k.Bytes()
return nil
}
func (kp *KeyPair) CreateClientCertificate(domain string, org []string, size int, ca *KeyPair) error {
c, k, err := CreateClientCertificate(domain, org, size, ca.CertPEM, ca.KeyPEM)
if err != nil {
return err
}
kp.CertPEM = c.Bytes()
kp.KeyPEM = k.Bytes()
return nil
}
// Certificate turns the KeyPair back into useful TLS constructs
// This attempts to populate the certificate.Leaf field with the x509 certificate for convenience
func (kp *KeyPair) Certificate() (*tls.Certificate, error) {
if kp.CertPEM == nil || kp.KeyPEM == nil {
return nil, errors.New("KeyPair has no data")
}
cert, err := tls.X509KeyPair(kp.CertPEM, kp.KeyPEM)
if err != nil {
return nil, err
}
// #nosec: Errors unhandled.
cert.Leaf, _, _ = ParseCertificate(kp.CertPEM, kp.KeyPEM)
return &cert, nil
}

View File

@@ -0,0 +1,122 @@
// Copyright 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 certificate
import (
"os"
"strings"
"testing"
"crypto/tls"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
const (
keyFile = "./key.pem"
certFile = "./cert.pem"
)
func TestMain(t *testing.T) {
log.SetLevel(log.DebugLevel)
trace.Logger.Level = log.DebugLevel
}
func TestCreateSelfSigned(t *testing.T) {
cert, key, err := CreateSelfSigned("somewhere.com", []string{"MyOrg"}, 2048)
if err != nil {
t.Errorf("CreateSelfSigned failed with error %s", err)
}
certString := cert.String()
keyString := key.String()
log.Infof("cert: %s", certString)
log.Infof("key: %s", keyString)
if !strings.HasPrefix(certString, "-----BEGIN CERTIFICATE-----") {
t.Errorf("Certificate lacks proper prefix; must not have been generated properly.")
}
if !strings.HasSuffix(certString, "-----END CERTIFICATE-----\n") {
t.Errorf("Certificate lacks proper suffix; must not have been generated properly.")
}
if !strings.HasPrefix(keyString, "-----BEGIN RSA PRIVATE KEY-----") {
t.Errorf("Private key lacks proper prefix; must not have been generated properly.")
}
if !strings.HasSuffix(keyString, "-----END RSA PRIVATE KEY-----\n") {
t.Errorf("Private key lacks proper suffix; must not have been generated properly.")
}
_, err = tls.X509KeyPair([]byte(certString), []byte(keyString))
if err != nil {
t.Errorf("Unable to load X509 key pair(%s,%s): %s", certString, keyString, err)
}
}
func TestGenerate(t *testing.T) {
log.SetLevel(log.InfoLevel)
if _, err := os.Stat(keyFile); err == nil {
os.Remove(keyFile)
}
pair := NewKeyPair(keyFile, certFile, nil, nil)
err := pair.CreateSelfSigned("somewhere.com", []string{"MyOrg"}, 2048)
assert.NoError(t, err, "Failed generating self-signed certificate")
err = pair.SaveCertificate()
assert.NoError(t, err, "Failed saving generated certificate")
defer os.Remove(keyFile)
defer os.Remove(certFile)
assert.NotEmpty(t, pair.KeyPEM, "Expected contents in key PEM data")
assert.NotEmpty(t, pair.CertPEM, "Expected contents in cert PEM data")
_, err = os.Stat(keyFile)
assert.NoError(t, err, "Key file was not created")
assert.Contains(t, string(pair.KeyPEM), "RSA PRIVATE KEY", "Key is not correctly generated")
}
func TestGetCertificate(t *testing.T) {
log.SetLevel(log.InfoLevel)
if _, err := os.Stat(keyFile); err == nil {
os.Remove(keyFile)
}
pair := NewKeyPair(keyFile, certFile, nil, nil)
err := pair.CreateSelfSigned("somewhere.com", []string{"MyOrg"}, 2048)
assert.NoError(t, err, "Failed generating self-signed certificate")
err = pair.SaveCertificate()
assert.NoError(t, err, "Failed saving generated certificate")
defer os.Remove(keyFile)
defer os.Remove(certFile)
pair2 := NewKeyPair(keyFile, certFile, nil, nil)
err = pair2.LoadCertificate()
assert.NoError(t, err, "Failed loading self-signed certificate")
assert.Equal(t, pair, pair2, "Expected loads to be consistent")
}

18
vendor/github.com/vmware/vic/pkg/dio/doc.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
// Copyright 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 dio adds dynamic behaviour to the standard io package mutliX types
package dio
var verbose = true

430
vendor/github.com/vmware/vic/pkg/dio/multi_test.go generated vendored Normal file
View File

@@ -0,0 +1,430 @@
// Copyright 2016-2017 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 dio
import (
"bytes"
"io"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
const (
count = 32
base = "base functionality"
dynamic = "dynamic add/remove functionality"
)
type filterf func(idx int) bool
func FilterBuffers(in []*bytes.Buffer, fn filterf) []*bytes.Buffer {
var out []*bytes.Buffer
for i := 0; i < len(in); i++ {
if fn(i) {
out = append(out, in[i])
}
}
return out
}
func FilterWriters(in []io.Writer, fn filterf) []io.Writer {
var out []io.Writer
for i := 0; i < len(in); i++ {
if fn(i) {
out = append(out, in[i])
}
}
return out
}
func FilterReaders(in []io.Reader, fn filterf) []io.Reader {
var out []io.Reader
for i := 0; i < len(in); i++ {
if fn(i) {
out = append(out, in[i])
}
}
return out
}
func write(t *testing.T, mwriter DynamicMultiWriter, p []byte) {
n, err := mwriter.Write(p)
if err != nil {
t.Errorf("write: %v", err)
}
if n != len(p) {
t.Errorf("short write: %d != %d", n, len(p))
}
}
func read(t *testing.T, mreader DynamicMultiReader, limit int) []byte {
total := 0
var buf = make([]byte, 32*1024)
for {
n, err := mreader.Read(buf[total:limit])
if err == io.EOF {
break
}
if err != nil {
t.Errorf("read: %v", err)
}
total += n
if total >= limit {
break
}
}
return buf[:limit]
}
func each(t *testing.T, buffers []*bytes.Buffer, s string) {
for i := range buffers {
assert.Equal(t, buffers[i].String(), s)
}
}
// TestMultiWrite creates multi writers and writes to them, then removes some of them and writes again
func TestMultiWrite(t *testing.T) {
var wg sync.WaitGroup
var writers []io.Writer
var buffers []*bytes.Buffer
// create & initialize writers and buffers
for i := 0; i < count; i++ {
var buffer bytes.Buffer
reader, writer := io.Pipe()
writers = append(writers, writer)
buffers = append(buffers, &buffer)
wg.Add(1)
go func() {
// set up a goroutine so we don't block writes
io.CopyN(&buffer, reader, int64(len(base)))
wg.Done()
}()
}
// create the multi writer
mwriter := MultiWriter(writers...)
// write and ensure io.Copy returns
write(t, mwriter, []byte(base))
wg.Wait()
each(t, buffers, base)
}
// TestMultiWrite creates bunch of multi writers and writes to them, then adds more and writes again
func TestWriteAdd(t *testing.T) {
var wg sync.WaitGroup
var wgAdded sync.WaitGroup
var writers []io.Writer
var buffers []*bytes.Buffer
// create & initialize writers and buffers into two categories
// *Added ones will be added to multi writer later
for i := 0; i < count; i++ {
var buffer bytes.Buffer
reader, writer := io.Pipe()
writers = append(writers, writer)
buffers = append(buffers, &buffer)
if i%3 != 0 {
wg.Add(1)
}
wgAdded.Add(1)
go func(i int) {
if i%3 != 0 {
io.CopyN(&buffer, reader, int64(len(base)))
wg.Done()
}
io.CopyN(&buffer, reader, int64(len(dynamic)))
wgAdded.Done()
}(i)
}
writersAdded := FilterWriters(writers, func(i int) bool { return i%3 == 0 })
writersLeft := FilterWriters(writers, func(i int) bool { return i%3 != 0 })
buffersAdded := FilterBuffers(buffers, func(i int) bool { return i%3 == 0 })
buffersLeft := FilterBuffers(buffers, func(i int) bool { return i%3 != 0 })
// create the multi writer
mwriter := MultiWriter(writersLeft...)
// write and ensure io.Copy returns
write(t, mwriter, []byte(base))
wg.Wait()
each(t, buffersLeft, base)
// add skipped writers to the writer
mwriter.Add(writersAdded...)
// write and ensure io.Copy returns
write(t, mwriter, []byte(dynamic))
wgAdded.Wait()
each(t, buffersLeft, base+dynamic)
each(t, buffersAdded, dynamic)
}
// TestMultiWrite creates multi writers and writes to them, then removes some of them and writes again
func TestWriteRemove(t *testing.T) {
var wg sync.WaitGroup
var wgRemoved sync.WaitGroup
var writers []io.Writer
var buffers []*bytes.Buffer
// create & initialize writers and buffers into two categories
// *Removed ones will be filtered out from multi writer later
for i := 0; i < count; i++ {
var buffer bytes.Buffer
reader, writer := io.Pipe()
writers = append(writers, writer)
buffers = append(buffers, &buffer)
// set up a goroutine so we don't block writes
wg.Add(1)
wgRemoved.Add(1)
go func(i int) {
// set up a goroutine so we don't block writes
io.CopyN(&buffer, reader, int64(len(base)))
wg.Done()
if i%3 == 0 {
wgRemoved.Done()
return
}
io.CopyN(&buffer, reader, int64(len(dynamic)))
wgRemoved.Done()
}(i)
}
// create the multi writer
mwriter := MultiWriter(writers...)
// write and ensure io.Copy returns
write(t, mwriter, []byte(base))
wg.Wait()
each(t, buffers, base)
// remove the writers
for i := 0; i < count; i++ {
if i%3 == 0 {
mwriter.Remove(writers[i])
}
}
// write and ensure io.Copy returns
write(t, mwriter, []byte(dynamic))
wgRemoved.Wait()
buffersLeft := FilterBuffers(buffers, func(i int) bool { return i%3 != 0 })
buffersRemoved := FilterBuffers(buffers, func(i int) bool { return i%3 == 0 })
each(t, buffersLeft, base+dynamic)
each(t, buffersRemoved, base)
}
// TestMultiRead creates multi readers and reads from them
func TestMultiRead(t *testing.T) {
var wg sync.WaitGroup
var readers []io.Reader
// create & initialize writers and buffers
for i := 0; i < count; i++ {
reader, writer := io.Pipe()
readers = append(readers, reader)
wg.Add(1)
go func() {
// set up a goroutine so we don't block reads
io.CopyN(writer, bytes.NewReader([]byte(base)), int64(len(base)))
wg.Done()
}()
}
// create the multi writer
mreader := MultiReader(readers...)
expected := strings.Repeat(base, count)
// read and ensure io.Copy returns
buffer := read(t, mreader, len(expected))
wg.Wait()
assert.Equal(t, expected, string(buffer))
}
// TestMultiRead creates multi readers and reads from them, then adds mores and reads again
func TestReadAdd(t *testing.T) {
var wg sync.WaitGroup
var wgAdded sync.WaitGroup
var wgLeft sync.WaitGroup
var readers []io.Reader
var pipereaders []io.PipeReader
wgLeft.Add(1)
// create & initialize writers and buffers
for i := 0; i < count; i++ {
reader, writer := io.Pipe()
readers = append(readers, reader)
if i%3 != 0 {
pipereaders = append(pipereaders, *reader)
wg.Add(1)
}
wgAdded.Add(1)
go func(i int) {
if i%3 != 0 {
io.CopyN(writer, bytes.NewReader([]byte(base)), int64(len(base)))
wg.Done()
}
wgLeft.Wait()
io.CopyN(writer, bytes.NewReader([]byte(dynamic)), int64(len(dynamic)))
wgAdded.Done()
}(i)
}
readersAdded := FilterReaders(readers, func(i int) bool { return i%3 == 0 })
readersLeft := FilterReaders(readers, func(i int) bool { return i%3 != 0 })
// create the multi writer
mreader := MultiReader(readersLeft...)
expected := strings.Repeat(base, len(readersLeft))
// read and ensure io.Copy returns
buffer := read(t, mreader, len(expected))
wg.Wait()
assert.Equal(t, expected, string(buffer))
// close the initial set otherwise they will block
for i := range pipereaders {
pipereaders[i].Close()
}
wgLeft.Done()
// add the rest of the readers
mreader.Add(readersAdded...)
expected = strings.Repeat(dynamic, len(readersAdded))
// read and ensure io.Copy returns
buffer = read(t, mreader, len(expected))
wgAdded.Wait()
assert.Equal(t, expected, string(buffer))
}
// TestReadRemove creates multi readers and reads from them, then removes some and reads again
func TestReadRemove(t *testing.T) {
var wg sync.WaitGroup
var wgRemoved sync.WaitGroup
var wgLeft sync.WaitGroup
var readers []io.Reader
var writers []io.Writer
wgLeft.Add(1)
// create & initialize writers and buffers
for i := 0; i < count; i++ {
reader, writer := io.Pipe()
readers = append(readers, reader)
writers = append(writers, writer)
// set up a goroutine so we don't block writes
wg.Add(1)
wgRemoved.Add(1)
go func(i int) {
// set up a goroutine so we don't block writes
io.CopyN(writer, bytes.NewReader([]byte(base)), int64(len(base)))
wg.Done()
if i%3 == 0 {
wgRemoved.Done()
return
}
wgLeft.Wait()
io.CopyN(writer, bytes.NewReader([]byte(dynamic)), int64(len(dynamic)))
wgRemoved.Done()
}(i)
}
// create the multi writer
mreader := MultiReader(readers...)
expected := strings.Repeat(base, count)
// read and ensure io.Copy returns
buffer := read(t, mreader, len(expected))
wg.Wait()
assert.Equal(t, expected, string(buffer))
wgLeft.Done()
readersLeft := FilterReaders(readers, func(i int) bool { return i%3 != 0 })
readersRemoved := FilterReaders(readers, func(i int) bool { return i%3 == 0 })
for i := range readersRemoved {
mreader.Remove(readersRemoved[i])
}
expected = strings.Repeat(dynamic, len(readersLeft))
// read and ensure io.Copy returns
buffer = read(t, mreader, len(expected))
wgRemoved.Wait()
assert.Equal(t, expected, string(buffer))
}

220
vendor/github.com/vmware/vic/pkg/dio/reader.go generated vendored Normal file
View File

@@ -0,0 +1,220 @@
// Copyright 2016-2017 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 dio adds dynamic behaviour to the standard io package mutliX types
package dio
import (
"io"
"sync"
log "github.com/Sirupsen/logrus"
)
// DynamicMultiReader adds dynamic add/remove to the base multireader behaviour
type DynamicMultiReader interface {
io.Reader
Add(...io.Reader)
Remove(io.Reader)
Close() error
PropagateEOF(bool)
}
type multiReader struct {
mutex sync.Mutex
cond *sync.Cond
err error
readers []io.Reader
honorInlineEOF bool
}
// PropagateEOF toggles whether to return EOF when all readers return EOF.
// Setting this to true will result in an EOF if there are no readers available
// when Read is next called
func (t *multiReader) PropagateEOF(val bool) {
t.mutex.Lock()
t.honorInlineEOF = val
t.cond.Broadcast()
t.mutex.Unlock()
}
func (t *multiReader) Read(p []byte) (int, error) {
var n int
var err error
var rTmp []io.Reader
if verbose {
defer func() {
log.Debugf("[%p] read %q from %d readers (err: %#+v)", t, string(p[:n]), len(rTmp), err)
}()
}
t.mutex.Lock()
// stash a copy of the t.err
err = t.err
t.mutex.Unlock()
// Close sets this
if err == io.EOF || err == io.ErrClosedPipe {
if verbose {
log.Debugf("[%p] read from closed multi-reader, returning EOF", t)
}
return 0, io.EOF
}
// if there's no readers we are steady state - has to be after t.err check to
// get correct Close behaviour.
// Blocking behaviour!
t.mutex.Lock()
for len(t.readers) == 0 && t.err == nil {
log.Debugf("[%p] Going into sleep with %d readers", t, len(t.readers))
t.cond.Wait()
log.Debugf("[%p] Woken from sleep %d readers", t, len(t.readers))
}
// stash a copy of the readers slie to iterate later
rTmp = make([]io.Reader, len(t.readers))
copy(rTmp, t.readers)
// stash a copy of the t.err
err = t.err
t.mutex.Unlock()
if err != nil {
return 0, err
}
// eof counter
eof := 0
for _, r := range rTmp {
slice := p[n:]
if len(slice) == 0 {
// we've run out of target space and don't know what
// the remaining readers have, so not EOF
return n, nil
}
x, err := r.Read(slice)
n += x
if err != nil {
if err != io.EOF && err != io.ErrClosedPipe {
t.mutex.Lock()
// if there was an actual error, return that
t.err = err
t.mutex.Unlock()
return n, err
}
// increment the EOF counter and remove the reader that retured EOF
log.Debugf("[%p] removing reader due to EOF", t)
// Remove grabs the lock
t.Remove(r)
eof++
}
}
// This means readers closed/removed while we iterate
// if no data is to be returned, there's no major error, and the number of
// reported EOFs matches the number of readers on entry to the main loop
if n == 0 && t.err == nil && eof == len(rTmp) {
log.Debugf("[%p] All of the readers returned EOF (%d)", t, len(rTmp))
t.mutex.Lock()
// queue up an EOF for the next time around if no new readers are added
if t.honorInlineEOF {
t.err = io.EOF
}
t.mutex.Unlock()
}
return n, nil
}
func (t *multiReader) Add(reader ...io.Reader) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.readers = append(t.readers, reader...)
// if we've got a new reader, we're not EOF any more until that reader EOFs
t.err = nil
t.cond.Broadcast()
if verbose {
log.Debugf("[%p] added reader - now %d readers", t, len(t.readers))
for i, r := range t.readers {
log.Debugf("[%p] Reader %d [%p]", t, i, r)
}
}
}
// TODO: add a WriteTo for more efficient copy
func (t *multiReader) Close() error {
t.mutex.Lock()
defer t.mutex.Unlock()
log.Debugf("[%p] Close on readers", t)
for _, r := range t.readers {
if c, ok := r.(io.Closer); ok {
log.Debugf("[%p] Closing reader %+v", t, r)
c.Close()
}
}
t.err = io.EOF
t.cond.Broadcast()
return nil
}
// Remove doesn't return an error if element isn't found as the end result is
// identical
func (t *multiReader) Remove(reader io.Reader) {
t.mutex.Lock()
defer t.mutex.Unlock()
if verbose {
log.Debugf("[%p] removing reader - currently %d readers", t, len(t.readers))
}
for i, r := range t.readers {
if r == reader {
t.readers = append(t.readers[:i], t.readers[i+1:]...)
// using range directly means that we're looping up, so indexes are now invalid
if verbose {
log.Debugf("[%p] removed reader - now %d readers", t, len(t.readers))
for i, r := range t.readers {
log.Debugf("[%p] Reader %d [%p]", t, i, r)
}
}
break
}
}
}
// MultiReader returns a Reader that's the logical concatenation of
// the provided input readers. They're read sequentially. Once all
// inputs have returned EOF, Read will return EOF. If any of the readers
// return a non-nil, non-EOF error, Read will return that error.
func MultiReader(readers ...io.Reader) DynamicMultiReader {
r := make([]io.Reader, len(readers))
copy(r, readers)
t := &multiReader{readers: r}
t.cond = sync.NewCond(&t.mutex)
if verbose {
log.Debugf("[%p] created multireader", t)
}
return t
}

181
vendor/github.com/vmware/vic/pkg/dio/writer.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
// Copyright 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 dio adds dynamic behaviour to the standard io package mutliX types
package dio
import (
"io"
"os"
"sync"
log "github.com/Sirupsen/logrus"
)
// DynamicMultiWriter adds dynamic add/remove to the base multiwriter behaviour
type DynamicMultiWriter interface {
io.Writer
Add(...io.Writer)
Remove(io.Writer)
Close() error
}
type multiWriter struct {
mutex sync.Mutex
waitGroup sync.WaitGroup
writers []io.Writer
}
func (t *multiWriter) Write(p []byte) (int, error) {
var n int
var err error
var wTmp []io.Writer
if verbose {
defer func() {
log.Debugf("[%p] write %q to %d writers (err: %#+v)", t, string(p[:n]), len(wTmp), err)
}()
}
t.mutex.Lock()
t.waitGroup.Add(1)
defer t.waitGroup.Done()
// stash a local copy of the slice as we never want to write twice to a single writer
// if remove is called during this flow
wTmp = make([]io.Writer, len(t.writers))
copy(wTmp, t.writers)
t.mutex.Unlock()
eof := 0
// possibly want to add buffering or parallelize this
for _, w := range wTmp {
n, err = w.Write(p)
if err != nil {
// remove the writer
log.Debugf("[%p] removing writer %p due to %s", t, w, err.Error())
// Remove grabs the lock
t.Remove(w)
if err == io.EOF {
eof++
}
}
// FIXME: figure out what semantics we need here - currently we may not write to
// everything as we abort
if n != len(p) {
// remove the writer
log.Debugf("[%p] removing writer %p due to short write: %d != %d", t, w, n, len(p))
// Remove grabs the lock
t.Remove(w)
}
}
// This means writers closed/removed while we iterate
if eof != 0 && n == 0 && err == nil && eof == len(wTmp) {
log.Debugf("[%p] All of the writers returned EOF (%d)", t, len(wTmp))
}
return len(p), nil
}
func (t *multiWriter) Add(writer ...io.Writer) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.writers = append(t.writers, writer...)
if verbose {
log.Debugf("[%p] added writer - now %d writers", t, len(t.writers))
for i, w := range t.writers {
log.Debugf("[%p] Writer %d [%p]", t, i, w)
}
}
}
// CloseWriter is an interface that implements structs
// that close input streams to prevent from writing.
type CloseWriter interface {
CloseWrite() error
}
// FIXME: provide a mechanism for selectively closing writers
// - currently this closes /dev/stdout and logging as well if present
func (t *multiWriter) Close() error {
t.mutex.Lock()
defer t.mutex.Unlock()
// allow any pending writes to complete
t.waitGroup.Wait()
log.Debugf("[%p] Close on writers", t)
for _, w := range t.writers {
log.Debugf("[%p] Closing writer %+v", t, w)
if c, ok := w.(CloseWriter); ok {
log.Debugf("[%p] is a CloseWriter", t, w)
c.CloseWrite()
} else if c, ok := w.(io.Closer); ok && c != os.Stdout && c != os.Stderr {
log.Debugf("[%p] is a Closer", t, w)
// squash closing of stdout/err if bound
c.Close()
}
}
return nil
}
// TODO: add a ReadFrom for more efficient copy
// Remove doesn't return an error if element isn't found as the end result is
// identical
func (t *multiWriter) Remove(writer io.Writer) {
t.mutex.Lock()
defer t.mutex.Unlock()
if verbose {
log.Debugf("[%p] removing writer %p - currently %d writers", t, writer, len(t.writers))
}
for i, w := range t.writers {
if w == writer {
t.writers = append(t.writers[:i], t.writers[i+1:]...)
if verbose {
log.Debugf("[%p] removed writer - now %d writers", t, len(t.writers))
for i, w := range t.writers {
log.Debugf("[%p] Writer %d [%p]", t, i, w)
}
}
break
}
}
}
// MultiWriter extends io.MultiWriter to allow add/remove of writers dynamically
// without disrupting existing writing
func MultiWriter(writers ...io.Writer) DynamicMultiWriter {
w := make([]io.Writer, len(writers))
copy(w, writers)
t := &multiWriter{writers: w}
if verbose {
log.Debugf("[%p] created multiwriter", t)
}
return t
}

40
vendor/github.com/vmware/vic/pkg/errors/errors.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 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 errors provides error handling functions.
//
package errors
import (
"fmt"
)
func ErrorStack(err error) string {
return err.Error()
}
func Errorf(format string, a ...interface{}) error {
return fmt.Errorf(format, a...)
}
func New(err string) error {
return fmt.Errorf("%s", err)
}
func Trace(err error) error {
if err == nil {
return nil
}
return err
}

54
vendor/github.com/vmware/vic/pkg/errors/errors_test.go generated vendored Normal file
View File

@@ -0,0 +1,54 @@
// Copyright 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 errors
import (
"testing"
"errors"
)
const errMsg = "Winter is coming"
func TestErrorStack(t *testing.T) {
e := errors.New(errMsg)
val := ErrorStack(e)
if val != errMsg {
t.Errorf("Got %s, expected %s", val, errMsg)
}
}
func TestErrorf(t *testing.T) {
val := Errorf("%s", errMsg)
if val.Error() != errMsg {
t.Errorf("Got %s, expected %s", val, errMsg)
}
}
func TestNew(t *testing.T) {
val := Errorf("%s", errMsg)
if val.Error() != errMsg {
t.Errorf("Got %s, expected %s", val, errMsg)
}
}
func TestTrace(t *testing.T) {
e := errors.New(errMsg)
val := Trace(e)
if val != e {
t.Errorf("Got %s, expected %s", val, errMsg)
}
}

58
vendor/github.com/vmware/vic/pkg/fetcher/errors.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 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 fetcher
import (
"fmt"
"net/url"
)
// DoNotRetry is an error wrapper indicating that the error cannot be resolved with a retry.
type DoNotRetry struct {
Err error
}
// Error returns the stringified representation of the encapsulated error.
func (e DoNotRetry) Error() string {
return fmt.Sprintf("download failed: %s", e.Err.Error())
}
// ImageNotFoundError is returned when an image is not found.
type ImageNotFoundError struct {
Err error
}
func (e ImageNotFoundError) Error() string {
return fmt.Sprintf("image not found: %s", e.Err.Error())
}
// TagNotFoundError is returned when an image's tag doesn't exist.
type TagNotFoundError struct {
Err error
}
func (e TagNotFoundError) Error() string {
return fmt.Sprintf("image tag not found: %s", e.Err.Error())
}
// AuthTokenError is returned when authentication with a registry fails
type AuthTokenError struct {
TokenServer url.URL
Err error
}
func (e AuthTokenError) Error() string {
return fmt.Sprintf("Failed to fetch auth token from %s", e.TokenServer.Host)
}

584
vendor/github.com/vmware/vic/pkg/fetcher/fetcher.go generated vendored Normal file
View File

@@ -0,0 +1,584 @@
// Copyright 2016-2017 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 fetcher
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress"
"golang.org/x/net/context/ctxhttp"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
const (
maxDownloadAttempts = 5
// DefaultTokenExpirationDuration specifies the default token expiration
DefaultTokenExpirationDuration = 60 * time.Second
)
// Fetcher interface
type Fetcher interface {
Fetch(ctx context.Context, url *url.URL, reqHdrs *http.Header, toFile bool, po progress.Output, id ...string) (string, error)
FetchAuthToken(url *url.URL) (*Token, error)
Ping(url *url.URL) (http.Header, error)
Head(url *url.URL) (http.Header, error)
ExtractOAuthURL(hdr string, repository *url.URL) (*url.URL, error)
IsStatusUnauthorized() bool
IsStatusOK() bool
IsStatusNotFound() bool
AuthURL() *url.URL
}
// Token represents https://docs.docker.com/registry/spec/auth/token/
type Token struct {
// An opaque Bearer token that clients should supply to subsequent requests in the Authorization header.
Token string `json:"token"`
// (Optional) The duration in seconds since the token was issued that it will remain valid. When omitted, this defaults to 60 seconds.
Expires time.Time
ExpiresIn int `json:"expires_in"`
IssueAt time.Time `json:"issued_at"`
}
// Options struct
type Options struct {
Timeout time.Duration
Username string
Password string
InsecureSkipVerify bool
Token *Token
// RootCAs will not be modified by fetcher.
RootCAs *x509.CertPool
}
// URLFetcher struct
type URLFetcher struct {
client *http.Client
OAuthEndpoint *url.URL
StatusCode int
options Options
}
// RegistryErrorRespBody is used for unmarshaling json error response body from image registries.
// Error response json is assumed to follow Docker API convention (field `details` is dropped).
// See: https://docs.docker.com/registry/spec/api/#errors
type RegistryErrorRespBody struct {
Errors []struct {
Code string
Message string
}
}
// NewURLFetcher creates a new URLFetcher
func NewURLFetcher(options Options) Fetcher {
/* #nosec */
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: options.InsecureSkipVerify,
RootCAs: options.RootCAs,
},
}
client := &http.Client{Transport: tr}
return &URLFetcher{
client: client,
options: options,
}
}
// Fetch fetches from a url and stores its content in a temporary file.
// hdrs is optional.
func (u *URLFetcher) Fetch(ctx context.Context, url *url.URL, reqHdrs *http.Header, toFile bool, po progress.Output, ids ...string) (string, error) {
defer trace.End(trace.Begin(url.String()))
// extract ID from ids. Existence of an ID enables progress reporting
ID := ""
if len(ids) > 0 {
ID = ids[0]
}
// ctx
ctx, cancel := context.WithTimeout(context.Background(), u.options.Timeout)
defer cancel()
var data string
var err error
var retries int
for {
if toFile {
data, err = u.fetchToFile(ctx, url, reqHdrs, ID, po)
} else {
data, err = u.fetchToString(ctx, url, reqHdrs, ID)
}
if err == nil {
return data, nil
}
// If an error was returned because the context was cancelled, we shouldn't retry.
select {
case <-ctx.Done():
return "", fmt.Errorf("download cancelled during download")
default:
}
retries++
// give up if we reached maxDownloadAttempts
if retries == maxDownloadAttempts {
log.Debugf("Hit max download attempts. Download failed: %v", err)
return "", err
}
switch err := err.(type) {
case DoNotRetry, TagNotFoundError, ImageNotFoundError:
log.Debugf("Error: %s", err.Error())
return "", err
}
// retry downloading again
log.Debugf("Download failed, retrying: %v", err)
delay := retries * 5
ticker := time.NewTicker(time.Second)
selectLoop:
for {
// Do not report progress back if ID is empty
if ID != "" && po != nil {
progress.Updatef(po, ID, "Retrying in %d second%s", delay, (map[bool]string{true: "s"})[delay != 1])
}
select {
case <-ticker.C:
delay--
if delay == 0 {
ticker.Stop()
break selectLoop
}
case <-ctx.Done():
ticker.Stop()
return "", fmt.Errorf("download cancelled during retry delay")
}
}
}
}
func (u *URLFetcher) FetchAuthToken(url *url.URL) (*Token, error) {
defer trace.End(trace.Begin(url.String()))
data, err := u.Fetch(context.Background(), url, nil, false, nil)
if err != nil {
log.Errorf("Download failed: %v", err)
return nil, err
}
token := &Token{}
err = json.Unmarshal([]byte(data), &token)
if err != nil {
log.Errorf("Incorrect token format: %v", err)
return nil, err
}
if token.ExpiresIn == 0 {
token.Expires = time.Now().Add(DefaultTokenExpirationDuration)
} else {
token.Expires = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
}
return token, nil
}
func (u *URLFetcher) fetch(ctx context.Context, url *url.URL, reqHdrs *http.Header, ID string) (io.ReadCloser, http.Header, error) {
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, nil, err
}
u.setBasicAuth(req)
u.setAuthToken(req)
u.setUserAgent(req)
// Add optional request headers
if reqHdrs != nil {
for k, values := range *reqHdrs {
for _, v := range values {
req.Header.Add(k, v)
}
}
}
res, err := ctxhttp.Do(ctx, u.client, req)
if err != nil {
return nil, nil, err
}
u.StatusCode = res.StatusCode
if u.IsNonretryableClientError() {
if u.options.Token == nil && u.IsStatusUnauthorized() {
hdr := res.Header.Get("www-authenticate")
if hdr == "" {
return nil, nil, DoNotRetry{fmt.Errorf("www-authenticate header is missing")}
}
u.OAuthEndpoint, err = u.ExtractOAuthURL(hdr, url)
if err != nil {
return nil, nil, err
}
return nil, nil, DoNotRetry{Err: fmt.Errorf("Authentication required")}
}
if u.IsStatusNotFound() {
err = fmt.Errorf("Not found: %d, URL: %s", u.StatusCode, url)
return nil, nil, TagNotFoundError{Err: err}
}
if u.IsStatusUnauthorized() {
hdr := res.Header.Get("www-authenticate")
// check if image is non-existent (#757)
if strings.Contains(hdr, "error=\"insufficient_scope\"") {
err = fmt.Errorf("image not found")
return nil, nil, ImageNotFoundError{Err: err}
} else if strings.Contains(hdr, "error=\"invalid_token\"") {
return nil, nil, fmt.Errorf("not authorized")
} else {
return nil, nil, fmt.Errorf("Unexpected http code: %d, URL: %s", u.StatusCode, url)
}
}
// for all other non-retryable client errors, grab the error message if there is one (#5951)
err := fmt.Errorf(u.buildRegistryErrMsg(url, res.Body))
return nil, nil, DoNotRetry{Err: err}
}
// FIXME: handle StatusTemporaryRedirect and StatusFound
// for all other unexpected http codes, grab the message out if there is one (#5951)
if !u.IsStatusOK() {
err := fmt.Errorf(u.buildRegistryErrMsg(url, res.Body))
return nil, nil, err
}
log.Debugf("URLFetcher.fetch() - %#v, %#v", res.Body, res.Header)
return res.Body, res.Header, nil
}
// fetch fetches the given URL using ctxhttp. It also streams back the progress bar only when ID is not an empty string.
func (u *URLFetcher) fetchToFile(ctx context.Context, url *url.URL, reqHdrs *http.Header, ID string, po progress.Output) (string, error) {
rdr, hdrs, err := u.fetch(ctx, url, reqHdrs, ID)
if err != nil {
return "", err
}
defer rdr.Close()
// stream progress as json and body into a file - only if we have an ID and a Content-Length header
if contLen := hdrs.Get("Content-Length"); ID != "" && contLen != "" {
cl, cerr := strconv.ParseInt(contLen, 10, 64)
if cerr != nil {
return "", cerr
}
if po != nil {
rdr = progress.NewProgressReader(
ioutils.NewCancelReadCloser(ctx, rdr), po, cl, ID, "Downloading",
)
defer rdr.Close()
} else {
rdr = ioutils.NewCancelReadCloser(ctx, rdr)
}
}
// Create a temporary file and stream the res.Body into it
out, err := ioutil.TempFile(os.TempDir(), ID)
if err != nil {
return "", DoNotRetry{Err: err}
}
defer out.Close()
// Stream into it
_, err = io.Copy(out, rdr)
if err != nil {
log.Errorf("Fetch (%s) to file failed to stream to file: %s", url.String(), err)
// cleanup
defer os.Remove(out.Name())
return "", DoNotRetry{Err: err}
}
// Return the temporary file name
return out.Name(), nil
}
// fetch fetches the given URL using ctxhttp. It also streams back the progress bar only when ID is not an empty string.
func (u *URLFetcher) fetchToString(ctx context.Context, url *url.URL, reqHdrs *http.Header, ID string) (string, error) {
rdr, _, err := u.fetch(ctx, url, reqHdrs, ID)
if err != nil {
log.Errorf("Fetch (%s) to string error: %s", url.String(), err)
return "", err
}
defer rdr.Close()
out := bytes.NewBuffer(nil)
// Stream into it
_, err = io.Copy(out, rdr)
if err != nil {
// cleanup
return "", DoNotRetry{Err: err}
}
// Return the string
return string(out.Bytes()), nil
}
// Ping sends a GET request to an url and returns the header if successful
func (u *URLFetcher) Ping(url *url.URL) (http.Header, error) {
ctx, cancel := context.WithTimeout(context.Background(), u.options.Timeout)
defer cancel()
res, err := ctxhttp.Get(ctx, u.client, url.String())
if err != nil {
return nil, err
}
defer res.Body.Close()
u.StatusCode = res.StatusCode
if u.IsStatusUnauthorized() || u.IsStatusOK() {
log.Debugf("header = %#v", res.Header)
return res.Header, nil
}
return nil, fmt.Errorf("Unexpected http code: %d, URL: %s", u.StatusCode, url)
}
// Head sends a HEAD request to url
func (u *URLFetcher) Head(url *url.URL) (http.Header, error) {
ctx, cancel := context.WithTimeout(context.Background(), u.options.Timeout)
defer cancel()
res, err := ctxhttp.Head(ctx, u.client, url.String())
if err != nil {
return nil, err
}
defer res.Body.Close()
u.StatusCode = res.StatusCode
if u.IsStatusUnauthorized() || u.IsStatusOK() {
return res.Header, nil
}
return nil, fmt.Errorf("Unexpected http code: %d, URL: %s", u.StatusCode, url)
}
// AuthURL returns the Oauth endpoint URL
func (u *URLFetcher) AuthURL() *url.URL {
return u.OAuthEndpoint
}
// IsStatusUnauthorized returns true if status code is StatusUnauthorized
func (u *URLFetcher) IsStatusUnauthorized() bool {
return u.StatusCode == http.StatusUnauthorized
}
// IsStatusOK returns true if status code is StatusOK
func (u *URLFetcher) IsStatusOK() bool {
return u.StatusCode == http.StatusOK
}
// IsStatusNotFound returns true if status code is StatusNotFound
func (u *URLFetcher) IsStatusNotFound() bool {
return u.StatusCode == http.StatusNotFound
}
// IsNonretryableClientError returns true if status code is a nonretryable 4XX error. This includes
// all 4XX errors except 'locked', and 'too many requests'.
func (u *URLFetcher) IsNonretryableClientError() bool {
s := u.StatusCode
return 400 <= s && s < 500 &&
s != http.StatusLocked && s != http.StatusTooManyRequests
}
// buildRegistryErrMsg builds error message for unexpected http code (nonretryable client errors and all other errors)
// and extracts message details from response body stream if there is one (#5951).
func (u *URLFetcher) buildRegistryErrMsg(url *url.URL, respBody io.ReadCloser) string {
errMsg := fmt.Sprintf("Unexpected http code: %d (%s), URL: %s", u.StatusCode, http.StatusText(u.StatusCode), url)
errDetail, err := extractErrResponseMessage(respBody)
if err != nil {
return errMsg
}
if strings.Contains(errDetail, "does not have permission") {
errMsg = fmt.Sprintf("unauthorized: %s", errDetail)
} else {
errMsg += fmt.Sprintf("Message: %s", errDetail)
}
return errMsg
}
// malformedJsonErrFormat is the error format for malformed json response body
// used in function extractErrResponseMessage
var errJSONFormat = fmt.Errorf("error response json has unconventional format")
// extractErrResponseMessage extracts `message` field from error response body stream.
func extractErrResponseMessage(rdr io.ReadCloser) (string, error) {
// close the stream after done
defer rdr.Close()
out := bytes.NewBuffer(nil)
_, err := io.Copy(out, rdr)
if err != nil {
log.Debugf("Error when copying from error response body stream: %s", err)
return "", err
}
res := []byte(out.Bytes())
log.Debugf("Error message json string: %s", string(res))
var errResponse RegistryErrorRespBody
err = json.Unmarshal(res, &errResponse)
if err != nil {
log.Debugf("Error when unmarshaling error response body: %s", err)
return "", err
}
if len(errResponse.Errors) == 0 {
log.Debugf("Error response wrong format. Response body: %s", string(res))
return "", errJSONFormat
}
// grab out every error message
var errString string
for i := range errResponse.Errors {
message := errResponse.Errors[i].Message
// only append the message when there is content in the field
if len(message) > 0 {
if i > 0 {
errString += ", "
}
errString += message
}
}
// if no message available, treat it as a malformed json error
if len(errString) == 0 {
return "", errJSONFormat
}
return errString, nil
}
func (u *URLFetcher) setUserAgent(req *http.Request) {
log.Debugf("Setting user-agent to vic/%s", version.Version)
req.Header.Set("User-Agent", "vic/"+version.Version)
}
func (u *URLFetcher) setBasicAuth(req *http.Request) {
if u.options.Username != "" && u.options.Password != "" {
log.Debugf("Setting BasicAuth: %s", u.options.Username)
req.SetBasicAuth(u.options.Username, u.options.Password)
}
}
func (u *URLFetcher) setAuthToken(req *http.Request) {
if u.options.Token != nil {
req.Header.Set("Authorization", "Bearer "+u.options.Token.Token)
}
}
// ExtractOAuthURL extracts the OAuth url from the www-authenticate header
func (u *URLFetcher) ExtractOAuthURL(hdr string, repository *url.URL) (*url.URL, error) {
tokens := strings.Split(hdr, " ")
if len(tokens) != 2 || strings.ToLower(tokens[0]) != "bearer" {
err := fmt.Errorf("www-authenticate header is corrupted")
return nil, DoNotRetry{Err: err}
}
tokens = strings.Split(tokens[1], ",")
var realm, service, scope string
for _, token := range tokens {
if strings.HasPrefix(token, "realm") {
realm = strings.Trim(token[len("realm="):], "\"")
}
if strings.HasPrefix(token, "service") {
service = strings.Trim(token[len("service="):], "\"")
}
if strings.HasPrefix(token, "scope") {
scope = strings.Trim(token[len("scope="):], "\"")
}
}
if realm == "" {
err := fmt.Errorf("missing realm in bearer auth challenge")
return nil, DoNotRetry{Err: err}
}
if service == "" {
err := fmt.Errorf("missing service in bearer auth challenge")
return nil, DoNotRetry{Err: err}
}
// The scope can be empty if we're not getting a token for a specific repo
if scope == "" && repository != nil {
err := fmt.Errorf("missing scope in bearer auth challenge")
return nil, DoNotRetry{Err: err}
}
auth, err := url.Parse(realm)
if err != nil {
return nil, err
}
q := auth.Query()
q.Add("service", service)
if scope != "" {
q.Add("scope", scope)
}
auth.RawQuery = q.Encode()
return auth, nil
}

View File

@@ -0,0 +1,104 @@
// Copyright 2017 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 fetcher
import (
"bytes"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
const (
errJSONStr401 = `{
"errors":
[{"code":"UNAUTHORIZED",
"message":"authentication required",
"detail":[{"Type":"repository","Class":"","Name":"library/jiowengew","Action":"pull"}]
}]
}
`
multipleErrJSONStr = `{
"errors":
[{"code":"UNAUTHORIZED",
"message":"authentication required",
"detail":[{"Type":"repository","Class":"","Name":"library/jiowengew","Action":"pull"}]
},
{"code":"NOTFOUND",
"message": "image not found",
"detail": "image not found"
}]
}
`
unexpectedStr = `random`
unexpectedJSONStr = `{"nope":"nope"}`
errJSONWithEmptyErrorsField = `{"errors":[]}`
errJSONWithNoMessageField = `{"errors":[{"code":"nope","detail":"nope"}]}`
errJSONWithEmptyMessageField = `{"errors":[{"code":"nope","message":""},{"message":""}]}`
)
func TestExtractErrResponseMessage(t *testing.T) {
// Test set up: create the io streams for testing purposes
// multiple streams needed: these streams only have read ends
singleErrTestStream := ioutil.NopCloser(bytes.NewReader([]byte(errJSONStr401)))
multipleErrTestStream := ioutil.NopCloser(bytes.NewReader([]byte(multipleErrJSONStr)))
unexpectedStrTestStream := ioutil.NopCloser(bytes.NewReader([]byte(unexpectedStr)))
malformedJSONTestStream := ioutil.NopCloser(bytes.NewReader([]byte(unexpectedJSONStr)))
emptyErrorsJSONTestStream := ioutil.NopCloser(bytes.NewReader([]byte(errJSONWithEmptyErrorsField)))
noMessageJSONTestStream := ioutil.NopCloser(bytes.NewReader([]byte(errJSONWithNoMessageField)))
emptyMessageJSONTestStream := ioutil.NopCloser(bytes.NewReader([]byte(errJSONWithEmptyMessageField)))
// Test 1: single error message extraction
msg, err := extractErrResponseMessage(singleErrTestStream)
assert.Nil(t, err, "test: (single error message) extraction should success for well-formatted error json")
assert.Equal(t, "authentication required", msg,
"test: (single error message) extracted message: %s; expected: authentication required", msg)
// Test 2: multiple error message extraction
msg, err = extractErrResponseMessage(multipleErrTestStream)
assert.Nil(t, err, "test: (multiple error messages) extraction should success for well-formatted error json")
assert.Equal(t, "authentication required, image not found", msg,
"test: (multiple error messages) extracted message: %s; expected: authentication required, image not found", msg)
// Test 3: random string in the stream that is not a json
msg, err = extractErrResponseMessage(unexpectedStrTestStream)
assert.Equal(t, "", msg, "test: (non-json string) no message should be extracted")
assert.NotNil(t, err, "test: (non-json string) extraction should fail")
// Test 4: malformed json string
msg, err = extractErrResponseMessage(malformedJSONTestStream)
assert.Equal(t, "", msg, "test: (malformed json string) no message should be extracted")
assert.Equal(t, errJSONFormat, err,
"test: (malformed json string) error: %s; expected error: %s", err)
// Test 5: malformed json with empty `errors` field
msg, err = extractErrResponseMessage(emptyErrorsJSONTestStream)
assert.Equal(t, "", msg, "test: (malformed json string, empty errors field) no message should be extracted")
assert.Equal(t, errJSONFormat, err,
"test: (malformerrJsonFormated json string, empty errors field) error: %s; expected error: %s", err, errJSONFormat)
// Test 6: malformed json with no `message` field
msg, err = extractErrResponseMessage(noMessageJSONTestStream)
assert.Equal(t, "", msg, "test: (malformed json string, no message field) no message should be extracted")
assert.Equal(t, errJSONFormat, err,
"test: (malformed json string, no message field) error: %s; expected error: %s", err, errJSONFormat)
// Test 7: malformed json with empty string in `message` field
msg, err = extractErrResponseMessage(emptyMessageJSONTestStream)
assert.Equal(t, "", msg, "test: (malformed json string, empty message field) no message should be extracted")
assert.Equal(t, errJSONFormat, err,
"test: (malformed json string, empty message field) error: %s; expected error: %s", err, errJSONFormat)
}

75
vendor/github.com/vmware/vic/pkg/filelock/flock.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2016-2017 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 filelock
import (
"os"
"path/filepath"
"sync"
"syscall"
)
// FileLock is a cross-process lock designed to work over FS that supports locking.
type FileLock struct {
LockFile string
LockName string
mu sync.Mutex
fh *os.File
}
// NewFileLock returns a new instance of the file based lock.
// it is a user responsibility to ensure lock name is unique and doesn't collide
// with any other file names in the TEMP directory.
func NewFileLock(lockName string) *FileLock {
return &FileLock{
LockName: lockName,
LockFile: filepath.Join("/var/run/lock", lockName),
}
}
// Acquire grabs the lock. If lock is already acquired, it will block.
// User should check for errors if lock is actually acquired, if lock is not acquired
// it will panic on Release.
func (fl *FileLock) Acquire() error {
fl.mu.Lock()
fh, err := os.Create(fl.LockFile)
if err != nil {
fl.mu.Unlock()
return err
}
fl.fh = fh
err = syscall.Flock(int(fh.Fd()), syscall.LOCK_EX)
if err != nil {
// #nosec: Errors unhandled
fh.Close()
fh = nil
fl.mu.Unlock()
}
return err
}
// Release lock. If lock is not acquired, it will panic.
func (fl *FileLock) Release() error {
if fl.fh == nil {
panic("Attempt to release not acquired lock!")
}
// #nosec: Errors unhandled
syscall.Flock(int(fl.fh.Fd()), syscall.LOCK_UN)
err := fl.fh.Close()
fl.fh = nil
fl.mu.Unlock()
return err
}

114
vendor/github.com/vmware/vic/pkg/filelock/flock_test.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 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 filelock
import (
"strconv"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestFlock(t *testing.T) {
lockName := "test_lock" + strconv.FormatInt(time.Now().UnixNano(), 10)
fl1 := NewFileLock(lockName)
fl2 := NewFileLock(lockName)
st := time.Now()
et := st
var wg sync.WaitGroup
wg.Add(1)
fl1.Acquire()
go func() {
if err := fl2.Acquire(); err != nil {
t.Fatal(err)
}
et = time.Now()
if err := fl2.Release(); err != nil {
t.Fatal(err)
}
wg.Done()
}()
time.Sleep(time.Millisecond * 100)
fl1.Release()
wg.Wait()
delta := (et.UnixNano() - st.UnixNano()) / 1000000
if delta < 50 {
t.Errorf("Wait time is less than 50: %d", delta)
}
}
func TestManyLocks(t *testing.T) {
lockName := "test_lock" + strconv.FormatInt(time.Now().UnixNano(), 10)
baseLock := NewFileLock(lockName)
var wg sync.WaitGroup
if err := baseLock.Acquire(); err != nil {
t.Fatal(err)
}
locksCount := 200
cnt := 0
for i := 0; i < locksCount; i++ {
wg.Add(1)
go func() {
time.Sleep(time.Millisecond)
defer wg.Done()
l := NewFileLock(lockName)
if err := l.Acquire(); err != nil {
t.Error(err)
} else {
cnt++
l.Release()
}
}()
}
baseLock.Release()
wg.Wait()
assert.Equal(t, locksCount, cnt)
}
func TestManyLocksWithNoBaseLock(t *testing.T) {
lockName := "test_lock" + strconv.FormatInt(time.Now().UnixNano(), 10)
var wg sync.WaitGroup
locksCount := 200
cnt := 0
for i := 0; i < locksCount; i++ {
wg.Add(1)
go func() {
time.Sleep(time.Millisecond)
defer wg.Done()
l := NewFileLock(lockName)
if err := l.Acquire(); err != nil {
t.Error(err)
} else {
cnt++
l.Release()
}
}()
}
wg.Wait()
assert.Equal(t, locksCount, cnt)
}

19
vendor/github.com/vmware/vic/pkg/filelock/glocks.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
// Copyright 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 filelock
// LogRotateLockName used to lock processes which can touch /var/log/vic logs to avoid race condition during
// export and logrotate run.
const LogRotateLockName = "logrotate_run.lock"

View File

@@ -0,0 +1,52 @@
// Copyright 2017 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 flags
import (
"flag"
"strconv"
)
type optionalBool struct {
val **bool
}
func (b *optionalBool) Set(s string) error {
v, err := strconv.ParseBool(s)
*b.val = &v
return err
}
func (b *optionalBool) Get() interface{} {
if *b.val == nil {
return nil
}
return **b.val
}
func (b *optionalBool) String() string {
if b.val == nil || *b.val == nil {
return "<nil>"
}
return strconv.FormatBool(**b.val)
}
func (b *optionalBool) IsBoolFlag() bool { return true }
// NewOptionalString returns a flag.Value implementation where there is no default value.
// This avoids sending a default value over the wire as using flag.StringVar() would.
func NewOptionalBool(b **bool) flag.Value {
return &optionalBool{b}
}

View File

@@ -0,0 +1,61 @@
// Copyright 2017 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 flags
import (
"flag"
"testing"
)
func TestOptionalBool(t *testing.T) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
var val *bool
fs.Var(NewOptionalBool(&val), "obool", "optional bool")
b := fs.Lookup("obool")
if b.DefValue != "<nil>" {
t.Fail()
}
if b.Value.String() != "<nil>" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != nil {
t.Fail()
}
b.Value.Set("true")
if b.Value.String() != "true" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != true {
t.Fail()
}
b.Value.Set("false")
if b.Value.String() != "false" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != false {
t.Fail()
}
}

52
vendor/github.com/vmware/vic/pkg/flags/optional_int.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2017 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 flags
import (
"flag"
"strconv"
)
type optionalInt struct {
val **int
}
func (b *optionalInt) Set(s string) error {
v, err := strconv.Atoi(s)
*b.val = &v
return err
}
func (b *optionalInt) Get() interface{} {
if *b.val == nil {
return nil
}
return **b.val
}
func (b *optionalInt) String() string {
if b.val == nil || *b.val == nil {
return "<nil>"
}
return strconv.Itoa(**b.val)
}
func (b *optionalInt) IsBoolFlag() bool { return false }
// NewOptionalString returns a flag.Value implementation where there is no default value.
// This avoids sending a default value over the wire as using flag.StringVar() would.
func NewOptionalInt(i **int) flag.Value {
return &optionalInt{i}
}

View File

@@ -0,0 +1,51 @@
// Copyright 2017 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 flags
import (
"flag"
"testing"
)
func TestOptionalInt(t *testing.T) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
var val *int
fs.Var(NewOptionalInt(&val), "oint", "optional int")
b := fs.Lookup("oint")
if b.DefValue != "<nil>" {
t.Fail()
}
if b.Value.String() != "<nil>" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != nil {
t.Fail()
}
b.Value.Set("1")
if b.Value.String() != "1" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != 1 {
t.Fail()
}
}

View File

@@ -0,0 +1,50 @@
// Copyright 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 flags
import (
"flag"
)
type optionalString struct {
val **string
}
func (b *optionalString) Set(s string) error {
*b.val = &s
return nil
}
func (b *optionalString) Get() interface{} {
if *b.val == nil {
return nil
}
return **b.val
}
func (b *optionalString) String() string {
if b.val == nil || *b.val == nil {
return "<nil>"
}
return **b.val
}
func (b *optionalString) IsBoolFlag() bool { return false }
// NewOptionalString returns a flag.Value implementation where there is no default value.
// This avoids sending a default value over the wire as using flag.StringVar() would.
func NewOptionalString(s **string) flag.Value {
return &optionalString{s}
}

View File

@@ -0,0 +1,51 @@
// Copyright 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 flags
import (
"flag"
"testing"
)
func TestOptionalString(t *testing.T) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
var val *string
fs.Var(NewOptionalString(&val), "obool", "optional bool")
b := fs.Lookup("obool")
if b.DefValue != "<nil>" {
t.Fail()
}
if b.Value.String() != "<nil>" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != nil {
t.Fail()
}
b.Value.Set("test")
if b.Value.String() != "test" {
t.Fail()
}
if b.Value.(flag.Getter).Get() != "test" {
t.Fail()
}
}

64
vendor/github.com/vmware/vic/pkg/flags/shares_flag.go generated vendored Normal file
View File

@@ -0,0 +1,64 @@
// Copyright 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 flags
import (
"fmt"
"strconv"
"strings"
"github.com/vmware/govmomi/vim25/types"
)
type ShareFlag struct {
shares **types.SharesInfo
}
func (s *ShareFlag) Set(val string) error {
if *s.shares == nil {
*s.shares = &types.SharesInfo{}
}
switch val = strings.ToLower(val); val {
case string(types.SharesLevelNormal), string(types.SharesLevelLow), string(types.SharesLevelHigh):
(*s.shares).Level = types.SharesLevel(val)
(*s.shares).Shares = 0
default:
n, err := strconv.Atoi(val)
if err != nil {
return err
}
(*s.shares).Level = types.SharesLevelCustom
(*s.shares).Shares = int32(n)
}
return nil
}
func (s *ShareFlag) String() string {
if s.shares == nil || *s.shares == nil {
return "<nil>"
}
switch (*s.shares).Level {
case types.SharesLevelCustom:
return fmt.Sprintf("%v", (*s.shares).Shares)
default:
return string((*s.shares).Level)
}
}
func NewSharesFlag(shares **types.SharesInfo) *ShareFlag {
return &ShareFlag{shares}
}

View File

@@ -0,0 +1,75 @@
// Copyright 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 flags
import (
"flag"
"strings"
"testing"
"github.com/vmware/govmomi/vim25/types"
)
func TestShareFlag(t *testing.T) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
var val *types.SharesInfo
fs.Var(NewSharesFlag(&val), "shares", "memory shares")
u := fs.Lookup("shares")
if u.DefValue != "<nil>" {
t.Errorf("DefValue: %s", u.DefValue)
}
if u.Value.String() != "<nil>" {
t.Errorf("Value: %s", u.Value)
}
ref := "2000"
u.Value.Set(ref)
if u.Value.String() != strings.ToLower(ref) {
t.Errorf("Value after set: %q", u.Value)
}
if val == nil {
t.Errorf("val is not set")
}
if val.Level != types.SharesLevelCustom {
t.Errorf("shares level is not set correctly: %s", val.Level)
}
if val.Shares != 2000 {
t.Errorf("shares Share is not set correctly: %d", val.Shares)
}
ref = "HIGH"
u.Value.Set(ref)
if u.Value.String() != strings.ToLower(ref) {
t.Errorf("Value after set: %q", u.Value)
}
if val == nil {
t.Errorf("val is not set")
}
if val.Level != types.SharesLevelHigh {
t.Errorf("shares level is not set correctly: %s", val.Level)
}
if val.Shares != 0 {
t.Errorf("shares Share is not set correctly: %d", val.Shares)
}
}

60
vendor/github.com/vmware/vic/pkg/flags/url_flag.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 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 flags
import (
"flag"
"net/url"
"regexp"
)
var schemeMatch = regexp.MustCompile(`^\w+://`)
type URLFlag struct {
u **url.URL
}
// Set will add a protocol (https) if there isn't a :// match. This
// ensures tha url.Parse can correctly extract user:password from
// raw URLs such as user:password@hostname
func (f *URLFlag) Set(s string) error {
var err error
// Default the scheme to https
if !schemeMatch.MatchString(s) {
s = "https://" + s
}
url, err := url.Parse(s)
*f.u = url
return err
}
func (f *URLFlag) Get() interface{} {
return *f.u
}
func (f *URLFlag) String() string {
if f.u == nil || *f.u == nil {
return "<nil>"
}
return (*f.u).String()
}
func (f *URLFlag) IsBoolFlag() bool { return false }
// NewURLFlag returns a flag.Value.
func NewURLFlag(u **url.URL) flag.Value {
return &URLFlag{u}
}

View File

@@ -0,0 +1,60 @@
// Copyright 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 flags
import (
"flag"
"net/url"
"testing"
)
func TestURLFlag(t *testing.T) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
var val *url.URL
fs.Var(NewURLFlag(&val), "url", "url flag")
u := fs.Lookup("url")
if u.DefValue != "<nil>" {
t.Errorf("DefValue: %s", u.DefValue)
}
if u.Value.String() != "<nil>" {
t.Errorf("Value: %s", u.Value)
}
ref := "http://x:y@127.0.0.1"
u.Value.Set(ref)
if u.Value.String() != ref {
t.Errorf("Value after set: %s", u.Value)
}
if val == nil {
t.Errorf("val is not set")
}
if val.String() != ref {
t.Errorf("val is not set correctly: %s", val.String())
}
if val.User == nil {
t.Fatalf("Expected user info to be parsed from url")
}
if val.User.Username() != "x" {
t.Errorf("user was not extracted correctly")
}
}

92
vendor/github.com/vmware/vic/pkg/fs/ext4.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
// Copyright 2016-2017 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 fs
import (
"os/exec"
"strings"
"github.com/docker/docker/pkg/mount"
"github.com/vmware/vic/pkg/trace"
)
// MaxLabelLength is the maximum allowed length of a label for this filesystem
const MaxLabelLength = 15
// Ext4 satisfies the Filesystem interface
type Ext4 struct{}
func NewExt4() *Ext4 {
return &Ext4{}
}
// Mkfs creates an ext4 fs on the given device and applices the given label
func (e *Ext4) Mkfs(op trace.Operation, devPath, label string) error {
defer trace.End(trace.Begin(devPath))
op.Infof("Creating ext4 filesystem on device %s", devPath)
// -v is verbose - this is only useful when things go wrong,
// -F is needed to use the entire disk without prompting
// we can't use -V as well for fs specific stuff as that prevents it actually being done.
// #nosec: Subprocess launching with variable
cmd := exec.Command("/sbin/mkfs.ext4", "-L", label, "-vF", devPath)
if output, err := cmd.CombinedOutput(); err != nil {
op.Errorf("vmdk storage driver failed to format disk %s: %s", devPath, err)
op.Errorf("mkfs output: %s", string(output))
return err
}
op.Debugf("Filesystem created on device %s", devPath)
return nil
}
// Mount mounts an ext4 formatted device at the given path. From the Docker
// mount pkg, args must in the form arg=val.
func (e *Ext4) Mount(op trace.Operation, devPath, targetPath string, options []string) error {
defer trace.End(trace.Begin(devPath))
op.Infof("Mounting %s to %s", devPath, targetPath)
return mount.Mount(devPath, targetPath, "ext4", strings.Join(options, ","))
}
// Unmount unmounts the disk.
// path can be a device path or a mount point
func (e *Ext4) Unmount(op trace.Operation, path string) error {
defer trace.End(trace.Begin(path))
op.Infof("Unmounting %s", path)
return mount.Unmount(path)
}
// SetLabel sets the label of an ext4 formated device
func (e *Ext4) SetLabel(op trace.Operation, devPath, labelName string) error {
defer trace.End(trace.Begin(devPath))
// Warn if truncating label
if len(labelName) > MaxLabelLength {
op.Debugf("Label truncated to %s", labelName[:MaxLabelLength])
}
// #nosec: Subprocess launching with variable
cmd := exec.Command("/sbin/e2label", devPath, labelName[:MaxLabelLength])
if output, err := cmd.CombinedOutput(); err != nil {
op.Errorf("failed to set label %s: %s", devPath, err)
op.Errorf(string(output))
return err
}
return nil
}

82
vendor/github.com/vmware/vic/pkg/fs/xfs.go generated vendored Normal file
View File

@@ -0,0 +1,82 @@
// Copyright 2016-2017 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 fs
import (
"os/exec"
"strings"
"github.com/docker/docker/pkg/mount"
"github.com/vmware/vic/pkg/trace"
)
// XFS satisfies the Filesystem interface
type XFS struct{}
// Create an XFS filesystem manager
func NewXFS() *XFS {
return &XFS{}
}
// Mkfs creates an xfs fs on the given device and applices the given label
func (e *XFS) Mkfs(op trace.Operation, devPath, label string) error {
defer trace.End(trace.Begin(devPath))
op.Infof("Creating xfs filesystem on device %s", devPath)
// #nosec: Subprocess launching with variable
cmd := exec.Command("/sbin/mkfs.xfs", "-n", "ftype=1", "-L", label, devPath)
if output, err := cmd.CombinedOutput(); err != nil {
op.Errorf("vmdk storage driver failed to format disk %s: %s", devPath, err)
op.Errorf("mkfs output: %s", string(output))
return err
}
op.Debugf("Filesystem created on device %s", devPath)
return nil
}
// Mount mounts an xfs formatted device at the given path. From the Docker
// mount pkg, args must in the form arg=val.
func (e *XFS) Mount(op trace.Operation, devPath, targetPath string, options []string) error {
defer trace.End(trace.Begin(devPath))
op.Infof("Mounting %s to %s", devPath, targetPath)
return mount.Mount(devPath, targetPath, "xfs", strings.Join(options, ","))
}
// Unmount unmounts the disk.
// path can be a device path or a mount point
func (e *XFS) Unmount(op trace.Operation, path string) error {
defer trace.End(trace.Begin(path))
op.Infof("Unmounting %s", path)
return mount.Unmount(path)
}
// SetLabel sets the label of an xfs formated device
func (e *XFS) SetLabel(op trace.Operation, devPath, labelName string) error {
defer trace.End(trace.Begin(devPath))
// #nosec: Subprocess launching with variable
cmd := exec.Command("/sbin/e2label", devPath, labelName)
if output, err := cmd.CombinedOutput(); err != nil {
op.Errorf("failed to set label %s: %s", devPath, err)
op.Errorf(string(output))
return err
}
return nil
}

148
vendor/github.com/vmware/vic/pkg/i18n/i18n.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
// Copyright 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 i18n provides functionality to retrieve strings from a
// messages catalog based on a key (string ID).
//
// The messages catalog is loaded from a file containing all of the
// strings for a given language. Here is an example:
//
// English
//
// File: messages/en
//
// greeting=hello
// thanks=thank you
//
// Spanish
//
// File: messages/es
//
// greeting=hola
// thanks=gracias
//
// To use the translation functionality, call LoadLanguage with the desired
// messages file for the local language. Replace uses of string literals
// with a call to T and a given string ID. If the string ID is not present
// in the loaded messages file, the string ID will be returned as the default.
//
// Once initialized, Printer can also be used directly with fmt-like print functions.
// This can be used to include format strings for localization as below:
//
// val := Printer.Sprintf("You know nothing %s", "Jon Snow")
//
// The corresponding messages catalogs would be:
//
// File: messages/en
//
// You know nothing %s=You know nothing %s
//
// File: messages/es
//
// You know nothing %s=No sabes nada %s
//
//
// Packaging message catalogs
//
// Instead of loading a messages file from an on disk file, it is possible to
// load it from a byte array that has been included with the source.
//
// For an executable's messages files in the messages directory, use
// https://github.com/jteeuwen/go-bindata to convert these files to Go source code.
// This MUST be done whenever any file in messages is added or edited.
//
// go-bindata -o messages.go messages
//
// Use the messages by recovering the byte[] corresponding to the file in the
// messages directory and loading it.
//
// data, err := Asset("messages/en")
// i18n.LoadLanguageBytes(language.English, data)
//
package i18n
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
// Printer is the message printer used by T
// to obtain a translated value.
var Printer *message.Printer
// DefaultLang is the default langugage, set to English
var DefaultLang = language.English
func getPrinter(scanner *bufio.Scanner, lang language.Tag) (*message.Printer, error) {
catalog := message.DefaultCatalog
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
s := scanner.Text()
line := strings.SplitN(s, "=", 2)
if len(line) != 2 {
return nil, fmt.Errorf("Invalid line in messages file: %v", line)
}
catalog.SetString(lang, line[0], line[1])
}
if err := scanner.Err(); err != nil {
return nil, err
}
return catalog.Printer(lang), nil
}
// LoadLanguage sets the package level Printer after loading
// the desired language file from the path messagesFile.
func LoadLanguage(lang language.Tag, messagesFile string) error {
f, err := os.Open(messagesFile)
if err != nil {
return fmt.Errorf("Failed to open messages file: %v", err)
}
defer f.Close()
s := bufio.NewScanner(f)
return loadLanguageScanner(lang, s)
}
// LoadLanguageBytes sets the package level Printer after loading
// the desired language data from a byte array
func LoadLanguageBytes(lang language.Tag, messagesData []byte) error {
data := bytes.NewReader(messagesData)
s := bufio.NewScanner(data)
return loadLanguageScanner(lang, s)
}
func loadLanguageScanner(lang language.Tag, s *bufio.Scanner) error {
printer, err := getPrinter(s, lang)
if err == nil {
Printer = printer
}
return err
}
// T (Translate) takes the message key stringID and returns the string
// value that the key maps to in the loaded messages file. If the key
// is not found, stringID is returned as the default value.
func T(stringID string) string {
k := message.Key(stringID, stringID)
if Printer == nil {
panic("Message file has not been loaded. Call LoadLanguage.")
}
return Printer.Sprintf(k)
}

109
vendor/github.com/vmware/vic/pkg/i18n/i18n_test.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
// Copyright 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 i18n
import (
"bufio"
"strings"
"testing"
"golang.org/x/text/language"
)
const testData = `key1=message1 english
key2=message2 english
format key %s=format message english %s`
const badTestData = `key1message1`
func TestGetPrinter(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(testData))
p, err := getPrinter(scanner, language.English)
if p == nil {
t.Errorf("Failed to getPrinter: %s", err)
}
}
func TestGetPrinterInvalid(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(badTestData))
p, err := getPrinter(scanner, language.English)
if err == nil {
t.Errorf("Failed to get an expected error.")
}
if p != nil {
t.Errorf("Got an unexpected printer from getPrinter")
}
}
func TestPrinterFormat(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(testData))
p, _ := getPrinter(scanner, language.English)
Printer = p
testKey := "format key %s"
expectedValue := "format message english HAI"
val := Printer.Sprintf(testKey, "HAI")
if val != expectedValue {
t.Errorf("Got: %s Expected: %s", val, expectedValue)
}
}
func TestTranslate(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(testData))
p, _ := getPrinter(scanner, language.English)
Printer = p
testKey := "key1"
expectedValue := "message1 english"
val := T(testKey)
if val != expectedValue {
t.Errorf("Got: %s Expected: %s", val, expectedValue)
}
}
func TestTranslateDefault(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(testData))
p, _ := getPrinter(scanner, language.English)
Printer = p
testKey := "undefined"
val := T(testKey)
if val != testKey {
t.Errorf("Got: %s Expected: %s", val, testKey)
}
}
func TestUnloadedTranslate(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("Did not receive expected panic.")
}
}()
Printer = nil
testKey := "key1"
T(testKey)
}
func TestLoadLanguageScanner(t *testing.T) {
scanner := bufio.NewScanner(strings.NewReader(testData))
err := loadLanguageScanner(language.English, scanner)
if err != nil {
t.Errorf("Failed to getPrinter")
}
if Printer == nil {
t.Errorf("Failed to set Printer")
}
}

222
vendor/github.com/vmware/vic/pkg/index/index.go generated vendored Normal file
View File

@@ -0,0 +1,222 @@
// Copyright 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 index
import (
"container/list"
"errors"
"fmt"
"sync"
log "github.com/Sirupsen/logrus"
)
var (
ErrNodeNotFound = errors.New("Node not found")
)
type Element interface {
// Returns the identifier of the node
Self() string
// Returns the string respresentation of the nodes parent (usually it's ID)
Parent() string
// Deep copy of the node
Copy() Element
}
type node struct {
Element
parent *node
children []*node
mask uint32
}
func (n *node) addChild(child *node) {
n.children = append(n.children, child)
}
type Index struct {
root *node
lookupTable map[string]*node
m sync.RWMutex
}
func NewIndex() *Index {
return &Index{
lookupTable: make(map[string]*node),
}
}
// Insert inserts a copy of the given node to the tree under the given parent.
func (i *Index) Insert(n Element) error {
defer i.m.Unlock()
i.m.Lock()
_, ok := i.lookupTable[n.Self()]
if ok {
return fmt.Errorf("node %s already exists in index", n.Self())
}
log.Debugf("Index: inserting %s (parent: %s) in index", n.Self(), n.Parent())
newNode := &node{
Element: n.Copy(),
}
if n.Parent() == n.Self() {
if i.root != nil {
return fmt.Errorf("node cannot point to self unless it's root")
}
// set root
i.root = newNode
} else {
p, ok := i.lookupTable[n.Parent()]
if !ok {
return fmt.Errorf("Can't find parent %s", n.Parent())
}
newNode.parent = p
p.addChild(newNode)
}
i.lookupTable[n.Self()] = newNode
return nil
}
// Get returns a Copy of the named node.
func (i *Index) Get(nodeID string) (Element, error) {
defer i.m.RUnlock()
i.m.RLock()
n, ok := i.lookupTable[nodeID]
if !ok {
return nil, ErrNodeNotFound
}
return n.Copy(), nil
}
// HasChildren returns whether a node has children or not
func (i *Index) HasChildren(nodeID string) (bool, error) {
defer i.m.RUnlock()
i.m.RLock()
n, ok := i.lookupTable[nodeID]
if !ok {
return false, ErrNodeNotFound
}
return (len(n.children) > 0), nil
}
func (i *Index) List() ([]Element, error) {
defer i.m.RUnlock()
i.m.RLock()
nodes := make([]Element, 0, len(i.lookupTable))
for _, v := range i.lookupTable {
nodes = append(nodes, v.Copy())
}
return nodes, nil
}
// Delete deletes a leaf node
func (i *Index) Delete(nodeID string) (Element, error) {
defer i.m.Unlock()
i.m.Lock()
return i.deleteNode(nodeID)
}
func (i *Index) deleteNode(nodeID string) (Element, error) {
log.Debugf("deleting %s", nodeID)
n, ok := i.lookupTable[nodeID]
if !ok {
return nil, fmt.Errorf("Node %s not found", nodeID)
}
if len(n.children) != 0 {
return nil, fmt.Errorf("Node %s has children %#v", nodeID, n.children)
}
// remove the reference to the node from its parent
parent := n.parent
var deleted bool
for idx, child := range parent.children {
if child.Self() == nodeID {
parent.children = append(parent.children[:idx], parent.children[idx+1:]...)
deleted = true
}
}
if !deleted {
err := fmt.Errorf("%s not found in tree", nodeID)
log.Errorf("%s", err)
return nil, err
}
// remove from the lookup table
delete(i.lookupTable, nodeID)
n.parent = nil
return n.Element, nil
}
type iterflag int
const (
NOOP iterflag = iota
STOP
)
type visitor func(Element) (iterflag, error)
func (i *Index) bfs(root *node, visitFunc visitor) error {
defer i.m.Unlock()
i.m.Lock()
// XXX Look into parallelizing this without breaking API boundaries.
return i.bfsworker(root, func(n *node) (iterflag, error) { return visitFunc(n.Element) })
}
func (i *Index) bfsworker(root *node, visitFunc func(*node) (iterflag, error)) error {
queue := list.New()
queue.PushBack(root)
for queue.Len() > 0 {
n := queue.Remove(queue.Front()).(*node)
flag, err := visitFunc(n)
if err != nil {
return err
}
if flag == STOP {
return nil
}
for _, child := range n.children {
queue.PushBack(child)
}
}
return nil
}

203
vendor/github.com/vmware/vic/pkg/index/index_test.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
// Copyright 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 index
import (
"strconv"
"sync"
"testing"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
type mockEntry struct {
number int
parent int
}
func newMockEntry(n, parent int) *mockEntry {
return &mockEntry{
number: n,
parent: parent,
}
}
func (m *mockEntry) Self() string {
return strconv.Itoa(m.number)
}
func (m *mockEntry) Parent() string {
return strconv.Itoa(m.parent)
}
func (m *mockEntry) Copy() Element {
return &mockEntry{
number: m.number,
parent: m.parent,
}
}
func TestInsertAndGet(t *testing.T) {
i := NewIndex()
root := newMockEntry(0, 0)
err := i.Insert(root)
if !assert.NoError(t, err) {
return
}
max := 10
// insert
for n := 1; n < max; n++ {
err = i.Insert(newMockEntry(n, n-1))
if !assert.NoError(t, err) {
return
}
}
// add an entry that already exists
err = i.Insert(newMockEntry(1, 1))
if !assert.Error(t, err) {
return
}
// check children
for _, node := range i.lookupTable {
if node.Self() != "9" && !assert.True(t, len(node.children) > 0) {
return
}
}
// Now get
wg := sync.WaitGroup{}
wg.Add(max)
for idx := 0; idx < max; idx++ {
go func(idx int) {
defer wg.Done()
_, err := i.Get(strconv.Itoa(idx))
if !assert.NoError(t, err) {
return
}
}(idx)
}
wg.Wait()
}
func TestBFS(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
i := NewIndex()
root := newMockEntry(0, 0)
i.Insert(root)
branches := 10
expectedNodes := createTree(t, i, branches)
expectedNodes[0] = root
var count int
err := i.bfs(i.root, func(n Element) (iterflag, error) {
mynode := n.(*mockEntry)
t.Logf("%#v\n", mynode)
// will point to different elements but check their values
assert.Equal(t, expectedNodes[mynode.number], mynode)
count++
return NOOP, nil
})
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, (4*(branches-1))+1, count) {
return
}
}
func TestDelete(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
i := NewIndex()
root := newMockEntry(0, 0)
i.Insert(root)
branches := 10
expectedNodes := createTree(t, i, branches)
expectedNodes[0] = root
// list what we added
before, err := i.List()
if !assert.NoError(t, err) || !assert.True(t, len(before) > 0) {
return
}
// is a leaf, should delete without issue
n, err := i.Delete("9000")
if !assert.NoError(t, err) || !assert.NotNil(t, n) {
return
}
// check it's gone
_, ok := i.lookupTable["9000"]
if !assert.False(t, ok) {
return
}
// isn't a leaf, should throw an error
n, err = i.Delete("9")
if !assert.Error(t, err) || !assert.Nil(t, n) {
return
}
// isn't in the index
n, err = i.Delete("foo")
if !assert.Error(t, err) || !assert.Nil(t, n) {
return
}
// list once more and make sure we nuked the image
after, err := i.List()
if !assert.NoError(t, err) || !assert.Equal(t, len(before), len(after)+1) {
return
}
}
func createTree(t *testing.T, i *Index, count int) map[int]*mockEntry {
expectedNodes := make(map[int]*mockEntry)
insert := func(n *mockEntry) {
if !assert.NoError(t, i.Insert(n)) {
return
}
}
// insert and create 3 children for each branch
for n := 1; n < count; n++ {
expectedNodes[n] = newMockEntry(n, 0)
expectedNodes[n*10] = newMockEntry(n*10, n)
expectedNodes[n*100] = newMockEntry(n*100, n)
expectedNodes[n*1000] = newMockEntry(n*1000, n)
insert(expectedNodes[n])
insert(expectedNodes[n*10])
insert(expectedNodes[n*100])
insert(expectedNodes[n*1000])
}
return expectedNodes
}

212
vendor/github.com/vmware/vic/pkg/ip/ip.go generated vendored Normal file
View File

@@ -0,0 +1,212 @@
// Copyright 2016-2017 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 ip
import (
"bytes"
"fmt"
"math"
"net"
"strconv"
"strings"
)
type Range struct {
FirstIP net.IP `vic:"0.1" scope:"read-only" key:"first"`
LastIP net.IP `vic:"0.1" scope:"read-only" key:"last"`
}
func NewRange(first, last net.IP) *Range {
return &Range{FirstIP: first, LastIP: last}
}
func (i *Range) Overlaps(other Range) bool {
if (bytes.Compare(i.FirstIP, other.FirstIP) <= 0 && bytes.Compare(other.FirstIP, i.LastIP) <= 0) ||
(bytes.Compare(i.FirstIP, other.LastIP) <= 0 && bytes.Compare(other.FirstIP, i.LastIP) <= 0) {
return true
}
return false
}
func (i *Range) String() string {
n := i.Network()
if n == nil {
return fmt.Sprintf("%s-%s", i.FirstIP, i.LastIP)
}
return n.String()
}
func (i *Range) Equal(other *Range) bool {
return i.FirstIP.Equal(other.FirstIP) && i.LastIP.Equal(other.LastIP)
}
// Network returns the network that this range represents, if any
func (i *Range) Network() *net.IPNet {
// only works for ipv4
first := i.FirstIP.To4()
last := i.LastIP.To4()
diff := net.IPv4(0, 0, 0, 0).To4()
for j := 0; j < net.IPv4len; j++ {
diff[j] = first[j] ^ last[j]
}
var m uint
for j := net.IPv4len - 1; j >= 0; j-- {
var k uint
for ; k < 8; k++ {
if diff[j]>>k == 0 {
break
}
}
m += k
if k < 8 {
break
}
}
if m == 0 {
return nil
}
mask := net.CIDRMask(32-int(m), 32)
for j, f := range first {
l := f | ^mask[j]
if l != last[j] {
return nil
}
}
return &net.IPNet{IP: first, Mask: mask}
}
func ParseRange(r string) *Range {
var first, last net.IP
// check if its a CIDR
// #nosec: Errors unhandled
_, ipnet, _ := net.ParseCIDR(r)
if ipnet != nil {
first = ipnet.IP
last := make(net.IP, len(first))
for i, f := range first {
last[i] = f | ^ipnet.Mask[i]
}
return &Range{
FirstIP: first,
LastIP: last,
}
}
comps := strings.Split(r, "-")
if len(comps) != 2 {
return nil
}
first = net.ParseIP(comps[0])
if first == nil {
return nil
}
last = net.ParseIP(comps[1])
if last == nil {
var end int
end, err := strconv.Atoi(comps[1])
if err != nil || end <= int(first[15]) || end > math.MaxUint8 {
return nil
}
last = net.IPv4(first[12], first[13], first[14], byte(end))
}
if bytes.Compare(first, last) > 0 {
return nil
}
return &Range{
FirstIP: first,
LastIP: last,
}
}
// MarshalText implements the encoding.TextMarshaler interface
func (i *Range) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UmarshalText implements the encoding.TextUnmarshaler interface
func (i *Range) UnmarshalText(text []byte) error {
s := string(text)
r := ParseRange(s)
if r == nil {
return fmt.Errorf("parse error: %s", s)
}
*i = *r
return nil
}
// ParseIPandMask parses a CIDR format address (e.g. 1.1.1.1/8)
func ParseIPandMask(s string) (net.IPNet, error) {
var i net.IPNet
ip, ipnet, err := net.ParseCIDR(s)
if err != nil {
return i, err
}
i.IP = ip
i.Mask = ipnet.Mask
return i, nil
}
// Empty determines if net.IPNet is empty
func Empty(i net.IPNet) bool {
return i.IP == nil && i.Mask == nil
}
func IsUnspecifiedIP(ip net.IP) bool {
return len(ip) == 0 || ip.IsUnspecified()
}
func IsUnspecifiedSubnet(n *net.IPNet) bool {
if n == nil || IsUnspecifiedIP(n.IP) {
return true
}
ones, bits := n.Mask.Size()
return bits == 0 || ones == 0
}
// AllZerosAddr returns the all-zeros address for a subnet
func AllZerosAddr(subnet *net.IPNet) net.IP {
return subnet.IP.Mask(subnet.Mask)
}
// AllOnesAddr returns the all-ones address for a subnet
func AllOnesAddr(subnet *net.IPNet) net.IP {
ones := net.IPv4(0, 0, 0, 0)
ip := subnet.IP.To16()
for i := range ip[12:] {
ones[12+i] = ip[12+i] | ^subnet.Mask[i]
}
return ones
}
func IsRoutableIP(ip net.IP, subnet *net.IPNet) bool {
return subnet.Contains(ip) && !ip.Equal(AllZerosAddr(subnet)) && !ip.Equal(AllOnesAddr(subnet))
}

159
vendor/github.com/vmware/vic/pkg/ip/ip_test.go generated vendored Normal file
View File

@@ -0,0 +1,159 @@
// Copyright 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 ip
import (
"fmt"
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRangeMarshalText(t *testing.T) {
var tests = []struct {
ipr *Range
s string
err error
}{
{&Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, "10.10.10.10-10.10.10.24", nil},
}
for _, te := range tests {
b, err := te.ipr.MarshalText()
if te.err != nil && err == nil {
t.Fatalf("MarshalText() => (%v, nil) want (nil, err)", b)
continue
}
if string(b) != te.s {
t.Fatalf("MarshalText() => (%s, %s) want (%s, nil)", string(b), err, te.s)
}
}
}
func TestRangeUnmarshalText(t *testing.T) {
var tests = []struct {
r string
ipr *Range
err error
}{
{"10.10.10.10-9", nil, fmt.Errorf("")},
{"10.10.10.10-10.10.10.9", nil, fmt.Errorf("")},
{"10.10.10.10-24", &Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, nil},
{"10.10.10.10-10.10.10.24", &Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, nil},
{"10.10.10.0/24", &Range{net.ParseIP("10.10.10.0"), net.ParseIP("10.10.10.255")}, nil},
}
for _, te := range tests {
ipr := &Range{}
err := ipr.UnmarshalText([]byte(te.r))
if te.err != nil {
if err == nil {
t.Fatalf("UnmarshalText(%s) => nil want err", te.r)
}
continue
}
if !te.ipr.FirstIP.Equal(ipr.FirstIP) ||
!te.ipr.LastIP.Equal(ipr.LastIP) {
t.Fatalf("UnmarshalText(%s) => %#v want %#v", te.r, ipr, te.ipr)
}
}
}
func TestRangeOverlap(t *testing.T) {
var tests = []struct {
ipr1, ipr2 Range
res bool
}{
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, true},
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.15"), net.ParseIP("10.10.10.24")}, true},
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.15"), net.ParseIP("10.10.10.20")}, true},
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.9"), net.ParseIP("10.10.10.25")}, true},
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.24"), net.ParseIP("10.10.10.25")}, true},
{Range{net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")}, Range{net.ParseIP("10.10.10.25"), net.ParseIP("10.10.10.50")}, false},
}
for _, te := range tests {
res := te.ipr1.Overlaps(te.ipr2)
if res != te.res {
t.Fatalf("(%s).Overlaps(%s) => %t want %t", te.ipr1, te.ipr2, res, te.res)
}
}
}
func TestAllZerosAddr(t *testing.T) {
var tests = []struct {
subnet *net.IPNet
addr net.IP
}{
{&net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}, net.ParseIP("192.168.0.0")},
{&net.IPNet{IP: net.ParseIP("192.168.100.0"), Mask: net.CIDRMask(24, 32)}, net.ParseIP("192.168.100.0")},
}
for _, te := range tests {
addr := AllZerosAddr(te.subnet)
if !te.addr.Equal(addr) {
t.Fatalf("AllZerosAddr(%s) => got %s, want %s", te.subnet, addr, te.addr)
}
}
}
func TestAllOnesAddr(t *testing.T) {
var tests = []struct {
subnet *net.IPNet
addr net.IP
}{
{&net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)}, net.ParseIP("192.168.255.255")},
{&net.IPNet{IP: net.ParseIP("192.168.100.0"), Mask: net.CIDRMask(24, 32)}, net.ParseIP("192.168.100.255")},
}
for _, te := range tests {
addr := AllOnesAddr(te.subnet)
if !te.addr.Equal(addr) {
t.Fatalf("AllOnesAddr(%s) => got %s, want %s", te.subnet, addr, te.addr)
}
}
}
func TestRangeNetwork(t *testing.T) {
var tests = []struct {
r *Range
n *net.IPNet
}{
{ParseRange("10.10.10.10/24"), &net.IPNet{IP: net.ParseIP("10.10.10.0"), Mask: net.CIDRMask(24, 32)}},
{ParseRange("10.10.10.10-10.10.14.11"), nil},
{ParseRange("10.10.10.10-10.10.10.11"), &net.IPNet{IP: net.ParseIP("10.10.10.10"), Mask: net.CIDRMask(31, 32)}},
}
for _, te := range tests {
n := te.r.Network()
if te.n != nil {
assert.NotNil(t, n)
} else {
assert.Nil(t, n)
continue
}
if !n.IP.Equal(te.n.IP) {
assert.FailNow(t, fmt.Sprintf("got %s, want %s", n, te.n))
}
assert.EqualValues(t, n.Mask, te.n.Mask)
}
}

75
vendor/github.com/vmware/vic/pkg/kvstore/backend.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 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 kvstore
import (
"context"
"fmt"
"io"
"net/http"
"os"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/stringutils"
"github.com/vmware/vic/pkg/vsphere/datastore"
)
type Backend interface {
// Save saves data to the specified path
Save(ctx context.Context, r io.Reader, path string) error
// Load loads data from the specified path
Load(ctx context.Context, path string) (io.ReadCloser, error)
}
func NewDatastoreBackend(ds *datastore.Helper) Backend {
return &dsBackend{ds: ds}
}
type dsBackend struct {
ds *datastore.Helper
}
// Save saves data to the specified path
func (d *dsBackend) Save(ctx context.Context, r io.Reader, path string) error {
// upload to an ephemeral file
tmpfile := fmt.Sprintf("%s-%s.tmp", path, stringutils.GenerateRandomAlphaOnlyString(10))
if err := d.ds.Upload(ctx, r, tmpfile); err != nil {
return err
}
log.Debugf("kv store upload of file (%s) was successful", tmpfile)
return d.ds.Mv(ctx, tmpfile, path)
}
func toOsError(err error) error {
switch err.Error() {
case fmt.Sprintf("%d %s", http.StatusNotFound, http.StatusText(http.StatusNotFound)):
return os.ErrNotExist
}
return err
}
// Load loads data from the specified path
func (d *dsBackend) Load(ctx context.Context, path string) (io.ReadCloser, error) {
rc, err := d.ds.Download(ctx, path)
if err != nil {
return nil, toOsError(err)
}
log.Debugf("kv store download of file (%s) was successful", path)
return rc, err
}

216
vendor/github.com/vmware/vic/pkg/kvstore/kvstore.go generated vendored Normal file
View File

@@ -0,0 +1,216 @@
// Copyright 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 kvstore
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
"sync"
log "github.com/Sirupsen/logrus"
)
var (
ErrKeyNotFound = errors.New("key not found")
)
// This package implements a very basic key/value store. It is up to the
// caller to provision the namespace.
type KeyValueStore interface {
// Set adds a new key or modifies an existing key in the key-value store
Put(ctx context.Context, key string, value []byte) error
// Get gets an existing key in the key-value store. Returns ErrKeyNotFound
// if key does not exist the key-value store.
Get(key string) ([]byte, error)
// List lists the key-value pairs whose keys match the regular expression
// passed in.
List(re string) (map[string][]byte, error)
// Delete deletes existing keys from the key-value store. Returns ErrKeyNotFound
// if key does not exist the key-value store.
Delete(ctx context.Context, key string) error
// Save saves the key-value store data to the backend.
Save(ctx context.Context) error
// Name returns the unique identifier/name for the key-value store. This is
// used to determine the path that is passed to the backend operations.
Name() string
}
type kv struct {
b Backend
kv map[string][]byte
name string
l sync.RWMutex
}
func fileName(name string) string {
return fmt.Sprintf("%s.dat", name)
}
// Create a new KeyValueStore instance using the given Backend with the given
// file. If the file exists on the Backend, it is restored.
func NewKeyValueStore(ctx context.Context, store Backend, name string) (KeyValueStore, error) {
p := &kv{
b: store,
kv: make(map[string][]byte),
name: name,
}
if err := p.restore(ctx); err != nil {
return nil, err
}
log.Infof("NewKeyValueStore(%s) restored %d keys", name, len(p.kv))
return p, nil
}
func (p *kv) Name() string {
return p.name
}
func (p *kv) restore(ctx context.Context) error {
p.l.Lock()
defer p.l.Unlock()
rc, err := p.b.Load(ctx, fileName(p.name))
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer rc.Close()
if err = json.NewDecoder(rc).Decode(&p.kv); err != nil {
return err
}
return nil
}
// Set a key to the KeyValueStore with the given value. If they key already
// exists, the value is overwritten.
func (p *kv) Put(ctx context.Context, key string, value []byte) error {
p.l.Lock()
defer p.l.Unlock()
// get the old value in case we need to roll back
oldvalue, ok := p.kv[key]
if ok && bytes.Compare(oldvalue, value) == 0 {
// NOOP
return nil
}
p.kv[key] = value
if err := p.save(ctx); err != nil && ok {
// revert if failure
p.kv[key] = oldvalue
return err
}
return nil
}
// Get retrieves a key from the KeyValueStore.
func (p *kv) Get(key string) ([]byte, error) {
p.l.RLock()
defer p.l.RUnlock()
v, ok := p.kv[key]
if !ok {
return []byte{}, ErrKeyNotFound
}
return v, nil
}
func (p *kv) List(re string) (map[string][]byte, error) {
p.l.RLock()
defer p.l.RUnlock()
regex, err := regexp.Compile(re)
if err != nil {
return nil, err
}
kv := make(map[string][]byte)
for k, v := range p.kv {
if regex.MatchString(k) {
kv[k] = v
}
}
if len(kv) == 0 {
return nil, ErrKeyNotFound
}
return kv, nil
}
// Delete removes a key from the KeyValueStore.
func (p *kv) Delete(ctx context.Context, key string) error {
p.l.Lock()
defer p.l.Unlock()
oldvalue, ok := p.kv[key]
if !ok {
return ErrKeyNotFound
}
delete(p.kv, key)
if err := p.save(ctx); err != nil {
// restore the key
p.kv[key] = oldvalue
return err
}
return nil
}
// Save persists the KeyValueStore to the Backend.
func (p *kv) Save(ctx context.Context) error {
p.l.Lock()
defer p.l.Unlock()
return p.save(ctx)
}
func (p *kv) save(ctx context.Context) error {
buf, err := json.Marshal(p.kv)
if err != nil {
return err
}
r := bytes.NewReader(buf)
if err = p.b.Save(ctx, r, fileName(p.name)); err != nil {
return fmt.Errorf("Error uploading %s: %s", fileName(p.name), err)
}
return nil
}

View File

@@ -0,0 +1,170 @@
// Copyright 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 kvstore
import (
"context"
"fmt"
"strconv"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
func save(t *testing.T, kv KeyValueStore, key string, expectedvalue []byte) {
op := trace.NewOperation(context.Background(), "save")
if !assert.NoError(t, kv.Put(op, key, expectedvalue)) {
return
}
}
func get(t *testing.T, kv KeyValueStore, key string, expectedval []byte) {
// get the value we added
v, err := kv.Get(key)
if !assert.NoError(t, err) || !assert.NotNil(t, v) || !assert.Equal(t, expectedval, v) {
return
}
}
func TestAddAndGet(t *testing.T) {
mb := &MockBackend{}
op := trace.NewOperation(context.Background(), "testaddsaveget")
// Save some entries in parallel
entries := 500
wg := sync.WaitGroup{}
wg.Add(entries)
expected := make(map[string][]byte)
firstkv, err := NewKeyValueStore(op, mb, "datfile")
if !assert.NoError(t, err) || !assert.NotNil(t, firstkv) {
return
}
for i := 0; i < entries; i++ {
k := fmt.Sprintf("key-%d", i)
v := []byte(strconv.Itoa(i))
expected[k] = v
go func() {
defer wg.Done()
save(t, firstkv, k, v)
get(t, firstkv, k, v)
}()
}
wg.Wait()
if t.Failed() {
return
}
// Restart the kv store by creating a new one and attempt to get the same
// entries.
secondkv, err := NewKeyValueStore(op, mb, "datfile")
if !assert.NoError(t, err) || !assert.NotNil(t, secondkv) {
return
}
wg.Add(entries)
for k, v := range expected {
go func(key string, value []byte) {
defer wg.Done()
get(t, secondkv, key, value)
}(k, v)
}
wg.Wait()
if t.Failed() {
return
}
// Ovewrite all of the values and verify again
wg.Add(entries)
for k := range expected {
newval := []byte("ddddd")
expected[k] = newval
go func(key string, value []byte) {
defer wg.Done()
save(t, secondkv, key, value)
get(t, secondkv, key, value)
}(k, newval)
}
wg.Wait()
if t.Failed() {
return
}
// Restart and verify the overwritten values match the expected
thirdkv, err := NewKeyValueStore(op, mb, "datfile")
if !assert.NoError(t, err) || !assert.NotNil(t, thirdkv) {
return
}
wg.Add(entries)
for k, v := range expected {
go func(key string, value []byte) {
defer wg.Done()
get(t, thirdkv, key, value)
}(k, v)
}
wg.Wait()
if t.Failed() {
return
}
// Remove all of the entries and assert nothing can be found
wg.Add(entries)
for k := range expected {
go func(key string) {
defer wg.Done()
if !assert.NoError(t, thirdkv.Delete(op, key)) {
return
}
_, err := thirdkv.Get(key)
if !assert.Error(t, err) {
return
}
}(k)
}
wg.Wait()
if t.Failed() {
return
}
// Check the kv is empty after restart
fourthkv, err := NewKeyValueStore(op, mb, "datfile")
wg.Add(entries)
for k := range expected {
go func(key string) {
defer wg.Done()
_, err := fourthkv.Get(key)
if !assert.Error(t, err) {
return
}
}(k)
}
wg.Wait()
}

158
vendor/github.com/vmware/vic/pkg/kvstore/mocks.go generated vendored Normal file
View File

@@ -0,0 +1,158 @@
// Copyright 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 kvstore
import (
"bytes"
"context"
"io"
"io/ioutil"
"os"
"github.com/stretchr/testify/mock"
)
type MockBackend struct {
buf []byte
}
// Creates path and ovewrites whatever is there
func (m *MockBackend) Save(ctx context.Context, r io.Reader, pth string) error {
buf, err := ioutil.ReadAll(r)
if err != nil {
return err
}
m.buf = buf
return nil
}
func (m *MockBackend) Load(ctx context.Context, pth string) (io.ReadCloser, error) {
if len(m.buf) == 0 {
return nil, os.ErrNotExist
}
return ioutil.NopCloser(bytes.NewReader(m.buf)), nil
}
// MockKeyValueStore is an autogenerated mock type for the KeyValueStore type
type MockKeyValueStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: ctx, key
func (_m *MockKeyValueStore) Delete(ctx context.Context, key string) error {
ret := _m.Called(ctx, key)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, key)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: key
func (_m *MockKeyValueStore) Get(key string) ([]byte, error) {
ret := _m.Called(key)
var r0 []byte
if rf, ok := ret.Get(0).(func(string) []byte); ok {
r0 = rf(key)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(key)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: re
func (_m *MockKeyValueStore) List(re string) (map[string][]byte, error) {
ret := _m.Called(re)
var r0 map[string][]byte
if rf, ok := ret.Get(0).(func(string) map[string][]byte); ok {
r0 = rf(re)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string][]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(re)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Name provides a mock function with given fields:
func (_m *MockKeyValueStore) Name() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Save provides a mock function with given fields: ctx
func (_m *MockKeyValueStore) Save(ctx context.Context) error {
ret := _m.Called(ctx)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
r0 = rf(ctx)
} else {
r0 = ret.Error(0)
}
return r0
}
// Set provides a mock function with given fields: ctx, key, value
func (_m *MockKeyValueStore) Put(ctx context.Context, key string, value []byte) error {
ret := _m.Called(ctx, key, value)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok {
r0 = rf(ctx, key, value)
} else {
r0 = ret.Error(0)
}
return r0
}
var _ KeyValueStore = (*MockKeyValueStore)(nil)

101
vendor/github.com/vmware/vic/pkg/log/log.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
// Copyright 2016-2017 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 log
import (
"sync"
"github.com/Sirupsen/logrus"
"github.com/vmware/vic/pkg/log/syslog"
)
type LoggingConfig struct {
Formatter logrus.Formatter
Level logrus.Level
Syslog *SyslogConfig
}
type SyslogConfig struct {
Network string
RAddr string
Tag string
Priority syslog.Priority
}
var initializer struct {
once sync.Once
err error
}
func NewLoggingConfig() *LoggingConfig {
return &LoggingConfig{
Formatter: NewTextFormatter(),
Level: logrus.InfoLevel,
}
}
func Init(cfg *LoggingConfig) error {
initializer.once.Do(
func() {
var err error
logger := logrus.StandardLogger()
f := logger.Formatter
l := logger.Level
defer func() {
initializer.err = err
if err != nil {
// revert
logrus.SetFormatter(f)
logrus.SetLevel(l)
}
}()
logrus.SetFormatter(cfg.Formatter)
logrus.SetLevel(cfg.Level)
logrus.Debugf("log cfg: %+v", *cfg)
hook, err := CreateSyslogHook(cfg)
if err == nil && hook != nil {
logrus.AddHook(hook)
}
})
return initializer.err
}
func CreateSyslogHook(cfg *LoggingConfig) (logrus.Hook, error) {
if cfg.Syslog == nil {
return nil, nil
}
hook, err := syslog.NewHook(
cfg.Syslog.Network,
cfg.Syslog.RAddr,
cfg.Syslog.Priority,
cfg.Syslog.Tag,
)
if err != nil {
// not a fatal error, so just log a warning
logrus.Warnf("error trying to initialize syslog: %s", err)
}
return hook, err
}
func (l *LoggingConfig) SetLogLevel(level uint8) {
l.Level = logrus.Level(level)
}

23
vendor/github.com/vmware/vic/pkg/log/syslog/dialer.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2016-2017 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 syslog
import "time"
const defaultDialTimeout = 1 * time.Minute
type dialer interface {
dial() (Writer, error)
}

View File

@@ -0,0 +1,64 @@
// Copyright 2016-2017 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 syslog
import (
"fmt"
"net"
"os"
"time"
)
type formatter interface {
Format(p Priority, ts time.Time, hostname, tag, msg string) string
}
type localFormatter struct{}
func (l *localFormatter) Format(p Priority, ts time.Time, _, tag, msg string) string {
return fmt.Sprintf("<%d>%s %s[%d]: %s", p, ts.Format(time.Stamp), tag, os.Getpid(), msg)
}
type rfc3164Formatter struct{}
func (c *rfc3164Formatter) Format(p Priority, ts time.Time, hostname, tag, msg string) string {
return fmt.Sprintf("<%d>%s %s %s[%d]: %s", p, ts.Format(time.RFC3339), hostname, tag, os.Getpid(), msg)
}
type netDialer interface {
dial() (net.Conn, error)
}
type defaultNetDialer struct {
network, address string
}
func (d *defaultNetDialer) dial() (net.Conn, error) {
Logger.Infof("trying to connect to %s://%s", d.network, d.address)
return net.DialTimeout(d.network, d.address, defaultDialTimeout)
}
func newFormatter(network string, f Format) formatter {
if network == "" {
return &localFormatter{}
}
switch f {
case RFC3164:
return &rfc3164Formatter{}
}
return nil
}

View File

@@ -0,0 +1,56 @@
// Copyright 2016-2017 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 syslog
import (
"fmt"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNewFormatter(t *testing.T) {
f := newFormatter("", RFC3164)
assert.IsType(t, &localFormatter{}, f)
f = newFormatter("tcp", RFC3164)
assert.IsType(t, &rfc3164Formatter{}, f)
f = newFormatter("tcp", 123)
assert.Nil(t, f)
}
func TestFormatterFormats(t *testing.T) {
var tests = []struct {
format string
tsLayout string
local bool
f formatter
}{
{"<%d>%s %s[%d]: %s", time.Stamp, true, &localFormatter{}},
{"<%d>%s %s %s[%d]: %s", time.RFC3339, false, &rfc3164Formatter{}},
}
for _, te := range tests {
ts := time.Now()
if te.local {
assert.Equal(t, fmt.Sprintf(te.format, priority, ts.Format(te.tsLayout), tag, os.Getpid(), "foo"), te.f.Format(priority, ts, "host", tag, "foo"))
} else {
assert.Equal(t, fmt.Sprintf(te.format, priority, ts.Format(te.tsLayout), "host", tag, os.Getpid(), "foo"), te.f.Format(priority, ts, "host", tag, "foo"))
}
}
}

71
vendor/github.com/vmware/vic/pkg/log/syslog/hook.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2016-2017 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 syslog
import "github.com/Sirupsen/logrus"
type Hook struct {
writer Writer
}
func NewHook(network, raddr string, priority Priority, tag string) (*Hook, error) {
return newHook(&defaultDialer{
network: network,
raddr: raddr,
priority: priority,
tag: tag,
})
}
func newHook(d dialer) (*Hook, error) {
hook := &Hook{}
var err error
hook.writer, err = d.dial()
if err != nil {
return nil, err
}
return hook, nil
}
func (hook *Hook) Fire(entry *logrus.Entry) error {
return hook.writeEntry(entry)
}
func (hook *Hook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (hook *Hook) writeEntry(entry *logrus.Entry) error {
// just use the message since the timestamp
// is added by the syslog package
line := entry.Message
switch entry.Level {
case logrus.PanicLevel, logrus.FatalLevel:
return hook.writer.Crit(line)
case logrus.ErrorLevel:
return hook.writer.Err(line)
case logrus.WarnLevel:
return hook.writer.Warning(line)
case logrus.InfoLevel:
return hook.writer.Info(line)
case logrus.DebugLevel:
return hook.writer.Debug(line)
}
return nil
}

View File

@@ -0,0 +1,126 @@
// Copyright 2016-2017 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 syslog
import (
"testing"
"time"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestNewSyslogHook(t *testing.T) {
// error case
d := &mockDialer{}
d.On("dial").Return(nil, assert.AnError)
h, err := newHook(d)
assert.Nil(t, h)
assert.Error(t, err)
assert.EqualError(t, err, assert.AnError.Error())
d.AssertCalled(t, "dial")
d.AssertNumberOfCalls(t, "dial", 1)
// no error
d = &mockDialer{}
w := &MockWriter{}
d.On("dial").Return(w, nil)
h, err = newHook(d)
assert.NotNil(t, h)
assert.NoError(t, err)
assert.Equal(t, w, h.writer)
d.AssertCalled(t, "dial")
d.AssertNumberOfCalls(t, "dial", 1)
}
func TestLevels(t *testing.T) {
m := &MockWriter{}
d := &mockDialer{}
d.On("dial").Return(m, nil)
h, err := newHook(d)
assert.NotNil(t, h)
assert.NoError(t, err)
m.On("Crit", mock.Anything).Return(nil)
m.On("Err", mock.Anything).Return(nil)
m.On("Warning", mock.Anything).Return(nil)
m.On("Debug", mock.Anything).Return(nil)
m.On("Info", mock.Anything).Return(nil)
var tests = []struct {
entry *logrus.Entry
f string
}{
{
entry: &logrus.Entry{Message: "panic", Level: logrus.PanicLevel},
f: "Crit",
},
{
entry: &logrus.Entry{Message: "fatal", Level: logrus.FatalLevel},
f: "Crit",
},
{
entry: &logrus.Entry{Message: "error", Level: logrus.ErrorLevel},
f: "Err",
},
{
entry: &logrus.Entry{Message: "warn", Level: logrus.WarnLevel},
f: "Warning",
},
{
entry: &logrus.Entry{Message: "info", Level: logrus.InfoLevel},
f: "Info",
},
{
entry: &logrus.Entry{Message: "debug", Level: logrus.DebugLevel},
f: "Debug",
},
}
calls := make(map[string]int)
for _, te := range tests {
calls[te.f] = 0
}
for _, te := range tests {
assert.NoError(t, h.writeEntry(te.entry))
calls[te.f]++
m.AssertCalled(t, te.f, te.entry.Message)
m.AssertNumberOfCalls(t, te.f, calls[te.f])
}
}
func TestConnect(t *testing.T) {
// attempt a connection to a server that
// does not exist
h, err := NewHook(
"tcp",
"foo:514",
Info,
"test",
)
assert.NoError(t, err)
assert.NotNil(t, h)
h.Fire(&logrus.Entry{
Message: "foo",
Level: logrus.InfoLevel,
})
<-time.After(5 * time.Second)
}

View File

@@ -0,0 +1,55 @@
// Copyright 2016-2017 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 syslog
import (
"net"
mock "github.com/stretchr/testify/mock"
)
// MockAddr is an autogenerated mock type for the Addr type
type MockAddr struct {
mock.Mock
}
// Network provides a mock function with given fields:
func (_m *MockAddr) Network() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// String provides a mock function with given fields:
func (_m *MockAddr) String() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
var _ net.Addr = (*MockAddr)(nil)

View File

@@ -0,0 +1,46 @@
// Copyright 2016-2017 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 syslog
import mock "github.com/stretchr/testify/mock"
// mockDialer is an autogenerated mock type for the dialer type
type mockDialer struct {
mock.Mock
}
// dial provides a mock function with given fields:
func (_m *mockDialer) dial() (Writer, error) {
ret := _m.Called()
var r0 Writer
if rf, ok := ret.Get(0).(func() Writer); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Writer)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
var _ dialer = (*mockDialer)(nil)

View File

@@ -0,0 +1,38 @@
// Copyright 2016-2017 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 syslog
import mock "github.com/stretchr/testify/mock"
import time "time"
// mockFormatter is an autogenerated mock type for the formatter type
type mockFormatter struct {
mock.Mock
}
// Format provides a mock function with given fields: p, ts, hostname, tag, msg
func (_m *mockFormatter) Format(p Priority, ts time.Time, hostname string, tag string, msg string) string {
ret := _m.Called(p, ts, hostname, tag, msg)
var r0 string
if rf, ok := ret.Get(0).(func(Priority, time.Time, string, string, string) string); ok {
r0 = rf(p, ts, hostname, tag, msg)
} else {
r0 = ret.Get(0).(string)
}
return r0
}
var _ formatter = (*mockFormatter)(nil)

View File

@@ -0,0 +1,155 @@
// Copyright 2016-2017 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 syslog
import mock "github.com/stretchr/testify/mock"
import net "net"
import time "time"
// MockConn is an autogenerated mock type for the Conn type
type MockNetConn struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *MockNetConn) Close() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// LocalAddr provides a mock function with given fields:
func (_m *MockNetConn) LocalAddr() net.Addr {
ret := _m.Called()
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// Read provides a mock function with given fields: b
func (_m *MockNetConn) Read(b []byte) (int, error) {
ret := _m.Called(b)
var r0 int
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoteAddr provides a mock function with given fields:
func (_m *MockNetConn) RemoteAddr() net.Addr {
ret := _m.Called()
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Addr)
}
}
return r0
}
// SetDeadline provides a mock function with given fields: t
func (_m *MockNetConn) SetDeadline(t time.Time) error {
ret := _m.Called(t)
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetReadDeadline provides a mock function with given fields: t
func (_m *MockNetConn) SetReadDeadline(t time.Time) error {
ret := _m.Called(t)
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetWriteDeadline provides a mock function with given fields: t
func (_m *MockNetConn) SetWriteDeadline(t time.Time) error {
ret := _m.Called(t)
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
} else {
r0 = ret.Error(0)
}
return r0
}
// Write provides a mock function with given fields: b
func (_m *MockNetConn) Write(b []byte) (int, error) {
ret := _m.Called(b)
var r0 int
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(b)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(b)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
var _ net.Conn = (*MockNetConn)(nil)

View File

@@ -0,0 +1,47 @@
// Copyright 2016-2017 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 syslog
import mock "github.com/stretchr/testify/mock"
import net "net"
// mockNetDialer is an autogenerated mock type for the netDialer type
type mockNetDialer struct {
mock.Mock
}
// dial provides a mock function with given fields:
func (_m *mockNetDialer) dial() (net.Conn, error) {
ret := _m.Called()
var r0 net.Conn
if rf, ok := ret.Get(0).(func() net.Conn); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(net.Conn)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
var _ netDialer = (*mockNetDialer)(nil)

View File

@@ -0,0 +1,174 @@
// Copyright 2016-2017 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 syslog
import mock "github.com/stretchr/testify/mock"
// MockWriter is an autogenerated mock type for the Writer type
type MockWriter struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *MockWriter) Close() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Crit provides a mock function with given fields: _a0
func (_m *MockWriter) Crit(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Debug provides a mock function with given fields: _a0
func (_m *MockWriter) Debug(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Emerg provides a mock function with given fields: _a0
func (_m *MockWriter) Emerg(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Err provides a mock function with given fields: _a0
func (_m *MockWriter) Err(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Info provides a mock function with given fields: _a0
func (_m *MockWriter) Info(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Warning provides a mock function with given fields: _a0
func (_m *MockWriter) Warning(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// WithPriority provides a mock function with given fields: priority
func (_m *MockWriter) WithPriority(priority Priority) Writer {
ret := _m.Called(priority)
var r0 Writer
if rf, ok := ret.Get(0).(func(Priority) Writer); ok {
r0 = rf(priority)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Writer)
}
}
return r0
}
// WithTag provides a mock function with given fields: tag
func (_m *MockWriter) WithTag(tag string) Writer {
ret := _m.Called(tag)
var r0 Writer
if rf, ok := ret.Get(0).(func(string) Writer); ok {
r0 = rf(tag)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Writer)
}
}
return r0
}
// Write provides a mock function with given fields: p
func (_m *MockWriter) Write(p []byte) (int, error) {
ret := _m.Called(p)
var r0 int
if rf, ok := ret.Get(0).(func([]byte) int); ok {
r0 = rf(p)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func([]byte) error); ok {
r1 = rf(p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
var _ Writer = (*MockWriter)(nil)

156
vendor/github.com/vmware/vic/pkg/log/syslog/syslog.go generated vendored Normal file
View File

@@ -0,0 +1,156 @@
// Copyright 2016-2017 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 syslog
import (
"errors"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
)
// Priority is a combination of the syslog facility and
// severity. For example, Alert | Ftp sends an alert severity
// message from the FTP facility. The default severity is Emerg;
// the default facility is Kern.
type Priority int
const severityMask = 0x07
const facilityMask = 0xf8
// maxLogBuffer was set to 100 but debug logging of config overflows that easily so pushing it up
const maxLogBuffer = 500
const (
// Severity.
// From /usr/include/sys/syslog.h.
// These are the same on Linux, BSD, and OS X.
Emerg Priority = iota // LOG_EMERG
Alert // LOG_ALERT
Crit // LOG_CRIT
Err // LOG_ERR
Warning // LOG_WARNING
Notice // LOG_NOTICE
Info // LOG_INFO
Debug // LOG_DEBUG
)
const (
// Facility.
// From /usr/include/sys/syslog.h.
// These are the same up to LOG_FTP on Linux, BSD, and OS X.
Kern Priority = iota << 3 // LOG_KERN
User // LOG_USER
Mail // LOG_MAIL
Daemon // LOG_DAEMON
Auth // LOG_AUTH
Syslog // LOG_SYSLOG
Lpr // LOG_LPR
News // LOG_NEWS
Uucp // LOG_UUCP
Cron // LOG_CRON
Authpriv // LOG_AUTHPRIV
Ftp // LOG_FTP
_ // unused
_ // unused
_ // unused
_ // unused
Local0 // LOG_LOCAL0
Local1 // LOG_LOCAL1
Local2 // LOG_LOCAL2
Local3 // LOG_LOCAL3
Local4 // LOG_LOCAL4
Local5 // LOG_LOCAL5
Local6 // LOG_LOCAL6
Local7 // LOG_LOCAL7
)
// New establishes a new connection to the system log daemon. Each
// write to the returned writer sends a log message with the given
// priority and prefix.
func New(priority Priority, tag string) (Writer, error) {
return Dial("", "", priority, tag)
}
// Dial establishes a connection to a log daemon by connecting to
// address raddr on the specified network. Each write to the returned
// writer sends a log message with the given facility, severity and
// tag.
// If network is empty, Dial will connect to the local syslog server.
func Dial(network, raddr string, priority Priority, tag string) (Writer, error) {
d := &defaultDialer{
network: network,
raddr: raddr,
tag: tag,
priority: priority,
}
return d.dial()
}
type defaultDialer struct {
network, raddr, tag string
priority Priority
}
func validPriority(priority Priority) bool {
return priority >= 0 && priority <= Local7|Debug
}
func (d *defaultDialer) dial() (Writer, error) {
if !validPriority(d.priority) {
return nil, errors.New("log/syslog: invalid priority")
}
tag := MakeTag("", d.tag)
// #nosec: Errors unhandled.
hostname, _ := os.Hostname()
w := newWriter(d.priority, tag, hostname, newNetDialer(d.network, d.raddr), newFormatter(d.network, RFC3164))
go w.run()
return w, nil
}
const sep = "/"
// MakeTag returns prfeix + sep + proc if prefix is not empty.
// If proc is empty, proc is set to filepath.Base(os.Args[0]).
// If prefix is empty, MakeTag returns proc.
func MakeTag(prefix, proc string) string {
if len(proc) == 0 {
proc = filepath.Base(os.Args[0])
}
if len(prefix) > 0 {
return prefix + sep + proc
}
return proc
}
// Logger is the logger object used by the package
var Logger = logrus.New()
// Format is the syslog format, e.g. RFC 3164
type Format int
const (
RFC3164 Format = iota
)

View File

@@ -0,0 +1,80 @@
// Copyright 2016-2017 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 syslog
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
var (
network = "tcp"
raddr = "localhost:514"
tag = "test"
priority = Info | Daemon
)
func TestMakeTag(t *testing.T) {
p := filepath.Base(os.Args[0])
var tests = []struct {
prefix string
proc string
out string
}{
{
prefix: "",
proc: "",
out: p,
},
{
prefix: "",
proc: "foo",
out: "foo",
},
{
prefix: "foo",
proc: "",
out: "foo" + sep + p,
},
{
prefix: "bar",
proc: "foo",
out: "bar" + sep + "foo",
},
}
for _, te := range tests {
out := MakeTag(te.prefix, te.proc)
assert.Equal(t, te.out, out)
}
}
func TestDefaultDialerBadPriority(t *testing.T) {
d := &defaultDialer{
priority: -1,
}
w, err := d.dial()
assert.Nil(t, w)
assert.Error(t, err)
d.priority = (Local7 | Debug) + 1
w, err = d.dial()
assert.Nil(t, w)
assert.Error(t, err)
}

View File

@@ -0,0 +1,53 @@
// Copyright 2016-2017 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.
// +build !windows,!nacl,!plan9
package syslog
import (
"errors"
"net"
)
type unixSyslogDialer struct{}
// unixSyslog opens a connection to the syslog daemon running on the
// local machine using a Unix domain socket.
func (u *unixSyslogDialer) dial() (net.Conn, error) {
logTypes := []string{"unixgram", "unix"}
logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"}
for _, network := range logTypes {
for _, path := range logPaths {
conn, err := net.Dial(network, path)
if err != nil {
continue
} else {
return conn, nil
}
}
}
return nil, errors.New("Unix syslog delivery error")
}
func newNetDialer(network, address string) netDialer {
if network == "" {
return &unixSyslogDialer{}
}
return &defaultNetDialer{
network: network,
address: address,
}
}

View File

@@ -0,0 +1,26 @@
// Copyright 2016-2017 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.
// +build !windows,!nacl,!plan9
package syslog
import "testing"
import "github.com/stretchr/testify/assert"
func TestNewNetDialer(t *testing.T) {
d := newNetDialer("", "")
assert.IsType(t, &unixSyslogDialer{}, d)
}

View File

@@ -0,0 +1,22 @@
// Copyright 2016-2017 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 syslog
func newNetDialer(network, address string) netDialer {
return &defaultNetDialer{
network: network,
address: address,
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2016-2017 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.
// +build windows
package syslog
import "testing"
import "github.com/stretchr/testify/assert"
func TestNewNetDialer(t *testing.T) {
d := newNetDialer("tcp", "foo")
assert.IsType(t, &defaultDialer{}, d)
}

270
vendor/github.com/vmware/vic/pkg/log/syslog/writer.go generated vendored Normal file
View File

@@ -0,0 +1,270 @@
// Copyright 2016-2017 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 syslog
import (
"errors"
"io"
"net"
"strings"
"sync"
"time"
)
type Writer interface {
io.WriteCloser
Emerg(string) error
Crit(string) error
Err(string) error
Warning(string) error
Info(string) error
Debug(string) error
WithTag(tag string) Writer
WithPriority(priority Priority) Writer
}
type writer struct {
priority Priority
tag string
hostname string
msgs chan *msg
once sync.Once
done, running chan struct{}
dialer netDialer
conn net.Conn
formatter formatter
parent *writer
}
type msg struct {
p Priority
tag string
msg string
}
func newWriter(priority Priority, tag, hostname string, dialer netDialer, f formatter) *writer {
return &writer{
priority: priority,
tag: tag,
hostname: hostname,
dialer: dialer,
msgs: make(chan *msg, maxLogBuffer),
done: make(chan struct{}),
running: make(chan struct{}),
formatter: f,
}
}
// connect makes a connection to the syslog server.
// It must be called with w.mu held.
func (w *writer) connect() (err error) {
if w.conn != nil {
// ignore err from close, it makes sense to continue anyway
w.conn.Close()
w.conn = nil
}
Logger.Infof("trying to connect to syslog server")
w.conn, err = w.dialer.dial()
if err == nil {
Logger.Info("successfully connected to syslog server")
if w.hostname == "" {
// #nosec: Errors unhandled.
w.hostname, _, _ = net.SplitHostPort(w.conn.LocalAddr().String())
}
}
return
}
// Write sends a log message to the syslog daemon.
func (w *writer) Write(b []byte) (int, error) {
w.queueWrite(w.priority, w.tag, string(b))
return len(b), nil
}
// Close closes a connection to the syslog daemon.
func (w *writer) Close() error {
for w.parent != nil {
w = w.parent
}
w.once.Do(func() {
close(w.msgs)
select {
case <-w.running:
<-w.done
}
})
return nil
}
// Emerg logs a message with severity Emerg, ignoring the severity
// passed to New.
func (w *writer) Emerg(m string) error {
return w.queueWrite(Emerg, w.tag, m)
}
// Alert logs a message with severity Alert, ignoring the severity
// passed to New.
func (w *writer) Alert(m string) error {
return w.queueWrite(Alert, w.tag, m)
}
// Crit logs a message with severity Crit, ignoring the severity
// passed to New.
func (w *writer) Crit(m string) error {
return w.queueWrite(Crit, w.tag, m)
}
// Err logs a message with severity Err, ignoring the severity
// passed to New.
func (w *writer) Err(m string) error {
return w.queueWrite(Err, w.tag, m)
}
// Warning logs a message with severity Warning, ignoring the
// severity passed to New.
func (w *writer) Warning(m string) error {
return w.queueWrite(Warning, w.tag, m)
}
// Notice logs a message with severity Notice, ignoring the
// severity passed to New.
func (w *writer) Notice(m string) error {
return w.queueWrite(Notice, w.tag, m)
}
// Info logs a message with severity Info, ignoring the severity
// passed to New.
func (w *writer) Info(m string) error {
return w.queueWrite(Info, w.tag, m)
}
// Debug logs a message with severity Debug, ignoring the severity
// passed to New.
func (w *writer) Debug(m string) error {
return w.queueWrite(Debug, w.tag, m)
}
func (w *writer) queueWrite(p Priority, tag, s string) error {
for w.parent != nil {
w = w.parent
}
select {
case w.msgs <- &msg{p: p, tag: tag, msg: s}:
default:
return errors.New("queue full or writer closed")
}
return nil
}
func (w *writer) writeAndRetry(p Priority, tag, s string) (int, error) {
if len(s) == 0 {
return 0, nil
}
pr := (w.priority & facilityMask) | (p & severityMask)
if w.conn != nil {
n, err := w.write(pr, tag, s)
if err == nil {
return n, err
}
Logger.Errorf("syslog write failed: %s", err)
}
if err := w.connect(); err != nil {
return 0, err
}
return w.write(pr, tag, s)
}
// write generates and writes a syslog formatted string. The
// format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
func (w *writer) write(p Priority, tag, msg string) (int, error) {
s := w.formatter.Format(p, time.Now(), w.hostname, tag, msg)
// ensure it ends in a \n
if !strings.HasSuffix(s, "\n") {
s = s + "\n"
}
_, err := w.conn.Write([]byte(s))
if err != nil {
return 0, err
}
// return len(msg), since we want to behave as an io.Writer
return len(msg), nil
}
func (w *writer) WithTag(tag string) Writer {
return &writer{
hostname: w.hostname,
tag: tag,
priority: w.priority,
parent: w,
}
}
func (w *writer) WithPriority(priority Priority) Writer {
if !validPriority(priority) {
return nil
}
return &writer{
hostname: w.hostname,
tag: w.tag,
priority: priority,
parent: w,
}
}
func (w *writer) run() {
Logger.Infof("run()")
defer func() {
Logger.Infof("exiting syslog writer loop")
if w.conn != nil {
w.conn.Close()
}
close(w.done)
}()
if err := w.connect(); err != nil {
switch err.(type) {
case *net.ParseError, *net.AddrError:
Logger.Errorf("could not connect to syslog server (will not try again): %s", err)
return
}
Logger.Errorf("error connecting to syslog server: %s", err)
}
close(w.running)
for m := range w.msgs {
for _, s := range strings.SplitAfter(m.msg, "\n") {
if _, err := w.writeAndRetry(m.p, m.tag, s); err != nil {
Logger.Errorf("could not write syslog message: %s", err)
}
}
}
}

View File

@@ -0,0 +1,278 @@
// Copyright 2016-2017 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 syslog
import (
"fmt"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestWriterReconnect(t *testing.T) {
dn := &mockNetDialer{}
dn.On("dial").Return(nil, assert.AnError)
w := newWriter(priority, tag, "", dn, nil)
go w.run()
<-w.running
calls := []func(string) error{
w.Emerg,
w.Crit,
w.Err,
w.Warning,
w.Info,
w.Debug,
}
for _, f := range calls {
err := f("test")
assert.NoError(t, err)
}
w.Close()
dn.AssertNumberOfCalls(t, "dial", 1+len(calls))
}
func TestWriterWrite(t *testing.T) {
msg := "foo"
f := &mockFormatter{}
f.On("Format", priority, mock.Anything, "host", tag, msg).Return("test")
a := &MockAddr{}
a.On("String").Return("host:123")
c := &MockNetConn{}
c.On("LocalAddr").Return(a)
c.On("Write", []byte("test\n")).Return(len(msg), nil)
c.On("Close").Return(nil)
dn := &mockNetDialer{}
dn.On("dial").Return(c, nil)
w := newWriter(priority, tag, "", dn, f)
n, err := w.Write([]byte(msg))
assert.NoError(t, err)
assert.Equal(t, len(msg), n)
go w.run()
<-w.running
w.Close()
c.AssertExpectations(t)
dn.AssertNumberOfCalls(t, "dial", 1)
}
func TestMaxLogBuffer(t *testing.T) {
f := &mockFormatter{}
dn := &mockNetDialer{}
c := &MockNetConn{}
a := &MockAddr{}
a.On("String").Return("foo")
c.On("LocalAddr").Return(a)
c.On("Close").Return(nil)
dn.On("dial").Return(c, nil)
w := newWriter(priority, tag, "", dn, f)
for i := 0; i < maxLogBuffer+1; i++ {
msg := fmt.Sprintf("%d", i)
f.On("Format", priority, mock.Anything, "", tag, msg).Return(msg)
c.On("Write", []byte(msg+"\n")).Return(len(msg), nil)
w.Write([]byte(msg))
}
go w.run()
<-w.running
w.Close()
for i := 0; i < maxLogBuffer; i++ {
if !f.AssertCalled(t, "Format", priority, mock.Anything, "", tag, fmt.Sprintf("%d", i)) ||
!c.AssertCalled(t, "Write", []byte(fmt.Sprintf("%d\n", i))) {
}
}
f.AssertNumberOfCalls(t, "Format", maxLogBuffer)
f.AssertNotCalled(t, "Format", priority, mock.Anything, "", tag, fmt.Sprintf("%d", maxLogBuffer))
}
func TestWriterReconnectWrite(t *testing.T) {
dn := &mockNetDialer{}
c := &MockNetConn{}
a := &MockAddr{}
a.On("String").Return("addr:123")
c.On("LocalAddr").Return(a)
c.On("Close").Return(nil)
dn.On("dial").Return(nil, assert.AnError)
f := &mockFormatter{}
w := newWriter(priority, tag, "", dn, f)
go w.run()
<-w.running
dn.AssertNumberOfCalls(t, "dial", 1)
dn = &mockNetDialer{}
dn.On("dial").Return(c, nil)
w.dialer = dn
f.On("Format", priority, mock.Anything, "addr", tag, "test").Return("test")
c.On("Write", []byte("test\n")).Return(len("test"), nil)
w.Write([]byte("test"))
w.Close()
dn.AssertNumberOfCalls(t, "dial", 1)
c.AssertNumberOfCalls(t, "Write", 1) // 1 call to writer.write
f.AssertNumberOfCalls(t, "Format", 1)
}
func TestWriterReconnectWriteError(t *testing.T) {
dn := &mockNetDialer{}
c := &MockNetConn{}
a := &MockAddr{}
a.On("String").Return("addr:123")
c.On("LocalAddr").Return(a)
c.On("Close").Return(nil)
dn.On("dial").Return(c, nil)
f := &mockFormatter{}
w := newWriter(priority, tag, "", dn, f)
go w.run()
<-w.running
dn.AssertNumberOfCalls(t, "dial", 1)
f.On("Format", priority, mock.Anything, "addr", tag, "test").Return("test")
c.On("Write", []byte("test\n")).Return(0, assert.AnError)
w.Write([]byte("test"))
w.Close()
f.AssertExpectations(t)
c.AssertExpectations(t)
}
func TestWriterWithTag(t *testing.T) {
f := &mockFormatter{}
f.On("Format", priority, mock.Anything, "addr", "child", "child").Return("child")
f.On("Format", priority, mock.Anything, "addr", "gchild", "gchild").Return("gchild")
dn := &mockNetDialer{}
c := &MockNetConn{}
a := &MockAddr{}
a.On("String").Return("addr:123")
c.On("LocalAddr").Return(a)
c.On("Close").Return(nil)
c.On("Write", []byte("child\n")).Return(len("child"), nil)
c.On("Write", []byte("gchild\n")).Return(len("gchild"), nil)
dn.On("dial").Return(c, nil)
w := newWriter(priority, tag, "", dn, f)
child := w.WithTag("child")
child.Write([]byte("child"))
gchild := child.WithTag("gchild")
gchild.Write([]byte("gchild"))
go w.run()
<-w.running
child.Close()
gchild.Close()
select {
case <-w.done:
default:
assert.FailNow(t, "parent writer is not closed by child Close call")
}
f.AssertExpectations(t)
c.AssertExpectations(t)
}
func TestWriterWithPriority(t *testing.T) {
f := &mockFormatter{}
f.On("Format", Err|Daemon, mock.Anything, "addr", tag, "err").Return("err")
f.On("Format", Debug|Daemon, mock.Anything, "addr", tag, "debug").Return("debug")
dn := &mockNetDialer{}
c := &MockNetConn{}
a := &MockAddr{}
a.On("String").Return("addr:123")
c.On("LocalAddr").Return(a)
c.On("Close").Return(nil)
c.On("Write", []byte("err\n")).Return(len("err"), nil)
c.On("Write", []byte("debug\n")).Return(len("debug"), nil)
dn.On("dial").Return(c, nil)
w := newWriter(priority, tag, "", dn, f)
errw := w.WithPriority(Err | Daemon)
errw.Write([]byte("err"))
debugw := errw.WithPriority(Debug | Daemon)
debugw.Write([]byte("debug"))
go w.run()
<-w.running
errw.Close()
select {
case <-w.done:
default:
assert.FailNow(t, "parent writer is not closed by child Close call")
}
f.AssertExpectations(t)
c.AssertExpectations(t)
}
func TestWriterInitialConnectError(t *testing.T) {
var tests = []error{
&net.ParseError{},
&net.AddrError{},
}
for _, e := range tests {
dn := &mockNetDialer{}
dn.On("dial").Return(nil, e)
w := newWriter(priority, tag, "", dn, &mockFormatter{})
w.run()
select {
case <-w.running:
assert.FailNow(t, "writer should not run when connect() fails initially")
default:
}
}
}

63
vendor/github.com/vmware/vic/pkg/log/text_formatter.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2016-2017 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 log
import "github.com/Sirupsen/logrus"
// level strings padded to match the length of the longest level,
// which is "UNKNOWN" currently. Indexed according to levels in
// logrus, e.g. levelStrs[logrus.InfoLevel] == "INFO ".
var levelStrs = []string{
"PANIC",
"FATAL",
"ERROR",
"WARN ",
"INFO ",
"DEBUG",
}
const unknownLevel = "UNKWN"
type TextFormatter struct {
// TimestampFormat is the format used to print the timestamp. By default
// an RFC3339 timestamp is used.
TimestampFormat string
}
// NewTextFormatter returns a text formatter
func NewTextFormatter() *TextFormatter {
return &TextFormatter{
TimestampFormat: "Jan _2 2006 15:04:05.000Z07:00",
}
}
func levelToString(level logrus.Level) string {
if level <= logrus.DebugLevel {
return levelStrs[level]
}
return unknownLevel
}
func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
t := f.timeStamp(entry)
l := levelToString(entry.Level)
return []byte(t + " " + l + " " + entry.Message + "\n"), nil
}
func (f *TextFormatter) timeStamp(entry *logrus.Entry) string {
return entry.Time.Format(f.TimestampFormat)
}

View File

@@ -0,0 +1,136 @@
// Copyright 2016-2017 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 log
import (
"bufio"
"fmt"
"strings"
"testing"
"time"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func BenchmarkFormatNonEmpty(b *testing.B) {
f := NewTextFormatter()
e := &logrus.Entry{
Time: time.Now(),
Level: logrus.InfoLevel,
Message: "the quick brown fox jumps over the lazy dog",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Format(e)
}
}
func BenchmarkFormatEmpty(b *testing.B) {
f := NewTextFormatter()
e := &logrus.Entry{
Time: time.Now(),
Level: logrus.InfoLevel,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Format(e)
}
}
func TestFormatEmpty(t *testing.T) {
ti := time.Now()
e := &logrus.Entry{Time: ti, Level: logrus.InfoLevel}
f := NewTextFormatter()
b, err := f.Format(e)
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%s %s \n", ti.Format(f.TimestampFormat), levelToString(e.Level)), string(b))
}
func TestFormatNonEmpty(t *testing.T) {
ti := time.Now()
m := "foo bar baz"
e := &logrus.Entry{Time: ti, Level: logrus.InfoLevel, Message: m}
f := NewTextFormatter()
b, err := f.Format(e)
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%s %s %s\n", ti.Format(f.TimestampFormat), levelToString(e.Level), m), string(b))
// test with multiple lines
pre := fmt.Sprintf("%s %s ", ti.Format(f.TimestampFormat), levelToString(e.Level))
var tests = []struct {
in string
out []string
}{
{
"foo",
[]string{
pre + "foo",
},
},
{
"\n",
[]string{
pre,
"",
},
},
{
"foo\n",
[]string{
pre + "foo",
"",
},
},
{
"\nfoo\n",
[]string{
pre + "",
"foo",
"",
},
},
{
"foo\n",
[]string{
pre + "foo",
"",
},
},
{
"foo \nbar\n baz ",
[]string{
pre + "foo ",
"bar",
" baz ",
},
},
}
for idx, te := range tests {
e.Message = te.in
b, err = f.Format(e)
assert.NoError(t, err)
s := bufio.NewScanner(strings.NewReader(string(b)))
i := 0
for s.Scan() {
assert.True(t, i < len(te.out), "case %d", idx)
assert.Equal(t, te.out[i], s.Text(), "case %d", idx)
i++
}
}
}

271
vendor/github.com/vmware/vic/pkg/logmgr/logmgr.go generated vendored Normal file
View File

@@ -0,0 +1,271 @@
// Copyright 2016-2017 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 logmgr
import (
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"github.com/vmware/vic/lib/tether"
"github.com/vmware/vic/pkg/filelock"
"github.com/vmware/vic/pkg/trace"
)
// RotateInterval defines a type for a log rotate frequency.
type RotateInterval uint32
// LogRotateBinary points to a logrotate path in the system. For testing purposes it should be overwritten to be empty.
var LogRotateBinary = "/usr/sbin/logrotate"
const (
// Daily to trim logs daily.
Daily RotateInterval = iota
// Hourly to trim logs hourly.
Hourly
// Weekly to trim logs weekly.
Weekly
// Monthly to trim logs monthly.
Monthly
)
type logRotateConfig struct {
rotateInterval RotateInterval
logFilePath string
logFileName string
maxLogSizeBytes int64
maxLogFiles int64
compress bool
}
// ConfigFileContent formats log configuration according to logrotate requirements.
func (lrc *logRotateConfig) ConfigFileContent() string {
b := make([]string, 0, 32)
if lrc.compress {
b = append(b, "compress")
}
switch lrc.rotateInterval {
case Hourly:
b = append(b, string("hourly"))
case Daily:
b = append(b, string("daily"))
case Weekly:
b = append(b, string("weekly"))
case Monthly:
fallthrough
default:
b = append(b, string("monthly"))
}
b = append(b, fmt.Sprintf("rotate %d", lrc.maxLogFiles))
if lrc.maxLogSizeBytes > 0 {
b = append(b, fmt.Sprintf("size %d", lrc.maxLogSizeBytes))
}
if lrc.maxLogSizeBytes > 2 {
b = append(b, fmt.Sprintf("minsize %d", lrc.maxLogSizeBytes-1))
}
// VIC doesn't support HUP yet, thus we are using logrotate copytruncate option that
// will copy and truncate log file.
b = append(b, "copytruncate")
b = append(b, "dateext")
b = append(b, "dateformat -%Y%m%d-%s")
for i, v := range b {
b[i] = " " + v
}
return fmt.Sprintf("%s {\n%s\n}\n", lrc.logFilePath, strings.Join(b, "\n"))
}
// LogManager runs logrotate for specified log files.
// TODO: Upload compressed logs into vSphere storage.
// TODO: Upload all logs into vSphere storage during graceful shutdown.
type LogManager struct {
// Frequency of running log rotate.
runInterval time.Duration
// list of log files and their log rotate parameters to rotate.
logFiles []*logRotateConfig
// channel gets closed on stop.
closed chan struct{}
op trace.Operation
// used to wait until logrotate goroutine stops.
wg sync.WaitGroup
// just to make sure start is not called twice accidentally.
once sync.Once
logConfig string
// Mostly for debug purposes to insure log rotate loop is running.
// It also will log on debug level periodically that it runs.
loopsCount uint64
}
// NewLogManager creates a new log manager instance.
func NewLogManager(runInterval time.Duration) (*LogManager, error) {
lm := &LogManager{
runInterval: runInterval,
op: trace.NewOperation(context.Background(), "logrotate"),
closed: make(chan struct{}),
}
// LogRotateBinary is set to empty during unit testing.
if s, err := os.Stat(LogRotateBinary); (err != nil || s.IsDir()) && LogRotateBinary != "" {
return nil, fmt.Errorf("logrotate is not available at %s, without it logs will not be rotated", LogRotateBinary)
}
return lm, nil
}
// AddLogRotate adds a log to rotate.
func (lm *LogManager) AddLogRotate(logFilePath string, ri RotateInterval, maxSize, maxLogFiles int64, compress bool) {
lm.logFiles = append(lm.logFiles, &logRotateConfig{
rotateInterval: ri,
logFilePath: logFilePath,
logFileName: filepath.Base(logFilePath),
maxLogSizeBytes: maxSize,
maxLogFiles: maxLogFiles,
compress: compress,
})
}
// Reload - just to satisfy Tether interface.
func (lm *LogManager) Reload(*tether.ExecutorConfig) error { return nil }
// Start log rotate loop.
func (lm *LogManager) Start() error {
if len(lm.logFiles) == 0 {
lm.op.Errorf("Attempt to start logrotate with no log files configured.")
return nil
}
lm.once.Do(func() {
lm.wg.Add(1)
lm.logConfig = lm.buildConfig()
lm.op.Debugf("logrotate config: %s", lm.logConfig)
go func() {
for {
lm.loopsCount++
if lm.loopsCount%10 == 0 {
lm.op.Debugf("logrotate has been run %d times", lm.loopsCount)
}
select {
case <-time.After(lm.runInterval):
case <-lm.closed:
lm.rotateLogs()
lm.wg.Done()
return
}
lm.rotateLogs()
}
}()
})
return nil
}
// Stop loop.
func (lm *LogManager) Stop() error {
select {
case <-lm.closed:
default:
close(lm.closed)
}
lm.wg.Wait()
return nil
}
func (lm *LogManager) saveConfig(logConf string) string {
tf, err := ioutil.TempFile("", "vic-logrotate-conf-")
if err != nil {
lm.op.Errorf("Failed to create temp file for logrotate: %v", err)
return ""
}
tempFilePath := tf.Name()
if _, err = tf.Write([]byte(logConf)); err != nil {
lm.op.Errorf("Failed to store logrotate config %s: %v", tempFilePath, err)
if err = tf.Close(); err != nil {
lm.op.Errorf("Failed to close temp file %s: %v", tempFilePath, err)
}
if err = os.Remove(tempFilePath); err != nil {
lm.op.Errorf("Failed to remove temp file %s: %v", tempFilePath, err)
}
return ""
}
if err = tf.Close(); err != nil {
lm.op.Errorf("Failed to close logrotate config file %s: %v", tempFilePath, err)
return ""
}
return tempFilePath
}
func (lm *LogManager) buildConfig() string {
c := make([]string, 0, len(lm.logFiles))
for _, v := range lm.logFiles {
c = append(c, v.ConfigFileContent())
}
return strings.Join(c, "\n")
}
func (lm *LogManager) rotateLogs() {
// Check if logrotate config exists, create one
configFile := lm.saveConfig(lm.logConfig)
logrotateLock := filelock.NewFileLock(filelock.LogRotateLockName)
// This lock is necessary to avoid race condition when user requests log bundle.
if err := logrotateLock.Acquire(); err != nil {
lm.op.Errorf("Failed to acquire logrotate lock: %v", err)
} else {
defer func() { logrotateLock.Release() }()
}
if configFile == "" {
lm.op.Errorf("Can not run logrotate dues to missing logrotate config")
return
}
// remove config file as soon as logrotate finishes its work.
defer os.Remove(configFile)
lm.op.Debugf("Running logrotate: %s %s", LogRotateBinary, configFile)
if LogRotateBinary == "" {
lm.op.Debugf("logrotate is not defined. Skipping.")
return
}
// #nosec: Subprocess launching with variable
output, err := exec.Command(LogRotateBinary, configFile).CombinedOutput()
if err == nil {
if len(output) > 0 {
lm.op.Debugf("Logrotate output: %s", output)
}
lm.op.Debugf("logrotate finished successfully")
} else {
lm.op.Errorf("logrotate exited with non 0 status: %v", err)
if len(output) > 0 {
lm.op.Errorf("Logrotate output: %s", output)
}
}
}

105
vendor/github.com/vmware/vic/pkg/logmgr/logmgr_test.go generated vendored Normal file
View File

@@ -0,0 +1,105 @@
// Copyright 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 logmgr
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// TestContentIsGeneratedAndSaved tests:
// 1. Config file content for log rotate is as expected.
// 2. Config file is saved on disk and can be read back.
func TestContentIsGeneratedAndSaved(t *testing.T) {
oldLogRotate := LogRotateBinary
LogRotateBinary = ""
defer func() { LogRotateBinary = oldLogRotate }()
m, e := NewLogManager(time.Millisecond)
if !assert.Nil(t, e) {
return
}
expectedConfig := `/var/log/test.log {
compress
hourly
rotate 3
size 10000
minsize 9999
copytruncate
dateext
dateformat -%Y%m%d-%s
}
/var/log/test1.log {
daily
rotate 2
size 20000
minsize 19999
copytruncate
dateext
dateformat -%Y%m%d-%s
}
`
m.AddLogRotate("/var/log/test.log", Hourly, 10000, 3, true)
m.AddLogRotate("/var/log/test1.log", Daily, 20000, 2, false)
actualConfig := m.buildConfig()
assert.Equal(t, expectedConfig, actualConfig)
fn := m.saveConfig(expectedConfig)
if !assert.NotEqual(t, "", fn) {
return
}
// delete temp file when test is done.
defer func() {
assert.Nil(t, os.Remove(fn))
}()
data, err := ioutil.ReadFile(fn)
if !assert.Nil(t, err) {
return
}
assert.Equal(t, expectedConfig, string(data))
}
// TestStartAndStop starts and stop the service checking that start/stop conditions are met.
func TestStartAndStop(t *testing.T) {
oldLogRotate := LogRotateBinary
LogRotateBinary = ""
defer func() { LogRotateBinary = oldLogRotate }()
m, e := NewLogManager(time.Millisecond)
if !assert.Nil(t, e) {
return
}
m.AddLogRotate("/var/log/test.log", Hourly, 10000, 3, true)
m.AddLogRotate("/var/log/test1.log", Daily, 20000, 2, false)
go func() {
assert.Nil(t, m.Start())
time.Sleep(time.Millisecond * 100)
assert.Nil(t, m.Stop())
}()
select {
case <-time.After(time.Second * 10):
assert.Fail(t, "timeout to start/stop log manager")
case _, ok := <-m.closed:
assert.False(t, ok)
}
assert.True(t, m.loopsCount > 0)
}

189
vendor/github.com/vmware/vic/pkg/registry/entry.go generated vendored Normal file
View File

@@ -0,0 +1,189 @@
// Copyright 2017 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 registry
import (
"net"
"net/url"
"strings"
glob "github.com/ryanuber/go-glob"
)
type Entry interface {
Contains(e Entry) bool
Match(e string) bool
Equal(other Entry) bool
String() string
IsCIDR() bool
IsURL() bool
}
type URLEntry interface {
Entry
URL() *url.URL
}
func ParseEntry(s string) Entry {
_, ipnet, err := net.ParseCIDR(s)
if err == nil {
return &cidrEntry{ipnet: ipnet}
}
if u := parseURL(s); u != nil {
return &urlEntry{u: u}
}
return nil
}
type cidrEntry struct {
ipnet *net.IPNet
}
func (c *cidrEntry) IsCIDR() bool {
return true
}
func (c *cidrEntry) IsURL() bool {
return false
}
func (c *cidrEntry) Contains(e Entry) bool {
switch e := e.(type) {
case *urlEntry:
if ip := net.ParseIP(e.u.Hostname()); ip != nil {
return c.ipnet.Contains(ip)
}
case *cidrEntry:
return c.ipnet.Contains(e.ipnet.IP.Mask(e.ipnet.Mask))
}
return false
}
func (c *cidrEntry) Match(s string) bool {
return c.Contains(ParseEntry(s))
}
func (c *cidrEntry) Equal(other Entry) bool {
return other.String() == c.ipnet.String()
}
func (c *cidrEntry) String() string {
return c.ipnet.String()
}
type urlEntry struct {
u *url.URL
}
func (u *urlEntry) IsCIDR() bool {
return false
}
func (u *urlEntry) IsURL() bool {
return true
}
func ensurePort(u *url.URL) *url.URL {
_, _, err := net.SplitHostPort(u.Host)
if err == nil {
return u // port already present
}
res := *u
switch u.Scheme {
case "http":
res.Host = u.Host + ":80"
case "https":
res.Host = u.Host + ":443"
}
return &res
}
func (u *urlEntry) Contains(e Entry) bool {
switch e := e.(type) {
case *urlEntry:
up := ensurePort(u.u)
ep := ensurePort(e.u)
if up.Port() == "" && ep.Port() != "" {
ep.Host = ep.Hostname()
}
return (u.u.Scheme == "" || u.u.Scheme == e.u.Scheme) &&
strings.HasPrefix(e.u.Path, u.u.Path) &&
glob.Glob(up.Host, ep.Host)
}
return false
}
func (u *urlEntry) Match(s string) bool {
q := ParseEntry(s)
query, ok := q.(URLEntry)
if !ok {
return false
}
// copy u to nu ("new u") so we don't modify the record
nu, _ := ParseEntry(u.String()).(URLEntry)
if query.URL().Scheme != "" && nu.URL().Scheme == "" {
query.URL().Scheme = nu.URL().Scheme
} else if nu.URL().Scheme != "" && query.URL().Scheme == "" {
nu.URL().Scheme = query.URL().Scheme
}
return nu.Contains(query)
}
func (u *urlEntry) String() string {
if u.u.Scheme == "" {
return strings.TrimPrefix(u.u.String(), "//")
}
return u.u.String()
}
func (u *urlEntry) Equal(other Entry) bool {
if other, ok := other.(*urlEntry); ok {
up := ensurePort(u.u)
otherp := ensurePort(other.u)
return up.String() == otherp.String()
}
return other.String() == u.u.String()
}
func (u *urlEntry) URL() *url.URL {
return u.u
}
func parseURL(s string) *url.URL {
for _, p := range []string{"", "https://"} {
u, err := url.Parse(p + s)
if err == nil && len(u.Host) > 0 {
if p != "" {
u.Scheme = "" // ignore the scheme
}
return u
}
}
return nil
}

370
vendor/github.com/vmware/vic/pkg/registry/entry_test.go generated vendored Normal file
View File

@@ -0,0 +1,370 @@
// Copyright 2017 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 registry
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntryContains(t *testing.T) {
var tests = []struct {
first, second Entry
res bool
}{
{
first: ParseEntry("192.168.0.1"),
second: ParseEntry("192.168.0.1"),
res: true,
},
{
first: ParseEntry("192.168.0.1"),
second: ParseEntry("192.168.0.1/16"),
res: false,
},
{
first: ParseEntry("192.168.0.1"),
second: ParseEntry("192.168.0.2"),
res: false,
},
{
first: ParseEntry("192.168.0.1/24"),
second: ParseEntry("192.168.0.11"),
res: true,
},
{
first: ParseEntry("172.16.0.0/12"),
second: ParseEntry("172.17.0.0/24"),
res: true,
},
{
first: ParseEntry("172.16.0.0/12"),
second: ParseEntry("172.15.0.0/24"),
res: false,
},
{
first: ParseEntry("172.16.0.0/12"),
second: ParseEntry("*.google.com"),
res: false,
},
{
first: ParseEntry("192.168.0.1/24"),
second: ParseEntry("192.168.1.0"),
res: false,
},
{
first: ParseEntry("192.168.0.1/24"),
second: ParseEntry("192.168.0.1/24"),
res: true,
},
{
first: ParseEntry("*.google.com"),
second: ParseEntry("*.com"),
res: false,
},
{
first: ParseEntry("mail.google.com"),
second: ParseEntry("*.google.com"),
res: false,
},
{
first: ParseEntry("*.google.com"),
second: ParseEntry("mail.google.com"),
res: true,
},
{
first: ParseEntry("*.com"),
second: ParseEntry("*.google.com"),
res: true,
},
{
first: ParseEntry("192.168.1.1:123"),
second: ParseEntry("192.168.1.1"),
res: false,
},
{
first: ParseEntry("foo:123"),
second: ParseEntry("foo"),
res: false,
},
{
first: ParseEntry("foo"),
second: ParseEntry("foo:123"),
res: true,
},
{
first: ParseEntry("192.168.1.1"),
second: ParseEntry("192.168.1.1:123"),
res: true,
},
}
for _, te := range tests {
assert.Equal(t, te.res, te.first.Contains(te.second), "test: %s contains %s", te.first, te.second)
}
}
func TestEntryMatch(t *testing.T) {
var tests = []struct {
e Entry
s string
res bool
}{
{
e: ParseEntry("192.168.0.1"),
s: "192.168.0.1",
res: true,
},
{
e: ParseEntry("192.168.0.1"),
s: "192.168.0",
res: false,
},
{
e: ParseEntry("192.168.0.1/24"),
s: "192.168.0.1",
res: true,
},
{
e: ParseEntry("192.168.0.1/24"),
s: "192.168.1.1",
res: false,
},
{
e: ParseEntry("192.168.0.1/24"),
s: "192.168.0.1/24",
res: true,
},
{
e: ParseEntry("*.google.com"),
s: "mail.google.com",
res: true,
},
{
e: ParseEntry("*.google.com"),
s: "mail.yahoo.com",
res: false,
},
{
e: ParseEntry("*.google.com"),
s: "google.com",
res: false,
},
{
e: ParseEntry("foo:123"),
s: "foo",
res: false,
},
{
e: ParseEntry("foo:123"),
s: "http://foo/bar", // this should be interpreted as http://foo:80/bar
res: false,
},
{
e: ParseEntry("foo:123"),
s: "http://foo:123/bar",
res: true,
},
{
e: ParseEntry("foo:123"),
s: "https://foo:123/bar",
res: true,
},
{
e: ParseEntry("foo"),
s: "foo:123",
res: true,
},
{
e: ParseEntry("192.168.1.1"),
s: "192.168.1.1:123",
res: true,
},
{
e: ParseEntry("http://192.168.1.1"),
s: "http://192.168.1.1",
res: true,
},
{
e: ParseEntry("http://192.168.1.1"),
s: "192.168.1.1/foo/bar",
res: true,
},
{
e: ParseEntry("http://192.168.1.1"),
s: "https://192.168.1.1",
res: false,
},
{
e: ParseEntry("https://192.168.1.1"),
s: "http://192.168.1.1",
res: false,
},
// fqdn and corresponding ip should not match
{
e: ParseEntry("https://google.com"),
s: "216.58.195.78",
res: false,
},
}
for _, te := range tests {
assert.Equal(t, te.res, te.e.Match(te.s), "test: %s match %s", te.e, te.s)
}
}
func TestEntryEqual(t *testing.T) {
var tests = []struct {
e, other Entry
res bool
}{
{
e: ParseEntry("192.168.0.1"),
other: ParseEntry("192.168.0.1"),
res: true,
},
{
e: ParseEntry("192.168.0.1"),
other: ParseEntry("192.168.0.2"),
res: false,
},
{
e: ParseEntry("192.168.0.1"),
other: ParseEntry("192.168.1.0/24"),
res: false,
},
{
e: ParseEntry("192.168.0.1"),
other: ParseEntry("*.google.com"),
res: false,
},
{
e: ParseEntry("192.168.0.1/24"),
other: ParseEntry("192.168.0.1/24"),
res: true,
},
{
e: ParseEntry("192.168.0.1/24"),
other: ParseEntry("192.168.0.1/16"),
res: false,
},
{
e: ParseEntry("192.168.0.1/24"),
other: ParseEntry("192.168.0.1"),
res: false,
},
{
e: ParseEntry("192.168.0.1/24"),
other: ParseEntry("*.google.com"),
res: false,
},
{
e: ParseEntry("*.google.com"),
other: ParseEntry("*.google.com"),
res: true,
},
{
e: ParseEntry("*.google.com"),
other: ParseEntry("mail.google.com"),
res: false,
},
{
e: ParseEntry("*.google.com"),
other: ParseEntry("*.yahoo.com"),
res: false,
},
{
e: ParseEntry("*.google.com"),
other: ParseEntry("192.168.0.1"),
res: false,
},
{
e: ParseEntry("*.google.com"),
other: ParseEntry("192.168.0.1/24"),
res: false,
},
}
for _, te := range tests {
assert.Equal(t, te.res, te.e.Equal(te.other), "test: %s equal %s", te.e, te.other)
}
}
func TestParseEntry(t *testing.T) {
var tests = []struct {
s string
res Entry
}{
{
s: "foo bar",
},
{
s: "192.168.0.1",
res: &urlEntry{u: parseURL("192.168.0.1")},
},
{
s: "192.168.0.1:80",
res: &urlEntry{u: parseURL("192.168.0.1:80")},
},
{
s: "192.168.0",
res: &urlEntry{u: parseURL("192.168.0")},
},
{
s: "192.168.0.1/24",
res: &cidrEntry{ipnet: &net.IPNet{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(24, 32)}},
},
{
s: "192.168.0/24",
res: &urlEntry{u: parseURL("192.168.0/24")},
},
{
s: "*.google.com",
res: &urlEntry{u: parseURL("*.google.com")},
},
{
s: "google.com",
res: &urlEntry{u: parseURL("google.com")},
},
{
s: "google.com:8080",
res: &urlEntry{u: parseURL("google.com:8080")},
},
{
s: "http://*.google.com",
res: &urlEntry{u: parseURL("http://*.google.com")},
},
{
s: "http://google.com",
res: &urlEntry{u: parseURL("http://google.com")},
},
{
s: "http://google.com:8080",
res: &urlEntry{u: parseURL("http://google.com:8080")},
},
}
for _, te := range tests {
res := ParseEntry(te.s)
if te.res == nil {
assert.Nil(t, res, "ParseEntry(%s) != %s, got %s", te.s, te.res, res)
continue
}
assert.True(t, te.res.Equal(res), "ParseEntry(%s) != %s, got %s", te.s, te.res, res)
}
}

View File

@@ -0,0 +1,107 @@
// Copyright 2017 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 registry
import mock "github.com/stretchr/testify/mock"
// MockEntry is an autogenerated mock type for the Entry type
type MockEntry struct {
mock.Mock
}
// Contains provides a mock function with given fields: e
func (_m *MockEntry) Contains(e Entry) bool {
ret := _m.Called(e)
var r0 bool
if rf, ok := ret.Get(0).(func(Entry) bool); ok {
r0 = rf(e)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Equal provides a mock function with given fields: other
func (_m *MockEntry) Equal(other Entry) bool {
ret := _m.Called(other)
var r0 bool
if rf, ok := ret.Get(0).(func(Entry) bool); ok {
r0 = rf(other)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// IsCIDR provides a mock function with given fields:
func (_m *MockEntry) IsCIDR() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// IsURL provides a mock function with given fields:
func (_m *MockEntry) IsURL() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Match provides a mock function with given fields: e
func (_m *MockEntry) Match(e string) bool {
ret := _m.Called(e)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(e)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// String provides a mock function with given fields:
func (_m *MockEntry) String() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
var _ Entry = (*MockEntry)(nil)

View File

@@ -0,0 +1,46 @@
// Copyright 2017 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 registry
import mock "github.com/stretchr/testify/mock"
// MockMerger is an autogenerated mock type for the Merger type
type MockMerger struct {
mock.Mock
}
// Merge provides a mock function with given fields: _a0, _a1
func (_m *MockMerger) Merge(_a0 Entry, _a1 Entry) (Entry, error) {
ret := _m.Called(_a0, _a1)
var r0 Entry
if rf, ok := ret.Get(0).(func(Entry, Entry) Entry); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Entry)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(Entry, Entry) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
var _ Merger = (*MockMerger)(nil)

81
vendor/github.com/vmware/vic/pkg/registry/set.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2017 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 registry
type Set []Entry
type Merger interface {
Merge(Entry, Entry) (Entry, error)
}
type defaultMerger struct{}
func (m *defaultMerger) Merge(orig, other Entry) (Entry, error) {
if orig.String() == other.String() {
return other, nil
}
return nil, nil
}
func (s Set) Match(m string) bool {
for _, e := range s {
if e.Match(m) {
return true
}
}
return false
}
func (s Set) Merge(other Set, merger Merger) (Set, error) {
if merger == nil {
merger = &defaultMerger{}
}
res := make([]Entry, len(s))
var adds Set
copy(res, s)
for _, o := range other {
merged := false
for i := 0; i < len(res); i++ {
e, err := merger.Merge(res[i], o)
if err != nil {
return nil, err
}
if e != nil {
res[i] = e
merged = true
break
}
}
if !merged {
adds = append(adds, o)
}
}
return append(res, adds...), nil
}
func (s Set) Strings() []string {
res := make([]string, len(s))
for i := range s {
res[i] = s[i].String()
}
return res
}

127
vendor/github.com/vmware/vic/pkg/registry/set_test.go generated vendored Normal file
View File

@@ -0,0 +1,127 @@
// Copyright 2017 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 registry
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestSetMerge(t *testing.T) {
var tests = []struct {
orig, other Set
res Set
err error
}{
{},
{
orig: nil,
other: Set{ParseEntry("192.168.0.1")},
res: Set{ParseEntry("192.168.0.1")},
},
{
orig: Set{ParseEntry("192.168.0.1")},
other: nil,
res: Set{ParseEntry("192.168.0.1")},
},
{
orig: Set{ParseEntry("192.168.0.1")},
other: Set{ParseEntry("192.168.0.1")},
res: Set{ParseEntry("192.168.0.1")},
},
{
orig: Set{ParseEntry("192.168.0.1")},
other: Set{ParseEntry("192.168.0.2")},
res: Set{
ParseEntry("192.168.0.1"),
ParseEntry("192.168.0.2"),
},
},
{
orig: Set{
ParseEntry("192.168.0.1"),
ParseEntry("192.168.0.2"),
},
other: Set{
ParseEntry("192.168.0.2"),
},
res: Set{
ParseEntry("192.168.0.1"),
ParseEntry("192.168.0.2"),
},
},
{
orig: Set{
ParseEntry("192.168.0.1"),
ParseEntry("192.168.0.2"),
},
other: Set{
ParseEntry("192.168.0.3"),
},
res: Set{
ParseEntry("192.168.0.1"),
ParseEntry("192.168.0.2"),
ParseEntry("192.168.0.3"),
},
},
}
for _, te := range tests {
res, err := te.orig.Merge(te.other, nil)
assert.Equal(t, te.err, err)
assert.Len(t, res, len(te.res))
for _, r := range res {
found := false
for _, r2 := range te.res {
if r2.String() == r.String() {
found = true
break
}
}
assert.True(t, found)
}
}
}
func TestSetMergeMergerError(t *testing.T) {
m := &MockMerger{}
m.On("Merge", mock.Anything, mock.Anything).Return(nil, assert.AnError)
s := Set{ParseEntry("192.168.0.1")}
other := Set{ParseEntry("192.168.0.1")}
res, err := s.Merge(other, m)
assert.Nil(t, res)
assert.Error(t, err)
assert.EqualValues(t, assert.AnError, err)
}
func TestSetMatch(t *testing.T) {
e1 := &MockEntry{}
e1.On("Match", mock.Anything).Return(false)
e2 := &MockEntry{}
e2.On("Match", mock.Anything).Return(true)
s := Set{e1, e2}
assert.True(t, s.Match("foo"))
s = Set{e2}
assert.True(t, s.Match("foo"))
s = Set{e1}
assert.False(t, s.Match("foo"))
}

58
vendor/github.com/vmware/vic/pkg/registry/utils.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2017 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 registry
import (
"crypto/x509"
"fmt"
"net/url"
"time"
log "github.com/Sirupsen/logrus"
urlfetcher "github.com/vmware/vic/pkg/fetcher"
)
// Reachable test if a registry is a valid registry for VIC use and returns a url with scheme prepended
func Reachable(registry, scheme, username, password string, registryCAs *x509.CertPool, timeout time.Duration, skipVerify bool) (string, error) {
registryPath := fmt.Sprintf("%s/v2/", registry)
if scheme != "" {
registryPath = fmt.Sprintf("%s://%s/v2/", scheme, registry)
}
url, err := url.Parse(registryPath)
if err != nil {
return "", err
}
log.Debugf("URL: %s", url)
fetcher := urlfetcher.NewURLFetcher(urlfetcher.Options{
Timeout: timeout,
Username: username,
Password: password,
InsecureSkipVerify: skipVerify,
RootCAs: registryCAs,
})
headers, err := fetcher.Head(url)
if err != nil {
return "", err
}
// v2 API requires this check
if headers.Get("Docker-Distribution-API-Version") != "registry/2.0" {
return "", fmt.Errorf("Missing Docker-Distribution-API-Version header")
}
return registryPath, nil
}

114
vendor/github.com/vmware/vic/pkg/retry/retry.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 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 retry
import (
"time"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/vmware/vic/pkg/trace"
)
const (
// Random numbers generated by fair dice roll :)
defaultInitialInterval = 10 * time.Millisecond
defaultRandomizationFactor = 0.5
defaultMultiplier = 10
defaultMaxInterval = 30 * time.Second
defaultMaxElapsedTime = 1 * time.Minute
)
// simplified config for configuring a back off object. Callers should populate and supply this to DoWithConfig
type BackoffConfig struct {
InitialInterval time.Duration
RandomizationFactor float64
Multiplier float64
MaxInterval time.Duration
// this field will indicate the maximum amount of "sleep" time that will occur.
MaxElapsedTime time.Duration
}
// Generate a new BackoffConfig with default values
func NewBackoffConfig() *BackoffConfig {
return &BackoffConfig{
InitialInterval: defaultInitialInterval,
RandomizationFactor: defaultRandomizationFactor,
Multiplier: defaultMultiplier,
MaxInterval: defaultMaxInterval,
MaxElapsedTime: defaultMaxElapsedTime,
}
}
// Do retries the given function until defaultMaxInterval time passes, while sleeping some time between unsuccessful attempts
// if retryOnError returns true, continue retry, otherwise, return error
func Do(operation func() error, retryOnError func(err error) bool) error {
bConf := &BackoffConfig{
InitialInterval: defaultInitialInterval,
RandomizationFactor: defaultRandomizationFactor,
Multiplier: defaultMultiplier,
MaxInterval: defaultMaxInterval,
MaxElapsedTime: defaultMaxElapsedTime,
}
return DoWithConfig(operation, retryOnError, bConf)
}
// DoWithConfig will attempt an operation while retrying using an exponential back off based on the config supplied by the caller. The retry decider is the supplied function retryOnError
func DoWithConfig(operation func() error, retryOnError func(err error) bool, config *BackoffConfig) error {
defer trace.End(trace.Begin(""))
var err error
var next time.Duration
b := &backoff.ExponentialBackOff{
InitialInterval: config.InitialInterval,
RandomizationFactor: config.RandomizationFactor,
Multiplier: config.Multiplier,
MaxInterval: config.MaxInterval,
MaxElapsedTime: config.MaxElapsedTime,
Clock: backoff.SystemClock,
}
// Reset the interval back to the initial retry interval and restart the timer
b.Reset()
for {
if err = operation(); err == nil {
return nil
}
if next = b.NextBackOff(); next == backoff.Stop {
log.Errorf("Will stop trying again. Operation failed with %#+v", err)
return err
}
// check error
if !retryOnError(err) {
log.Errorf("Operation failed with %#+v", err)
return err
}
// Expected error
log.Warnf("Will try again in %s. Operation failed with detected error", next)
// sleep and try again
time.Sleep(next)
continue
}
}
// OnError is the simplest of retry deciders. If an error occurs it will indicate a retry is needed.
func OnError(err error) bool {
return err != nil
}

41
vendor/github.com/vmware/vic/pkg/retry/retry_test.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 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 retry
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestRetry(t *testing.T) {
i := 0
operation := func() error {
i++
t.Logf("Retry called: %s", time.Now().UTC())
return fmt.Errorf("designed error")
}
retryOnError := func(err error) bool {
if i < 4 {
return true
}
return false
}
err := Do(operation, retryOnError)
assert.True(t, i == 4, "should retried 4 times")
assert.True(t, err != nil, fmt.Sprintf("retry do not depend on error status"))
}

30
vendor/github.com/vmware/vic/pkg/serial/debug.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 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 serial
var (
verbose = false
tracing = false
)
// EnableTracing enables trace output for the serial package
func EnableTracing() {
tracing = true
}
// DisableTracing disables trace output for the serial package
func DisableTracing() {
tracing = false
}

221
vendor/github.com/vmware/vic/pkg/serial/handshake.go generated vendored Normal file
View File

@@ -0,0 +1,221 @@
// Copyright 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.
//
//
// Client:
// generate a random uint8 (#)
// send 2 bytes Syn|#
//
// Server:
// generate a random uint8 (&)
// read at least 2 bytes and make sure Syn|# is received
// send 3 bytes Ack|#+1|& (or Nak)
//
// Client:
// read at least 3 bytes and make sure Ack|#+1|& is received
// send 2 bytes Ack|&+1
//
// Server:
// read at least 2 bytes and make sure Ack|&+1 is received
// send 1 byte Ack (or Nak)
// Client:
// read at leat 1 byte and make sure Ack is received
package serial
import (
"fmt"
"io"
"math"
"math/rand"
"time"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/pkg/trace"
)
const (
flagSyn byte = 0x16
flagAck = 0x06
flagNak = 0x15
)
// HandshakeError should only occure if the protocol between HandshakeServer and HandshakeClient was violated.
type HandshakeError struct {
msg string
}
func (he *HandshakeError) Error() string {
return he.msg
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// ReadAtLeastN reads at least l bytes and returns those l bytes or errors
// We get lots of garbage data when we get the initial connection which handshake supposed to clear them and leave the connection in a known state so that the real ssh handshake can start.
// Client and server is looping with different frequencies so client could send multiple requests before server even had a chance to read.
// By getting the last l bytes we are saying that we are not interested with garbage data and also eliminating duplicated flags by only using the last one
func ReadAtLeastN(conn io.ReadWriter, buffer []byte, l int) ([]byte, error) {
n, err := io.ReadAtLeast(conn, buffer, l)
if err != nil {
return nil, err
}
// however if we read more than l, it means buffer is not empty
if n != l {
buffer = buffer[n-l:]
}
return buffer, nil
}
// HandshakeClient establishes connection with the server making sure
// they both are in sync.
func HandshakeClient(conn io.ReadWriter) error {
if tracing {
defer trace.End(trace.Begin(""))
}
// generate a random pos between [0, math.MaxUint8)
pos := uint8(rand.Intn(math.MaxUint8))
buffer := make([]byte, 32*1024)
// send syn with pos
log.Debugf("HandshakeClient: Sending syn with pos %d", pos)
if _, err := conn.Write([]byte{flagSyn, pos}); err != nil {
log.Errorf("syn: write failed")
return err
}
// read ack with pos+1 and token
buffer, err := ReadAtLeastN(conn, buffer, 3)
if err != nil {
return err
}
// extract pos and the token from it
flag, posack, token := uint8(buffer[0]), uint8(buffer[1]), uint8(buffer[2])
if flag == flagNak {
return &HandshakeError{
msg: "HandshakeClient: Server declined handshake request",
}
}
if flag != flagAck {
return &HandshakeError{
msg: fmt.Sprintf("HandshakeClient: Unexpected server response: %#v", flag),
}
}
if posack != pos+1 {
return &HandshakeError{
msg: fmt.Sprintf("HandshakeClient: Unexpected ack position: %d, expected %d", posack, pos+1),
}
}
log.Debugf("HandshakeClient: Sending ack with %d", token+1)
if _, err := conn.Write([]byte{flagAck, token + 1}); err != nil {
return err
}
// last ack packet is 1 byte and could be followed by SSH handshake so read only 1 byteand leave the rest in the net.Conn buffer
buffer = buffer[:1]
if _, err := conn.Read(buffer); err != nil {
return err
}
if buffer[0] != flagAck {
return &HandshakeError{
msg: fmt.Sprintf("HandshakeClient: Unexpected server response: %#v", flag),
}
}
log.Debug("HandshakeClient: Connection established.")
return nil
}
// HandshakeServer establishes connection with the client making sure
// they both are in sync.
func HandshakeServer(conn io.ReadWriter) error {
if tracing {
defer trace.End(trace.Begin(""))
}
// generate a random pos between [0, math.MaxUint8)
pos := uint8(rand.Intn(math.MaxUint8))
buffer := make([]byte, 32*1024)
log.Debug("HandshakeServer: Waiting for incoming syn request...")
// Sync packet is 2 bytes, however if we read more than 2 it means buffer is not empty and data is not trusted for this sync.
buffer, err := ReadAtLeastN(conn, buffer, 2)
if err != nil {
return err
}
// Read 2 bytes, extract flag and the token from it
flag, token := uint8(buffer[0]), uint8(buffer[1])
if flag != flagSyn {
if _, err := conn.Write([]byte{flagNak}); err != nil {
return err
}
return &HandshakeError{
msg: fmt.Sprintf("Unexpected syn packet: %x", flag),
}
}
log.Debugf("HandshakeServer: Received syn with pos %d. Writing syn-ack with %d and %d", token, token+1, pos)
// token contains position token that needs to be incremented by one to send it back.
if _, err := conn.Write([]byte{flagAck, token + 1, pos}); err != nil {
return err
}
// ACK packet is 2 bytes, however if we read more than 2 it means buffer is not empty and data is not trusted for this sync.
buffer, err = ReadAtLeastN(conn, buffer, 2)
if err != nil {
return err
}
// Read 2 bytes, extract flag and the token from it
flag, token = uint8(buffer[0]), uint8(buffer[1])
if flag != flagAck {
if _, err := conn.Write([]byte{flagNak}); err != nil {
return err
}
return &HandshakeError{
msg: fmt.Sprintf("Unexpected syn packet: %x", flag),
}
}
// token should contain incremented pos
if token != pos+1 {
if _, err := conn.Write([]byte{flagNak}); err != nil {
return err
}
return &HandshakeError{
msg: fmt.Sprintf("HandshakeServer: Unexpected position %x, expected: %x", token, pos+1),
}
}
log.Debugf("HandshakeServer: Received ACK with %d.", token)
// send the last ACK
if _, err := conn.Write([]byte{flagAck}); err != nil {
return err
}
log.Debug("HandshakeServer: Connection established.")
return nil
}

View File

@@ -0,0 +1,429 @@
// Copyright 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 serial
import (
"errors"
"sync"
"testing"
"time"
log "github.com/Sirupsen/logrus"
)
const (
timeout = 10 * time.Second
)
type readErr struct {
err error
n int
}
type BlockingSendReceiver struct {
c chan byte
deadline chan struct{}
}
func NewBlockingSendReceiver() *BlockingSendReceiver {
return &BlockingSendReceiver{
c: make(chan byte, 10240),
deadline: make(chan struct{}, 1),
}
}
func (f *BlockingSendReceiver) Send(b []byte) (int, error) {
for i := 0; i < len(b); i++ {
f.c <- b[i]
}
return 0, nil
}
func (f *BlockingSendReceiver) Timeout(d time.Duration) *BlockingSendReceiver {
go func() {
time.Sleep(d)
f.deadline <- struct{}{}
}()
return f
}
func (f *BlockingSendReceiver) Receive(b []byte) (int, error) {
select {
case <-f.deadline:
return 0, errors.New("Timeout error")
default:
}
count := 0
for count < len(b) {
select {
case v := <-f.c:
b[count] = v
count++
case _, ok := <-f.deadline:
if ok {
close(f.deadline)
}
return 0, errors.New("Timeout error")
default:
if count == 0 {
time.Sleep(time.Millisecond)
} else {
return count, nil
}
}
}
return count, nil
}
type BiChannel struct {
L *BlockingSendReceiver
R *BlockingSendReceiver
}
func (bc *BiChannel) Write(b []byte) (int, error) {
return bc.L.Send(b)
}
func (bc *BiChannel) Read(b []byte) (int, error) {
return bc.R.Receive(b)
}
func NewFakeConnection(t time.Duration) (*BiChannel, *BiChannel) {
l := NewBlockingSendReceiver().Timeout(t)
r := NewBlockingSendReceiver().Timeout(t)
return &BiChannel{L: l, R: r}, &BiChannel{L: r, R: l}
}
func TestHandshakeServerNormalCaseScenario(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
buf := make([]byte, 10)
clientConn.Write([]byte{flagSyn, 200})
if n, e := clientConn.Read(buf); e != nil || n != 3 {
t.Errorf("Only 3 bytes are expected: %x, received: %d", buf[:n], n)
return
}
if buf[0] != flagAck || buf[1] != 201 {
t.Errorf("Error, unexpected data: %v", buf[:3])
return
}
clientConn.Write([]byte{flagAck, buf[2] + 1})
}()
if e := HandshakeServer(serverConn); e != nil {
t.Errorf("Unexpected error: %v", e)
}
}
func TestHandshakeServerLotsOfTrashOnTheLine(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
buf := make([]byte, 10)
// Do not send too many bytes, otherwise "write" will block on server side due too many flagNak.
x := "sdfkgn sdflkjsfdfdgis dfgs"
for i := 0; i < 5; i++ {
x += x
}
clientConn.Write([]byte(x))
clientConn.Write([]byte{flagSyn, 200})
n, e := clientConn.Read(buf)
if e != nil {
t.Errorf("Unexpected server error: %v", e)
return
}
if n < 3 {
t.Errorf("Unexpected server error: %v", e)
return
}
if buf[0] == flagNak {
t.Errorf("Unexpected server error: %v", e)
return
}
if buf[0] != flagAck || buf[1] != 201 {
t.Errorf("Error, unexpected data: %x", buf[:3])
return
}
clientConn.Write([]byte{flagAck, buf[2] + 1})
}()
e := HandshakeServer(serverConn)
if e != nil {
if _, ok := e.(*HandshakeError); !ok {
t.Errorf("Unexpected error: %v", e)
return
}
}
if e != nil {
e = HandshakeServer(serverConn)
if _, ok := e.(*HandshakeError); !ok {
t.Errorf("Unexpected error: %v", e)
}
}
}
func TestHandshakeServerComportSync(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
buf := make([]byte, 10)
// this is the sequence we see from Linux serial driver on the real world
clientConn.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22})
for {
clientConn.Write([]byte{flagSyn, 200})
n, e := clientConn.Read(buf)
if e != nil {
t.Errorf("Unexpected server error: %v", e)
return
}
if n < 3 {
continue
}
data := buf[n-3:]
if data[0] == flagNak {
continue
}
if data[0] != flagAck || data[1] != 201 {
t.Errorf("Error, unexpected data: %x", data[:3])
return
}
clientConn.Write([]byte{flagAck, data[2] + 1})
break
}
}()
for {
if e := HandshakeServer(serverConn); e == nil {
break
} else {
if _, ok := e.(*HandshakeError); !ok {
t.Errorf("Unexpected error: %v", e)
return
}
}
}
}
func TestHandshakeServerAckNakResponse(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
buf := make([]byte, 10)
// Do not send too many bytes, otherwise "write" will block on server side due too many flagNak.
clientConn.Write([]byte{flagSyn, 200})
n, e := clientConn.Read(buf)
if e != nil {
t.Errorf("Unexpected server error: %v", e)
return
}
data := buf[n-3:]
if data[0] != flagAck || data[1] != 201 {
t.Errorf("Error, unexpected data: %x", data[:3])
return
}
// intentional error. data[2] has to be incremented.
clientConn.Write([]byte{flagAck, data[2]})
if n, err := clientConn.Read(buf); n != 1 || err != nil || buf[0] != flagNak {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
clientConn.Write([]byte{flagSyn, 200})
n, e = clientConn.Read(buf)
if e != nil {
t.Errorf("Unexpected server error: %v", e)
return
}
data = buf[n-3:]
if data[0] != flagAck || data[1] != 201 {
t.Errorf("Error, unexpected data: %x", data[:3])
return
}
// intentional error. 99 in a wrong code.
clientConn.Write([]byte{99, data[2] + 1})
if n, err := clientConn.Read(buf); n != 1 || err != nil || buf[0] != flagNak {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
}()
e := HandshakeServer(serverConn)
if e != nil {
if _, ok := e.(*HandshakeError); !ok {
t.Errorf("Unexpected error: %v", e)
return
}
}
if e != nil {
e = HandshakeServer(serverConn)
if _, ok := e.(*HandshakeError); !ok {
t.Errorf("Unexpected error: %v", e)
}
}
}
func TestHandshakeClientNormalConnection(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
pos := byte(200)
buf := make([]byte, 1024)
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagSyn {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
serverConn.Write([]byte{flagAck, buf[1] + 1, pos})
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagAck || buf[1] != pos+1 {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
serverConn.Write([]byte{flagAck})
}()
e := HandshakeClient(clientConn)
if e != nil {
t.Errorf("Unexpected error: %v", e)
}
}
func TestHandshakeClientWrongServerAckPos(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
pos := byte(200)
buf := make([]byte, 1024)
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagSyn {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
// writing the wrong buf[1] that supposed to be incremented.
serverConn.Write([]byte{flagAck, buf[1], pos})
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagAck || buf[1] != pos+1 {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
}()
err, ok := HandshakeClient(clientConn).(*HandshakeError)
if !ok {
t.Errorf("Unexpected error: %v", err)
}
}
func TestHandshakeClientWrongServerAck(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
go func() {
pos := byte(200)
buf := make([]byte, 1024)
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagSyn {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
// writing 90 instead of flagAck
serverConn.Write([]byte{90, buf[1] + 1, pos})
if n, err := serverConn.Read(buf); n != 2 || err != nil || buf[0] != flagAck || buf[1] != pos+1 {
t.Errorf("Unexpected data or error %d, %v", n, err)
return
}
}()
err, ok := HandshakeClient(clientConn).(*HandshakeError)
if !ok {
t.Errorf("Unexpected error: %v", err)
}
}
func TestHandshakeServerVsClient(t *testing.T) {
log.SetLevel(log.InfoLevel)
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
}
clientConn, serverConn := NewFakeConnection(timeout)
w := sync.WaitGroup{}
w.Add(2)
go func() {
defer w.Done()
err := HandshakeClient(clientConn)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}()
go func() {
defer w.Done()
err := HandshakeServer(serverConn)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}()
w.Wait()
}

47
vendor/github.com/vmware/vic/pkg/serial/rawaddr.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
// Copyright 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 serial
import "github.com/vmware/vic/pkg/trace"
type RawAddr struct {
Net string
Addr string
}
func (addr RawAddr) Network() string {
if tracing {
defer trace.End(trace.Begin(""))
}
return addr.Net
}
func (addr RawAddr) String() string {
if tracing {
defer trace.End(trace.Begin(""))
}
return addr.Network() + "://" + addr.Addr
}
func NewRawAddr(net string, addr string) *RawAddr {
if tracing {
defer trace.End(trace.Begin(""))
}
return &RawAddr{
Net: net,
Addr: addr,
}
}

231
vendor/github.com/vmware/vic/pkg/serial/rawconn.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
// Copyright 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 serial
import (
"io"
"net"
"os"
"runtime"
"sync"
"time"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/pkg/trace"
)
type NamedReadChannel interface {
io.ReadCloser
Name() string
Fd() uintptr
}
type NamedWriteChannel interface {
io.WriteCloser
Name() string
Fd() uintptr
}
type RawConn struct {
rchannel NamedReadChannel
wchannel NamedWriteChannel
localAddr net.Addr
remoteAddr net.Addr
err chan error
mutex sync.Mutex
closed bool
}
func NewTypedConn(r NamedReadChannel, w NamedWriteChannel, net string) (*RawConn, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
conn := &RawConn{
rchannel: r,
wchannel: w,
localAddr: *NewRawAddr(net, r.Name()),
remoteAddr: *NewRawAddr(net, w.Name()),
err: make(chan error, 1),
closed: false,
}
return conn, nil
}
// NewFileConn creates a connection of the provided file - assumes file is a
// full duplex comm mechanism
func NewFileConn(file *os.File) (*RawConn, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
return NewTypedConn(file, file, "file")
}
// NewRawConn creates a connection via the provided file descriptor - assumes file is a
// full duplex comm mechanism
func NewRawConn(fd uintptr, name string, net string) (*RawConn, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
file := os.NewFile(fd, name)
return NewTypedConn(file, file, net)
}
// NewHalfDuplexFileConn creates a connection via the provided files - this assumes that
// each file is a half-duplex mechanism, such as a linux fifo pipe
func NewHalfDuplexFileConn(read *os.File, write *os.File, name string, net string) (*RawConn, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
return NewTypedConn(read, write, net)
}
// Read reads data from the connection.
func (conn *RawConn) Read(b []byte) (int, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
var n int
var err error
if verbose {
defer func() {
log.Debugf("Returning error and bytes from read (%s:%s): %d, %s", conn.rchannel.Name(), conn.wchannel.Name(), n, err)
}()
}
// TODO: this is horrific from a performance perspective - really need a better
// way to interrupt that file.Read call
bytes := make(chan int, 1)
go func() {
n, err = conn.rchannel.Read(b)
// if we've got any bytes we need to pass them back so we cannot return
// the error via conn.err
bytes <- n
close(bytes)
}()
conn.mutex.Lock()
closed := conn.closed
conn.mutex.Unlock()
select {
case n = <-bytes:
if err != nil && closed {
err = io.EOF
}
return n, err
case e := <-conn.err:
log.Debugf("Returning error from read: %s", e)
// only one close will send an error and we have that, so this won't block
// we do need to interrupt all reads
conn.err <- e
return n, e
}
}
// Write writes data to the connection
func (conn *RawConn) Write(b []byte) (int, error) {
if tracing {
defer trace.End(trace.Begin(""))
}
return conn.wchannel.Write(b)
}
// Close closes the connection.
func (conn *RawConn) Close() error {
if tracing {
defer trace.End(trace.Begin(""))
}
var closed bool
conn.mutex.Lock()
closed = conn.closed
conn.closed = true
conn.mutex.Unlock()
if closed {
log.Debugf("Close called again on RawConn (%s:%s) - dropping", conn.rchannel.Name(), conn.wchannel.Name())
return nil
}
// process the close
log.Debugf("Closing the RawConn (%s:%s)", conn.rchannel.Name(), conn.wchannel.Name())
errR := conn.rchannel.Close()
errW := conn.wchannel.Close()
if verbose {
buf := make([]byte, 4096)
bytes := runtime.Stack(buf, false)
log.Debugf("Close called on RawConn (%s:%s):\n%s", conn.rchannel.Name(), conn.wchannel.Name(), string(buf[:bytes]))
}
log.Debugf("Pushing EOF to any blocked readers on the raw connection (%s:%s)", conn.rchannel.Name(), conn.wchannel.Name())
conn.err <- io.EOF
if errR != nil {
return errR
}
return errW
}
// LocalAddr returns the local network address.
func (conn *RawConn) LocalAddr() net.Addr {
if tracing {
defer trace.End(trace.Begin(""))
}
return conn.localAddr
}
// RemoteAddr returns the remote network address.
func (conn *RawConn) RemoteAddr() net.Addr {
if tracing {
defer trace.End(trace.Begin(""))
}
return conn.remoteAddr
}
// SetDeadline sets the read and write deadlines associated
// with the connection
func (conn *RawConn) SetDeadline(t time.Time) error {
if tracing {
defer trace.End(trace.Begin(t.String()))
}
// https://golang.org/src/net/fd_poll_runtime.go#L133
// consider implementing this by making RawConn a netFD
// if we can find a way around the lack of export
return nil
}
// SetReadDeadline sets the deadline for future Read calls.
func (conn *RawConn) SetReadDeadline(t time.Time) error {
if tracing {
defer trace.End(trace.Begin(t.String()))
}
return nil
}
// SetWriteDeadline sets the deadline for future Write calls.
func (conn *RawConn) SetWriteDeadline(t time.Time) error {
if tracing {
defer trace.End(trace.Begin(t.String()))
}
return nil
}

253
vendor/github.com/vmware/vic/pkg/telnet/connection.go generated vendored Normal file
View File

@@ -0,0 +1,253 @@
// Copyright 2017 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 telnet
import (
"bytes"
"errors"
"io"
"io/ioutil"
"net"
"sync"
log "github.com/Sirupsen/logrus"
)
type optCallBackFunc func(byte, byte)
type connOpts struct {
// conn is the underlying connection
conn net.Conn
fsm *fsm
serverOpts map[byte]bool
clientOpts map[byte]bool
optCallback optCallBackFunc
Handlers
}
// Conn is the struct representing the telnet connection
type Conn struct {
connOpts
// the connection write channel. Everything required to be written to the connection goes to this channel
writeCh chan []byte
// dataRW is the data buffer. It is written to by the FSM and read from by the data handler
dataRW io.ReadWriter
cmdBuffer bytes.Buffer
handlerWriter io.WriteCloser
// used in the dataHandlerWrapper to notify that the telnet connection is closed
dataHandlerCloseCh chan chan struct{}
// used in the dataHandlerWrapper to notify that data has been writeen to the dataRW buffer
dataWrittenCh chan bool
// connWriteDoneCh closes the write loop when the telnet connection is closed
connWriteDoneCh chan chan struct{}
closedMutex sync.Mutex
closed bool
}
// Safely read/write concurrently to the data Buffer
// databuffer is written to by the FSM and it is read from by the dataHandler
type dataReadWriter struct {
buf bytes.Buffer
sync.Mutex
}
func (drw *dataReadWriter) Read(p []byte) (int, error) {
drw.Lock()
defer drw.Unlock()
return drw.buf.Read(p)
}
func (drw *dataReadWriter) Write(p []byte) (int, error) {
drw.Lock()
defer drw.Unlock()
return drw.buf.Write(p)
}
// This is the Writer that is passed to the handlers to write to the telnet connection
type connectionWriter struct {
ch chan []byte
}
func (cw *connectionWriter) Write(b []byte) (int, error) {
if cw.ch != nil {
cw.ch <- b
}
return len(b), nil
}
func (cw *connectionWriter) Close() error {
close(cw.ch)
cw.ch = nil
return nil
}
func newConn(opts *connOpts) *Conn {
tc := &Conn{
connOpts: *opts,
writeCh: make(chan []byte),
dataHandlerCloseCh: make(chan chan struct{}),
dataWrittenCh: make(chan bool),
connWriteDoneCh: make(chan chan struct{}),
closed: false,
}
if tc.optCallback == nil {
tc.optCallback = tc.handleOptionCommand
}
tc.handlerWriter = &connectionWriter{
ch: tc.writeCh,
}
tc.dataRW = &dataReadWriter{}
tc.fsm.tc = tc
return tc
}
//UnderlyingConnection returns the underlying TCP connection
func (c *Conn) UnderlyingConnection() net.Conn {
return c.conn
}
func (c *Conn) writeLoop() {
log.Debugf("entered write loop")
for {
select {
case writeBytes := <-c.writeCh:
c.conn.Write(writeBytes)
case ch := <-c.connWriteDoneCh:
ch <- struct{}{}
return
}
}
}
func (c *Conn) startNegotiation() {
for k := range c.serverOpts {
log.Infof("sending WILL %d", k)
c.sendCmd(Will, k)
}
for k := range c.clientOpts {
log.Infof("sending DO %d", k)
c.sendCmd(Do, k)
}
}
// close closes the telnet connection
func (c *Conn) close() {
c.closedMutex.Lock()
defer c.closedMutex.Unlock()
c.closed = true
log.Infof("Closing the connection")
c.conn.Close()
c.closeConnLoopWrite()
c.closeDatahandler()
c.handlerWriter.Close()
log.Infof("telnet connection closed")
// calling the CloseHandler passed by vspc
c.CloseHandler(c)
}
func (c *Conn) closeConnLoopWrite() {
connLoopWriteCh := make(chan struct{})
c.connWriteDoneCh <- connLoopWriteCh
<-connLoopWriteCh
log.Debugf("connection loop write-side closed")
}
func (c *Conn) closeDatahandler() {
dataCh := make(chan struct{})
c.dataHandlerCloseCh <- dataCh
<-dataCh
}
func (c *Conn) sendCmd(cmd byte, opt byte) {
c.writeCh <- []byte{Iac, cmd, opt}
log.Debugf("Sending command: %v %v", cmd, opt)
}
func (c *Conn) handleOptionCommand(cmd byte, opt byte) {
if cmd == Will || cmd == Wont {
if _, ok := c.clientOpts[opt]; !ok {
c.sendCmd(Dont, opt)
return
}
c.sendCmd(Do, opt)
}
if cmd == Do || cmd == Dont {
if _, ok := c.serverOpts[opt]; !ok {
c.sendCmd(Wont, opt)
return
}
log.Debugf("Sending WILL command")
c.sendCmd(Will, opt)
}
}
func (c *Conn) dataHandlerWrapper(w io.Writer, r io.Reader) {
defer func() {
log.Debugf("data handler closed")
}()
for {
select {
case ch := <-c.dataHandlerCloseCh:
ch <- struct{}{}
return
case <-c.dataWrittenCh:
// #nosec: Errors unhandled.
if b, _ := ioutil.ReadAll(r); len(b) > 0 {
c.DataHandler(w, b, c)
}
}
}
}
func (c *Conn) cmdHandlerWrapper(w io.Writer, r io.Reader) {
// #nosec: Errors unhandled.
if cmd, _ := ioutil.ReadAll(r); len(cmd) > 0 {
c.CmdHandler(w, cmd, c)
}
}
// IsClosed returns true if the connection is already closed
func (c *Conn) IsClosed() bool {
c.closedMutex.Lock()
defer c.closedMutex.Unlock()
return c.closed
}
// WriteData writes telnet data to the underlying connection doubling every IAC
func (c *Conn) WriteData(b []byte) (int, error) {
var escaped []byte
for _, v := range b {
if v == Iac {
escaped = append(escaped, 255)
}
escaped = append(escaped, v)
}
if c.IsClosed() {
return -1, errors.New("telnet connection is already closed")
}
c.writeCh <- escaped
return len(b), nil
}

View File

@@ -0,0 +1,124 @@
// Copyright 2017 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 telnet
import (
"bytes"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type dummyConn struct {
dataBuf bytes.Buffer
}
func (c *dummyConn) Read(b []byte) (n int, err error) {
return 3, nil
}
func (c *dummyConn) Write(b []byte) (n int, err error) {
return c.dataBuf.Write(b)
}
func (c *dummyConn) Close() error {
return nil
}
func (c *dummyConn) LocalAddr() net.Addr {
return nil
}
func (c *dummyConn) RemoteAddr() net.Addr {
return nil
}
func (c *dummyConn) SetDeadline(t time.Time) error {
return nil
}
func (c *dummyConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *dummyConn) SetWriteDeadline(t time.Time) error {
return nil
}
func newTestItem() *Conn {
opts := connOpts{
conn: &dummyConn{},
serverOpts: map[byte]bool{
Binary: true,
Echo: true,
},
clientOpts: map[byte]bool{
Naocrd: true,
Naohts: true,
},
}
return &Conn{
connOpts: opts,
writeCh: make(chan []byte),
connWriteDoneCh: make(chan chan struct{}),
}
}
func TestWriteData(t *testing.T) {
conn := newTestItem()
data := [][]byte{{10, 15, 23, 210}, {10, Iac, 30, 40}, {10, Iac, Iac, 30, 40}}
expected := [][]byte{{10, 15, 23, 210}, {10, Iac, Iac, 30, 40}, {10, Iac, Iac, Iac, Iac, 30, 40}}
for i, d := range data {
go conn.WriteData(d)
received := <-conn.writeCh
assert.Equal(t, expected[i], received)
}
}
func TestSendCmd(t *testing.T) {
conn := newTestItem()
go conn.sendCmd(Do, Binary)
received := <-conn.writeCh
assert.Equal(t, []byte{Iac, Do, Binary}, received)
}
func TestNegotiation(t *testing.T) {
conn := newTestItem()
go conn.startNegotiation()
expected := map[byte][]byte{
Binary: {Iac, Will, Binary},
Echo: {Iac, Will, Echo},
Naocrd: {Iac, Do, Naocrd},
Naohts: {Iac, Do, Naohts},
}
for i := 0; i < 4; i++ {
r := <-conn.writeCh
assert.Equal(t, expected[r[2]], r)
}
}
func TestWriteLoop(t *testing.T) {
conn := newTestItem()
go conn.writeLoop()
conn.writeCh <- []byte{1, 2, 3, 4}
conn.writeCh <- []byte{5, 6}
conn.writeCh <- []byte{7}
ch := make(chan struct{})
conn.connWriteDoneCh <- ch
<-ch
assert.Equal(t, []byte{1, 2, 3, 4, 5, 6, 7}, conn.connOpts.conn.(*dummyConn).dataBuf.Bytes())
}

130
vendor/github.com/vmware/vic/pkg/telnet/fsm.go generated vendored Normal file
View File

@@ -0,0 +1,130 @@
// Copyright 2017 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 telnet
import log "github.com/Sirupsen/logrus"
type state int
const (
dataState state = iota
optionNegotiationState
cmdState
subnegState
subnegEndState
errorState
)
type fsm struct {
curState state
tc *Conn
}
func newFSM() *fsm {
f := &fsm{
curState: dataState,
}
return f
}
func (fsm *fsm) start() {
defer func() {
log.Infof("FSM closed")
}()
for {
b := make([]byte, 4096)
n, err := fsm.readFromRawConnection(b)
if n > 0 {
for i := 0; i < n; i++ {
ch := b[i]
ns := fsm.nextState(ch)
fsm.curState = ns
}
}
if err != nil {
log.Debugf("connection read: %v", err)
fsm.tc.close()
break
}
}
}
func (fsm *fsm) readFromRawConnection(b []byte) (int, error) {
return fsm.tc.conn.Read(b)
}
// this function returns what the next state is and performs the appropriate action
func (fsm *fsm) nextState(ch byte) state {
var nextState state
b := []byte{ch}
switch fsm.curState {
case dataState:
if ch != Iac {
fsm.tc.dataRW.Write(b)
fsm.tc.dataWrittenCh <- true
nextState = dataState
} else {
nextState = cmdState
}
case cmdState:
if ch == Iac { // this is an escaping of IAC to send it as data
fsm.tc.dataRW.Write(b)
fsm.tc.dataWrittenCh <- true
nextState = dataState
} else if ch == Do || ch == Dont || ch == Will || ch == Wont {
fsm.tc.cmdBuffer.WriteByte(ch)
nextState = optionNegotiationState
} else if ch == Sb {
fsm.tc.cmdBuffer.WriteByte(ch)
nextState = subnegState
} else { // anything else
fsm.tc.cmdBuffer.WriteByte(ch)
fsm.tc.cmdHandlerWrapper(fsm.tc.handlerWriter, &fsm.tc.cmdBuffer)
fsm.tc.cmdBuffer.Reset()
nextState = dataState
}
case optionNegotiationState:
fsm.tc.cmdBuffer.WriteByte(ch)
opt := ch
cmd := fsm.tc.cmdBuffer.Bytes()[0]
fsm.tc.optCallback(cmd, opt)
fsm.tc.cmdBuffer.Reset()
nextState = dataState
case subnegState:
if ch == Iac {
nextState = subnegEndState
} else {
nextState = subnegState
fsm.tc.cmdBuffer.WriteByte(ch)
}
case subnegEndState:
if ch == Se {
fsm.tc.cmdBuffer.WriteByte(ch)
fsm.tc.cmdHandlerWrapper(fsm.tc.handlerWriter, &fsm.tc.cmdBuffer)
fsm.tc.cmdBuffer.Reset()
nextState = dataState
} else if ch == Iac { // escaping IAC
nextState = subnegState
fsm.tc.cmdBuffer.WriteByte(ch)
} else {
nextState = errorState
}
case errorState:
nextState = dataState
log.Infof("Finite state machine is in an error state. This should not happen for correct telnet protocol syntax")
}
return nextState
}

176
vendor/github.com/vmware/vic/pkg/telnet/fsm_test.go generated vendored Normal file
View File

@@ -0,0 +1,176 @@
// Copyright 2017 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 telnet
import (
"bytes"
"io"
"log"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type mockConn struct {
r io.Reader
w io.Writer
}
func (c *mockConn) Read(b []byte) (int, error) {
return c.r.Read(b)
}
func (c *mockConn) Write(b []byte) (int, error) {
return c.w.Write(b)
}
func (c *mockConn) Close() error {
return nil
}
func (c *mockConn) LocalAddr() net.Addr {
return nil
}
func (c *mockConn) RemoteAddr() net.Addr {
return nil
}
func (c *mockConn) SetDeadline(t time.Time) error {
return nil
}
func (c *mockConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *mockConn) SetWriteDeadline(t time.Time) error {
return nil
}
func newMockConn(r io.Reader, w io.Writer) net.Conn {
return &mockConn{
r: r,
w: w,
}
}
type cmd struct {
cmdBuf []byte
called bool
}
func (d *cmd) mockCmdHandler(w io.Writer, b []byte, tc *Conn) {
d.called = true
d.cmdBuf = b
}
type opt struct {
cmd byte
optn byte
called bool
}
func (o *opt) optCallback(cmd, option byte) {
o.cmd = cmd
o.optn = option
o.called = true
}
type testSample struct {
inputSeq []byte
expState []state
expOpt []*opt
expCmd []*cmd
}
var samples = []testSample{
{
inputSeq: []byte{10, 20, 5, 12, 34, 125, 98},
expState: []state{0, 0, 0, 0, 0, 0, 0},
expOpt: []*opt{nil, nil, nil, nil, nil, nil, nil},
expCmd: []*cmd{nil, nil, nil, nil, nil, nil, nil},
},
{
inputSeq: []byte{Iac, Do, Echo, 10, 20, Iac, Will, Sga},
expState: []state{cmdState, optionNegotiationState, dataState, dataState, dataState, cmdState, optionNegotiationState, dataState},
expOpt: []*opt{nil, nil, {Do, Echo, true}, nil, nil, nil, nil, {Will, Sga, true}},
expCmd: []*cmd{nil, nil, nil, nil, nil, nil, nil, nil},
},
{
inputSeq: []byte{10, 20, Iac, Ayt, 5, Iac, Ao},
expState: []state{dataState, dataState, cmdState, dataState, dataState, cmdState, dataState},
expOpt: []*opt{nil, nil, nil, nil, nil, nil, nil},
expCmd: []*cmd{nil, nil, nil, {[]byte{Ayt}, true}, nil, nil, {[]byte{Ao}, true}},
},
{
inputSeq: []byte{10, Iac, Sb, 5, 12, Iac, Se},
expState: []state{dataState, cmdState, subnegState, subnegState, subnegState, subnegEndState, dataState},
expOpt: []*opt{nil, nil, nil, nil, nil, nil, nil},
expCmd: []*cmd{nil, nil, nil, nil, nil, nil, {[]byte{Sb, 5, 12, Se}, true}},
},
}
func TestFSM(t *testing.T) {
for count, s := range samples {
log.Printf("test sample %d", count)
b := make([]byte, 512)
outBuf := bytes.NewBuffer(b)
inBuf := bytes.NewBuffer(s.inputSeq)
cmdPtr := &cmd{
called: false,
}
optPtr := &opt{
called: false,
}
dummyConn := newMockConn(inBuf, outBuf)
fsm := newFSM()
opts := connOpts{
conn: dummyConn,
fsm: fsm,
Handlers: Handlers{
CmdHandler: cmdPtr.mockCmdHandler,
DataHandler: defaultDataHandlerFunc,
},
optCallback: optPtr.optCallback,
}
tc := newConn(&opts)
go tc.dataHandlerWrapper(tc.handlerWriter, tc.dataRW)
assert.Equal(t, fsm.curState, dataState)
for i, ch := range s.inputSeq {
ns := fsm.nextState(ch)
fsm.curState = ns
assert.Equal(t, s.expState[i], ns)
if optPtr.called {
assert.Equal(t, s.expOpt[i].cmd, optPtr.cmd)
assert.Equal(t, s.expOpt[i].optn, optPtr.optn)
} else {
assert.Nil(t, s.expOpt[i])
}
if cmdPtr.called {
exp := s.expCmd[i].cmdBuf
actual := cmdPtr.cmdBuf
assert.Equal(t, exp, actual)
} else {
assert.Nil(t, s.expCmd[i])
}
optPtr.called = false
cmdPtr.called = false
}
}
}

94
vendor/github.com/vmware/vic/pkg/telnet/protocol.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
// Copyright 2017 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 telnet
// Telnet protocol specific values
const (
// Iac is Interpret as Command
Iac byte = 255
Dont byte = 254
Do byte = 253
Wont byte = 252
Will byte = 251
Null byte = 0
Se byte = 240 // Subnegotiation End
Nop byte = 241 // No Operation
Dm byte = 242 // Data Mark
Brk byte = 243 // Break
IP byte = 244 // Interrupt process
Ao byte = 245 // Abort output
Ayt byte = 246 // Are You There
Ec byte = 247 // Erase Character
El byte = 248 // Erase Line
Ga byte = 249 // Go Ahead
Sb byte = 250 // Subnegotiation Begin
Binary byte = 0 // 8-bit data path
Echo byte = 1 // echo
Rcp byte = 2 // prepare to reconnect
Sga byte = 3 // suppress go ahead
Nams byte = 4 // approximate message size
Status byte = 5 // give status
Tm byte = 6 // timing mark
Rcte byte = 7 // remote controlled transmission and echo
Naol byte = 8 // negotiate about output line width
Naop byte = 9 // negotiate about output page size
Naocrd byte = 10 // negotiate about CR disposition
Naohts byte = 11 // negotiate about horizontal tabstops
Naohtd byte = 12 // negotiate about horizontal tab disposition
Naoffd byte = 13 // negotiate about formfeed disposition
Naovts byte = 14 // negotiate about vertical tab stops
Naovtd byte = 15 // negotiate about vertical tab disposition
Naolfd byte = 16 // negotiate about output LF disposition
Xascii byte = 17 // extended ascii character set
Logout byte = 18 // force logout
Bm byte = 19 // byte macro
Det byte = 20 // data entry terminal
Supdup byte = 21 // supdup protocol
SupdupOutput byte = 22 // supdup output
SndLoc byte = 23 // send location
Ttype byte = 24 // terminal type
Eor byte = 25 // end or record
TuID byte = 26 // TACACS user identification
OutMrk byte = 27 // output marking
TtyLoc byte = 28 // terminal location number
Vt3270Regime byte = 29 // 3270 regime
X3Pad byte = 30 // X.3 PAD
Naws byte = 31 // window size
Tspeed byte = 32 // terminal speed
Lflow byte = 33 // remote flow control
LineMode byte = 34 // Linemode option
XDispLoc byte = 35 // X Display Location
OldEnviron byte = 36 // Old - Environment variables
Authentication byte = 37 // Authenticate
Encrypt byte = 38 // Encryption option
NewEnviron byte = 39 // New - Environment variables
TN3270E byte = 40 // TN3270E
XAuth byte = 41 // XAUTH
Charset byte = 42 // CHARSET
Rsp byte = 43 // Telnet Remote Serial Port
ComPortOption byte = 44 // Com Port Control Option
SupLocalEcho byte = 45 // Telnet Suppress Local Echo
TLS byte = 46 // Telnet Start TLS
Kermit byte = 47 // KERMIT
SendURL byte = 48 // SEND-URL
ForwardX byte = 49 // FORWARD_X
PragmaLogon byte = 138 // TELOPT PRAGMA LOGON
SspiLogin byte = 139 // TELOPT SSPI LOGON
PragmaHeartbeat byte = 140 // TELOPT PRAGMA HEARTBEAT
ExtOptList byte = 255 // Extended-Options-List
NoOp byte = 0
)

116
vendor/github.com/vmware/vic/pkg/telnet/server.go generated vendored Normal file
View File

@@ -0,0 +1,116 @@
// Copyright 2017 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 telnet
import (
"fmt"
"io"
"net"
log "github.com/Sirupsen/logrus"
)
// DataHandlerFunc is the callback function in the event of receiving data from the telnet client
type DataHandlerFunc func(w io.Writer, data []byte, tc *Conn)
// CmdHandlerFunc is the callback function in the event of receiving a command from the telnet client
type CmdHandlerFunc func(w io.Writer, cmd []byte, tc *Conn)
// CloseHandlerFunc is the callback function in the event of receiving EOF from the telnet client
type CloseHandlerFunc func(tc *Conn)
var defaultDataHandlerFunc = func(w io.Writer, data []byte, tc *Conn) {}
var defaultCmdHandlerFunc = func(w io.Writer, cmd []byte, tc *Conn) {}
var defaultCloseHandlerFunc = func(tc *Conn) {}
type Handlers struct {
DataHandler DataHandlerFunc
CmdHandler CmdHandlerFunc
CloseHandler CloseHandlerFunc
}
// ServerOpts is the telnet server constructor options
type ServerOpts struct {
Addr string
ServerOpts []byte
ClientOpts []byte
Handlers
}
// Server is the struct representing the telnet server
type Server struct {
ServerOptions map[byte]bool
ClientOptions map[byte]bool
Handlers
ln net.Listener
}
// NewServer is the constructor of the telnet server
func NewServer(opts ServerOpts) *Server {
ts := new(Server)
ts.ClientOptions = make(map[byte]bool)
ts.ServerOptions = make(map[byte]bool)
for _, v := range opts.ServerOpts {
ts.ServerOptions[v] = true
}
for _, v := range opts.ClientOpts {
ts.ClientOptions[v] = true
}
ts.DataHandler = defaultDataHandlerFunc
if opts.DataHandler != nil {
ts.DataHandler = opts.DataHandler
}
ts.CmdHandler = defaultCmdHandlerFunc
if opts.CmdHandler != nil {
ts.CmdHandler = opts.CmdHandler
}
ts.CloseHandler = defaultCloseHandlerFunc
if opts.CloseHandler != nil {
ts.CloseHandler = opts.CloseHandler
}
ln, err := net.Listen("tcp", opts.Addr)
if err != nil {
panic(fmt.Sprintf("cannot start telnet server: %v", err))
}
ts.ln = ln
return ts
}
// Accept accepts a connection and returns the Telnet connection
func (ts *Server) Accept() (*Conn, error) {
// #nosec: Errors unhandled.
conn, _ := ts.ln.Accept()
log.Info("connection received")
opts := connOpts{
conn: conn,
Handlers: ts.Handlers,
clientOpts: ts.ClientOptions,
fsm: newFSM(),
}
tc := newConn(&opts)
go tc.writeLoop()
go tc.dataHandlerWrapper(tc.handlerWriter, tc.dataRW)
go tc.fsm.start()
go tc.startNegotiation()
return tc, nil
}

45
vendor/github.com/vmware/vic/pkg/trace/entry.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
// Copyright 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 trace
import (
"github.com/Sirupsen/logrus"
)
// Entry is like logrus.Entry, but for Operation. Functionality is added as needed.
type Entry struct {
// Contains all the fields set by the user.
Data logrus.Fields
// A reference to the operation-local logger the entry was constructed for.
local *logrus.Logger
}
func (o *Operation) WithFields(fields logrus.Fields) *Entry {
entry := Entry{
Data: fields,
local: o.Logger,
}
return &entry
}
func (e *Entry) Debug(args ...interface{}) {
Logger.WithFields(e.Data).Debug(args...)
if e.local != nil {
e.local.WithFields(e.Data).Debug(args...)
}
}

318
vendor/github.com/vmware/vic/pkg/trace/operation.go generated vendored Normal file
View File

@@ -0,0 +1,318 @@
// Copyright 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 trace
import (
"bytes"
"context"
"fmt"
"os"
"sync/atomic"
"time"
"github.com/Sirupsen/logrus"
"github.com/vmware/govmomi/vim25/types"
)
type OperationKey string
const OpTraceKey OperationKey = "traceKey"
var opIDPrefix = os.Getpid()
// opCount is a monotonic counter which increments on Start()
var opCount uint64
type Operation struct {
context.Context
operation
// Logger is used to configure an Operation-specific destination for log messages, in addition
// to the global logger. This logger is passed to any children which are created.
Logger *logrus.Logger
}
type operation struct {
t []Message
id string
}
func newOperation(ctx context.Context, id string, skip int, msg string) Operation {
op := operation{
// Can be used to trace based on this number which is unique per chain
// of operations
id: id,
// Start the trace.
t: []Message{*newTrace(msg, skip, id)},
}
// We need to be able to identify this operation across API (and process)
// boundaries. So add the trace as a value to the embedded context. We
// stash the values individually in the context because we can't assign
// the operation itself as a value to the embedded context (it's circular)
ctx = context.WithValue(ctx, OpTraceKey, op)
// By adding the op.id any operations passed to govmomi will result
// in the op.id being logged in vSphere (vpxa / hostd) as the prefix to opID
// For example if the op.id was 299.16 hostd would show
// verbose hostd[12281B70] [Originator@6876 sub=PropertyProvider opID=299.16-5b05 user=root]
ctx = context.WithValue(ctx, types.ID{}, op.id)
o := Operation{
Context: ctx,
operation: op,
}
return o
}
// Creates a header string to be printed.
func (o *Operation) header() string {
return fmt.Sprintf("op=%s", o.id)
}
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
func (o Operation) Err() error {
// Walk up the contexts from which this context was created and get their errors
if err := o.Context.Err(); err != nil {
buf := &bytes.Buffer{}
// Add a frame for this Err call, then walk the stack
currFrame := newTrace("Err", 2, o.id)
fmt.Fprintf(buf, "%s: %s error: %s\n", currFrame.funcName, o.t[0].msg, err)
// handle the carriage return
numFrames := len(o.t)
for i, t := range o.t {
fmt.Fprintf(buf, "%-15s:%d %s", t.funcName, t.lineNo, t.msg)
// don't add a cr on the last frame
if i != numFrames-1 {
buf.WriteByte('\n')
}
}
// Print the error
o.Errorf(buf.String())
return err
}
return nil
}
func (o Operation) String() string {
return o.header()
}
func (o *Operation) ID() string {
return o.id
}
func (o *Operation) Infof(format string, args ...interface{}) {
o.Info(fmt.Sprintf(format, args...))
}
func (o *Operation) Info(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Infof("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Info(msg)
}
}
func (o *Operation) Debugf(format string, args ...interface{}) {
o.Debug(fmt.Sprintf(format, args...))
}
func (o *Operation) Debug(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Debugf("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Debug(msg)
}
}
func (o *Operation) Warnf(format string, args ...interface{}) {
o.Warn(fmt.Sprintf(format, args...))
}
func (o *Operation) Warn(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Warnf("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Warn(msg)
}
}
func (o *Operation) Errorf(format string, args ...interface{}) {
o.Error(fmt.Sprintf(format, args...))
}
func (o *Operation) Error(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Errorf("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Error(msg)
}
}
func (o *Operation) Panicf(format string, args ...interface{}) {
o.Panic(fmt.Sprintf(format, args...))
}
func (o *Operation) Panic(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Panicf("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Panic(msg)
}
}
func (o *Operation) Fatalf(format string, args ...interface{}) {
o.Fatal(fmt.Sprintf(format, args...))
}
func (o *Operation) Fatal(args ...interface{}) {
msg := fmt.Sprint(args...)
Logger.Fatalf("%s: %s", o.header(), msg)
if o.Logger != nil {
o.Logger.Fatal(msg)
}
}
func (o *Operation) newChild(ctx context.Context, msg string) Operation {
child := newOperation(ctx, o.id, 4, msg)
child.t = append(child.t, o.t...)
child.Logger = o.Logger
return child
}
func opID(opNum uint64) string {
return fmt.Sprintf("%d.%d", opIDPrefix, opNum)
}
// NewOperation will return a new operation with operationID added as a value to the context
func NewOperation(ctx context.Context, format string, args ...interface{}) Operation {
o := newOperation(ctx, opID(atomic.AddUint64(&opCount, 1)), 3, fmt.Sprintf(format, args...))
frame := o.t[0]
o.Debugf("[NewOperation] %s [%s:%d]", o.header(), frame.funcName, frame.lineNo)
return o
}
// NewOperationWithLoggerFrom will return a new operation with operationID added as a value to the
// context and logging settings copied from the supplied operation.
//
// Deprecated: This method was added to aid in converting old code to use operation-based logging.
// Its use almost always indicates a broken context/operation model (e.g., a context
// being improperly stored in a struct instead of being passed between functions).
func NewOperationWithLoggerFrom(ctx context.Context, oldOp Operation, format string, args ...interface{}) Operation {
op := NewOperation(ctx, format, args...)
op.Logger = oldOp.Logger
return op
}
// WithTimeout creates a new operation from parent with context.WithTimeout
func WithTimeout(parent *Operation, timeout time.Duration, format string, args ...interface{}) (Operation, context.CancelFunc) {
ctx, cancelFunc := context.WithTimeout(parent.Context, timeout)
op := parent.newChild(ctx, fmt.Sprintf(format, args...))
return op, cancelFunc
}
// WithDeadline creates a new operation from parent with context.WithDeadline
func WithDeadline(parent *Operation, expiration time.Time, format string, args ...interface{}) (Operation, context.CancelFunc) {
ctx, cancelFunc := context.WithDeadline(parent.Context, expiration)
op := parent.newChild(ctx, fmt.Sprintf(format, args...))
return op, cancelFunc
}
// WithCancel creates a new operation from parent with context.WithCancel
func WithCancel(parent *Operation, format string, args ...interface{}) (Operation, context.CancelFunc) {
ctx, cancelFunc := context.WithCancel(parent.Context)
op := parent.newChild(ctx, fmt.Sprintf(format, args...))
return op, cancelFunc
}
// WithValue creates a new operation from parent with context.WithValue
func WithValue(parent *Operation, key, val interface{}, format string, args ...interface{}) Operation {
ctx := context.WithValue(parent.Context, key, val)
op := parent.newChild(ctx, fmt.Sprintf(format, args...))
return op
}
// FromOperation creates a child operation from the one supplied
// uses the same context as the parent
func FromOperation(parent Operation, format string, args ...interface{}) Operation {
return parent.newChild(parent.Context, fmt.Sprintf(format, args...))
}
// FromContext will return an Operation
//
// The Operation returned will be one of the following:
// The operation in the context value
// The operation passed as the context param
// A new operation
func FromContext(ctx context.Context, message string, args ...interface{}) Operation {
// do we have an operation
if op, ok := ctx.(Operation); ok {
return op
}
// do we have a context w/the op added as a value
if op, ok := ctx.Value(OpTraceKey).(operation); ok {
// ensure we have an initialized operation
if op.id == "" {
return NewOperation(ctx, message, args...)
}
// return an operation based off the context value
return Operation{
Context: ctx,
operation: op,
}
}
op := newOperation(ctx, opID(atomic.AddUint64(&opCount, 1)), 3, fmt.Sprintf(message, args...))
frame := op.t[0]
Logger.Debugf("%s: [OperationFromContext] [%s:%d]", op.id, frame.funcName, frame.lineNo)
// return the new operation
return op
}

View File

@@ -0,0 +1,306 @@
// Copyright 2016-2017 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 trace
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestContextUnpack(t *testing.T) {
Logger.Level = logrus.DebugLevel
cnt := 100
wg := &sync.WaitGroup{}
wg.Add(cnt)
for i := 0; i < cnt; i++ {
go func(i int) {
defer wg.Done()
ctx := NewOperation(context.TODO(), "testmsg")
// unpack an Operation via the context using it's Values fields
c := FromContext(ctx, "test")
c.Infof("test info message %d", i)
}(i) // fix race in test
}
wg.Wait()
}
// If we timeout a child, test a stack is printed of contexts
func TestNestedLogging(t *testing.T) {
// create a buf to check the log
buf := new(bytes.Buffer)
Logger.Out = buf
root := NewOperation(context.Background(), "root")
var ctxFunc func(parent Operation, level int) Operation
levels := 10
ctxFunc = func(parent Operation, level int) Operation {
if level == levels {
return parent
}
child, _ := WithDeadline(&parent, time.Time{}, fmt.Sprintf("level %d", level))
return ctxFunc(child, level+1)
}
child := ctxFunc(root, 0)
// Assert the child has an error and prints a stack. The parent doesn't
// see this and should not have an error. Only cancelation trickles up the
// stack to the parent.
if !assert.NoError(t, root.Err()) || !assert.Error(t, child.Err()) {
return
}
// Assert we got a stack trace in the log
log := buf.String()
lines := strings.Count(log, "\n")
t.Log(log)
// Sample stack
//
// ERRO[0000] op=21598.101: github.com/vmware/vic/pkg/trace.TestNestedLogging: level 9 error: context deadline exceeded
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 9
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 8
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 7
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 6
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 5
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 4
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 3
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 2
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 1
// github.com/vmware/vic/pkg/trace.TestNestedLogging.func1:71 level 0
// github.com/vmware/vic/pkg/trace.TestNestedLogging:61 root
// We arrive at 2 because we have the err line (line 0), then the root
// (line 11) of where we created the ctx.
if assert.False(t, lines < levels) {
t.Logf("exepected at least %d and got %d", levels, lines)
return
}
}
// Just checking behavior of the context package
func TestSanity(t *testing.T) {
Logger.Level = logrus.InfoLevel
levels := 10
root, cancel := context.WithDeadline(context.Background(), time.Time{})
defer cancel()
var ctxFunc func(parent context.Context, level int) context.Context
ctxFunc = func(parent context.Context, level int) context.Context {
if level == levels {
return parent
}
child, cancel := context.WithDeadline(parent, time.Now().Add(time.Hour))
defer cancel()
return ctxFunc(child, level+1)
}
child := ctxFunc(root, 0)
if !assert.Error(t, child.Err()) {
t.FailNow()
}
err := root.Err()
if !assert.Error(t, err) {
return
}
}
// MockHook is a testify mock that can be registered as a logrus hook
type MockHook struct {
mock.Mock
}
// Levels indicates that the mock log hook supports all log levels
func (m *MockHook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Fire records that it has been called and returns an error if configured
func (m *MockHook) Fire(entry *logrus.Entry) error {
args := m.Called(entry)
return args.Error(0)
}
// cases defines the set of messages we expect to see and the level we expect to see each at
var cases = map[string]logrus.Level{
"DebugfMessage": logrus.DebugLevel,
"DebugMessage": logrus.DebugLevel,
"InfofMessage": logrus.InfoLevel,
"InfoMessage": logrus.InfoLevel,
"WarnfMessage": logrus.WarnLevel,
"WarnMessage": logrus.WarnLevel,
"ErrorfMessage": logrus.ErrorLevel,
"ErrorMessage": logrus.ErrorLevel,
}
// buildMatcher creates a testify MatchedBy function for the supplied operation
func buildMatcher(op Operation, shouldContainOpID bool) func(entry *logrus.Entry) bool {
return func(entry *logrus.Entry) bool {
if shouldContainOpID && !strings.Contains(entry.Message, op.id) {
return false // Log message should have contained the operation id, but did not
}
if !shouldContainOpID && strings.Contains(entry.Message, op.id) {
return false // Log message should not have contained the operation id, but did
}
for message, level := range cases {
if entry.Level == level && strings.Contains(entry.Message, message) {
return true
}
}
return false
}
}
// TestLogging demonstrates that log messages are relayed from the Operation to the Logger global
func TestLogging(t *testing.T) {
defer func(original *logrus.Logger) { Logger = original }(Logger)
Logger = logrus.New()
op := NewOperation(context.Background(), "TestOperation")
m := new(MockHook)
Logger.Hooks.Add(m)
Logger.Level = logrus.DebugLevel
m.On("Fire", mock.MatchedBy(buildMatcher(op, true))).Return(nil)
op.Debugf("DebugfMessage")
op.Debug("DebugMessage")
op.Infof("InfofMessage")
op.Info("InfoMessage")
op.Warnf("WarnfMessage")
op.Warn("WarnMessage")
op.Errorf("ErrorfMessage")
op.Error(fmt.Errorf("ErrorMessage"))
m.AssertExpectations(t)
m.AssertNumberOfCalls(t, "Fire", 8)
}
// TestLogMuxing verifies that an operation-specific Logger can be configured and that both it and
// the global Logger receive messages when logging methods are called on Operation
func TestLogMuxing(t *testing.T) {
defer func(original *logrus.Logger) { Logger = original }(Logger)
Logger = logrus.New()
op := NewOperation(context.Background(), "TestOperation")
gm := new(MockHook)
Logger.Hooks.Add(gm)
Logger.Level = logrus.DebugLevel
lm := new(MockHook)
op.Logger = logrus.New()
op.Logger.Hooks.Add(lm)
op.Logger.Level = logrus.DebugLevel
gm.On("Fire", mock.MatchedBy(buildMatcher(op, true))).Return(nil)
lm.On("Fire", mock.MatchedBy(buildMatcher(op, false))).Return(nil)
op.Debugf("DebugfMessage")
op.Debug("DebugMessage")
op.Infof("InfofMessage")
op.Info("InfoMessage")
op.Warnf("WarnfMessage")
op.Warn("WarnMessage")
op.Errorf("ErrorfMessage")
op.Error(fmt.Errorf("ErrorMessage"))
gm.AssertExpectations(t)
gm.AssertNumberOfCalls(t, "Fire", 8)
lm.AssertExpectations(t)
lm.AssertNumberOfCalls(t, "Fire", 8)
}
// TestLogIsolation verifies that an operation-specific Loggers are actually operation-specific
func TestLogIsolation(t *testing.T) {
op1 := NewOperation(context.Background(), "TestOperation")
op2 := NewOperation(context.Background(), "TestOperation")
lm1 := new(MockHook)
op1.Logger = logrus.New()
op1.Logger.Hooks.Add(lm1)
op1.Logger.Level = logrus.DebugLevel
lm2 := new(MockHook)
op2.Logger = logrus.New()
op2.Logger.Hooks.Add(lm2)
op2.Logger.Level = logrus.DebugLevel
lm1.On("Fire", mock.MatchedBy(buildMatcher(op1, false))).Return(nil)
op1.Debugf("DebugfMessage")
op1.Info("InfoMessage")
op1.Warnf("WarnfMessage")
op1.Errorf("ErrorfMessage")
op1.Error(fmt.Errorf("ErrorMessage"))
lm1.AssertExpectations(t)
lm1.AssertNumberOfCalls(t, "Fire", 5)
lm2.AssertExpectations(t)
lm2.AssertNumberOfCalls(t, "Fire", 0)
}
// TestLogInheritance verifies that an operation-specific Loggers are inherited by children
func TestLogInheritance(t *testing.T) {
op := NewOperation(context.Background(), "TestOperation")
lm := new(MockHook)
op.Logger = logrus.New()
op.Logger.Hooks.Add(lm)
op.Logger.Level = logrus.DebugLevel
c1, _ := WithCancel(&op, "CancelChild")
c2 := WithValue(&c1, "foo", "bar", "ValueChild")
c3 := FromOperation(c2, "NormalChild")
c4 := FromContext(c3, "(Should == c3)")
lm.On("Fire", mock.MatchedBy(buildMatcher(op, false))).Return(nil)
op.Debugf("DebugfMessage")
c1.Infof("InfofMessage")
c2.Warnf("WarnfMessage")
c3.Errorf("ErrorfMessage")
c4.Error("ErrorMessage")
lm.AssertExpectations(t)
lm.AssertNumberOfCalls(t, "Fire", 5)
}

143
vendor/github.com/vmware/vic/pkg/trace/trace.go generated vendored Normal file
View File

@@ -0,0 +1,143 @@
// Copyright 2016-2017 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 trace
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/pkg/log"
)
var tracingEnabled = true
// Enable global tracing.
func EnableTracing() {
tracingEnabled = true
}
// Disable global tracing.
func DisableTracing() {
tracingEnabled = false
}
var Logger = &logrus.Logger{
Out: os.Stderr,
// We're using our own text formatter to skip the \n and \t escaping logrus
// was doing on non TTY Out (we redirect to a file) descriptors.
Formatter: log.NewTextFormatter(),
Hooks: make(logrus.LevelHooks),
Level: logrus.InfoLevel,
}
// trace object used to grab run-time state
type Message struct {
msg string
funcName string
lineNo int
operationID string
startTime time.Time
}
func (t *Message) delta() time.Duration {
if t == nil {
return 0
}
return time.Now().Sub(t.startTime)
}
// Add Syslog hook
// This method is not thread safe, this is currently
// not a problem because it is only called once from main
func InitLogger(cfg *log.LoggingConfig) error {
hook, err := log.CreateSyslogHook(cfg)
if err == nil && hook != nil {
Logger.Hooks.Add(hook)
}
return err
}
// begin a trace from this stack frame less the skip.
func newTrace(msg string, skip int, opID string) *Message {
pc, _, line, ok := runtime.Caller(skip)
if !ok {
return nil
}
// lets only return the func name from the repo (vic)
// down - i.e. vic/lib/etc vs. github.com/vmware/vic/lib/etc
// if github.com/vmware doesn't match then the original is returned
name := strings.TrimPrefix(runtime.FuncForPC(pc).Name(), "github.com/vmware/")
message := Message{
msg: msg,
funcName: name,
lineNo: line,
startTime: time.Now(),
}
// if we have an operationID then format the output
if opID != "" {
message.operationID = fmt.Sprintf("op=%s", opID)
}
return &message
}
// Begin starts the trace. Msg is the msg to log.
// context provided to allow tracing of operationID
// context added as optional to avoid breaking current usage
func Begin(msg string, ctx ...context.Context) *Message {
if tracingEnabled && Logger.Level >= logrus.DebugLevel {
var opID string
// populate operationID if provided
if len(ctx) == 1 {
if id, ok := ctx[0].Value(types.ID{}).(string); ok {
opID = id
}
}
if t := newTrace(msg, 2, opID); t != nil {
if msg == "" {
Logger.Debugf("[BEGIN] %s [%s:%d]", t.operationID, t.funcName, t.lineNo)
} else {
Logger.Debugf("[BEGIN] %s [%s:%d] %s", t.operationID, t.funcName, t.lineNo, t.msg)
}
return t
}
}
return nil
}
// End ends the trace.
func End(t *Message) {
if t == nil {
return
}
Logger.Debugf("[ END ] %s [%s:%d] [%s] %s", t.operationID, t.funcName, t.lineNo, t.delta(), t.msg)
}
func SetLogLevel(level uint8) {
Logger.Level = logrus.Level(level)
}

56
vendor/github.com/vmware/vic/pkg/uid/uid.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 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 uid
import (
"regexp"
"github.com/docker/docker/pkg/stringid"
)
// UID is a unique id
type UID string
// NilUID is a placeholder for an empty ID
const NilUID UID = UID("")
var (
idRegex = regexp.MustCompile("^[0-9a-f]{64}$")
shortIDRegex = regexp.MustCompile("^[0-9a-f]{12}$")
)
// New generates a UID
func New() UID {
return Parse(stringid.GenerateNonCryptoID())
}
// Parse converts a string to UID
func Parse(u string) UID {
if idRegex.MatchString(u) || shortIDRegex.MatchString(u) {
return UID(u)
}
return NilUID
}
// Truncate returns the truncated UID
func (u UID) Truncate() UID {
return Parse(stringid.TruncateID(string(u)))
}
// String converts a UID to a string
func (u UID) String() string {
return string(u)
}

67
vendor/github.com/vmware/vic/pkg/uid/uid_test.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 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 uid
import "testing"
import "github.com/stretchr/testify/assert"
func TestParse(t *testing.T) {
// valid ids
var tests = []string{
"abcdef01234567890123456789abcdefabcdef01234567890123456789abcdef",
"abcdefabcdef", // short id
}
for _, te := range tests {
id := Parse(te)
assert.NotEqual(t, id, NilUID)
assert.Equal(t, te, id.String())
}
// invalid ids
tests = []string{
"foobar",
"",
"abcde",
"abcdefe",
"abcdef01234567890123456789abcdefabcdef01234567890123456789abcdefe",
"abcdef01234567890123456789abcdefabcdef01234567890123456789abcde",
}
for _, te := range tests {
id := Parse(te)
assert.Equal(t, id, NilUID)
}
}
func TestTruncate(t *testing.T) {
var tests = []struct {
in, out UID
}{
{Parse("abcdef01234567890123456789abcdefabcdef01234567890123456789abcdef"), Parse("abcdef012345")},
{Parse("abcdefabcdef"), Parse("abcdefabcdef")},
{NilUID, NilUID},
}
for _, te := range tests {
assert.Equal(t, te.out, te.in.Truncate())
}
}
func TestNew(t *testing.T) {
assert.NotEqual(t, NilUID, New())
}

149
vendor/github.com/vmware/vic/pkg/version/version.go generated vendored Normal file
View File

@@ -0,0 +1,149 @@
// Copyright 2016-2017 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 version
import (
"flag"
"fmt"
"runtime"
"strconv"
"strings"
"github.com/vmware/vic/lib/migration/feature"
)
// These fields are set by the compiler using the linker flags upon build via Makefile.
var (
Version string
GitCommit string
BuildDate string
BuildNumber string
State string
v bool
)
const (
DockerAPIVersion = "1.25"
DockerDefaultVersion = "1.25"
DockerMinimumVersion = "1.19"
DockerServerVersion = "1.13.0"
)
type Build struct {
Version string
GitCommit string
BuildDate string
BuildNumber string
State string
PluginVersion int
}
func init() {
flag.BoolVar(&v, "version", false, "Show version info")
}
// Show returns whether -version flag is set
func Show() bool {
return v
}
// String returns a string representation of the version
func String() string {
return GetBuild().String()
}
// UserAgent returns component/version in HTTP User-Agent header value format
func UserAgent(component string) string {
v := Version
if strings.HasPrefix(v, "v") {
v = v[1:]
}
return fmt.Sprintf("%s/%s", component, v)
}
func GetBuild() *Build {
if BuildNumber == "" {
BuildNumber = "0"
}
return &Build{
Version: Version,
GitCommit: GitCommit,
BuildDate: BuildDate,
BuildNumber: BuildNumber,
State: State,
PluginVersion: feature.MaxPluginVersion - 1,
}
}
func (v *Build) String() string {
if v.State == "" {
v.State = "clean"
}
if v.BuildNumber == "" {
v.BuildNumber = "N/A"
}
return fmt.Sprintf("%s git:%s-%s build:%s id:%s runtime:%s", v.Version, v.GitCommit, v.State, v.BuildDate, v.BuildNumber, runtime.Version())
}
func (v *Build) ShortVersion() string {
if v == nil {
return "unknown"
}
return fmt.Sprintf("%s-%s-%s", v.Version, v.BuildNumber, v.GitCommit)
}
// Equal determines if v is equal to b based on BuildNumber
func (v *Build) Equal(b *Build) bool {
return v.BuildNumber == b.BuildNumber
}
// IsOlder determines if v is older than b based on BuildNumber
func (v *Build) IsOlder(b *Build) (bool, error) {
if v.Equal(b) {
return false, nil
}
if v.BuildNumber == "" || b.BuildNumber == "" {
return false, fmt.Errorf("invalid BuildNumber - comparing %q to %q", v.BuildNumber, b.BuildNumber)
}
vi, errv := strconv.Atoi(v.BuildNumber)
bi, errb := strconv.Atoi(b.BuildNumber)
if errv != nil {
return false, fmt.Errorf("invalid BuildNumber format %s: %s", v, errv)
}
if errb != nil {
return false, fmt.Errorf("invalid BuildNumber format %s: %s", b, errb)
}
buildBefore := vi < bi
return buildBefore, nil
}
// IsNewer determines if v is newer than b based on BuildNumber
func (v *Build) IsNewer(b *Build) (bool, error) {
if v.Equal(b) {
return false, nil
}
older, err := v.IsOlder(b)
if err != nil {
return false, err
}
return !older, nil
}

View File

@@ -0,0 +1,160 @@
// Copyright 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 version
import (
"errors"
"testing"
)
var (
a = &Build{
Version: "v1.2.3",
GitCommit: "aaaaaaa",
BuildDate: "2009/11/10@23:00:00",
BuildNumber: "10",
State: "",
}
b = &Build{
Version: "v1.2.3",
GitCommit: "bbbbbbb",
BuildDate: "2009/11/10@23:00:01",
BuildNumber: "10",
State: "",
}
c = &Build{
Version: "v1.2.4",
GitCommit: "aaaaaaa",
BuildDate: "2009/11/10@23:00:00",
BuildNumber: "10",
State: "",
}
d = &Build{
Version: "v1.2.3",
GitCommit: "aaaaaaa",
BuildDate: "2009/11/10@23:00:00",
BuildNumber: "11",
State: "",
}
e = &Build{
Version: "v1.2.3",
GitCommit: "aaaaaaa",
BuildDate: "2009/11/10@23:00:00",
BuildNumber: "",
State: "",
}
f = &Build{
Version: "v1.2.3",
GitCommit: "aaaaaaa",
BuildDate: "2009/11/10@23:00:00",
BuildNumber: "wow",
State: "",
}
)
func TestEqual(t *testing.T) {
var tests = []struct {
b1, b2 *Build
expected bool
}{
{a, a, true},
{a, b, true},
{a, c, true},
{a, d, false},
}
for _, te := range tests {
res := te.b1.Equal(te.b2)
if res != te.expected {
t.Errorf("%s %s Got: %t Expected: %t", te.b1, te.b2, res, te.expected)
}
}
}
func TestIsOlder(t *testing.T) {
var tests = []struct {
b1, b2 *Build
expected bool
expectedErr error
}{
{a, a, false, nil},
{a, b, false, nil},
{a, c, false, nil},
{a, d, true, nil},
{a, e, false, errors.New("")},
{a, f, false, errors.New("")},
}
for _, te := range tests {
res, err := te.b1.IsOlder(te.b2)
if te.expectedErr != nil {
if err == nil {
t.Errorf("%s %s Got error: %s Expected error: %s", te.b1, te.b2, err, te.expectedErr)
}
}
if res != te.expected {
t.Errorf("%s %s Got: %t Expected: %t", te.b1, te.b2, res, te.expected)
}
}
}
func TestIsNewer(t *testing.T) {
var tests = []struct {
b1, b2 *Build
expected bool
expectedErr error
}{
{a, a, false, nil},
{a, b, false, nil},
{b, a, false, nil},
{a, c, false, nil},
{c, a, false, nil},
{a, d, false, nil},
{d, a, true, nil},
{a, e, false, errors.New("")},
{a, f, false, errors.New("")},
}
for _, te := range tests {
res, err := te.b1.IsNewer(te.b2)
if te.expectedErr != nil {
if err == nil {
t.Errorf("%s %s Got error: %s Expected error: %s", te.b1, te.b2, err, te.expectedErr)
}
}
if res != te.expected {
t.Errorf("%s %s Got: %t Expected: %t", te.b1, te.b2, res, te.expected)
}
}
}
func TestUserAgent(t *testing.T) {
for _, v := range []string{"0.0.1", "v0.0.1"} {
Version = v
a := UserAgent("foo")
if a != "foo/0.0.1" {
t.Error(a)
}
}
}

142
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp.go generated vendored Normal file
View File

@@ -0,0 +1,142 @@
// Copyright 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 compute
import (
"context"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/vm"
)
// ResourcePool struct defines the ResourcePool which provides additional
// VIC specific methods over object.ResourcePool as well as keeps some state
type ResourcePool struct {
*object.ResourcePool
*session.Session
}
// NewResourcePool returns a New ResourcePool object
func NewResourcePool(ctx context.Context, session *session.Session, moref types.ManagedObjectReference) *ResourcePool {
return &ResourcePool{
ResourcePool: object.NewResourcePool(
session.Vim25(),
moref,
),
Session: session,
}
}
func (rp *ResourcePool) GetChildrenVMs(ctx context.Context, s *session.Session) ([]*vm.VirtualMachine, error) {
op := trace.FromContext(ctx, "GetChildrenVMs")
var err error
var mrp mo.ResourcePool
var vms []*vm.VirtualMachine
if err = rp.Properties(op, rp.Reference(), []string{"vm"}, &mrp); err != nil {
op.Errorf("Unable to get children vm of resource pool %s: %s", rp.Name(), err)
return vms, err
}
for _, o := range mrp.Vm {
v := vm.NewVirtualMachine(op, s, o)
vms = append(vms, v)
}
return vms, nil
}
func (rp *ResourcePool) GetChildVM(ctx context.Context, s *session.Session, name string) (*vm.VirtualMachine, error) {
op := trace.FromContext(ctx, "GetChildVM")
searchIndex := object.NewSearchIndex(s.Client.Client)
child, err := searchIndex.FindChild(op, rp.Reference(), name)
if err != nil {
return nil, errors.Errorf("Unable to find VM(%s): %s", name, err.Error())
}
if child == nil {
return nil, nil
}
// instantiate the vm object
return vm.NewVirtualMachine(op, s, child.Reference()), nil
}
func (rp *ResourcePool) GetCluster(ctx context.Context) (*object.ComputeResource, error) {
op := trace.FromContext(ctx, "GetCluster")
var err error
var mrp mo.ResourcePool
if err = rp.Properties(op, rp.Reference(), []string{"owner"}, &mrp); err != nil {
op.Errorf("Unable to get cluster of resource pool %s: %s", rp.Name(), err)
return nil, err
}
return object.NewComputeResource(rp.Client.Client, mrp.Owner), nil
}
func (rp *ResourcePool) GetDatacenter(ctx context.Context) (*object.Datacenter, error) {
op := trace.FromContext(ctx, "GetDatacenter")
dcRef, err := rp.getLowestAncestor(op, "Datacenter")
if err != nil || dcRef == nil {
op.Errorf("Unable to get datacenter ancestor of rp %s: %s", rp.Name(), err)
return nil, errors.Errorf("Unable to get datacenter ancestor of rp %s: %s", rp.Name(), err)
}
return object.NewDatacenter(rp.Client.Client, *dcRef), nil
}
func (rp *ResourcePool) getAncestors(op trace.Operation, inType string) ([]types.ManagedObjectReference, error) {
client := rp.Session.Vim25()
ancestors, err := mo.Ancestors(op, client, client.ServiceContent.PropertyCollector, rp.Reference())
if err != nil {
op.Errorf("Unable to get ancestors of rp %s: %s", rp.Name(), err)
return nil, err
}
outAncestors := make([]types.ManagedObjectReference, 0, len(ancestors))
for _, ancestor := range ancestors {
if ancestor.Self.Type == inType {
a := ancestor.Self
outAncestors = append(outAncestors, a)
}
}
return outAncestors, nil
}
func (rp *ResourcePool) getLowestAncestor(op trace.Operation, inType string) (*types.ManagedObjectReference, error) {
ancestors, err := rp.getAncestors(op, inType)
if err != nil {
op.Errorf("Unable to get ancestors of rp %s: %s", rp.Name(), err)
return nil, err
}
if len(ancestors) == 0 {
return nil, nil
}
index := len(ancestors) - 1
return &ancestors[index], nil
}

View File

@@ -0,0 +1,103 @@
// Copyright 2016-2017 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 compute
import (
"context"
"net/url"
"testing"
"github.com/vmware/govmomi/simulator"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/test"
)
func TestRp(t *testing.T) {
ctx := context.Background()
for i, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
t.Logf("%d", i)
defer model.Remove()
err := model.Create()
if err != nil {
t.Fatal(err)
}
s := model.Service.NewServer()
defer s.Close()
s.URL.User = url.UserPassword("user", "pass")
t.Logf("server URL: %s", s.URL)
var sess *session.Session
if i == 0 {
sess, err = test.SessionWithESX(ctx, s.URL.String())
} else {
sess, err = test.SessionWithVPX(ctx, s.URL.String())
}
if err != nil {
t.Fatal(err)
}
defer sess.Logout(ctx)
testGetChildrenVMs(ctx, sess, t)
testGetChildVM(ctx, sess, t)
testGetCluster(ctx, sess, t)
testGetDatacenter(ctx, sess, t)
}
}
func testGetChildrenVMs(ctx context.Context, sess *session.Session, t *testing.T) {
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
vms, err := rp.GetChildrenVMs(ctx, sess)
if err != nil {
t.Errorf("Failed to get children vm of resource pool %s, %s", rp.Name(), err)
}
// if vms == nil || len(vms) == 0 {
// t.Error("Didn't get children VM")
// }
for _, vm := range vms {
t.Logf("vm: %s", vm)
}
}
func testGetChildVM(ctx context.Context, sess *session.Session, t *testing.T) {
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
vm, err := rp.GetChildVM(ctx, sess, "random")
if err == nil && vm != nil {
t.Logf("vm: %s", vm.Reference())
t.Errorf("Should not find VM random")
}
}
func testGetCluster(ctx context.Context, sess *session.Session, t *testing.T) {
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
cluster, err := rp.GetCluster(ctx)
if err != nil {
t.Logf("Failed to owner cluster: %s", err)
t.Errorf("Should get owner")
}
t.Logf("Cluster: %s", cluster)
}
func testGetDatacenter(ctx context.Context, sess *session.Session, t *testing.T) {
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
datacenter, err := rp.GetDatacenter(ctx)
if err != nil {
t.Logf("Failed to find parent Datacenter: %s", err)
t.Errorf("Should get Datacenter")
}
t.Logf("Datacenter: %s", datacenter)
}

View File

@@ -0,0 +1,43 @@
// Copyright 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 compute
import (
"context"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/pkg/vsphere/session"
)
// VirtualApp struct defines the VirtualApp which provides additional
// VIC specific methods over object.VirtualApp as well as keeps some state
type VirtualApp struct {
*object.VirtualApp
*session.Session
}
// NewResourcePool returns a New ResourcePool object
func NewVirtualApp(ctx context.Context, session *session.Session, moref types.ManagedObjectReference) *VirtualApp {
return &VirtualApp{
VirtualApp: object.NewVirtualApp(
session.Vim25(),
moref,
),
Session: session,
}
}

View File

@@ -0,0 +1,395 @@
// Copyright 2017 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 datastore
import (
"context"
"errors"
"fmt"
"io"
"net/url"
"os"
"path"
"regexp"
"strings"
"github.com/google/uuid"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/tasks"
)
// Helper gives access to the datastore regardless of type (esx, esx + vc,
// or esx + vc + vsan). Also wraps paths to a given root directory
type Helper struct {
// The Datastore API likes everything in "path/to/thing" format.
ds *object.Datastore
s *session.Session
// The FileManager API likes everything in "[dsname] path/to/thing" format.
fm *object.FileManager
// The datastore url (including root) in "[dsname] /path" format.
RootURL object.DatastorePath
}
// NewDatastore returns a Datastore.
// ctx is a context,
// s is an authenticated session
// ds is the vsphere datastore
// rootdir is the top level directory to root all data. If root does not exist,
// it will be created. If it already exists, NOOP. This cannot be empty.
func NewHelper(ctx context.Context, s *session.Session, ds *object.Datastore, rootdir string) (*Helper, error) {
op := trace.FromContext(ctx, "NewHelper")
d := &Helper{
ds: ds,
s: s,
fm: object.NewFileManager(s.Vim25()),
}
if path.IsAbs(rootdir) {
rootdir = rootdir[1:]
}
if err := d.mkRootDir(op, rootdir); err != nil {
op.Infof("error creating root directory %s: %s", rootdir, err)
return nil, err
}
if d.RootURL.Path == "" {
return nil, fmt.Errorf("failed to create root directory")
}
op.Infof("Datastore path is %s", d.RootURL.String())
return d, nil
}
func NewHelperFromURL(ctx context.Context, s *session.Session, u *url.URL) (*Helper, error) {
fm := object.NewFileManager(s.Vim25())
vsDs, err := s.Finder.DatastoreOrDefault(ctx, u.Host)
if err != nil {
return nil, err
}
d := &Helper{
ds: vsDs,
s: s,
fm: fm,
}
d.RootURL.FromString(u.Path)
return d, nil
}
func NewHelperFromSession(ctx context.Context, s *session.Session) *Helper {
return &Helper{
ds: s.Datastore,
s: s,
fm: object.NewFileManager(s.Vim25()),
}
}
// GetDatastores returns a map of datastores given a map of names and urls
func GetDatastores(ctx context.Context, s *session.Session, dsURLs map[string]*url.URL) (map[string]*Helper, error) {
stores := make(map[string]*Helper)
for name, dsURL := range dsURLs {
d, err := NewHelperFromURL(ctx, s, dsURL)
if err != nil {
return nil, err
}
stores[name] = d
}
return stores, nil
}
func (d *Helper) Summary(ctx context.Context) (*types.DatastoreSummary, error) {
var mds mo.Datastore
if err := d.ds.Properties(ctx, d.ds.Reference(), []string{"info", "summary"}, &mds); err != nil {
return nil, err
}
return &mds.Summary, nil
}
func mkdir(op trace.Operation, sess *session.Session, fm *object.FileManager, createParentDirectories bool, path string) (string, error) {
op.Infof("Creating directory %s", path)
if err := fm.MakeDirectory(op, path, sess.Datacenter, createParentDirectories); err != nil {
if soap.IsSoapFault(err) {
soapFault := soap.ToSoapFault(err)
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
op.Debugf("File already exists: %s", path)
return "", os.ErrExist
}
}
op.Debugf("Creating %s error: %s", path, err)
return "", err
}
return path, nil
}
// Mkdir creates directories.
func (d *Helper) Mkdir(ctx context.Context, createParentDirectories bool, dirs ...string) (string, error) {
op := trace.FromContext(ctx, "Mkdir")
return mkdir(op, d.s, d.fm, createParentDirectories, path.Join(d.RootURL.String(), path.Join(dirs...)))
}
// Ls returns a list of dirents at the given path (relative to root)
//
// A note aboutpaths and the datastore browser.
// None of these work paths work
// r, err := ds.Ls(ctx, "ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[vsanDatastore] ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/")
// r, err := ds.Ls(ctx, "[vsanDatastore] //vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/")
// r, err := ds.Ls(ctx, "[] ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[] /vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[] ../vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[] ./vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[52a67632ac3497a3-411916fd50bedc27] /0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[vsan:52a67632ac3497a3-411916fd50bedc27] /0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[vsan:52a67632ac3497a3-411916fd50bedc27] 0ea65357-0494-d42d-2ede-000c292dc5b5")
// r, err := ds.Ls(ctx, "[vsanDatastore] /vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
// The only URI that works on VC + VSAN.
// r, err := ds.Ls(ctx, "[vsanDatastore] /0ea65357-0494-d42d-2ede-000c292dc5b5")
//
func (d *Helper) Ls(ctx context.Context, p string) (*types.HostDatastoreBrowserSearchResults, error) {
spec := types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{"*"},
Details: &types.FileQueryFlags{
FileType: true,
FileOwner: types.NewBool(true),
},
}
b, err := d.ds.Browser(ctx)
if err != nil {
return nil, err
}
task, err := b.SearchDatastore(ctx, path.Join(d.RootURL.String(), p), &spec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(ctx, nil)
if err != nil {
return nil, err
}
res := info.Result.(types.HostDatastoreBrowserSearchResults)
return &res, nil
}
// LsDirs returns a list of dirents at the given path (relative to root)
func (d *Helper) LsDirs(ctx context.Context, p string) (*types.ArrayOfHostDatastoreBrowserSearchResults, error) {
spec := &types.HostDatastoreBrowserSearchSpec{
MatchPattern: []string{"*"},
Details: &types.FileQueryFlags{
FileType: true,
FileOwner: types.NewBool(true),
},
}
b, err := d.ds.Browser(ctx)
if err != nil {
return nil, err
}
task, err := b.SearchDatastoreSubFolders(ctx, path.Join(d.RootURL.String(), p), spec)
if err != nil {
return nil, err
}
info, err := task.WaitForResult(ctx, nil)
if err != nil {
return nil, err
}
res := info.Result.(types.ArrayOfHostDatastoreBrowserSearchResults)
return &res, nil
}
func (d *Helper) Upload(ctx context.Context, r io.Reader, pth string) error {
return d.ds.Upload(ctx, r, path.Join(d.RootURL.Path, pth), &soap.DefaultUpload)
}
func (d *Helper) Download(ctx context.Context, pth string) (io.ReadCloser, error) {
rc, _, err := d.ds.Download(ctx, path.Join(d.RootURL.Path, pth), &soap.DefaultDownload)
return rc, err
}
func (d *Helper) Stat(ctx context.Context, pth string) (types.BaseFileInfo, error) {
i, err := d.ds.Stat(ctx, path.Join(d.RootURL.Path, pth))
if err != nil {
switch err.(type) {
case object.DatastoreNoSuchDirectoryError:
return nil, os.ErrNotExist
default:
return nil, err
}
}
return i, nil
}
func (d *Helper) Mv(ctx context.Context, fromPath, toPath string) error {
op := trace.FromContext(ctx, "Mv")
from := path.Join(d.RootURL.String(), fromPath)
to := path.Join(d.RootURL.String(), toPath)
op.Infof("Moving %s to %s", from, to)
err := tasks.Wait(ctx, func(context.Context) (tasks.Task, error) {
return d.fm.MoveDatastoreFile(ctx, from, d.s.Datacenter, to, d.s.Datacenter, true)
})
return err
}
func (d *Helper) Rm(ctx context.Context, pth string) error {
op := trace.FromContext(ctx, "Rm")
f := path.Join(d.RootURL.String(), pth)
op.Infof("Removing %s", pth)
return d.ds.NewFileManager(d.s.Datacenter, true).Delete(ctx, f) // TODO: NewHelper should create the DatastoreFileManager
}
func (d *Helper) IsVSAN(ctx context.Context) bool {
// #nosec: Errors unhandled.
dsType, _ := d.ds.Type(ctx)
return dsType == types.HostFileSystemVolumeFileSystemTypeVsan
}
// This creates the root directory in the datastore and sets the rooturl and
// rootdir in the datastore struct so we can reuse it for other routines. This
// handles vsan + vc, vsan + esx, and esx. The URI conventions are not the
// same for each and this tries to create the directory and stash the relevant
// result so the URI doesn't need to be recomputed for every datastore
// operation.
func (d *Helper) mkRootDir(op trace.Operation, rootdir string) error {
if rootdir == "" {
return fmt.Errorf("root directory is empty")
}
if path.IsAbs(rootdir) {
return fmt.Errorf("root directory (%s) must not be an absolute path", rootdir)
}
// Handle vsan
// Vsan will complain if the root dir exists. Just call it directly and
// swallow the error if it's already there.
if d.IsVSAN(op) {
comps := strings.Split(rootdir, "/")
nm := object.NewDatastoreNamespaceManager(d.s.Vim25())
// This returns the vmfs path (including the datastore and directory
// UUIDs). Use the directory UUID in future operations because it is
// the stable path which we can use regardless of vsan state.
uuid, err := nm.CreateDirectory(op, d.ds, comps[0], "")
if err != nil {
if !soap.IsSoapFault(err) {
return err
}
soapFault := soap.ToSoapFault(err)
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); !ok {
return err
}
// XXX UGLY HACK until we move this into the installer. Use the
// display name if the dir exists since we can't get the UUID after the
// directory is created.
uuid = comps[0]
err = nil
}
rootdir = path.Join(path.Base(uuid), path.Join(comps[1:]...))
}
rooturl := d.ds.Path(rootdir)
// create the rest of the root dir in case of vSAN, otherwise
// create the full path
if _, err := mkdir(op, d.s, d.fm, true, rooturl); err != nil {
if !os.IsExist(err) {
return err
}
op.Infof("datastore root %s already exists", rooturl)
}
d.RootURL.FromString(rooturl)
return nil
}
func PathFromString(dsp string) (*object.DatastorePath, error) {
var p object.DatastorePath
if !p.FromString(dsp) {
return nil, errors.New(dsp + " not a datastore path")
}
return &p, nil
}
// Parse the datastore format ([datastore1] /path/to/thing) to groups.
var datastoreFormat = regexp.MustCompile(`^\[([\w\d\(\)-_\.\s]+)\]`)
var pathFormat = regexp.MustCompile(`\s([\/\w-_\.]*$)`)
// Converts `[datastore] /path` to ds:// URL
func ToURL(ds string) (*url.URL, error) {
u := new(url.URL)
var matches []string
if matches = datastoreFormat.FindStringSubmatch(ds); len(matches) != 2 {
return nil, fmt.Errorf("Ambiguous datastore hostname format encountered from input: %s.", ds)
}
u.Host = matches[1]
if matches = pathFormat.FindStringSubmatch(ds); len(matches) != 2 {
return nil, fmt.Errorf("Ambiguous datastore path format encountered from input: %s.", ds)
}
u.Path = path.Clean(matches[1])
u.Scheme = "ds"
return u, nil
}
// Converts ds:// URL for datastores to datastore format ([datastore1] /path/to/thing)
func URLtoDatastore(u *url.URL) (string, error) {
scheme := "ds"
if u.Scheme != scheme {
return "", fmt.Errorf("url (%s) is not a datastore", u.String())
}
return fmt.Sprintf("[%s] %s", u.Host, u.Path), nil
}
// TestName builds a unique datastore name
func TestName(suffix string) string {
return uuid.New().String()[0:16] + "-" + suffix
}

View File

@@ -0,0 +1,219 @@
// Copyright 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 datastore
import (
"math/rand"
"net/url"
"os"
"path"
"testing"
"time"
"context"
"github.com/stretchr/testify/assert"
)
// test if we can get a Datastore via the rooturl
func TestDatastoreGetDatastores(t *testing.T) {
ctx, ds, cleanupfunc := DSsetup(t)
if t.Failed() {
return
}
defer cleanupfunc()
firstSummary, err := ds.Summary(ctx)
if !assert.NoError(t, err) {
return
}
t.Logf("Name:\t%s\n", firstSummary.Name)
t.Logf(" Path:\t%s\n", ds.ds.InventoryPath)
t.Logf(" Type:\t%s\n", firstSummary.Type)
t.Logf(" URL:\t%s\n", firstSummary.Url)
t.Logf(" Capacity:\t%.1f GB\n", float64(firstSummary.Capacity)/(1<<30))
t.Logf(" Free:\t%.1f GB\n", float64(firstSummary.FreeSpace)/(1<<30))
inMap := make(map[string]*url.URL)
p, err := url.Parse(ds.RootURL.String())
if !assert.NoError(t, err) {
return
}
inMap["foo"] = p
dstores, err := GetDatastores(context.TODO(), ds.s, inMap)
if !assert.NoError(t, err) || !assert.NotNil(t, dstores) {
return
}
secondSummary, err := ds.Summary(ctx)
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, firstSummary, secondSummary) {
return
}
}
func TestDatastoreRestart(t *testing.T) {
// creates a root in the datastore
ctx, ds, cleanupfunc := DSsetup(t)
if t.Failed() {
return
}
defer cleanupfunc()
// Create a nested dir in the root and use that as the datastore
nestedRoot := path.Join(ds.RootURL.Path, "foo")
ds, err := NewHelper(ctx, ds.s, ds.s.Datastore, nestedRoot)
if !assert.NoError(t, err) {
return
}
// test we can ls the root
_, err = ds.Ls(ctx, "")
if !assert.NoError(t, err) {
return
}
// create a dir
_, err = ds.Mkdir(ctx, true, "baz")
if !assert.NoError(t, err) {
return
}
// create a new datastore object with the same path as the nested one
ds, err = NewHelper(ctx, ds.s, ds.s.Datastore, nestedRoot)
if !assert.NoError(t, err) {
return
}
// try to create the same baz dir, assert it exists
_, err = ds.Mkdir(ctx, true, "baz")
if !assert.Error(t, err) || !assert.True(t, os.IsExist(err)) {
return
}
assert.NotEmpty(t, ds.RootURL)
}
func TestDatastoreCreateDir(t *testing.T) {
ctx, ds, cleanupfunc := DSsetup(t)
if t.Failed() {
return
}
defer cleanupfunc()
_, err := ds.Ls(ctx, "")
if !assert.NoError(t, err) {
return
}
// assert create dir of a dir that exists is os.ErrExists
_, err = ds.Mkdir(ctx, true, "foo")
if !assert.NoError(t, err) {
return
}
_, err = ds.Mkdir(ctx, true, "foo")
if !assert.Error(t, err) || !assert.True(t, os.IsExist(err)) {
return
}
}
func TestDatastoreMkdirAndLs(t *testing.T) {
ctx, ds, cleanupfunc := DSsetup(t)
if t.Failed() {
return
}
defer cleanupfunc()
dirs := []string{"dir1", "dir1/child1"}
// create the dir then test it exists by calling ls
for _, dir := range dirs {
_, err := ds.Mkdir(ctx, true, dir)
if !assert.NoError(t, err) {
return
}
_, err = ds.Ls(ctx, dir)
if !assert.NoError(t, err) {
return
}
}
}
func TestDatastoreToURLParsing(t *testing.T) {
expectedURL := "ds://datastore1/path/to/thing"
input := [][]string{
{"[datastore1] /path/to/thing", expectedURL},
{"[datastore1] path/to/thing", expectedURL},
{"[datastore1] ///path////to/thing", expectedURL},
{"[Datastore (1)] /path/to/thing", "ds://Datastore%20(1)/path/to/thing"},
{"[datastore1] path", "ds://datastore1/path"},
{"[datastore1] pa-th", "ds://datastore1/pa-th"},
{"[datastore1] pa_th", "ds://datastore1/pa_th"},
{"[data_store1] pa_th", "ds://data_store1/pa_th"},
}
dsoutputs := []string{
"[datastore1] /path/to/thing",
"[datastore1] path/to/thing",
"[datastore1] /path/to/thing",
"[Datastore (1)] /path/to/thing",
"[datastore1] path",
"[datastore1] pa-th",
"[datastore1] pa_th",
"[data_store1] pa_th",
}
for i, in := range input {
u, err := ToURL(in[0])
if !assert.NoError(t, err) || !assert.NotNil(t, u) {
return
}
if !assert.Equal(t, in[1], u.String()) {
return
}
out, err := URLtoDatastore(u)
if !assert.NoError(t, err) || !assert.True(t, len(out) > 0) {
return
}
if !assert.Equal(t, dsoutputs[i], out) {
return
}
}
}
// From https://siongui.github.io/2015/04/13/go-generate-random-string/
func RandomString(strlen int) string {
rand.Seed(time.Now().UTC().UnixNano())
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
result[i] = chars[rand.Intn(len(chars))]
}
return string(result)
}

View File

@@ -0,0 +1,83 @@
// Copyright 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 datastore
import (
"context"
"testing"
"time"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/vsphere/session"
"github.com/vmware/vic/pkg/vsphere/tasks"
"github.com/vmware/vic/pkg/vsphere/test/env"
)
// Used in testing
func Session(ctx context.Context, t *testing.T) *session.Session {
config := &session.Config{
Service: env.URL(t),
/// XXX Why does this insist on having this field populated?
DatastorePath: env.DS(t),
Insecure: true,
Keepalive: time.Duration(5) * time.Minute,
}
s := session.NewSession(config)
_, err := s.Connect(ctx)
if err != nil {
s.Client.Logout(ctx)
t.Log(err.Error())
t.SkipNow()
}
_, err = s.Populate(ctx)
if err != nil {
t.Log(err.Error())
t.SkipNow()
}
return s
}
func DSsetup(t *testing.T) (context.Context, *Helper, func()) {
log.SetLevel(log.DebugLevel)
ctx := context.Background()
sess := Session(ctx, t)
ds, err := NewHelper(ctx, sess, sess.Datastore, TestName("dstests"))
if !assert.NoError(t, err) {
return ctx, nil, nil
}
f := func() {
log.Infof("Removing test root %s", ds.RootURL.String())
err := tasks.Wait(ctx, func(context.Context) (tasks.Task, error) {
return ds.fm.DeleteDatastoreFile(ctx, ds.RootURL.String(), sess.Datacenter)
})
if err != nil {
log.Errorf(err.Error())
return
}
}
return ctx, ds, f
}

152
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag.go generated vendored Normal file
View File

@@ -0,0 +1,152 @@
// Copyright 2016-2017 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 diag
import (
"bytes"
"context"
"crypto/tls"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/vmware/vic/pkg/trace"
)
// StatusCodeFatalThreshold defines a threshold after which all codes can be treated as fatal.
const StatusCodeFatalThreshold = 64
const (
// VCStatusOK vSphere API is available.
VCStatusOK = 0
// VCStatusInvalidURL Provided vSphere API URL is wrong.
VCStatusInvalidURL = 64
// VCStatusErrorQuery Error happened trying to query vSphere API
VCStatusErrorQuery = 65
// VCStatusErrorResponse Received response doesn't contain expected data.
VCStatusErrorResponse = 66
// VCStatusIncorrectResponse Received in case if returned data from server is different from expected.
VCStatusIncorrectResponse = 67
// VCStatusNotXML Received response is not XML
VCStatusNotXML = 68
// VCStatusUnknownHost is returned in case if DNS failed to resolve name.
VCStatusUnknownHost = 69
// VCStatusHostIsNotReachable
VCStatusHostIsNotReachable = 70
)
// UserReadableVCAPITestDescription convert API test code into user readable text
func UserReadableVCAPITestDescription(code int) string {
switch code {
case VCStatusOK:
return "vSphere API target responds as expected"
case VCStatusInvalidURL:
return "vSphere API target url is invalid"
case VCStatusErrorQuery:
return "vSphere API target failed to respond to the query"
case VCStatusIncorrectResponse:
return "vSphere API target returns unexpected response"
case VCStatusErrorResponse:
return "vSphere API target returns error"
case VCStatusNotXML:
return "vSphere API target returns non XML response"
case VCStatusUnknownHost:
return "vSphere API target can not be resolved from VCH"
case VCStatusHostIsNotReachable:
return "vSphere API target is out of reach. Wrong routing table?"
default:
return "vSphere API target test returned unknown code"
}
}
// CheckAPIAvailability accesses vSphere API to ensure it is a correct end point that is up and running.
func CheckAPIAvailability(targetURL string) int {
op := trace.NewOperation(context.Background(), "api test")
errorCode := VCStatusErrorQuery
u, err := url.Parse(targetURL)
if err != nil {
return VCStatusInvalidURL
}
u.Path = "/sdk/vimService.wsdl"
apiURL := u.String()
op.Debugf("Checking access to: %s", apiURL)
for attempts := 5; errorCode != VCStatusOK && attempts > 0; attempts-- {
// #nosec: TLS InsecureSkipVerify set true
c := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
// Is 20 seconds enough to receive any response from vSphere target server?
Timeout: time.Second * 20,
}
errorCode = queryAPI(op, c.Get, apiURL)
}
return errorCode
}
func queryAPI(op trace.Operation, getter func(string) (*http.Response, error), apiURL string) int {
resp, err := getter(apiURL)
if err != nil {
errTxt := err.Error()
op.Errorf("Query error: %s", err)
if strings.Contains(errTxt, "no such host") {
return VCStatusUnknownHost
}
if strings.Contains(errTxt, "no route to host") {
return VCStatusHostIsNotReachable
}
if strings.Contains(errTxt, "host is down") {
return VCStatusHostIsNotReachable
}
return VCStatusErrorQuery
}
data := make([]byte, 65636)
n, err := io.ReadFull(resp.Body, data)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
op.Errorf("Query error: %s", err)
return VCStatusErrorResponse
}
if n >= len(data) {
// #nosec: Errors unhandled.
io.Copy(ioutil.Discard, resp.Body)
}
// #nosec: Errors unhandled.
resp.Body.Close()
contentType := strings.ToLower(resp.Header.Get("Content-Type"))
if !strings.Contains(contentType, "text/xml") {
op.Errorf("Unexpected content type %s, should be text/xml", contentType)
op.Errorf("Response from the server: %s", string(data))
return VCStatusNotXML
}
// we just want to make sure that response contains something familiar that we could
// use as vSphere API marker.
if !bytes.Contains(data, []byte("urn:vim25Service")) {
op.Errorf("Server response doesn't contain 'urn:vim25Service': %s", string(data))
return VCStatusIncorrectResponse
}
return VCStatusOK
}

View File

@@ -0,0 +1,117 @@
// Copyright 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 diag
import (
"bytes"
"context"
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
func TestCheckAPIAvailability(t *testing.T) {
assert.Equal(t, VCStatusErrorQuery, CheckAPIAvailability("http://127.0.0.1:65535"))
assert.Equal(t, VCStatusErrorQuery, CheckAPIAvailability("http://127.0.0.1:65536"))
}
func TestCheckAPIAvailabilityQueryWithGetterError(t *testing.T) {
op := trace.NewOperation(context.Background(), "test")
f := func(s string) (*http.Response, error) { return nil, errors.New("wrong query") }
code := queryAPI(op, f, "testurl")
assert.Equal(t, VCStatusErrorQuery, code)
}
type readerWithError struct {
err error
data *bytes.Reader
}
func (r *readerWithError) Read(b []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
return r.data.Read(b)
}
func (r *readerWithError) Close() error {
return r.err
}
func TestCheckAPIAvailabilityQueryReadError(t *testing.T) {
op := trace.NewOperation(context.Background(), "test")
f := func(s string) (*http.Response, error) {
hr := &http.Response{
Body: &readerWithError{
err: errors.New("read error happened"),
},
}
return hr, nil
}
code := queryAPI(op, f, "testurl")
assert.Equal(t, VCStatusErrorResponse, code)
}
func TestCheckAPIAvailabilityQueryIncorrectDataType(t *testing.T) {
op := trace.NewOperation(context.Background(), "test")
f := func(s string) (*http.Response, error) {
hr := &http.Response{
Body: &readerWithError{
data: bytes.NewReader([]byte("some data")),
},
}
return hr, nil
}
code := queryAPI(op, f, "testurl")
assert.Equal(t, VCStatusNotXML, code)
}
func TestCheckAPIAvailabilityQueryIncorrectData(t *testing.T) {
op := trace.NewOperation(context.Background(), "test")
f := func(s string) (*http.Response, error) {
hr := &http.Response{
Body: &readerWithError{
data: bytes.NewReader([]byte("some data")),
},
Header: http.Header{"Content-Type": []string{"text/xml"}},
}
return hr, nil
}
code := queryAPI(op, f, "testurl")
assert.Equal(t, VCStatusIncorrectResponse, code)
}
func TestCheckAPIAvailabilityQueryCorrectData(t *testing.T) {
op := trace.NewOperation(context.Background(), "test")
f := func(s string) (*http.Response, error) {
hr := &http.Response{
Body: &readerWithError{
data: bytes.NewReader([]byte("some urn:vim25Service data")),
},
Header: http.Header{"Content-Type": []string{"text/xml"}},
}
return hr, nil
}
code := queryAPI(op, f, "testurl")
assert.Equal(t, VCStatusOK, code)
}
func TestCheckAPIAvailabilityIncorrectDNSName(t *testing.T) {
assert.Equal(t, VCStatusUnknownHost, CheckAPIAvailability("https://example.notexisting.domain"))
}

View File

@@ -0,0 +1,39 @@
// Copyright 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 diagnostic
import (
"github.com/vmware/govmomi/object"
"github.com/vmware/vic/pkg/vsphere/session"
)
// Manager struct defines the Manager which provides additional
// VIC specific methods over object.DiagnosticManager
type Manager struct {
*object.DiagnosticManager
*session.Session
}
// NewDiagnosticManager returns a new DiagnosticManager object
func NewDiagnosticManager(session *session.Session) *Manager {
return &Manager{
DiagnosticManager: object.NewDiagnosticManager(
session.Vim25(),
),
Session: session,
}
}

Some files were not shown because too many files have changed in this diff Show More