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:
359
vendor/github.com/vmware/vic/pkg/certificate/certificate.go
generated
vendored
Normal file
359
vendor/github.com/vmware/vic/pkg/certificate/certificate.go
generated
vendored
Normal 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
|
||||
}
|
||||
122
vendor/github.com/vmware/vic/pkg/certificate/certificate_test.go
generated
vendored
Normal file
122
vendor/github.com/vmware/vic/pkg/certificate/certificate_test.go
generated
vendored
Normal 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
40
vendor/github.com/vmware/vic/pkg/certificate/errors.go
generated
vendored
Normal 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
127
vendor/github.com/vmware/vic/pkg/certificate/keypair.go
generated
vendored
Normal 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
|
||||
}
|
||||
122
vendor/github.com/vmware/vic/pkg/certificate/keypair_test.go
generated
vendored
Normal file
122
vendor/github.com/vmware/vic/pkg/certificate/keypair_test.go
generated
vendored
Normal 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
18
vendor/github.com/vmware/vic/pkg/dio/doc.go
generated
vendored
Normal 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
430
vendor/github.com/vmware/vic/pkg/dio/multi_test.go
generated
vendored
Normal 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
220
vendor/github.com/vmware/vic/pkg/dio/reader.go
generated
vendored
Normal 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
181
vendor/github.com/vmware/vic/pkg/dio/writer.go
generated
vendored
Normal 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
40
vendor/github.com/vmware/vic/pkg/errors/errors.go
generated
vendored
Normal 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
54
vendor/github.com/vmware/vic/pkg/errors/errors_test.go
generated
vendored
Normal 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
58
vendor/github.com/vmware/vic/pkg/fetcher/errors.go
generated
vendored
Normal 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
584
vendor/github.com/vmware/vic/pkg/fetcher/fetcher.go
generated
vendored
Normal 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
|
||||
}
|
||||
104
vendor/github.com/vmware/vic/pkg/fetcher/fetcher_test.go
generated
vendored
Normal file
104
vendor/github.com/vmware/vic/pkg/fetcher/fetcher_test.go
generated
vendored
Normal 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
75
vendor/github.com/vmware/vic/pkg/filelock/flock.go
generated
vendored
Normal 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
114
vendor/github.com/vmware/vic/pkg/filelock/flock_test.go
generated
vendored
Normal 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
19
vendor/github.com/vmware/vic/pkg/filelock/glocks.go
generated
vendored
Normal 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"
|
||||
52
vendor/github.com/vmware/vic/pkg/flags/optional_bool.go
generated
vendored
Normal file
52
vendor/github.com/vmware/vic/pkg/flags/optional_bool.go
generated
vendored
Normal 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}
|
||||
}
|
||||
61
vendor/github.com/vmware/vic/pkg/flags/optional_bool_test.go
generated
vendored
Normal file
61
vendor/github.com/vmware/vic/pkg/flags/optional_bool_test.go
generated
vendored
Normal 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
52
vendor/github.com/vmware/vic/pkg/flags/optional_int.go
generated
vendored
Normal 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}
|
||||
}
|
||||
51
vendor/github.com/vmware/vic/pkg/flags/optional_int_test.go
generated
vendored
Normal file
51
vendor/github.com/vmware/vic/pkg/flags/optional_int_test.go
generated
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
50
vendor/github.com/vmware/vic/pkg/flags/optional_string.go
generated
vendored
Normal file
50
vendor/github.com/vmware/vic/pkg/flags/optional_string.go
generated
vendored
Normal 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}
|
||||
}
|
||||
51
vendor/github.com/vmware/vic/pkg/flags/optional_string_test.go
generated
vendored
Normal file
51
vendor/github.com/vmware/vic/pkg/flags/optional_string_test.go
generated
vendored
Normal 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
64
vendor/github.com/vmware/vic/pkg/flags/shares_flag.go
generated
vendored
Normal 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}
|
||||
}
|
||||
75
vendor/github.com/vmware/vic/pkg/flags/shares_flag_test.go
generated
vendored
Normal file
75
vendor/github.com/vmware/vic/pkg/flags/shares_flag_test.go
generated
vendored
Normal 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
60
vendor/github.com/vmware/vic/pkg/flags/url_flag.go
generated
vendored
Normal 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}
|
||||
}
|
||||
60
vendor/github.com/vmware/vic/pkg/flags/url_flag_test.go
generated
vendored
Normal file
60
vendor/github.com/vmware/vic/pkg/flags/url_flag_test.go
generated
vendored
Normal 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
92
vendor/github.com/vmware/vic/pkg/fs/ext4.go
generated
vendored
Normal 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
82
vendor/github.com/vmware/vic/pkg/fs/xfs.go
generated
vendored
Normal 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
148
vendor/github.com/vmware/vic/pkg/i18n/i18n.go
generated
vendored
Normal 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
109
vendor/github.com/vmware/vic/pkg/i18n/i18n_test.go
generated
vendored
Normal 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
222
vendor/github.com/vmware/vic/pkg/index/index.go
generated
vendored
Normal 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
203
vendor/github.com/vmware/vic/pkg/index/index_test.go
generated
vendored
Normal 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
212
vendor/github.com/vmware/vic/pkg/ip/ip.go
generated
vendored
Normal 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
159
vendor/github.com/vmware/vic/pkg/ip/ip_test.go
generated
vendored
Normal 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
75
vendor/github.com/vmware/vic/pkg/kvstore/backend.go
generated
vendored
Normal 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
216
vendor/github.com/vmware/vic/pkg/kvstore/kvstore.go
generated
vendored
Normal 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
|
||||
}
|
||||
170
vendor/github.com/vmware/vic/pkg/kvstore/kvstore_test.go
generated
vendored
Normal file
170
vendor/github.com/vmware/vic/pkg/kvstore/kvstore_test.go
generated
vendored
Normal 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
158
vendor/github.com/vmware/vic/pkg/kvstore/mocks.go
generated
vendored
Normal 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
101
vendor/github.com/vmware/vic/pkg/log/log.go
generated
vendored
Normal 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
23
vendor/github.com/vmware/vic/pkg/log/syslog/dialer.go
generated
vendored
Normal 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)
|
||||
}
|
||||
64
vendor/github.com/vmware/vic/pkg/log/syslog/formatter.go
generated
vendored
Normal file
64
vendor/github.com/vmware/vic/pkg/log/syslog/formatter.go
generated
vendored
Normal 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
|
||||
}
|
||||
56
vendor/github.com/vmware/vic/pkg/log/syslog/formatter_test.go
generated
vendored
Normal file
56
vendor/github.com/vmware/vic/pkg/log/syslog/formatter_test.go
generated
vendored
Normal 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
71
vendor/github.com/vmware/vic/pkg/log/syslog/hook.go
generated
vendored
Normal 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
|
||||
}
|
||||
126
vendor/github.com/vmware/vic/pkg/log/syslog/hook_test.go
generated
vendored
Normal file
126
vendor/github.com/vmware/vic/pkg/log/syslog/hook_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
55
vendor/github.com/vmware/vic/pkg/log/syslog/mock_addr_test.go
generated
vendored
Normal file
55
vendor/github.com/vmware/vic/pkg/log/syslog/mock_addr_test.go
generated
vendored
Normal 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)
|
||||
46
vendor/github.com/vmware/vic/pkg/log/syslog/mock_dialer_test.go
generated
vendored
Normal file
46
vendor/github.com/vmware/vic/pkg/log/syslog/mock_dialer_test.go
generated
vendored
Normal 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)
|
||||
38
vendor/github.com/vmware/vic/pkg/log/syslog/mock_formatter_test.go
generated
vendored
Normal file
38
vendor/github.com/vmware/vic/pkg/log/syslog/mock_formatter_test.go
generated
vendored
Normal 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)
|
||||
155
vendor/github.com/vmware/vic/pkg/log/syslog/mock_netconn_test.go
generated
vendored
Normal file
155
vendor/github.com/vmware/vic/pkg/log/syslog/mock_netconn_test.go
generated
vendored
Normal 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)
|
||||
47
vendor/github.com/vmware/vic/pkg/log/syslog/mock_netdialer_test.go
generated
vendored
Normal file
47
vendor/github.com/vmware/vic/pkg/log/syslog/mock_netdialer_test.go
generated
vendored
Normal 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)
|
||||
174
vendor/github.com/vmware/vic/pkg/log/syslog/mock_writer_test.go
generated
vendored
Normal file
174
vendor/github.com/vmware/vic/pkg/log/syslog/mock_writer_test.go
generated
vendored
Normal 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
156
vendor/github.com/vmware/vic/pkg/log/syslog/syslog.go
generated
vendored
Normal 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
|
||||
)
|
||||
80
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_test.go
generated
vendored
Normal file
80
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
53
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_unix.go
generated
vendored
Normal file
53
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_unix.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_unix_test.go
generated
vendored
Normal file
26
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_unix_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
22
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_windows.go
generated
vendored
Normal file
22
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_windows.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_windows_test.go
generated
vendored
Normal file
25
vendor/github.com/vmware/vic/pkg/log/syslog/syslog_windows_test.go
generated
vendored
Normal 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
270
vendor/github.com/vmware/vic/pkg/log/syslog/writer.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
278
vendor/github.com/vmware/vic/pkg/log/syslog/writer_test.go
generated
vendored
Normal file
278
vendor/github.com/vmware/vic/pkg/log/syslog/writer_test.go
generated
vendored
Normal 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
63
vendor/github.com/vmware/vic/pkg/log/text_formatter.go
generated
vendored
Normal 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)
|
||||
}
|
||||
136
vendor/github.com/vmware/vic/pkg/log/text_formatter_test.go
generated
vendored
Normal file
136
vendor/github.com/vmware/vic/pkg/log/text_formatter_test.go
generated
vendored
Normal 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
271
vendor/github.com/vmware/vic/pkg/logmgr/logmgr.go
generated
vendored
Normal 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
105
vendor/github.com/vmware/vic/pkg/logmgr/logmgr_test.go
generated
vendored
Normal 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
189
vendor/github.com/vmware/vic/pkg/registry/entry.go
generated
vendored
Normal 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
370
vendor/github.com/vmware/vic/pkg/registry/entry_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
107
vendor/github.com/vmware/vic/pkg/registry/mock_Entry_test.go
generated
vendored
Normal file
107
vendor/github.com/vmware/vic/pkg/registry/mock_Entry_test.go
generated
vendored
Normal 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)
|
||||
46
vendor/github.com/vmware/vic/pkg/registry/mock_Merger_test.go
generated
vendored
Normal file
46
vendor/github.com/vmware/vic/pkg/registry/mock_Merger_test.go
generated
vendored
Normal 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
81
vendor/github.com/vmware/vic/pkg/registry/set.go
generated
vendored
Normal 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
127
vendor/github.com/vmware/vic/pkg/registry/set_test.go
generated
vendored
Normal 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
58
vendor/github.com/vmware/vic/pkg/registry/utils.go
generated
vendored
Normal 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
114
vendor/github.com/vmware/vic/pkg/retry/retry.go
generated
vendored
Normal 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
41
vendor/github.com/vmware/vic/pkg/retry/retry_test.go
generated
vendored
Normal 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
30
vendor/github.com/vmware/vic/pkg/serial/debug.go
generated
vendored
Normal 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
221
vendor/github.com/vmware/vic/pkg/serial/handshake.go
generated
vendored
Normal 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
|
||||
}
|
||||
429
vendor/github.com/vmware/vic/pkg/serial/handshake_test.go
generated
vendored
Normal file
429
vendor/github.com/vmware/vic/pkg/serial/handshake_test.go
generated
vendored
Normal 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
47
vendor/github.com/vmware/vic/pkg/serial/rawaddr.go
generated
vendored
Normal 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
231
vendor/github.com/vmware/vic/pkg/serial/rawconn.go
generated
vendored
Normal 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
253
vendor/github.com/vmware/vic/pkg/telnet/connection.go
generated
vendored
Normal 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
|
||||
}
|
||||
124
vendor/github.com/vmware/vic/pkg/telnet/connection_test.go
generated
vendored
Normal file
124
vendor/github.com/vmware/vic/pkg/telnet/connection_test.go
generated
vendored
Normal 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
130
vendor/github.com/vmware/vic/pkg/telnet/fsm.go
generated
vendored
Normal 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
176
vendor/github.com/vmware/vic/pkg/telnet/fsm_test.go
generated
vendored
Normal 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
94
vendor/github.com/vmware/vic/pkg/telnet/protocol.go
generated
vendored
Normal 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
116
vendor/github.com/vmware/vic/pkg/telnet/server.go
generated
vendored
Normal 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
45
vendor/github.com/vmware/vic/pkg/trace/entry.go
generated
vendored
Normal 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
318
vendor/github.com/vmware/vic/pkg/trace/operation.go
generated
vendored
Normal 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
|
||||
}
|
||||
306
vendor/github.com/vmware/vic/pkg/trace/operation_test.go
generated
vendored
Normal file
306
vendor/github.com/vmware/vic/pkg/trace/operation_test.go
generated
vendored
Normal 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
143
vendor/github.com/vmware/vic/pkg/trace/trace.go
generated
vendored
Normal 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
56
vendor/github.com/vmware/vic/pkg/uid/uid.go
generated
vendored
Normal 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
67
vendor/github.com/vmware/vic/pkg/uid/uid_test.go
generated
vendored
Normal 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
149
vendor/github.com/vmware/vic/pkg/version/version.go
generated
vendored
Normal 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
|
||||
}
|
||||
160
vendor/github.com/vmware/vic/pkg/version/version_test.go
generated
vendored
Normal file
160
vendor/github.com/vmware/vic/pkg/version/version_test.go
generated
vendored
Normal 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
142
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp.go
generated
vendored
Normal 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
|
||||
}
|
||||
103
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp_test.go
generated
vendored
Normal file
103
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
43
vendor/github.com/vmware/vic/pkg/vsphere/compute/vapp.go
generated
vendored
Normal file
43
vendor/github.com/vmware/vic/pkg/vsphere/compute/vapp.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
395
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore.go
generated
vendored
Normal file
395
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore.go
generated
vendored
Normal 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
|
||||
}
|
||||
219
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_test.go
generated
vendored
Normal file
219
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
83
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_util.go
generated
vendored
Normal file
83
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_util.go
generated
vendored
Normal 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
152
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag.go
generated
vendored
Normal 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
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag_test.go
generated
vendored
Normal file
117
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag_test.go
generated
vendored
Normal 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"))
|
||||
}
|
||||
39
vendor/github.com/vmware/vic/pkg/vsphere/diagnostic/manager.go
generated
vendored
Normal file
39
vendor/github.com/vmware/vic/pkg/vsphere/diagnostic/manager.go
generated
vendored
Normal 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
Reference in New Issue
Block a user