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:
37
vendor/github.com/vmware/vic/lib/portlayer/attach/bind.go
generated
vendored
Normal file
37
vendor/github.com/vmware/vic/lib/portlayer/attach/bind.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 attach
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Bind sets the *Connected fields of the VirtualSerialPort
|
||||
func Bind(h interface{}, id string) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
if handle.MigrationError != nil {
|
||||
return nil, fmt.Errorf("Migration failed %s", handle.MigrationError)
|
||||
}
|
||||
|
||||
return toggle(handle, id, true)
|
||||
}
|
||||
135
vendor/github.com/vmware/vic/lib/portlayer/attach/common.go
generated
vendored
Normal file
135
vendor/github.com/vmware/vic/lib/portlayer/attach/common.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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 attach
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/migration/feature"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
func lookupVCHIP() (net.IP, error) {
|
||||
// FIXME: THERE MUST BE ANOTHER WAY
|
||||
// following is from Create@exec.go
|
||||
ips, err := net.LookupIP(constants.ManagementHostName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("No IP found on %s", constants.ManagementHostName)
|
||||
}
|
||||
|
||||
if len(ips) > 1 {
|
||||
return nil, fmt.Errorf("Multiple IPs found on %s: %#v", constants.ManagementHostName, ips)
|
||||
}
|
||||
return ips[0], nil
|
||||
}
|
||||
|
||||
func toggle(handle *exec.Handle, id string, connected bool) (*exec.Handle, error) {
|
||||
// check to see whether id is in Execs, if so set its RunBlock property to connected
|
||||
session, ok := handle.ExecConfig.Execs[id]
|
||||
if ok {
|
||||
if err := compatible(handle); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if session.Attach {
|
||||
session.RunBlock = connected
|
||||
}
|
||||
}
|
||||
|
||||
// get the virtual device list
|
||||
devices := object.VirtualDeviceList(handle.Config.Hardware.Device)
|
||||
|
||||
// select the virtual serial ports
|
||||
serials := devices.SelectByBackingInfo((*types.VirtualSerialPortURIBackingInfo)(nil))
|
||||
if len(serials) == 0 {
|
||||
return nil, fmt.Errorf("Unable to find a device with desired backing")
|
||||
}
|
||||
if len(serials) > 1 {
|
||||
return nil, fmt.Errorf("Multiple matches found with desired backing")
|
||||
}
|
||||
serial := serials[0]
|
||||
|
||||
ip, err := lookupVCHIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Found a device with desired backing: %#v", serial)
|
||||
|
||||
c := serial.GetVirtualDevice().Connectable
|
||||
b := serial.GetVirtualDevice().Backing.(*types.VirtualSerialPortURIBackingInfo)
|
||||
|
||||
serviceURI := fmt.Sprintf("tcp://127.0.0.1:%d", constants.AttachServerPort)
|
||||
proxyURI := fmt.Sprintf("telnet://%s:%d", ip, constants.SerialOverLANPort)
|
||||
|
||||
if b.ProxyURI == proxyURI && c.Connected == connected {
|
||||
log.Debugf("Already in the desired state, (connected: %t, proxyURI: %s)", connected, proxyURI)
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// set the values
|
||||
log.Debugf("Setting Connected to %t", connected)
|
||||
c.Connected = connected
|
||||
if connected && handle.ExecConfig.Sessions[handle.ExecConfig.ID].Attach {
|
||||
log.Debugf("Setting the start connected state to %t", connected)
|
||||
c.StartConnected = handle.ExecConfig.Sessions[handle.ExecConfig.ID].Attach
|
||||
}
|
||||
|
||||
log.Debugf("Setting ServiceURI to %s", serviceURI)
|
||||
b.ServiceURI = serviceURI
|
||||
|
||||
log.Debugf("Setting the ProxyURI to %s", proxyURI)
|
||||
b.ProxyURI = proxyURI
|
||||
|
||||
config := &types.VirtualDeviceConfigSpec{
|
||||
Device: serial,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationEdit,
|
||||
}
|
||||
handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config)
|
||||
|
||||
// check to see whether id is in Sessions, if so set its RunBlock property to connected
|
||||
// if attach happens before start then this property will be persist in the vmx
|
||||
// if attach happens after start then this propery will be thrown away by commit (one cannot change persistent extraconfig values if the vm is powered on)
|
||||
session, ok = handle.ExecConfig.Sessions[id]
|
||||
if ok {
|
||||
if session.Attach {
|
||||
session.RunBlock = connected
|
||||
}
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func compatible(h interface{}) error {
|
||||
if handle, ok := h.(*exec.Handle); ok {
|
||||
if handle.DataVersion < feature.ExecSupportedVersion {
|
||||
return fmt.Errorf("attaching exec tasks not supported for this container")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Type assertion failed for %#+v", h)
|
||||
}
|
||||
484
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/connector.go
generated
vendored
Normal file
484
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/connector.go
generated
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
// 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 communication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/serial"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
const (
|
||||
VersionString = "SSH-2.0-VIC"
|
||||
ClientTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// Connector defines the connection and interactions
|
||||
type Connector struct {
|
||||
mutex sync.RWMutex
|
||||
cond *sync.Cond
|
||||
interactions map[string]*LazySessionInteractor
|
||||
|
||||
listener net.Listener
|
||||
// Quit channel for serve
|
||||
done chan struct{}
|
||||
|
||||
// deduplication of incoming calls
|
||||
fg singleflight.Group
|
||||
|
||||
// graceful shutdown
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewConnector returns a new Connector
|
||||
func NewConnector(listener net.Listener) *Connector {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
connector := &Connector{
|
||||
interactions: make(map[string]*LazySessionInteractor),
|
||||
listener: listener,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
connector.cond = sync.NewCond(connector.mutex.RLocker())
|
||||
|
||||
return connector
|
||||
}
|
||||
|
||||
// SessionIfAlive returns SessionInteractor or error
|
||||
func (c *Connector) SessionIfAlive(ctx context.Context, id string) (SessionInteractor, error) {
|
||||
c.mutex.RLock()
|
||||
v, ok := c.interactions[id]
|
||||
c.mutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("attach connector: no such connection in the map")
|
||||
}
|
||||
// we have an entry in the map, let's check its status
|
||||
var conn SessionInteractor
|
||||
var err error
|
||||
|
||||
conn, err = v.Initialize()
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
|
||||
log.Debugf("attach connector: Pinging for %s", id)
|
||||
if err = conn.Ping(); err != nil {
|
||||
goto Error
|
||||
}
|
||||
|
||||
log.Debugf("attach connector: Unblocking for %s", id)
|
||||
if err = conn.Unblock(); err != nil {
|
||||
goto Error
|
||||
}
|
||||
log.Debugf("attach connector: Unblocked %s, returning", id)
|
||||
|
||||
return conn, nil
|
||||
|
||||
Error:
|
||||
log.Debugf("attach connector: liveness check failed, removing %s from connection map", id)
|
||||
|
||||
c.mutex.Lock()
|
||||
delete(c.interactions, id)
|
||||
c.mutex.Unlock()
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Interaction returns the interactor corresponding to the specified ID. If the connection doesn't exist
|
||||
// the method will wait for the specified timeout, returning when the connection is created
|
||||
// or the timeout expires, whichever occurs first
|
||||
func (c *Connector) Interaction(ctx context.Context, id string) (SessionInteractor, error) {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
// make sure that we have only one call in-flight for each ID at any given time
|
||||
si, err, shared := c.fg.Do(id, func() (interface{}, error) {
|
||||
return c.interaction(ctx, id)
|
||||
})
|
||||
if err != nil {
|
||||
c.fg.Forget(id)
|
||||
return nil, err
|
||||
}
|
||||
if shared {
|
||||
log.Debugf("Eliminated duplicated calls to Interaction for %s", id)
|
||||
}
|
||||
return si.(SessionInteractor), nil
|
||||
}
|
||||
|
||||
func (c *Connector) interaction(ctx context.Context, id string) (SessionInteractor, error) {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
conn, err := c.SessionIfAlive(ctx, id)
|
||||
if conn != nil && err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, fmt.Errorf("attach connector: no such connection")
|
||||
}
|
||||
|
||||
result := make(chan SessionInteractor, 1)
|
||||
go func() {
|
||||
ok := false
|
||||
var v *LazySessionInteractor
|
||||
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
|
||||
for !ok && ctx.Err() == nil {
|
||||
v, ok = c.interactions[id]
|
||||
if ok {
|
||||
conn, err := v.Initialize()
|
||||
if conn != nil && err == nil {
|
||||
// no need to test this connection as we just created it, unblock if needed
|
||||
log.Debugf("attach connector: Unblocking for %s", id)
|
||||
err = conn.Unblock()
|
||||
if err == nil {
|
||||
log.Debugf("attach connector: Unblocked %s, returning", id)
|
||||
|
||||
result <- conn
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
|
||||
// block until cond is updated
|
||||
log.Infof("attach connector: Connection not found yet for %s", id)
|
||||
c.cond.Wait()
|
||||
}
|
||||
log.Debugf("attach connector: Giving up on connection for %s", id)
|
||||
}()
|
||||
|
||||
select {
|
||||
case client := <-result:
|
||||
log.Debugf("attach connector: Found connection for %s: %p", id, client)
|
||||
return client, nil
|
||||
case <-ctx.Done():
|
||||
err := fmt.Errorf("attach connector: Connection not found error for id:%s: %s", id, ctx.Err())
|
||||
log.Error(err)
|
||||
// wake up the result gofunc before returning
|
||||
c.mutex.RLock()
|
||||
c.cond.Broadcast()
|
||||
c.mutex.RUnlock()
|
||||
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveInteraction removes the session the inteactions map
|
||||
func (c *Connector) RemoveInteraction(id string) error {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
var err error
|
||||
|
||||
c.mutex.Lock()
|
||||
v, ok := c.interactions[id]
|
||||
if ok {
|
||||
log.Debugf("attach connector: Removing %s from the connection map", id)
|
||||
delete(c.interactions, id)
|
||||
c.fg.Forget(id)
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
// the !ok case, but let's check the actual condition that impacts us
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn := v.SessionInteractor()
|
||||
if conn != nil {
|
||||
err = conn.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Start starts the connector
|
||||
func (c *Connector) Start() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
c.wg.Add(1)
|
||||
go c.serve()
|
||||
}
|
||||
|
||||
// Stop stops the connector
|
||||
func (c *Connector) Stop() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
c.listener.Close()
|
||||
close(c.done)
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
// Starts the connector listening on the specified source
|
||||
// TODO: should have mechanism for stopping this, and probably handing off the interactions to another
|
||||
// routine to insert into the map
|
||||
func (c *Connector) serve() {
|
||||
defer c.wg.Done()
|
||||
for {
|
||||
if c.listener == nil {
|
||||
log.Debugf("attach connector: listener closed")
|
||||
break
|
||||
}
|
||||
|
||||
// check to see whether we should stop accepting new connections and exit
|
||||
select {
|
||||
case <-c.done:
|
||||
log.Debugf("attach connector: done closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn, err := c.listener.Accept()
|
||||
if err != nil {
|
||||
log.Errorf("Error waiting for incoming connection: %s", errors.ErrorStack(err))
|
||||
continue
|
||||
}
|
||||
log.Debugf("attach connector: Received incoming connection")
|
||||
|
||||
go c.processIncoming(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// takes the base connection, determines the ID of the source and stashes it in the map
|
||||
func (c *Connector) processIncoming(conn net.Conn) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil && conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugf("Initiating ssh handshake with new connection attempt")
|
||||
for {
|
||||
if conn == nil {
|
||||
log.Infof("attach connector: connection closed")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO needs timeout handling. This could take 30s.
|
||||
|
||||
// Timeout for client handshake should be reasonably small.
|
||||
// Server will try to drain a buffer and if the buffer doesn't contain
|
||||
// 2 or more bytes it will just wait, so client should timeout.
|
||||
// However, if timeout is too short, client will flood server with Syn requests.
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
conn.SetReadDeadline(deadline)
|
||||
}
|
||||
if err = serial.HandshakeClient(conn); err == nil {
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
log.Debugf("HandshakeClient: connection handshake established")
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case *serial.HandshakeError:
|
||||
log.Debugf("HandshakeClient: %v", e)
|
||||
continue
|
||||
case *net.OpError:
|
||||
if e.Temporary() || e.Timeout() {
|
||||
// if it's a passing error or timeout then try again
|
||||
continue
|
||||
}
|
||||
// if it's not a temporary condition, then treat it as a transport error
|
||||
log.Errorf("HandshakeClient: transport op-error: %v", e)
|
||||
conn.Close()
|
||||
return
|
||||
default: // includes the io.EOF case
|
||||
// treat everything unknown as transport errror
|
||||
log.Errorf("HandshakeClient: transport error: %v (%T)", e, e)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
callback := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: "daemon",
|
||||
HostKeyCallback: callback,
|
||||
ClientVersion: VersionString,
|
||||
Timeout: ClientTimeout,
|
||||
}
|
||||
|
||||
// create the SSH connection
|
||||
clientConn, chans, reqs, err := ssh.NewClientConn(conn, "", config)
|
||||
if err != nil {
|
||||
log.Errorf("SSH connection could not be established: %s", errors.ErrorStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
// ask the IDs
|
||||
ids, err := ContainerIDs(clientConn)
|
||||
if err != nil {
|
||||
log.Errorf("SSH connection could not be established: %s", errors.ErrorStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Handle global requests
|
||||
go c.reqs(reqs, clientConn, ids)
|
||||
// Handle channel open messages
|
||||
go c.chans(chans)
|
||||
|
||||
// create the connections
|
||||
c.ids(clientConn, ids)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ids iterates over the gived ids and
|
||||
// - calls Ping for existing connections
|
||||
// - calls NewSSHInteraction for new connections and fills the connection map
|
||||
func (c *Connector) ids(conn ssh.Conn, ids []string) {
|
||||
for _, id := range ids {
|
||||
// needed for following closure - https://golang.org/doc/faq#closures_and_goroutines
|
||||
id := id
|
||||
|
||||
c.mutex.RLock()
|
||||
v, ok := c.interactions[id]
|
||||
c.mutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
si, err := v.Initialize()
|
||||
if si != nil && err == nil {
|
||||
if err := si.Ping(); err == nil {
|
||||
log.Debugf("Connection %s found and alive", id)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
log.Warnf("Connection found but it wasn't alive. Creating a new one")
|
||||
}
|
||||
|
||||
// this is a new connection so learn the version
|
||||
version, err := ContainerVersion(conn)
|
||||
if err != nil {
|
||||
log.Errorf("SSH version could not be learned (id=%s): %s", id, errors.ErrorStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
lazy := &LazySessionInteractor{
|
||||
fn: func() (SessionInteractor, error) {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
return NewSSHInteraction(conn, id, version)
|
||||
},
|
||||
}
|
||||
|
||||
log.Infof("Established connection with container VM: %s", id)
|
||||
|
||||
c.mutex.Lock()
|
||||
|
||||
c.interactions[id] = lazy
|
||||
|
||||
c.cond.Broadcast()
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// reqs is the global request channel of the portlayer side of the connection
|
||||
// we keep a list of sessions associated with this connection and drop them from the map when the global mux exits
|
||||
func (c *Connector) reqs(reqs <-chan *ssh.Request, conn ssh.Conn, ids []string) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var pending func()
|
||||
|
||||
// list of session ids mux'ed on this connection
|
||||
droplist := make(map[string]struct{})
|
||||
|
||||
// fill the map with the initial ids
|
||||
for _, id := range ids {
|
||||
droplist[id] = struct{}{}
|
||||
}
|
||||
|
||||
for req := range reqs {
|
||||
ok := true
|
||||
|
||||
log.Infof("received global request type %v", req.Type)
|
||||
switch req.Type {
|
||||
case msgs.ContainersReq:
|
||||
pending = func() {
|
||||
ids := msgs.ContainersMsg{}
|
||||
if err := ids.Unmarshal(req.Payload); err != nil {
|
||||
log.Errorf("Unmarshal failed with %s", err)
|
||||
return
|
||||
}
|
||||
c.ids(conn, ids.IDs)
|
||||
|
||||
// drop the drop list to clear no longer active sessions from the map
|
||||
droplist = make(map[string]struct{})
|
||||
|
||||
// fill the droplist with the latest info
|
||||
for _, id := range ids.IDs {
|
||||
droplist[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
|
||||
// make sure that errors get send back if we failed
|
||||
if req.WantReply {
|
||||
log.Infof("Sending global request reply %t", ok)
|
||||
if err := req.Reply(ok, nil); err != nil {
|
||||
log.Warnf("Failed to reply a request back")
|
||||
}
|
||||
}
|
||||
|
||||
// run any pending work now that a reply has been sent
|
||||
if pending != nil {
|
||||
log.Debug("Invoking pending work for global mux")
|
||||
go pending()
|
||||
pending = nil
|
||||
}
|
||||
}
|
||||
|
||||
// global mux closed so it is time to do cleanup
|
||||
for id := range droplist {
|
||||
log.Infof("Droping %s from connection map", id)
|
||||
c.RemoveInteraction(id)
|
||||
}
|
||||
}
|
||||
|
||||
// this is the channel mux for the ssh channel . It is configured to reject everything (required)
|
||||
func (c *Connector) chans(chans <-chan ssh.NewChannel) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
for ch := range chans {
|
||||
ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
||||
}
|
||||
}
|
||||
253
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/interactor.go
generated
vendored
Normal file
253
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/interactor.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 communication
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/matryer/resync"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/vic/lib/migration/feature"
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
attachChannelType = "attach"
|
||||
)
|
||||
|
||||
// SessionInteractor defines the interaction interface
|
||||
type SessionInteractor interface {
|
||||
// Send specific signal
|
||||
Signal(signal ssh.Signal) error
|
||||
// Stdout stream
|
||||
Stdout() io.Reader
|
||||
// Stderr stream
|
||||
Stderr() io.Reader
|
||||
// Stdin stream
|
||||
Stdin() io.WriteCloser
|
||||
Close() error
|
||||
|
||||
// Resize the terminal
|
||||
Resize(cols, rows, widthpx, heightpx uint32) error
|
||||
|
||||
CloseStdin() error
|
||||
|
||||
Ping() error
|
||||
Unblock() error
|
||||
}
|
||||
|
||||
// interaction implements SessionInteractor using SSH
|
||||
type interaction struct {
|
||||
channel ssh.Channel
|
||||
|
||||
// to serialize unblock requests
|
||||
mu sync.Mutex
|
||||
// avoid spamming unblock messages
|
||||
unblocked resync.Once
|
||||
|
||||
// current feature version that the container provides
|
||||
version uint32
|
||||
}
|
||||
|
||||
// ContainerVersion asks the version of the containers on the other hand and return them to the caller
|
||||
func ContainerVersion(conn ssh.Conn) (uint32, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ok, reply, err := conn.SendRequest(msgs.VersionReq, true, nil)
|
||||
if !ok && err == nil {
|
||||
log.Warnf("VersionReq not supported by the container")
|
||||
return 0, nil
|
||||
}
|
||||
if !ok || err != nil {
|
||||
return 0, fmt.Errorf("failed to get container version from remote: %s", err)
|
||||
}
|
||||
|
||||
version := msgs.VersionMsg{}
|
||||
if err = version.Unmarshal(reply); err != nil {
|
||||
return 0, fmt.Errorf("failed to unmarshal version from remote: %s", err)
|
||||
}
|
||||
|
||||
return version.Version, nil
|
||||
}
|
||||
|
||||
// ContainerIDs asks the ids of the containers on the other hand and return them to the caller
|
||||
func ContainerIDs(conn ssh.Conn) ([]string, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ok, reply, err := conn.SendRequest(msgs.ContainersReq, true, nil)
|
||||
if !ok || err != nil {
|
||||
return nil, fmt.Errorf("failed to get container IDs from remote: %s", err)
|
||||
}
|
||||
|
||||
ids := msgs.ContainersMsg{}
|
||||
if err = ids.Unmarshal(reply); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal ids from remote: %s", err)
|
||||
}
|
||||
|
||||
return ids.IDs, nil
|
||||
}
|
||||
|
||||
// NewSSHInteraction returns a stream connection to the requested session
|
||||
// The ssh conn is assumed to be connected to the Executor hosting the session
|
||||
func NewSSHInteraction(conn ssh.Conn, id string, version uint32) (SessionInteractor, error) {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
channel, _, err := conn.OpenChannel(attachChannelType, []byte(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := &interaction{
|
||||
channel: channel,
|
||||
version: version,
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (t *interaction) Signal(signal ssh.Signal) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
msg := msgs.SignalMsg{Signal: signal}
|
||||
ok, err := t.channel.SendRequest(msgs.SignalReq, true, msg.Marshal())
|
||||
if err == nil && !ok {
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("signal error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *interaction) CloseStdin() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// configure remote to relay EOFs
|
||||
ok, err := t.channel.SendRequest(msgs.CloseStdinReq, true, nil)
|
||||
if err == nil && !ok {
|
||||
return fmt.Errorf("unknown error closing stdin")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("close stdin request error: %s", err)
|
||||
}
|
||||
|
||||
// send inline EOF on the stdin stream
|
||||
err = t.channel.CloseWrite()
|
||||
if err != nil {
|
||||
return fmt.Errorf("close stdin error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *interaction) Stdout() io.Reader {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return t.channel
|
||||
}
|
||||
|
||||
func (t *interaction) Stderr() io.Reader {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return t.channel.Stderr()
|
||||
}
|
||||
|
||||
func (t *interaction) Stdin() io.WriteCloser {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return t.channel
|
||||
}
|
||||
|
||||
func (t *interaction) Close() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return t.channel.Close()
|
||||
}
|
||||
|
||||
// Resize resizes the terminal.
|
||||
func (t *interaction) Resize(cols, rows, widthpx, heightpx uint32) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
msg := msgs.WindowChangeMsg{
|
||||
Columns: cols,
|
||||
Rows: rows,
|
||||
WidthPx: widthpx,
|
||||
HeightPx: heightpx,
|
||||
}
|
||||
ok, err := t.channel.SendRequest(msgs.WindowChangeReq, true, msg.Marshal())
|
||||
if err == nil && !ok {
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("resize error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ping checks the liveliness of the connection
|
||||
func (t *interaction) Ping() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if t.version < feature.ExecSupportedVersion {
|
||||
log.Warnf("Running container does not support Ping request, skipping.")
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := t.channel.SendRequest(msgs.PingReq, true, []byte(msgs.PingMsg))
|
||||
if !ok || err != nil {
|
||||
return fmt.Errorf("failed to ping the other side: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unblock sends an unblock msg
|
||||
func (t *interaction) Unblock() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var ok bool
|
||||
var err error
|
||||
var reset bool
|
||||
|
||||
if t.version < feature.ExecSupportedVersion {
|
||||
log.Warnf("Running container does not support Unblock request, skipping.")
|
||||
return nil
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.unblocked.Do(func() {
|
||||
if ok, err = t.channel.SendRequest(msgs.UnblockReq, true, []byte(msgs.UnblockMsg)); !ok || err != nil {
|
||||
log.Errorf("failed to unblock the other side: %s", err)
|
||||
// #5038: resync package is not reentrant so we need to call Reset after this
|
||||
reset = true
|
||||
}
|
||||
})
|
||||
|
||||
if reset {
|
||||
t.unblocked.Reset()
|
||||
}
|
||||
return err
|
||||
}
|
||||
60
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/lazy.go
generated
vendored
Normal file
60
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/lazy.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 communication
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LazyInitializer defines the function that returns SessionInteractor
|
||||
type LazyInitializer func() (SessionInteractor, error)
|
||||
|
||||
// LazySessionInteractor holds lazily initialized SessionInteractor
|
||||
type LazySessionInteractor struct {
|
||||
mu sync.Mutex
|
||||
si SessionInteractor
|
||||
fn LazyInitializer
|
||||
}
|
||||
|
||||
// Initialize either returns either already initialized connection or returns the connection after initializing it
|
||||
func (l *LazySessionInteractor) Initialize() (SessionInteractor, error) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if l.si != nil {
|
||||
return l.si, nil
|
||||
}
|
||||
|
||||
if l.si == nil && l.fn == nil {
|
||||
panic("both si and fn are nil")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// l.si is nil but l.fn is not
|
||||
l.si, err = l.fn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.si, nil
|
||||
}
|
||||
|
||||
// SessionInteractor returns either an initialized connection, or nil if it was never initialized
|
||||
func (l *LazySessionInteractor) SessionInteractor() SessionInteractor {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
return l.si
|
||||
}
|
||||
55
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/lazy_test.go
generated
vendored
Normal file
55
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/lazy_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 communication
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLazySessionInteractor_Initialize(t *testing.T) {
|
||||
type fields struct {
|
||||
si SessionInteractor
|
||||
fn LazyInitializer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want SessionInteractor
|
||||
wantErr bool
|
||||
}{
|
||||
{"FnIsNil", fields{si: &interaction{}}, &interaction{}, false},
|
||||
{"SiIsNil", fields{si: nil, fn: func() (SessionInteractor, error) { return &interaction{}, nil }}, &interaction{}, false},
|
||||
{"FnAndSIAreNotNil", fields{si: &interaction{}, fn: func() (SessionInteractor, error) { return nil, fmt.Errorf("failure") }}, &interaction{}, false},
|
||||
{"SiIsNilFnWillFail", fields{si: nil, fn: func() (SessionInteractor, error) { return nil, fmt.Errorf("failure") }}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LazySessionInteractor{
|
||||
si: tt.fields.si,
|
||||
fn: tt.fields.fn,
|
||||
}
|
||||
got, err := l.Initialize()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LazySessionInteractor.Initialize() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("LazySessionInteractor.Initialize() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
120
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/server.go
generated
vendored
Normal file
120
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/server.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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 communication
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Server waits for TCP client connections on serialOverLANPort, then
|
||||
// once connected, attempts to negotiate an SSH connection to the attached
|
||||
// client. The client is the ssh server.
|
||||
type Server struct {
|
||||
port int
|
||||
ip string
|
||||
|
||||
m sync.RWMutex
|
||||
l *net.TCPListener
|
||||
c *Connector
|
||||
}
|
||||
|
||||
// NewServer returns a Server instance
|
||||
func NewServer(ip string, port int) *Server {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
return &Server{
|
||||
ip: ip,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the connector with given listener
|
||||
func (n *Server) Start() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
n.m.Lock()
|
||||
defer n.m.Unlock()
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", n.ip, n.port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Attach server error %s:%d: %s", n.ip, n.port, errors.ErrorStack(err))
|
||||
}
|
||||
|
||||
n.l, err = net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Attach server error %s: %s", addr, errors.ErrorStack(err))
|
||||
}
|
||||
|
||||
log.Infof("Attach server listening on %s:%d", n.ip, n.port)
|
||||
|
||||
// starts serving requests immediately
|
||||
n.c = NewConnector(n.l)
|
||||
n.c.Start()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the connector
|
||||
func (n *Server) Stop() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
n.m.Lock()
|
||||
defer n.m.Unlock()
|
||||
|
||||
err := n.l.Close()
|
||||
n.c.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Addr returns the address of the underlying listener
|
||||
func (n *Server) Addr() string {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
n.m.RLock()
|
||||
defer n.m.RUnlock()
|
||||
|
||||
return n.l.Addr().String()
|
||||
}
|
||||
|
||||
// Interaction returns the session interface for the given container. If the container
|
||||
// cannot be found, this call will wait for the given timeout.
|
||||
// id is ID of the container.
|
||||
func (n *Server) Interaction(ctx context.Context, id string) (SessionInteractor, error) {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
n.m.RLock()
|
||||
defer n.m.RUnlock()
|
||||
|
||||
return n.c.Interaction(ctx, id)
|
||||
}
|
||||
|
||||
// RemoveInteraction removes the session interface from underlying connector
|
||||
func (n *Server) RemoveInteraction(id string) error {
|
||||
defer trace.End(trace.Begin(id))
|
||||
|
||||
n.m.Lock()
|
||||
defer n.m.Unlock()
|
||||
|
||||
return n.c.RemoveInteraction(id)
|
||||
}
|
||||
185
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/server_test.go
generated
vendored
Normal file
185
vendor/github.com/vmware/vic/lib/portlayer/attach/communication/server_test.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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 communication
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/migration/feature"
|
||||
"github.com/vmware/vic/lib/tether/msgs"
|
||||
"github.com/vmware/vic/pkg/serial"
|
||||
)
|
||||
|
||||
// Start the server, make 200 client connections, test they connect, then Stop.
|
||||
func TestAttachStartStop(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
s := NewServer("localhost", 0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
dial := func() {
|
||||
defer wg.Done()
|
||||
|
||||
c, err := net.Dial("tcp", s.l.Addr().String())
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, c) {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
c.SetReadDeadline(time.Now().Add(time.Second))
|
||||
c.Read(buf)
|
||||
|
||||
// This will pass if the client has written a second syn packet by the time it's called. As such we set an
|
||||
// unbounded readdeadline on the connection.
|
||||
// We can assert behaviours that take a while, but cannot reliably assert behaviours that require fast scheduling
|
||||
// of lots of threads on all systems running the CI.
|
||||
c.SetReadDeadline(time.Time{})
|
||||
if !assert.NoError(t, serial.HandshakeServer(c), "Expected handshake to succeed on 2nd syn packet from client") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
assert.NoError(t, s.Start())
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go dial()
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fail()
|
||||
}
|
||||
assert.NoError(t, s.Stop())
|
||||
|
||||
_, err := net.Dial("tcp", s.Addr())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAttachSshSession(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
s := NewServer("localhost", 0)
|
||||
assert.NoError(t, s.Start())
|
||||
defer s.Stop()
|
||||
|
||||
expectedID := "foo"
|
||||
|
||||
// This should block until the ssh server returns its container ID
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.c.Interaction(ctx, expectedID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Dial the attach server. This is a TCP client
|
||||
networkClientCon, err := net.Dial("tcp", s.Addr())
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, serial.HandshakeServer(networkClientCon)) {
|
||||
return
|
||||
}
|
||||
|
||||
containerConfig := &ssh.ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(testdata.PEMBytes["dsa"])
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
containerConfig.AddHostKey(signer)
|
||||
|
||||
// create the SSH server on the client. The attach server will ssh connect to this.
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(networkClientCon, containerConfig)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer sshConn.Close()
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
exit := 0
|
||||
for req := range reqs {
|
||||
if req.Type == msgs.ContainersReq {
|
||||
msg := msgs.ContainersMsg{IDs: []string{expectedID}}
|
||||
req.Reply(true, msg.Marshal())
|
||||
exit++
|
||||
}
|
||||
if req.Type == msgs.VersionReq {
|
||||
msg := msgs.VersionMsg{Version: feature.MaxPluginVersion - 1}
|
||||
req.Reply(true, msg.Marshal())
|
||||
exit++
|
||||
}
|
||||
if exit == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for ch := range chans {
|
||||
assert.Equal(t, ch.ChannelType(), attachChannelType)
|
||||
_, reqs, _ = ch.Accept()
|
||||
for req := range reqs {
|
||||
if req.Type == msgs.UnblockReq {
|
||||
req.Reply(true, nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
61
vendor/github.com/vmware/vic/lib/portlayer/attach/join.go
generated
vendored
Normal file
61
vendor/github.com/vmware/vic/lib/portlayer/attach/join.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 attach
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Join adds network backed serial port to the caller and configures them
|
||||
func Join(h interface{}) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
|
||||
// Tether serial port - backed by network
|
||||
serial := &types.VirtualSerialPort{
|
||||
VirtualDevice: types.VirtualDevice{
|
||||
Backing: &types.VirtualSerialPortURIBackingInfo{
|
||||
VirtualDeviceURIBackingInfo: types.VirtualDeviceURIBackingInfo{
|
||||
Direction: string(types.VirtualDeviceURIBackingOptionDirectionClient),
|
||||
ProxyURI: fmt.Sprintf("telnet://0.0.0.0:%d", constants.SerialOverLANPort),
|
||||
// Set it to 0.0.0.0 during Join call, VCH IP will be set when we call Bind
|
||||
ServiceURI: fmt.Sprintf("tcp://127.0.0.1:%d", constants.AttachServerPort),
|
||||
},
|
||||
},
|
||||
Connectable: &types.VirtualDeviceConnectInfo{
|
||||
Connected: false,
|
||||
StartConnected: false,
|
||||
AllowGuestControl: true,
|
||||
},
|
||||
},
|
||||
YieldOnPoll: true,
|
||||
}
|
||||
config := &types.VirtualDeviceConfigSpec{
|
||||
Device: serial,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
}
|
||||
handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config)
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
36
vendor/github.com/vmware/vic/lib/portlayer/attach/unbind.go
generated
vendored
Normal file
36
vendor/github.com/vmware/vic/lib/portlayer/attach/unbind.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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 attach
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Unbind unsets the *Connected fields of the VirtualSerialPort
|
||||
func Unbind(h interface{}, id string) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
if handle.MigrationError != nil {
|
||||
return nil, fmt.Errorf("Migration failed %s", handle.MigrationError)
|
||||
}
|
||||
return toggle(handle, id, false)
|
||||
}
|
||||
40
vendor/github.com/vmware/vic/lib/portlayer/event/collector/collector.go
generated
vendored
Normal file
40
vendor/github.com/vmware/vic/lib/portlayer/event/collector/collector.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 collector
|
||||
|
||||
import (
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
type Collector interface {
|
||||
|
||||
// AddMonitoredObject will add the object for event listening
|
||||
AddMonitoredObject(ref string) error
|
||||
|
||||
// RemoveMonitoredObject will remove the object from event listening
|
||||
RemoveMonitoredObject(ref string)
|
||||
|
||||
// Start listening for events and publish to function
|
||||
Start() error
|
||||
|
||||
// Stop listening for events
|
||||
Stop()
|
||||
|
||||
// Register a callback function
|
||||
Register(func(events.Event))
|
||||
|
||||
// Name returns the collector name
|
||||
Name() string
|
||||
}
|
||||
218
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/collector.go
generated
vendored
Normal file
218
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/collector.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
|
||||
vmwEvents "github.com/vmware/govmomi/event"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "vSphere Event Collector"
|
||||
)
|
||||
|
||||
type EventCollector struct {
|
||||
vmwManager *vmwEvents.Manager
|
||||
mos monitoredCache
|
||||
callback func(events.Event)
|
||||
|
||||
lastProcessedID int32
|
||||
}
|
||||
|
||||
type monitoredCache struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
mos map[string]types.ManagedObjectReference
|
||||
}
|
||||
|
||||
func NewCollector(client *vim25.Client, objects ...string) *EventCollector {
|
||||
ec := &EventCollector{
|
||||
vmwManager: vmwEvents.NewManager(client),
|
||||
mos: monitoredCache{mos: make(map[string]types.ManagedObjectReference)},
|
||||
// initialize to an index that will not be present in a page
|
||||
lastProcessedID: -1,
|
||||
}
|
||||
|
||||
for i := range objects {
|
||||
// #nosec: Errors unhandled.
|
||||
ec.AddMonitoredObject(objects[i])
|
||||
}
|
||||
|
||||
return ec
|
||||
}
|
||||
|
||||
func (ec *EventCollector) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
// Register an event manager callback with the collector
|
||||
func (ec *EventCollector) Register(callback func(events.Event)) {
|
||||
ec.callback = callback
|
||||
}
|
||||
|
||||
func (ec *EventCollector) AddMonitoredObject(ref string) error {
|
||||
ec.mos.mu.Lock()
|
||||
defer ec.mos.mu.Unlock()
|
||||
|
||||
moRef := types.ManagedObjectReference{}
|
||||
if !moRef.FromString(ref) {
|
||||
return fmt.Errorf("%s received an invalid Object to monitor(%s)", name, ref)
|
||||
}
|
||||
ec.mos.mos[ref] = moRef
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ec *EventCollector) RemoveMonitoredObject(ref string) {
|
||||
ec.mos.mu.Lock()
|
||||
defer ec.mos.mu.Unlock()
|
||||
|
||||
delete(ec.mos.mos, ref)
|
||||
}
|
||||
|
||||
func (ec *EventCollector) monitoredObjects() []types.ManagedObjectReference {
|
||||
ec.mos.mu.RLock()
|
||||
defer ec.mos.mu.RUnlock()
|
||||
|
||||
refs := make([]types.ManagedObjectReference, 0, len(ec.mos.mos))
|
||||
for k := range ec.mos.mos {
|
||||
refs = append(refs, ec.mos.mos[k])
|
||||
}
|
||||
return refs
|
||||
}
|
||||
func (ec *EventCollector) Stop() {
|
||||
_, err := ec.vmwManager.Destroy(context.Background())
|
||||
if err != nil {
|
||||
log.Warnf("%s failed to destroy the govmomi manager: %s", name, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// eventTypes is used to filter the event collector so we only receive these event types.
|
||||
var eventTypes []string
|
||||
|
||||
func init() {
|
||||
events := []types.BaseEvent{
|
||||
(*types.VmGuestShutdownEvent)(nil),
|
||||
(*types.VmPoweredOnEvent)(nil),
|
||||
(*types.DrsVmPoweredOnEvent)(nil),
|
||||
(*types.VmPoweredOffEvent)(nil),
|
||||
(*types.VmRemovedEvent)(nil),
|
||||
(*types.VmSuspendedEvent)(nil),
|
||||
(*types.VmMigratedEvent)(nil),
|
||||
(*types.DrsVmMigratedEvent)(nil),
|
||||
(*types.VmRelocatedEvent)(nil),
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
eventTypes = append(eventTypes, reflect.TypeOf(event).Elem().Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Start the event collector
|
||||
func (ec *EventCollector) Start() error {
|
||||
// array of managed objects
|
||||
refs := ec.monitoredObjects()
|
||||
|
||||
// only continue if we have object to monitor
|
||||
if len(refs) == 0 {
|
||||
return fmt.Errorf("%s requires at least one Monitored Object: objects[%d]", name, 0)
|
||||
}
|
||||
|
||||
log.Debugf("%s starting collection for %d managed objects", name, len(refs))
|
||||
|
||||
// we don't want the event listener to timeout
|
||||
ctx := context.Background()
|
||||
|
||||
// pageSize is the number of events on the last page of the eventCollector
|
||||
// as new events are added the oldest are removed. Originally this value
|
||||
// was 1 and we encountered missed events due to them being evicted
|
||||
// before being processed. We bumped to 25 but we still miss events during
|
||||
// storms such as a host HA event.
|
||||
// Setting pageSize to 1000 overwhelmed hostd via the task history and caused
|
||||
// memory exhaustion. Setting pagesize to 200 while filtering for the specific
|
||||
// types we require showed directly comparable memory overhead vs the 25 page
|
||||
// size setting when running full ci. We may still have significantly higher
|
||||
// memory usage in the scenario where we legitimately have events of interest
|
||||
// at a rate of greater than 25 per page.
|
||||
// This should eventually be replaced with a smaller maximum page size, a page
|
||||
// cursor, and maybe a sliding window for the actual page size.
|
||||
pageSize := int32(200)
|
||||
// bool to follow the stream
|
||||
followStream := true
|
||||
// don't exceed the govmomi object limit
|
||||
force := false
|
||||
|
||||
//TODO: need a proper way to handle failures / status
|
||||
go func(pageSize int32, follow bool, ff bool, refs []types.ManagedObjectReference, ec *EventCollector) error {
|
||||
// the govmomi event listener can only be configured once per session -- so if it's already listening it
|
||||
// will be replaced
|
||||
//
|
||||
// the manager will be closed with the session
|
||||
|
||||
for {
|
||||
err := ec.vmwManager.Events(ctx, refs, pageSize, followStream, force, func(_ types.ManagedObjectReference, page []types.BaseEvent) error {
|
||||
evented(ec, page)
|
||||
return nil
|
||||
}, eventTypes...)
|
||||
// TODO: this will disappear in the ether
|
||||
if err != nil {
|
||||
log.Debugf("Error configuring %s: %s", name, err.Error())
|
||||
}
|
||||
}
|
||||
}(pageSize, followStream, force, refs, ec)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// evented will process the event and execute the registered callback
|
||||
//
|
||||
// Initial implmentation will only act on certain events -- future implementations
|
||||
// may provide more flexibility
|
||||
func evented(ec *EventCollector, page []types.BaseEvent) {
|
||||
if ec.callback == nil {
|
||||
log.Warn("No callback defined for EventManager")
|
||||
return
|
||||
}
|
||||
|
||||
if len(page) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// skip events already seen
|
||||
oldIndex := len(page)
|
||||
for i := range page {
|
||||
if page[i].GetEvent().Key == ec.lastProcessedID {
|
||||
oldIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
// events appear in page with most recent first - need to reverse for sane ordering
|
||||
// we start from the first new event after the last one processed
|
||||
for i := oldIndex - 1; i >= 0; i-- {
|
||||
ec.callback(NewVMEvent(page[i]))
|
||||
|
||||
ec.lastProcessedID = page[i].GetEvent().Key
|
||||
}
|
||||
|
||||
}
|
||||
143
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/collector_test.go
generated
vendored
Normal file
143
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/collector_test.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
LifeCycle = iota
|
||||
Reconfigure
|
||||
Mixed
|
||||
)
|
||||
|
||||
// used to test callbacks
|
||||
var callcount int
|
||||
|
||||
func newVMMO() *types.ManagedObjectReference {
|
||||
return &types.ManagedObjectReference{Value: "101", Type: "vm"}
|
||||
}
|
||||
|
||||
func TestMonitoredObject(t *testing.T) {
|
||||
|
||||
mgr := newCollector()
|
||||
mo := newVMMO()
|
||||
|
||||
mgr.AddMonitoredObject(mo.String())
|
||||
mos := mgr.monitoredObjects()
|
||||
assert.Equal(t, 1, len(mos))
|
||||
mgr.RemoveMonitoredObject(mo.String())
|
||||
mos = mgr.monitoredObjects()
|
||||
assert.Equal(t, 0, len(mos))
|
||||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
mgr := newCollector()
|
||||
|
||||
mgr.Register(callMe)
|
||||
assert.NotNil(t, mgr.callback)
|
||||
|
||||
}
|
||||
|
||||
func TestEvented(t *testing.T) {
|
||||
mgr := newCollector()
|
||||
callcount = 0
|
||||
|
||||
// register local callback
|
||||
mgr.Register(callMe)
|
||||
|
||||
// test lifecycle events
|
||||
page := eventPage(3, LifeCycle)
|
||||
evented(mgr, page)
|
||||
assert.Equal(t, 3, callcount)
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
mgr := newCollector()
|
||||
assert.NotNil(t, mgr.Name())
|
||||
assert.Equal(t, name, mgr.Name())
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
mgr := newCollector()
|
||||
// start should fail as no objects registered
|
||||
assert.Error(t, mgr.Start())
|
||||
}
|
||||
|
||||
func TestEventTypes(t *testing.T) {
|
||||
if len(eventTypes) != 9 {
|
||||
t.Fatalf("eventTypes=%d", len(eventTypes))
|
||||
}
|
||||
|
||||
f := types.TypeFunc()
|
||||
|
||||
for _, name := range eventTypes {
|
||||
_, ok := f(name)
|
||||
if !ok {
|
||||
t.Errorf("unknown event type: %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newCollector() *EventCollector {
|
||||
return &EventCollector{mos: monitoredCache{mos: make(map[string]types.ManagedObjectReference)}, lastProcessedID: -1}
|
||||
}
|
||||
|
||||
// simple callback counter
|
||||
func callMe(vm events.Event) {
|
||||
callcount++
|
||||
}
|
||||
|
||||
// utility function to mock a vsphere event
|
||||
//
|
||||
// size is the number of events to create
|
||||
// lifeCycle is true when we want to generate state events
|
||||
// lifeCycle events == poweredOn, poweredOff, etc..
|
||||
|
||||
func eventPage(size int, eventType int) []types.BaseEvent {
|
||||
page := make([]types.BaseEvent, 0, size)
|
||||
moid := 100
|
||||
for i := 0; i < size; i++ {
|
||||
var eve types.BaseEvent
|
||||
var eType int
|
||||
moid++
|
||||
vm := types.ManagedObjectReference{Value: strconv.Itoa(moid), Type: "vm"}
|
||||
eType = eventType
|
||||
if eType == Mixed {
|
||||
if i%2 == 0 {
|
||||
eType = LifeCycle
|
||||
} else {
|
||||
eType = Reconfigure
|
||||
}
|
||||
}
|
||||
if eType == LifeCycle {
|
||||
eve = types.BaseEvent(&types.VmPoweredOnEvent{VmEvent: types.VmEvent{Event: types.Event{Vm: &types.VmEventArgument{Vm: vm}}}})
|
||||
} else {
|
||||
eve = types.BaseEvent(&types.VmReconfiguredEvent{VmEvent: types.VmEvent{Event: types.Event{Vm: &types.VmEventArgument{Vm: vm}}}})
|
||||
}
|
||||
|
||||
page = append(page, eve)
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
71
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/vm_event.go
generated
vendored
Normal file
71
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/vm_event.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 vsphere
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
type VMEvent struct {
|
||||
*events.BaseEvent
|
||||
}
|
||||
|
||||
func NewVMEvent(be types.BaseEvent) *VMEvent {
|
||||
var ee string
|
||||
// vm events that we care about
|
||||
switch be.(type) {
|
||||
case *types.VmPoweredOnEvent,
|
||||
*types.DrsVmPoweredOnEvent:
|
||||
ee = events.ContainerPoweredOn
|
||||
case *types.VmPoweredOffEvent:
|
||||
ee = events.ContainerPoweredOff
|
||||
case *types.VmSuspendedEvent:
|
||||
ee = events.ContainerSuspended
|
||||
case *types.VmRemovedEvent:
|
||||
ee = events.ContainerRemoved
|
||||
case *types.VmGuestShutdownEvent:
|
||||
ee = events.ContainerShutdown
|
||||
case *types.VmMigratedEvent:
|
||||
ee = events.ContainerMigrated
|
||||
case *types.DrsVmMigratedEvent:
|
||||
ee = events.ContainerMigratedByDrs
|
||||
case *types.VmRelocatedEvent:
|
||||
ee = events.ContainerRelocated
|
||||
default:
|
||||
panic("Unknown event")
|
||||
}
|
||||
e := be.GetEvent()
|
||||
return &VMEvent{
|
||||
&events.BaseEvent{
|
||||
Event: ee,
|
||||
ID: strconv.Itoa(int(e.Key)),
|
||||
Detail: e.FullFormattedMessage,
|
||||
Ref: e.Vm.Vm.String(),
|
||||
CreatedTime: e.CreatedTime,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (vme *VMEvent) Topic() string {
|
||||
if vme.Type == "" {
|
||||
vme.Type = events.NewEventType(vme)
|
||||
}
|
||||
return vme.Type.Topic()
|
||||
}
|
||||
43
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/vm_event_test.go
generated
vendored
Normal file
43
vendor/github.com/vmware/vic/lib/portlayer/event/collector/vsphere/vm_event_test.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 vsphere
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewEvent(t *testing.T) {
|
||||
vm := newVMMO()
|
||||
k := 1
|
||||
msg := "jojo the idiot circus boy"
|
||||
tt := time.Now().UTC()
|
||||
vmwEve := &types.VmPoweredOnEvent{VmEvent: types.VmEvent{Event: types.Event{CreatedTime: tt, FullFormattedMessage: msg, Key: int32(k), Vm: &types.VmEventArgument{Vm: *vm}}}}
|
||||
vme := NewVMEvent(vmwEve)
|
||||
assert.NotNil(t, vme)
|
||||
assert.Equal(t, events.ContainerPoweredOn, vme.String())
|
||||
assert.Equal(t, vm.String(), vme.Reference())
|
||||
assert.Equal(t, strconv.Itoa(k), vme.EventID())
|
||||
assert.Equal(t, msg, vme.Message())
|
||||
assert.Equal(t, "vsphere.VMEvent", vme.Topic())
|
||||
assert.Equal(t, tt, vme.Created())
|
||||
|
||||
}
|
||||
39
vendor/github.com/vmware/vic/lib/portlayer/event/doc.go
generated
vendored
Normal file
39
vendor/github.com/vmware/vic/lib/portlayer/event/doc.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 event manages events via a simple pub / sub mechanism. Events could be created
|
||||
by vic components or registered Collectors.
|
||||
|
||||
Basic Overview
|
||||
|
||||
The Event Manager provides basic pub / sub functionality. A subscription consists of a
|
||||
topic (any defined Event), a subscription name (string) and a callback function. When an event is
|
||||
published the event manager will determine the event type and check to see if any components
|
||||
have registered a callback for that event type. For all subscriptions the event manager will
|
||||
facilitate the callback. Publication of events can be accomplished by any component that has
|
||||
a pointer to the event manager or via the registered collectors.
|
||||
|
||||
Collectors are responsible for collecting events or data from external systems and then publishing
|
||||
relevant vic events to the event manager. In theory the collector could monitor anything and when
|
||||
certain criteria are meet publish vic events to the manager. Collectors are registered with the
|
||||
event manager which instructs the collector where to publish. Multiple collectors are allowed per
|
||||
event manager, but each collector has a single publish target.
|
||||
|
||||
An example of a collector is the vSphere Event Collector which uses the vSphere EventHistoryCollector
|
||||
to monitor the vSphere event stream and publish relevant events to vic. In the initial implementation
|
||||
the vSphere Event Collector is focused on a subset of VM Events that are then transformed to vic Events
|
||||
and published to the event manager.
|
||||
*/
|
||||
package event
|
||||
45
vendor/github.com/vmware/vic/lib/portlayer/event/event_manager.go
generated
vendored
Normal file
45
vendor/github.com/vmware/vic/lib/portlayer/event/event_manager.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 event
|
||||
|
||||
import (
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
// EventManager will provide a basic event pub/sub implementation
|
||||
type EventManager interface {
|
||||
|
||||
// RegisterCollector a collector with the manager
|
||||
RegisterCollector(collector.Collector)
|
||||
|
||||
// Collectors returns registered collectors
|
||||
Collectors() map[string]collector.Collector
|
||||
|
||||
// Subscribe for event callbacks
|
||||
Subscribe(eventTopic string, caller string, callback func(events.Event)) Subscriber
|
||||
|
||||
// Unsubscribe from event callbacks
|
||||
Unsubscribe(eventTopic string, caller string)
|
||||
|
||||
// Subscribers will return the subscriber map
|
||||
Subscribers() map[string]map[string]Subscriber
|
||||
|
||||
// Subscribed returns subscriber count
|
||||
Subscribed() int
|
||||
|
||||
// Publish the event to the subscribers
|
||||
Publish(e events.Event)
|
||||
}
|
||||
68
vendor/github.com/vmware/vic/lib/portlayer/event/events/base_event.go
generated
vendored
Normal file
68
vendor/github.com/vmware/vic/lib/portlayer/event/events/base_event.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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 events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EventType string
|
||||
|
||||
type BaseEvent struct {
|
||||
Type EventType
|
||||
Event string
|
||||
ID string
|
||||
Detail string
|
||||
Ref string
|
||||
CreatedTime time.Time
|
||||
}
|
||||
|
||||
func (be *BaseEvent) EventID() string {
|
||||
return be.ID
|
||||
}
|
||||
|
||||
// return event type / description
|
||||
func (be *BaseEvent) String() string {
|
||||
return be.Event
|
||||
}
|
||||
|
||||
func (be *BaseEvent) Message() string {
|
||||
return be.Detail
|
||||
}
|
||||
|
||||
func (be *BaseEvent) Reference() string {
|
||||
return be.Ref
|
||||
}
|
||||
|
||||
func (be *BaseEvent) Created() time.Time {
|
||||
return be.CreatedTime
|
||||
}
|
||||
|
||||
// NewEventType utility function that uses reflection to return
|
||||
// the event type
|
||||
func NewEventType(kind interface{}) EventType {
|
||||
t := reflect.TypeOf(kind)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return EventType(fmt.Sprintf("%s.%s", path.Base(t.PkgPath()), t.Name()))
|
||||
}
|
||||
|
||||
func (t EventType) Topic() string {
|
||||
return string(t)
|
||||
}
|
||||
43
vendor/github.com/vmware/vic/lib/portlayer/event/events/base_event_test.go
generated
vendored
Normal file
43
vendor/github.com/vmware/vic/lib/portlayer/event/events/base_event_test.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 events
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewEventType(t *testing.T) {
|
||||
topic := NewEventType(BaseEvent{})
|
||||
assert.Contains(t, topic.Topic(), "events", ".", "BaseEvent")
|
||||
topic = NewEventType(&BaseEvent{})
|
||||
assert.Contains(t, topic.Topic(), "events", ".", "BaseEvent")
|
||||
}
|
||||
|
||||
func TestBaseEvent(t *testing.T) {
|
||||
be := &BaseEvent{
|
||||
Event: "PoweredOn",
|
||||
ID: "12",
|
||||
Detail: "VM 123 PoweredOn",
|
||||
Ref: "vm:12",
|
||||
}
|
||||
|
||||
assert.Equal(t, "PoweredOn", be.String())
|
||||
assert.Equal(t, "12", be.EventID())
|
||||
assert.Equal(t, "VM 123 PoweredOn", be.Message())
|
||||
assert.Equal(t, "vm:12", be.Reference())
|
||||
|
||||
}
|
||||
43
vendor/github.com/vmware/vic/lib/portlayer/event/events/container_event.go
generated
vendored
Normal file
43
vendor/github.com/vmware/vic/lib/portlayer/event/events/container_event.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 events
|
||||
|
||||
const (
|
||||
ContainerCreated = "Created"
|
||||
ContainerFailed = "Failed"
|
||||
ContainerMigrated = "Migrated"
|
||||
ContainerMigratedByDrs = "MigratedByDrs"
|
||||
ContainerPoweredOff = "PoweredOff"
|
||||
ContainerPoweredOn = "PoweredOn"
|
||||
ContainerReconfigured = "Reconfigured"
|
||||
ContainerRelocated = "Relocated"
|
||||
ContainerRemoved = "Removed"
|
||||
ContainerResumed = "Resumed"
|
||||
ContainerShutdown = "Shutdown"
|
||||
ContainerStarted = "Started"
|
||||
ContainerStopped = "Stopped"
|
||||
ContainerSuspended = "Suspended"
|
||||
)
|
||||
|
||||
type ContainerEvent struct {
|
||||
*BaseEvent
|
||||
}
|
||||
|
||||
func (ce *ContainerEvent) Topic() string {
|
||||
if ce.Type == "" {
|
||||
ce.Type = NewEventType(ce)
|
||||
}
|
||||
return ce.Type.Topic()
|
||||
}
|
||||
37
vendor/github.com/vmware/vic/lib/portlayer/event/events/events.go
generated
vendored
Normal file
37
vendor/github.com/vmware/vic/lib/portlayer/event/events/events.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 events
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Event interface {
|
||||
EventTopic
|
||||
// id of event
|
||||
EventID() string
|
||||
// event (PowerOn, PowerOff, etc)
|
||||
String() string
|
||||
// reference evented object
|
||||
Reference() string
|
||||
// event message
|
||||
Message() string
|
||||
|
||||
Created() time.Time
|
||||
}
|
||||
|
||||
type EventTopic interface {
|
||||
Topic() string
|
||||
}
|
||||
61
vendor/github.com/vmware/vic/lib/portlayer/event/events/vsphere/state_event.go
generated
vendored
Normal file
61
vendor/github.com/vmware/vic/lib/portlayer/event/events/vsphere/state_event.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2018 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 vsphere
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type StateEvent struct {
|
||||
*events.BaseEvent
|
||||
}
|
||||
|
||||
func NewStateEvent(op trace.Operation, state types.VirtualMachinePowerState, ref types.ManagedObjectReference) *StateEvent {
|
||||
var ee string
|
||||
// vm power states that we care about
|
||||
switch state {
|
||||
case types.VirtualMachinePowerStatePoweredOn:
|
||||
ee = events.ContainerPoweredOn
|
||||
case types.VirtualMachinePowerStatePoweredOff:
|
||||
ee = events.ContainerPoweredOff
|
||||
case types.VirtualMachinePowerStateSuspended:
|
||||
ee = events.ContainerSuspended
|
||||
default:
|
||||
panic("Unknown event")
|
||||
}
|
||||
|
||||
return &StateEvent{
|
||||
&events.BaseEvent{
|
||||
Event: ee,
|
||||
ID: op.ID(),
|
||||
Detail: "Created from power state " + string(state),
|
||||
Ref: ref.String(),
|
||||
CreatedTime: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (se *StateEvent) Topic() string {
|
||||
if se.Type == "" {
|
||||
se.Type = events.NewEventType(se)
|
||||
}
|
||||
return se.Type.Topic()
|
||||
}
|
||||
163
vendor/github.com/vmware/vic/lib/portlayer/event/manager.go
generated
vendored
Normal file
163
vendor/github.com/vmware/vic/lib/portlayer/event/manager.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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 event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
cos collectorCache
|
||||
subs subscriberCache
|
||||
eventQ chan events.Event
|
||||
}
|
||||
|
||||
const eventQSize = 1000
|
||||
|
||||
type collectorCache struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
collectors map[string]collector.Collector
|
||||
}
|
||||
|
||||
type subscriberCache struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
subscribers map[string]map[string]Subscriber
|
||||
}
|
||||
|
||||
func NewEventManager(collectors ...collector.Collector) *Manager {
|
||||
mgr := &Manager{
|
||||
cos: collectorCache{
|
||||
collectors: make(map[string]collector.Collector),
|
||||
},
|
||||
subs: subscriberCache{
|
||||
subscribers: make(map[string]map[string]Subscriber),
|
||||
},
|
||||
eventQ: make(chan events.Event, eventQSize),
|
||||
}
|
||||
|
||||
// register any collectors provided
|
||||
for i := range collectors {
|
||||
mgr.RegisterCollector(collectors[i])
|
||||
}
|
||||
|
||||
// event processor routine
|
||||
go func() {
|
||||
for e := range mgr.eventQ {
|
||||
// subscribers for this event
|
||||
mgr.subs.mu.RLock()
|
||||
subs := mgr.subs.subscribers[e.Topic()]
|
||||
mgr.subs.mu.RUnlock()
|
||||
|
||||
log.Debugf("Found %d subscribers to %s: %s - %s", len(subs), e.EventID(), e.Topic(), e.Message())
|
||||
|
||||
for sub, s := range subs {
|
||||
log.Debugf("Event manager calling back to %s for Event(%s): %s", sub, e.EventID(), e.Topic())
|
||||
s.onEvent(e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (mgr *Manager) RegisterCollector(collector collector.Collector) {
|
||||
if collector == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mgr.cos.mu.Lock()
|
||||
defer mgr.cos.mu.Unlock()
|
||||
|
||||
collector.Register(mgr.Publish)
|
||||
|
||||
mgr.cos.collectors[collector.Name()] = collector
|
||||
}
|
||||
|
||||
func (mgr *Manager) Collectors() map[string]collector.Collector {
|
||||
mgr.cos.mu.RLock()
|
||||
defer mgr.cos.mu.RUnlock()
|
||||
|
||||
c := make(map[string]collector.Collector)
|
||||
for name, collector := range mgr.cos.collectors {
|
||||
c[name] = collector
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Subscribe to the event manager for callback
|
||||
func (mgr *Manager) Subscribe(eventTopic string, caller string, callback func(events.Event)) Subscriber {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("%s:%s", eventTopic, caller)))
|
||||
mgr.subs.mu.Lock()
|
||||
defer mgr.subs.mu.Unlock()
|
||||
|
||||
if _, ok := mgr.subs.subscribers[eventTopic]; !ok {
|
||||
mgr.subs.subscribers[eventTopic] = make(map[string]Subscriber)
|
||||
}
|
||||
s := newSubscriber(eventTopic, caller, callback)
|
||||
mgr.subs.subscribers[eventTopic][caller] = s
|
||||
return s
|
||||
}
|
||||
|
||||
// Unsubscribe from callbacks
|
||||
func (mgr *Manager) Unsubscribe(eventTopic string, caller string) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("%s:%s", eventTopic, caller)))
|
||||
mgr.subs.mu.Lock()
|
||||
defer mgr.subs.mu.Unlock()
|
||||
if _, ok := mgr.subs.subscribers[eventTopic]; ok {
|
||||
delete(mgr.subs.subscribers[eventTopic], caller)
|
||||
}
|
||||
}
|
||||
|
||||
func (mgr *Manager) Subscribers() map[string]map[string]Subscriber {
|
||||
mgr.subs.mu.RLock()
|
||||
defer mgr.subs.mu.RUnlock()
|
||||
s := make(map[string]map[string]Subscriber)
|
||||
for i, m := range mgr.subs.subscribers {
|
||||
|
||||
if _, ok := s[i]; !ok {
|
||||
s[i] = make(map[string]Subscriber)
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
s[i][k] = v
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// RegistryCount returns the callback count
|
||||
func (mgr *Manager) Subscribed() int {
|
||||
mgr.subs.mu.RLock()
|
||||
defer mgr.subs.mu.RUnlock()
|
||||
count := 0
|
||||
for _, m := range mgr.subs.subscribers {
|
||||
count += len(m)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Publish events to subscribers
|
||||
func (mgr *Manager) Publish(e events.Event) {
|
||||
mgr.eventQ <- e
|
||||
}
|
||||
98
vendor/github.com/vmware/vic/lib/portlayer/event/manager_test.go
generated
vendored
Normal file
98
vendor/github.com/vmware/vic/lib/portlayer/event/manager_test.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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 event
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
mgr := NewEventManager()
|
||||
assert.NotNil(t, mgr)
|
||||
}
|
||||
|
||||
func TestTopic(t *testing.T) {
|
||||
vmEvent := newVMEvent()
|
||||
assert.Equal(t, vmEvent.Topic(), "vsphere.VMEvent")
|
||||
}
|
||||
|
||||
func TestSubscribe(t *testing.T) {
|
||||
mgr := NewEventManager()
|
||||
topic := events.NewEventType(vsphere.VMEvent{}).Topic()
|
||||
mgr.Subscribe(topic, "tester", callback)
|
||||
subs := mgr.Subscribers()
|
||||
assert.Equal(t, 1, len(subs))
|
||||
assert.Equal(t, 1, mgr.Subscribed())
|
||||
|
||||
mgr.Subscribe(topic, "tester2", callback)
|
||||
subs = mgr.Subscribers()
|
||||
|
||||
// should still have 1 topic
|
||||
assert.Equal(t, 1, len(subs))
|
||||
// now two subscribers for that topic
|
||||
assert.Equal(t, 2, mgr.Subscribed())
|
||||
|
||||
mgr.Subscribe(events.NewEventType(&vsphere.VMEvent{}).Topic(), "tester3", callback)
|
||||
subs = mgr.Subscribers()
|
||||
// should still have 1 topic
|
||||
assert.Equal(t, 1, len(subs))
|
||||
// now two subscribers for that topic
|
||||
assert.Equal(t, 3, mgr.Subscribed())
|
||||
|
||||
mgr.Unsubscribe(topic, "tester2")
|
||||
subs = mgr.Subscribers()
|
||||
// should still have 1 topic
|
||||
assert.Equal(t, 1, len(subs))
|
||||
// now two subscribers for that topic
|
||||
assert.Equal(t, 2, mgr.Subscribed())
|
||||
|
||||
mgr.Unsubscribe(events.NewEventType(&vsphere.VMEvent{}).Topic(), "tester3")
|
||||
subs = mgr.Subscribers()
|
||||
// should still have 1 topic
|
||||
assert.Equal(t, 1, len(subs))
|
||||
// now one subscribers for that topic
|
||||
assert.Equal(t, 1, mgr.Subscribed())
|
||||
|
||||
}
|
||||
|
||||
func TestRegisterCollector(t *testing.T) {
|
||||
mgr := NewEventManager()
|
||||
// register nil
|
||||
mgr.RegisterCollector(nil)
|
||||
assert.Equal(t, 0, len(mgr.Collectors()))
|
||||
}
|
||||
|
||||
// utility methods
|
||||
func newVMMO() *types.ManagedObjectReference {
|
||||
return &types.ManagedObjectReference{Value: "101", Type: "vm"}
|
||||
}
|
||||
|
||||
func newBaseEvent() types.BaseEvent {
|
||||
vm := newVMMO()
|
||||
return types.BaseEvent(&types.VmPoweredOnEvent{VmEvent: types.VmEvent{Event: types.Event{Vm: &types.VmEventArgument{Vm: *vm}}}})
|
||||
}
|
||||
|
||||
func newVMEvent() *vsphere.VMEvent {
|
||||
return vsphere.NewVMEvent(newBaseEvent())
|
||||
}
|
||||
|
||||
func callback(e events.Event) {}
|
||||
209
vendor/github.com/vmware/vic/lib/portlayer/event/subscriber.go
generated
vendored
Normal file
209
vendor/github.com/vmware/vic/lib/portlayer/event/subscriber.go
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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 event
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
const (
|
||||
suspendDisabled int32 = iota
|
||||
suspendDiscard
|
||||
suspendQueue
|
||||
)
|
||||
|
||||
type Subscriber interface {
|
||||
// Topic returns the topic this subscriber is subscribed to
|
||||
Topic() string
|
||||
// Name returns the name of the subscriber
|
||||
Name() string
|
||||
|
||||
// Suspend suspends processing events by the subscriber. If
|
||||
// queueEvents is true, the events are queued until Resume()
|
||||
// is called. If queueEvents is false, events passed into
|
||||
// onEvent() after this call are discarded.
|
||||
Suspend(queueEvents bool)
|
||||
// Resume resumes processing of events by the subscriber.
|
||||
// If Suspend() was called with queueEvents as true, any events
|
||||
// that were passed to onEvent() after Suspend() returned are
|
||||
// processed first.
|
||||
Resume()
|
||||
// IsSuspended returns true if the subscriber is suspended.
|
||||
IsSuspended() bool
|
||||
|
||||
// Discarded returns the number of packets that were discarded by
|
||||
// the subscriber as a result of Pause() being called with
|
||||
// queueEvents as false.
|
||||
Discarded() uint64
|
||||
// Dropped returns the number of packets that were dropped when
|
||||
// the event queue overflows. This only happens when Pause()
|
||||
// is called with queueEvents as true.
|
||||
Dropped() uint64
|
||||
|
||||
// onEvent is called by event.Manager to send an event to
|
||||
// a subscriber
|
||||
onEvent(events.Event)
|
||||
}
|
||||
|
||||
type subscriber struct {
|
||||
topic string
|
||||
name string
|
||||
callback func(e events.Event)
|
||||
eventQ chan events.Event
|
||||
suspendState int32
|
||||
discarded, dropped uint64
|
||||
suspend chan suspendCmd
|
||||
}
|
||||
|
||||
type suspendCmd struct {
|
||||
suspend bool
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
const maxEventQueueSize = 1000
|
||||
|
||||
// newSubscriber creates a new subscriber to topic
|
||||
func newSubscriber(topic, name string, callback func(e events.Event)) Subscriber {
|
||||
s := &subscriber{
|
||||
topic: topic,
|
||||
name: name,
|
||||
callback: callback,
|
||||
eventQ: make(chan events.Event, maxEventQueueSize),
|
||||
suspend: make(chan suspendCmd),
|
||||
}
|
||||
|
||||
go func() {
|
||||
suspended := false
|
||||
var done chan struct{}
|
||||
for {
|
||||
if done != nil {
|
||||
done <- struct{}{}
|
||||
done = nil
|
||||
}
|
||||
|
||||
if suspended {
|
||||
select {
|
||||
case c := <-s.suspend:
|
||||
suspended = c.suspend
|
||||
done = c.done
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// not suspended
|
||||
select {
|
||||
case e := <-s.eventQ:
|
||||
s.callback(e)
|
||||
case c := <-s.suspend:
|
||||
suspended = c.suspend
|
||||
done = c.done
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Topic returns the topic this subscriber is subscribed to
|
||||
func (s *subscriber) Topic() string {
|
||||
return s.topic
|
||||
}
|
||||
|
||||
// Name returns the name of the subscriber
|
||||
func (s *subscriber) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// onEvent is called by event.Manager to send an event to
|
||||
// a subscriber
|
||||
func (s *subscriber) onEvent(e events.Event) {
|
||||
switch atomic.LoadInt32(&s.suspendState) {
|
||||
case suspendDisabled:
|
||||
s.eventQ <- e
|
||||
case suspendDiscard:
|
||||
log.Warnf("discarding event %q", e)
|
||||
atomic.AddUint64(&s.discarded, 1)
|
||||
case suspendQueue:
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case s.eventQ <- e:
|
||||
done = true
|
||||
default:
|
||||
// make room; discard oldest
|
||||
log.Warnf("dropping event %q", <-s.eventQ)
|
||||
atomic.AddUint64(&s.dropped, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend suspends processing events by the subscriber. If
|
||||
// queueEvents is true, the events are queued until Resume()
|
||||
// is called. If queueEvents is false, events passed into
|
||||
// onEvent() after this call are discarded.
|
||||
func (s *subscriber) Suspend(queueEvents bool) {
|
||||
defer func() {
|
||||
done := make(chan struct{})
|
||||
s.suspend <- suspendCmd{suspend: true, done: done}
|
||||
<-done
|
||||
close(done)
|
||||
}()
|
||||
|
||||
if queueEvents {
|
||||
atomic.StoreInt32(&s.suspendState, suspendQueue)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&s.suspendState, suspendDiscard)
|
||||
}
|
||||
|
||||
// Resume resumes processing of events by the subscriber.
|
||||
// If Suspend() was called with queueEvents as true, any events
|
||||
// that were passed to onEvent() after Suspend() returned are
|
||||
// processed first.
|
||||
func (s *subscriber) Resume() {
|
||||
defer func() {
|
||||
done := make(chan struct{})
|
||||
s.suspend <- suspendCmd{suspend: false, done: done}
|
||||
<-done
|
||||
close(done)
|
||||
}()
|
||||
atomic.StoreInt32(&s.suspendState, suspendDisabled)
|
||||
}
|
||||
|
||||
// IsSuspended returns true if the subscriber is suspended.
|
||||
func (s *subscriber) IsSuspended() bool {
|
||||
return atomic.LoadInt32(&s.suspendState) != suspendDisabled
|
||||
}
|
||||
|
||||
// Discarded returns the number of packets that were discarded by
|
||||
// the subscriber as a result of Pause() being called with
|
||||
// queueEvents as false.
|
||||
func (s *subscriber) Discarded() uint64 {
|
||||
return atomic.LoadUint64(&s.discarded)
|
||||
}
|
||||
|
||||
// Dropped returns the number of packets that were dropped when
|
||||
// the event queue overflows. This only happens when Pause()
|
||||
// is called with queueEvents as true.
|
||||
func (s *subscriber) Dropped() uint64 {
|
||||
return atomic.LoadUint64(&s.dropped)
|
||||
}
|
||||
218
vendor/github.com/vmware/vic/lib/portlayer/event/subscriber_test.go
generated
vendored
Normal file
218
vendor/github.com/vmware/vic/lib/portlayer/event/subscriber_test.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// 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 event
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
type mockCollector struct {
|
||||
c func(events.Event)
|
||||
}
|
||||
|
||||
// AddMonitoredObject will add the object for event listening
|
||||
func (m *mockCollector) AddMonitoredObject(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveMonitoredObject will remove the object from event listening
|
||||
func (m *mockCollector) RemoveMonitoredObject(_ string) {
|
||||
}
|
||||
|
||||
// Start listening for events and publish to function
|
||||
func (m *mockCollector) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop listening for events
|
||||
func (m *mockCollector) Stop() {}
|
||||
|
||||
// Register a callback function
|
||||
func (m *mockCollector) Register(c func(events.Event)) {
|
||||
m.c = c
|
||||
}
|
||||
|
||||
// Name returns the collector name
|
||||
func (m *mockCollector) Name() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
type mockEvent struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// id of event
|
||||
func (e *mockEvent) EventID() string {
|
||||
return e.id
|
||||
}
|
||||
|
||||
// event (PowerOn, PowerOff, etc)
|
||||
func (e *mockEvent) String() string {
|
||||
return e.id
|
||||
}
|
||||
|
||||
// reference evented object
|
||||
func (e *mockEvent) Reference() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// event message
|
||||
func (e *mockEvent) Message() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *mockEvent) Created() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (e *mockEvent) Topic() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func TestSuspendQueue(t *testing.T) {
|
||||
c := &mockCollector{}
|
||||
m := NewEventManager(c)
|
||||
var evs []events.Event
|
||||
done := make(chan struct{})
|
||||
s := m.Subscribe("test", "test", func(e events.Event) {
|
||||
evs = append(evs, e)
|
||||
if len(evs) == 100 {
|
||||
close(done)
|
||||
}
|
||||
})
|
||||
|
||||
suspended := false
|
||||
for i := 0; i < 100; i++ {
|
||||
if !suspended && i >= 50 {
|
||||
s.Suspend(true)
|
||||
assert.True(t, s.IsSuspended())
|
||||
suspended = true
|
||||
}
|
||||
c.c(&mockEvent{id: strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
assert.Fail(t, "unexpectedly got all events despite suspend")
|
||||
case <-time.After(2 * time.Second):
|
||||
assert.Condition(t, func() bool { return len(evs) <= 50 })
|
||||
}
|
||||
|
||||
s.Resume()
|
||||
assert.False(t, s.IsSuspended())
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
assert.Fail(t, "timed out waiting for suspended events")
|
||||
}
|
||||
|
||||
// check for dups
|
||||
for i := range evs {
|
||||
for j := range evs {
|
||||
if j == i {
|
||||
continue
|
||||
}
|
||||
if evs[j].EventID() == evs[i].EventID() {
|
||||
assert.Fail(t, "dup event found for id %d", evs[j].EventID())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuspendDiscard(t *testing.T) {
|
||||
c := &mockCollector{}
|
||||
m := NewEventManager(c)
|
||||
var evs []events.Event
|
||||
s := m.Subscribe("test", "test", func(e events.Event) {
|
||||
assert.Fail(t, "got an event %q when expecting none", e)
|
||||
})
|
||||
|
||||
// discard events
|
||||
s.Suspend(false)
|
||||
assert.True(t, s.IsSuspended())
|
||||
for i := 0; i < 50; i++ {
|
||||
c.c(&mockEvent{id: strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
<-time.After(5 * time.Second)
|
||||
|
||||
assert.Empty(t, evs)
|
||||
assert.Equal(t, uint64(50), s.Discarded())
|
||||
assert.Equal(t, uint64(0), s.Dropped())
|
||||
}
|
||||
|
||||
func TestSuspendOverflow(t *testing.T) {
|
||||
c := &mockCollector{}
|
||||
m := NewEventManager(c)
|
||||
var evs []events.Event
|
||||
done := make(chan struct{})
|
||||
s := m.Subscribe("test", "test", func(e events.Event) {
|
||||
evs = append(evs, e)
|
||||
if len(evs) == maxEventQueueSize {
|
||||
close(done)
|
||||
}
|
||||
})
|
||||
|
||||
s.Suspend(true)
|
||||
assert.True(t, s.IsSuspended())
|
||||
for i := 0; i < maxEventQueueSize+1; i++ {
|
||||
c.c(&mockEvent{id: strconv.Itoa(i)})
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
assert.Fail(t, "unexpectedly got all events despite suspend")
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
|
||||
s.Resume()
|
||||
assert.False(t, s.IsSuspended())
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
assert.Fail(t, "timed out waiting for sentinel event")
|
||||
}
|
||||
|
||||
assert.Len(t, evs, maxEventQueueSize)
|
||||
assert.Equal(t, uint64(1), s.Dropped())
|
||||
assert.Equal(t, uint64(0), s.Discarded())
|
||||
|
||||
// check for dups
|
||||
for i := range evs {
|
||||
// should not have an event with id 0
|
||||
assert.NotEqual(t, 0, evs[i].EventID(), "got event with event id 0")
|
||||
for j := range evs {
|
||||
if j == i {
|
||||
continue
|
||||
}
|
||||
if evs[j].EventID() == evs[i].EventID() {
|
||||
assert.Fail(t, "dup event found for id %d", evs[j].EventID())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
440
vendor/github.com/vmware/vic/lib/portlayer/exec/base.go
generated
vendored
Normal file
440
vendor/github.com/vmware/vic/lib/portlayer/exec/base.go
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
// 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 exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/vmware/govmomi/guest"
|
||||
"github.com/vmware/govmomi/task"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/migration"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// NotYetExistError is returned when a call that requires a VM exist is made
|
||||
type NotYetExistError struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (e NotYetExistError) Error() string {
|
||||
return fmt.Sprintf("%s is not completely created", e.ID)
|
||||
}
|
||||
|
||||
// containerBase holds fields common between Handle and Container. The fields and
|
||||
// methods in containerBase should not require locking as they're primary use is:
|
||||
// a. for read-only reference when used in Container
|
||||
// b. single use/no-concurrent modification when used in Handle
|
||||
type containerBase struct {
|
||||
ExecConfig *executor.ExecutorConfig
|
||||
|
||||
// Migrated is used during in memory migration to assign whether an execConfig is viable for a commit phase
|
||||
Migrated bool
|
||||
// MigrationError means the errors happens during data migration, some operation might fail for we cannot extract the whole container configuration
|
||||
MigrationError error
|
||||
DataVersion int
|
||||
|
||||
// original - can be pointers so long as refreshes
|
||||
// use different instances of the structures
|
||||
Config *types.VirtualMachineConfigInfo
|
||||
Runtime *types.VirtualMachineRuntimeInfo
|
||||
|
||||
// doesn't change so can be copied here
|
||||
vm *vm.VirtualMachine
|
||||
}
|
||||
|
||||
func newBase(vm *vm.VirtualMachine, c *types.VirtualMachineConfigInfo, r *types.VirtualMachineRuntimeInfo) *containerBase {
|
||||
base := &containerBase{
|
||||
ExecConfig: &executor.ExecutorConfig{},
|
||||
Config: c,
|
||||
Runtime: r,
|
||||
vm: vm,
|
||||
}
|
||||
|
||||
// construct a working copy of the exec config
|
||||
if c != nil && c.ExtraConfig != nil {
|
||||
var migratedConf map[string]string
|
||||
containerExecKeyValues := vmomi.OptionValueMap(c.ExtraConfig)
|
||||
// #nosec: Errors unhandled.
|
||||
base.DataVersion, _ = migration.ContainerDataVersion(containerExecKeyValues)
|
||||
migratedConf, base.Migrated, base.MigrationError = migration.MigrateContainerConfig(containerExecKeyValues)
|
||||
extraconfig.Decode(extraconfig.MapSource(migratedConf), base.ExecConfig)
|
||||
}
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
// String returns the string representation of ContainerBase
|
||||
func (c *containerBase) String() string {
|
||||
return c.ExecConfig.ID
|
||||
}
|
||||
|
||||
// VMReference will provide the vSphere vm managed object reference
|
||||
func (c *containerBase) VMReference() types.ManagedObjectReference {
|
||||
var moref types.ManagedObjectReference
|
||||
if c.vm != nil {
|
||||
moref = c.vm.Reference()
|
||||
}
|
||||
return moref
|
||||
}
|
||||
|
||||
// unlocked refresh of container state
|
||||
func (c *containerBase) refresh(op trace.Operation) error {
|
||||
base, err := c.updates(op)
|
||||
if err != nil {
|
||||
op.Errorf("Update: unable to update container %s: %s", c, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// copy over the new state
|
||||
*c = *base
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates acquires updates from the infrastructure without holding a lock
|
||||
func (c *containerBase) updates(op trace.Operation) (*containerBase, error) {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
var o mo.VirtualMachine
|
||||
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return nil, NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
if c.Config != nil {
|
||||
op.Debugf("Update: for %s, refreshing from change version %s", c, c.Config.ChangeVersion)
|
||||
}
|
||||
|
||||
if err := c.vm.Properties(op, c.vm.Reference(), []string{"config", "runtime"}, &o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := &containerBase{
|
||||
vm: c.vm,
|
||||
Config: o.Config,
|
||||
Runtime: &o.Runtime,
|
||||
ExecConfig: &executor.ExecutorConfig{},
|
||||
}
|
||||
|
||||
// Get the ExtraConfig
|
||||
var migratedConf map[string]string
|
||||
containerExecKeyValues := vmomi.OptionValueMap(o.Config.ExtraConfig)
|
||||
if containerExecKeyValues["guestinfo.vice./common/id"] == "" {
|
||||
return nil, fmt.Errorf("Update: change version %s failed assertion extraconfig id != nil", o.Config.ChangeVersion)
|
||||
}
|
||||
|
||||
op.Debugf("Update: for %s, change version %s, extraconfig id: %+v", c, o.Config.ChangeVersion, containerExecKeyValues["guestinfo.vice./common/id"])
|
||||
// #nosec: Errors unhandled.
|
||||
base.DataVersion, _ = migration.ContainerDataVersion(containerExecKeyValues)
|
||||
migratedConf, base.Migrated, base.MigrationError = migration.MigrateContainerConfig(containerExecKeyValues)
|
||||
extraconfig.Decode(extraconfig.MapSource(migratedConf), base.ExecConfig)
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (c *containerBase) ReloadConfig(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
return c.startGuestProgram(op, "reload", "")
|
||||
}
|
||||
|
||||
// WaitForExec waits exec'ed task to set started field or timeout
|
||||
func (c *containerBase) WaitForExec(op trace.Operation, id string) error {
|
||||
defer trace.End(trace.Begin(id, op))
|
||||
|
||||
return c.waitForExec(op, id)
|
||||
}
|
||||
|
||||
// WaitForSession waits non-exec'ed task to set started field or timeout
|
||||
func (c *containerBase) WaitForSession(ctx context.Context, id string) error {
|
||||
defer trace.End(trace.Begin(id, ctx))
|
||||
|
||||
return c.waitForSession(ctx, id)
|
||||
}
|
||||
|
||||
func (c *containerBase) startGuestProgram(op trace.Operation, name string, args string) error {
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID+":"+name, op))
|
||||
o := guest.NewOperationsManager(c.vm.Client.Client, c.vm.Reference())
|
||||
m, err := o.ProcessManager(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := types.GuestProgramSpec{
|
||||
ProgramPath: name,
|
||||
Arguments: args,
|
||||
}
|
||||
|
||||
auth := types.NamePasswordAuthentication{
|
||||
Username: c.ExecConfig.ID,
|
||||
}
|
||||
|
||||
_, err = m.StartProgram(op, &auth, &spec)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *containerBase) start(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
// Power on
|
||||
_, err := c.vm.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return c.vm.PowerOn(op)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *containerBase) stop(op trace.Operation, waitTime *int32) error {
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
// get existing state and set to stopping
|
||||
// if there's a failure we'll revert to existing
|
||||
|
||||
err := c.shutdown(op, waitTime)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
op.Warnf("stopping %s via hard power off due to: %s", c, err.Error())
|
||||
|
||||
return c.poweroff(op)
|
||||
}
|
||||
|
||||
func (c *containerBase) kill(op trace.Operation) error {
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
wait := 10 * time.Second // default
|
||||
timeout, cancel := trace.WithTimeout(&op, wait, "kill")
|
||||
defer cancel()
|
||||
|
||||
sig := string(ssh.SIGKILL)
|
||||
timeout.Infof("sending kill -%s %s", sig, c)
|
||||
|
||||
err := c.startGuestProgram(timeout, "kill", sig)
|
||||
if err == nil && timeout.Err() != nil {
|
||||
timeout.Warnf("timeout (%s) waiting for %s to power off via SIG%s", wait, c, sig)
|
||||
}
|
||||
if err != nil {
|
||||
timeout.Warnf("killing %s attempt resulted in: %s", c, err.Error())
|
||||
|
||||
if isInvalidPowerStateError(err) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Even if startGuestProgram failed above, it may actually have executed. If the container came up and then
|
||||
// we kill it before VC gets a chance to detect the toolbox, vSphere can execute the kill but report an
|
||||
// error 3016 indicating the guest toolbox wasn't found. If we then try to poweroff, it may throw vSphere
|
||||
// into an invalid transition and will need to recover. If we try to grab properties at this time, the
|
||||
// power state may be incorrect. We work around this by waiting on the power state, regardless of error
|
||||
// from startGuestProgram. https://github.com/vmware/vic/issues/5803
|
||||
timeout.Infof("waiting %s for %s to power off", wait, c)
|
||||
err = c.vm.WaitForPowerState(timeout, types.VirtualMachinePowerStatePoweredOff)
|
||||
if err == nil {
|
||||
return nil // VM has powered off
|
||||
}
|
||||
|
||||
timeout.Warnf("killing %s via hard power off", c)
|
||||
|
||||
// stop wait time is not applied for the hard kill
|
||||
return c.poweroff(op)
|
||||
}
|
||||
|
||||
func (c *containerBase) shutdown(op trace.Operation, waitTime *int32) error {
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
wait := 10 * time.Second // default
|
||||
if waitTime != nil && *waitTime > 0 {
|
||||
wait = time.Duration(*waitTime) * time.Second
|
||||
}
|
||||
|
||||
cs := c.ExecConfig.Sessions[c.ExecConfig.ID]
|
||||
stop := []string{cs.StopSignal, string(ssh.SIGKILL)}
|
||||
if stop[0] == "" {
|
||||
stop[0] = string(ssh.SIGTERM)
|
||||
}
|
||||
|
||||
var killed bool
|
||||
|
||||
for _, sig := range stop {
|
||||
msg := fmt.Sprintf("sending kill -%s %s", sig, c)
|
||||
op.Infof(msg)
|
||||
|
||||
timeout, cancel := trace.WithTimeout(&op, wait, "shutdown")
|
||||
defer cancel()
|
||||
|
||||
err := c.startGuestProgram(timeout, "kill", sig)
|
||||
if err != nil {
|
||||
// Just warn and proceed to waiting for power state per issue https://github.com/vmware/vic/issues/5803
|
||||
// Description above in function kill()
|
||||
timeout.Warnf("%s: %s", msg, err)
|
||||
|
||||
// If the error tells us "The attempted operation cannot be performed in the current state (Powered off)" (InvalidPowerState),
|
||||
// we can avoid hard poweroff (issues #6236 and #6252). Here we wait for the power state changes instead of return
|
||||
// immediately to avoid excess vSphere queries
|
||||
if isInvalidPowerStateError(err) {
|
||||
killed = true
|
||||
}
|
||||
}
|
||||
|
||||
timeout.Infof("waiting %s for %s to power off", wait, c)
|
||||
err = c.vm.WaitForPowerState(timeout, types.VirtualMachinePowerStatePoweredOff)
|
||||
if err == nil {
|
||||
return nil // VM has powered off
|
||||
}
|
||||
|
||||
if timeout.Err() == nil {
|
||||
return err // error other than timeout
|
||||
}
|
||||
|
||||
timeout.Warnf("timeout (%s) waiting for %s to power off via SIG%s", wait, c, sig)
|
||||
|
||||
if killed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to shutdown %s via kill signals %s", c, stop)
|
||||
}
|
||||
|
||||
func (c *containerBase) poweroff(op trace.Operation) error {
|
||||
// make sure we have vm
|
||||
if c.vm == nil {
|
||||
return NotYetExistError{c.ExecConfig.ID}
|
||||
}
|
||||
|
||||
_, err := c.vm.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return c.vm.PowerOff(op)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
// It is possible the VM has finally shutdown in between, ignore the error in that case
|
||||
if terr, ok := err.(task.Error); ok {
|
||||
switch terr := terr.Fault().(type) {
|
||||
case *types.InvalidPowerState:
|
||||
if terr.ExistingState == types.VirtualMachinePowerStatePoweredOff {
|
||||
op.Warnf("power off %s task skipped (state was already %s)", c, terr.ExistingState)
|
||||
return nil
|
||||
}
|
||||
op.Warnf("invalid power state during power off: %s", terr.ExistingState)
|
||||
|
||||
case *types.GenericVmConfigFault:
|
||||
|
||||
// Check if the poweroff task was canceled due to a concurrent guest shutdown
|
||||
if len(terr.FaultMessage) > 0 {
|
||||
k := terr.FaultMessage[0].Key
|
||||
if k == vmNotSuspendedKey || k == vmPoweringOffKey {
|
||||
op.Infof("power off %s task skipped due to guest shutdown", c)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
op.Warnf("generic vm config fault during power off: %#v", terr)
|
||||
|
||||
default:
|
||||
op.Warnf("hard power off failed due to: %#v", terr)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *containerBase) waitForPowerState(ctx context.Context, max time.Duration, state types.VirtualMachinePowerState) (bool, error) {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, ctx))
|
||||
|
||||
timeout, cancel := context.WithTimeout(ctx, max)
|
||||
defer cancel()
|
||||
|
||||
err := c.vm.WaitForPowerState(timeout, state)
|
||||
if err != nil {
|
||||
return timeout.Err() != nil, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *containerBase) waitForSession(ctx context.Context, id string) error {
|
||||
defer trace.End(trace.Begin(id, ctx))
|
||||
|
||||
// guestinfo key that we want to wait for
|
||||
key := extraconfig.CalculateKeys(c.ExecConfig, fmt.Sprintf("Sessions.%s.Started", id), "")[0]
|
||||
return c.waitFor(ctx, key)
|
||||
}
|
||||
|
||||
func (c *containerBase) waitForExec(op trace.Operation, id string) error {
|
||||
defer trace.End(trace.Begin(id, op))
|
||||
|
||||
// guestinfo key that we want to wait for
|
||||
key := extraconfig.CalculateKeys(c.ExecConfig, fmt.Sprintf("Execs.%s.Started", id), "")[0]
|
||||
return c.waitFor(op, key)
|
||||
}
|
||||
|
||||
func (c *containerBase) waitFor(ctx context.Context, key string) error {
|
||||
detail, err := c.vm.WaitForKeyInExtraConfig(ctx, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to wait for process launch status: %s", err)
|
||||
}
|
||||
|
||||
if detail != "true" {
|
||||
return fmt.Errorf("%s", detail)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isInvalidPowerStateError verifies if an error is the InvalidPowerStateError
|
||||
func isInvalidPowerStateError(err error) bool {
|
||||
if soap.IsSoapFault(err) {
|
||||
_, ok := soap.ToSoapFault(err).VimFault().(types.InvalidPowerState)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
255
vendor/github.com/vmware/vic/lib/portlayer/exec/commit.go
generated
vendored
Normal file
255
vendor/github.com/vmware/vic/lib/portlayer/exec/commit.go
generated
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
// 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 exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Commit executes the requires steps on the handle
|
||||
func Commit(op trace.Operation, sess *session.Session, h *Handle, waitTime *int32) error {
|
||||
defer trace.End(trace.Begin(h.ExecConfig.ID, op))
|
||||
|
||||
c := Containers.Container(h.ExecConfig.ID)
|
||||
creation := h.vm == nil
|
||||
if creation {
|
||||
if h.Spec == nil {
|
||||
return fmt.Errorf("a spec must be provided for create operations")
|
||||
}
|
||||
|
||||
if sess == nil {
|
||||
// session must not be nil
|
||||
return fmt.Errorf("no session provided for create operations")
|
||||
}
|
||||
|
||||
// the only permissible operation is to create a VM
|
||||
if h.Spec == nil {
|
||||
return fmt.Errorf("only create operations can be committed without an existing VM")
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
return fmt.Errorf("a container already exists in the cache with this ID")
|
||||
}
|
||||
|
||||
var res *types.TaskInfo
|
||||
var err error
|
||||
if sess.IsVC() && Config.VirtualApp.ResourcePool != nil {
|
||||
// Create the vm
|
||||
res, err = tasks.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return Config.VirtualApp.CreateChildVM(op, *h.Spec.Spec(), nil)
|
||||
})
|
||||
} else {
|
||||
// Create the vm
|
||||
res, err = tasks.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return sess.VMFolder.CreateVM(op, *h.Spec.Spec(), Config.ResourcePool, nil)
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
op.Errorf("An error occurred while waiting for a creation operation to complete. Spec was %+v", *h.Spec.Spec())
|
||||
return err
|
||||
}
|
||||
|
||||
h.vm = vm.NewVirtualMachine(op, sess, res.Result.(types.ManagedObjectReference))
|
||||
h.vm.DisableDestroy(op)
|
||||
c = newContainer(&h.containerBase)
|
||||
Containers.Put(c)
|
||||
// inform of creation irrespective of remaining operations
|
||||
publishContainerEvent(op, c.ExecConfig.ID, time.Now().UTC(), events.ContainerCreated)
|
||||
|
||||
// clear the spec as we've acted on it - this prevents a reconfigure from occurring in follow-on
|
||||
// processing
|
||||
h.Spec = nil
|
||||
}
|
||||
|
||||
// if we're stopping the VM, do so before the reconfigure to preserve the extraconfig
|
||||
if h.TargetState() == StateStopped {
|
||||
if h.Runtime == nil {
|
||||
op.Warnf("Commit called with incomplete runtime state for %s", h.ExecConfig.ID)
|
||||
}
|
||||
|
||||
if h.Runtime != nil && h.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOff {
|
||||
op.Infof("Dropping duplicate power off operation for %s", h.ExecConfig.ID)
|
||||
} else {
|
||||
// stop the container
|
||||
if err := c.stop(op, waitTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we must refresh now to get the new ChangeVersion - this is used to gate on powerstate in the reconfigure
|
||||
// because we cannot set the ExtraConfig if the VM is powered on. There is still a race here unfortunately because
|
||||
// tasks don't appear to contain the new ChangeVersion
|
||||
h.refresh(op)
|
||||
|
||||
// inform of state change irrespective of remaining operations - but allow remaining operations to complete first
|
||||
// to avoid data race on container config
|
||||
defer publishContainerEvent(op, h.ExecConfig.ID, time.Now().UTC(), events.ContainerStopped)
|
||||
}
|
||||
}
|
||||
|
||||
// reconfigure operation
|
||||
if h.Spec != nil {
|
||||
if h.Runtime == nil {
|
||||
op.Errorf("Refusing to perform reconfigure operation with incomplete runtime state for %s", h.ExecConfig.ID)
|
||||
} else {
|
||||
// ensure that our logic based on Runtime state remains valid
|
||||
|
||||
// NOTE: this inline refresh can be removed when switching away from guestinfo where we have non-persistence issues
|
||||
// when updating ExtraConfig via the API with a powered on VM - we therefore have to be absolutely certain about the
|
||||
// power state to decide if we can continue without nilifying extraconfig
|
||||
//
|
||||
// For the power off path this depends on handle.refresh() having been called to update the ChangeVersion
|
||||
s := h.Spec.Spec()
|
||||
|
||||
op.Infof("Reconfigure: attempting update to %s with change version %q (%s)", h.ExecConfig.ID, s.ChangeVersion, h.Runtime.PowerState)
|
||||
|
||||
// nilify ExtraConfig if container configuration is migrated
|
||||
// in this case, VCH and container are in different version. Migrated configuration cannot be written back to old container, to avoid data loss in old version's container
|
||||
if h.Migrated {
|
||||
op.Debugf("Reconfigure: dropping extraconfig as configuration of container %s is migrated", h.ExecConfig.ID)
|
||||
s.ExtraConfig = nil
|
||||
}
|
||||
|
||||
// address the race between power operation and refresh of config (and therefore ChangeVersion) in StateStopped block above
|
||||
if s.ExtraConfig != nil && h.TargetState() == StateStopped && h.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff {
|
||||
detail := fmt.Sprintf("Reconfigure: collision of concurrent operations - expected power state poweredOff, found %s", h.Runtime.PowerState)
|
||||
op.Warnf(detail)
|
||||
|
||||
// log out current vm power state and runtime power state got from refresh, to see if there is anything mismatch,
|
||||
// cause in issue #6127, we see the runtime power state is not updated even after 1 minute
|
||||
ps, _ := h.vm.PowerState(op)
|
||||
op.Debugf("Container %s power state: %s, runtime power state: %s", h.ExecConfig.ID, ps, h.Runtime.PowerState)
|
||||
// this should cause a second attempt at the power op. This could result repeated contention that fails to resolve, but the randomness in the backoff and the tight timing
|
||||
// to hit this scenario should mean it will resolve in a reasonable timeframe.
|
||||
return ConcurrentAccessError{errors.New(detail)}
|
||||
}
|
||||
|
||||
_, err := h.vm.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return h.vm.Reconfigure(op, *s)
|
||||
})
|
||||
if err != nil {
|
||||
op.Errorf("Reconfigure: failed update to %s with change version %s: %+v", h.ExecConfig.ID, s.ChangeVersion, err)
|
||||
|
||||
// Check whether we get ConcurrentAccess and wrap it if needed
|
||||
if f, ok := err.(types.HasFault); ok {
|
||||
switch f.Fault().(type) {
|
||||
case *types.ConcurrentAccess:
|
||||
op.Errorf("Reconfigure: failed update to %s due to ConcurrentAccess, our change version %s", h.ExecConfig.ID, s.ChangeVersion)
|
||||
|
||||
return ConcurrentAccessError{err}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
op.Infof("Reconfigure: committed update to %s with change version: %s", h.ExecConfig.ID, s.ChangeVersion)
|
||||
|
||||
// trigger a configuration reload in the container if needed
|
||||
err = reloadConfig(op, h, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// best effort update of container cache using committed state - this will not reflect the power on below, however
|
||||
// this is primarily for updating ExtraConfig state.
|
||||
if !creation {
|
||||
defer c.RefreshFromHandle(op, h)
|
||||
}
|
||||
|
||||
if h.TargetState() == StateRunning {
|
||||
if h.Runtime != nil && h.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn {
|
||||
op.Infof("Dropping duplicate power on operation for %s", h.ExecConfig.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if h.Runtime == nil && !creation {
|
||||
op.Warnf("Commit called with incomplete runtime state for %s", h.ExecConfig.ID)
|
||||
}
|
||||
|
||||
// start the container
|
||||
if err := c.start(op); err != nil {
|
||||
// We observed that PowerOn_Task could get stuck on VC time to time even though the VM was starting fine on the host ESXi.
|
||||
// Eventually the task was getting timed out (After 20 min.) and that was setting the container state back to Stopped.
|
||||
// During that time VC was not generating any other event so the persona listener was getting nothing.
|
||||
// This new event is for signaling the eventmonitor so that it can autoremove the container after this failure.
|
||||
publishContainerEvent(op, h.ExecConfig.ID, time.Now().UTC(), events.ContainerFailed)
|
||||
return err
|
||||
}
|
||||
|
||||
// publish started event
|
||||
publishContainerEvent(op, h.ExecConfig.ID, time.Now().UTC(), events.ContainerStarted)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS BELOW
|
||||
|
||||
// reloadConfig is responsible for triggering a guest_reconfigure in order to perform an operation on a running cVM
|
||||
// this function needs to be resilient to intermittent config errors and task errors, but will pass concurrent
|
||||
// modification issues back immediately.
|
||||
func reloadConfig(op trace.Operation, h *Handle, c *Container) error {
|
||||
|
||||
op.Infof("Attempting to perform a guest reconfigure operation on (%s)", h.ExecConfig.ID)
|
||||
retryFunc := func() error {
|
||||
if h.reload && h.Runtime != nil && h.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn {
|
||||
err := c.ReloadConfig(op)
|
||||
|
||||
if err != nil {
|
||||
op.Debugf("Error occurred during an attempt to reload the container config for an exec operation: (%s)", err)
|
||||
|
||||
// we will request the powerstate directly(this could be very costly without the vmomi gateway)
|
||||
state, err := c.vm.PowerState(op)
|
||||
if err != nil && state == types.VirtualMachinePowerStatePoweredOff {
|
||||
// TODO: probably should make this error a specific type such as PowerOffDuringExecError( or a better name ofcourse)
|
||||
return fmt.Errorf("container(%s) was powered down during the requested operation.", h.ExecConfig.ID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nothing to be done.
|
||||
return nil
|
||||
}
|
||||
|
||||
err := retry.Do(retryFunc, isIntermittentFailure)
|
||||
if err != nil {
|
||||
op.Debugf("Failed an exec operation with err: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: refactor later, I need to test this and we need to unify the Task package and the retry Package(make task use retry imo)
|
||||
// right now this just looks silly...
|
||||
func isIntermittentFailure(err error) bool {
|
||||
// in the future commit should be using the trace.operation for these calls and this function can act as a passthrough.
|
||||
op := trace.NewOperation(context.TODO(), "")
|
||||
return tasks.IsRetryError(op, err)
|
||||
}
|
||||
56
vendor/github.com/vmware/vic/lib/portlayer/exec/config.go
generated
vendored
Normal file
56
vendor/github.com/vmware/vic/lib/portlayer/exec/config.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 exec
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/portlayer/event"
|
||||
)
|
||||
|
||||
var Config Configuration
|
||||
|
||||
// Configuration is a slice of the VCH config that is relevant to the exec part of the port layer
|
||||
type Configuration struct {
|
||||
// Turn on debug logging
|
||||
DebugLevel int `vic:"0.1" scope:"read-only" key:"init/diagnostics/debug"`
|
||||
|
||||
SysLogConfig *executor.SysLogConfig `vic:"0.1" scope:"read-only" key:"init/diagnostics/syslog"`
|
||||
|
||||
// Port Layer - exec
|
||||
config.Container `vic:"0.1" scope:"read-only" key:"container"`
|
||||
|
||||
// Resource pool is the working version of the compute resource config
|
||||
ResourcePool *object.ResourcePool
|
||||
// Parent resource will be a VirtualApp on VC
|
||||
VirtualApp *object.VirtualApp
|
||||
|
||||
// For now throw the Event Manager here
|
||||
EventManager event.EventManager
|
||||
|
||||
// Information about the VCH resource pool and about the real host that we want
|
||||
// tol retrieve just once.
|
||||
VCHMhz int64
|
||||
VCHMemoryLimit int64
|
||||
HostOS string
|
||||
HostOSVersion string
|
||||
HostProductName string //'VMware vCenter Server' or 'VMare ESXi'
|
||||
|
||||
// Datastore URLs for image stores - the top layer is [0], the bottom layer is [len-1]
|
||||
ImageStores []url.URL `vic:"0.1" scope:"read-only" key:"storage/image_stores"`
|
||||
}
|
||||
896
vendor/github.com/vmware/vic/lib/portlayer/exec/container.go
generated
vendored
Normal file
896
vendor/github.com/vmware/vic/lib/portlayer/exec/container.go
generated
vendored
Normal file
@@ -0,0 +1,896 @@
|
||||
// Copyright 2016-2018 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 exec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/iolog"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
stateevents "github.com/vmware/vic/lib/portlayer/event/events/vsphere"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type State int
|
||||
|
||||
const (
|
||||
StateUnknown State = iota
|
||||
StateStarting
|
||||
StateRunning
|
||||
StateStopping
|
||||
StateStopped
|
||||
StateSuspending
|
||||
StateSuspended
|
||||
StateCreated
|
||||
StateCreating
|
||||
StateRemoving
|
||||
StateRemoved
|
||||
|
||||
containerLogName = "output.log"
|
||||
|
||||
vmNotSuspendedKey = "msg.suspend.powerOff.notsuspended"
|
||||
vmPoweringOffKey = "msg.rpc.error.poweringoff"
|
||||
)
|
||||
|
||||
func (s State) String() string {
|
||||
switch s {
|
||||
case StateCreated:
|
||||
return "Created"
|
||||
case StateStarting:
|
||||
return "Starting"
|
||||
case StateRunning:
|
||||
return "Running"
|
||||
case StateRemoving:
|
||||
return "Removing"
|
||||
case StateRemoved:
|
||||
return "Removed"
|
||||
case StateStopping:
|
||||
return "Stopping"
|
||||
case StateStopped:
|
||||
return "Stopped"
|
||||
case StateUnknown:
|
||||
return "Unknown"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NotFoundError is returned when a types.ManagedObjectNotFound is returned from a vmomi call
|
||||
type NotFoundError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r NotFoundError) Error() string {
|
||||
return "VM has either been deleted or has not been fully created"
|
||||
}
|
||||
|
||||
func IsNotFoundError(err error) bool {
|
||||
if soap.IsSoapFault(err) {
|
||||
fault := soap.ToSoapFault(err).VimFault()
|
||||
if _, ok := fault.(types.ManagedObjectNotFound); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemovePowerError is returned when attempting to remove a containerVM that is powered on
|
||||
type RemovePowerError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r RemovePowerError) Error() string {
|
||||
return r.err.Error()
|
||||
}
|
||||
|
||||
// ConcurrentAccessError is returned when concurrent calls tries to modify same object
|
||||
type ConcurrentAccessError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r ConcurrentAccessError) Error() string {
|
||||
return r.err.Error()
|
||||
}
|
||||
|
||||
func IsConcurrentAccessError(err error) bool {
|
||||
_, ok := err.(ConcurrentAccessError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type DevicesInUseError struct {
|
||||
Devices []string
|
||||
}
|
||||
|
||||
func (e DevicesInUseError) Error() string {
|
||||
return fmt.Sprintf("device %s in use", strings.Join(e.Devices, ","))
|
||||
}
|
||||
|
||||
// Container is used to return data about a container during inspection calls
|
||||
// It is a copy rather than a live reflection and does not require locking
|
||||
type ContainerInfo struct {
|
||||
containerBase
|
||||
|
||||
state State
|
||||
|
||||
// Size of the leaf (unused)
|
||||
VMUnsharedDisk int64
|
||||
}
|
||||
|
||||
// Container is used for an entry in the container cache - this is a "live" representation
|
||||
// of containers in the infrastructure.
|
||||
// DANGEROUS USAGE CONSTRAINTS:
|
||||
// None of the containerBase fields should be partially updated - consider them immutable once they're
|
||||
// part of a cache entry
|
||||
// i.e. Do not make changes in containerBase.ExecConfig - only swap, under lock, the pointer for a
|
||||
// completely new ExecConfig.
|
||||
// This constraint allows us to avoid deep copying those structs every time a container is inspected
|
||||
type Container struct {
|
||||
m sync.Mutex
|
||||
|
||||
ContainerInfo
|
||||
|
||||
logFollowers []io.Closer
|
||||
|
||||
newStateEvents map[State]chan struct{}
|
||||
}
|
||||
|
||||
// newContainer constructs a Container suitable for adding to the cache
|
||||
// it's state is set from the Runtime.PowerState field, or StateCreated if that is not
|
||||
// viable
|
||||
// This copies (shallow) the containerBase that's provided
|
||||
func newContainer(base *containerBase) *Container {
|
||||
c := &Container{
|
||||
ContainerInfo: ContainerInfo{
|
||||
containerBase: *base,
|
||||
state: StateCreated,
|
||||
},
|
||||
newStateEvents: make(map[State]chan struct{}),
|
||||
}
|
||||
|
||||
// if this is a creation path, then Runtime will be nil
|
||||
if base.Runtime != nil {
|
||||
// set state
|
||||
switch base.Runtime.PowerState {
|
||||
case types.VirtualMachinePowerStatePoweredOn:
|
||||
// the containerVM is poweredOn, so set state to starting
|
||||
// then check to see if a start was successful
|
||||
c.state = StateStarting
|
||||
// If any sessions successfully started then set to running
|
||||
for _, s := range base.ExecConfig.Sessions {
|
||||
if s.Started != "" {
|
||||
c.state = StateRunning
|
||||
break
|
||||
}
|
||||
}
|
||||
case types.VirtualMachinePowerStatePoweredOff:
|
||||
// check if any of the sessions was started
|
||||
for _, s := range base.ExecConfig.Sessions {
|
||||
if s.Started != "" {
|
||||
c.state = StateStopped
|
||||
break
|
||||
}
|
||||
}
|
||||
case types.VirtualMachinePowerStateSuspended:
|
||||
c.state = StateSuspended
|
||||
log.Warnf("container VM %s: invalid power state %s", base.vm.Reference(), base.Runtime.PowerState)
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func GetContainer(ctx context.Context, id uid.UID) *Handle {
|
||||
// get from the cache
|
||||
container := Containers.Container(id.String())
|
||||
if container != nil {
|
||||
return container.NewHandle(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContainerInfo) String() string {
|
||||
return c.ExecConfig.ID
|
||||
}
|
||||
|
||||
// State returns the state at the time the ContainerInfo object was created
|
||||
func (c *ContainerInfo) State() State {
|
||||
return c.state
|
||||
}
|
||||
|
||||
func (c *Container) String() string {
|
||||
return c.ExecConfig.ID
|
||||
}
|
||||
|
||||
// Info returns a copy of the public container configuration that
|
||||
// is consistent and copied under lock
|
||||
func (c *Container) Info() *ContainerInfo {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
info := c.ContainerInfo
|
||||
return &info
|
||||
}
|
||||
|
||||
// CurrentState returns current state.
|
||||
func (c *Container) CurrentState() State {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
return c.state
|
||||
}
|
||||
|
||||
// SetState changes container state.
|
||||
func (c *Container) SetState(op trace.Operation, s State) State {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
return c.updateState(op, s)
|
||||
}
|
||||
|
||||
func (c *Container) updateState(op trace.Operation, s State) State {
|
||||
op.Debugf("Updating container %s state: %s->%s", c, c.state, s)
|
||||
prevState := c.state
|
||||
if s != c.state {
|
||||
c.state = s
|
||||
if ch, ok := c.newStateEvents[s]; ok {
|
||||
delete(c.newStateEvents, s)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
return prevState
|
||||
}
|
||||
|
||||
// transitionState changes the container state to finalState if the current state is initialState
|
||||
// and returns an error otherwise.
|
||||
func (c *Container) transitionState(op trace.Operation, initialState, finalState State) error {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.state == initialState {
|
||||
c.state = finalState
|
||||
op.Debugf("Set container %s state: %s->%s", c, initialState, finalState)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("container state is %s and was not changed to %s", c.state, finalState)
|
||||
}
|
||||
|
||||
var closedEventChannel = func() <-chan struct{} {
|
||||
a := make(chan struct{})
|
||||
close(a)
|
||||
return a
|
||||
}()
|
||||
|
||||
// WaitForState subscribes a caller to an event returning
|
||||
// a channel that will be closed when an expected state is set.
|
||||
// If expected state is already set the caller will receive a closed channel immediately.
|
||||
func (c *Container) WaitForState(s State) <-chan struct{} {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if s == c.state {
|
||||
return closedEventChannel
|
||||
}
|
||||
|
||||
if ch, ok := c.newStateEvents[s]; ok {
|
||||
return ch
|
||||
}
|
||||
|
||||
eventChan := make(chan struct{})
|
||||
c.newStateEvents[s] = eventChan
|
||||
return eventChan
|
||||
}
|
||||
|
||||
func (c *Container) NewHandle(ctx context.Context) *Handle {
|
||||
// Call property collector to fill the data
|
||||
if c.vm != nil {
|
||||
op := trace.FromContext(ctx, "NewHandle")
|
||||
// FIXME: this should be calling the cache to decide if a refresh is needed
|
||||
if err := c.Refresh(op); err != nil {
|
||||
op.Errorf("refreshing container %s failed: %s", c, err)
|
||||
return nil // nil indicates error
|
||||
}
|
||||
}
|
||||
|
||||
// return a handle that represents zero changes over the current configuration
|
||||
// for this container
|
||||
return newHandle(c)
|
||||
}
|
||||
|
||||
// Refresh updates config and runtime info, holding a lock only while swapping
|
||||
// the new data for the old
|
||||
func (c *Container) Refresh(op trace.Operation) error {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if err := c.refresh(op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// conditionally sync state (see issue 4872, 6372)
|
||||
event := stateevents.NewStateEvent(op, c.containerBase.Runtime.PowerState, c.VMReference())
|
||||
state := eventedState(op, event, c.state)
|
||||
|
||||
// trigger internal event publishing if c.state -> state is a transition we care about
|
||||
// this will update container state and trigger follow up port layer events as needed
|
||||
c.onEvent(op, state, event)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) refresh(op trace.Operation) error {
|
||||
return c.containerBase.refresh(op)
|
||||
}
|
||||
|
||||
// RefreshFromHandle updates config and runtime info, holding a lock only while swapping
|
||||
// the new data for the old
|
||||
func (c *Container) RefreshFromHandle(op trace.Operation, h *Handle) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.Config != nil && (h.Config == nil || h.Config.ChangeVersion != c.Config.ChangeVersion) {
|
||||
op.Warnf("container and handle ChangeVersions do not match for %s: %s != %s", c, c.Config.ChangeVersion, h.Config.ChangeVersion)
|
||||
return
|
||||
}
|
||||
|
||||
// power off doesn't necessarily cause a change version increment and bug1898149 occasionally impacts power on
|
||||
if c.Runtime != nil && (h.Runtime == nil || h.Runtime.PowerState != c.Runtime.PowerState) {
|
||||
op.Warnf("container and handle PowerStates do not match: %s != %s", c.Runtime.PowerState, h.Runtime.PowerState)
|
||||
return
|
||||
}
|
||||
|
||||
// copy over the new state
|
||||
c.containerBase = h.containerBase
|
||||
if c.Config != nil {
|
||||
op.Debugf("Update: updated change version from handle: %s", c.Config.ChangeVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts a container vm with the given params
|
||||
func (c *Container) start(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
if c.vm == nil {
|
||||
return fmt.Errorf("vm not set")
|
||||
}
|
||||
// Set state to Starting
|
||||
c.SetState(op, StateStarting)
|
||||
|
||||
err := c.containerBase.start(op)
|
||||
if err != nil {
|
||||
// change state to stopped because start task failed
|
||||
c.SetState(op, StateStopped)
|
||||
|
||||
// check if locked disk error
|
||||
devices := disk.LockedDisks(err)
|
||||
if len(devices) > 0 {
|
||||
for i := range devices {
|
||||
// get device id from datastore file path
|
||||
// FIXME: find a reasonable way to get device ID from datastore path in exec
|
||||
devices[i] = strings.TrimSuffix(path.Base(devices[i]), ".vmdk")
|
||||
}
|
||||
return DevicesInUseError{devices}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// wait task to set started field to something
|
||||
op, cancel := trace.WithTimeout(&op, constants.PropertyCollectorTimeout, "WaitForSession")
|
||||
defer cancel()
|
||||
|
||||
err = c.waitForSession(op, c.ExecConfig.ID)
|
||||
if err != nil {
|
||||
// leave this in state starting - if it powers off then the event
|
||||
// will cause transition to StateStopped which is likely our original state
|
||||
// if the container was just taking a very long time it'll eventually
|
||||
// become responsive.
|
||||
|
||||
// TODO: mechanism to trigger reinspection of long term transitional states
|
||||
return err
|
||||
}
|
||||
|
||||
// Transition the state to Running only if it's Starting.
|
||||
// The current state is already Stopped if the container's process has exited or
|
||||
// a poweredoff event has been processed.
|
||||
if err = c.transitionState(op, StateStarting, StateRunning); err != nil {
|
||||
op.Debugf(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) stop(op trace.Operation, waitTime *int32) error {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
defer c.onStop()
|
||||
|
||||
// get existing state and set to stopping
|
||||
// if there's a failure we'll revert to existing
|
||||
finalState := c.SetState(op, StateStopping)
|
||||
|
||||
err := c.containerBase.stop(op, waitTime)
|
||||
if err != nil {
|
||||
// we've got no idea what state the container is in at this point
|
||||
// running is an _optimistic_ statement
|
||||
// If the current state is Stopping, revert it to the old state.
|
||||
if stateErr := c.transitionState(op, StateStopping, finalState); stateErr != nil {
|
||||
op.Debugf(stateErr.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Transition the state to Stopped only if it's Stopping.
|
||||
if err = c.transitionState(op, StateStopping, StateStopped); err != nil {
|
||||
op.Debugf(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) Signal(op trace.Operation, num int64) error {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
|
||||
if c.vm == nil {
|
||||
return fmt.Errorf("vm not set")
|
||||
}
|
||||
|
||||
if num == int64(syscall.SIGKILL) {
|
||||
return c.containerBase.kill(op)
|
||||
}
|
||||
|
||||
return c.startGuestProgram(op, "kill", fmt.Sprintf("%d", num))
|
||||
}
|
||||
|
||||
func (c *Container) onStop() {
|
||||
lf := c.logFollowers
|
||||
c.logFollowers = nil
|
||||
|
||||
log.Debugf("Container(%s) closing %d log followers", c, len(lf))
|
||||
for _, l := range lf {
|
||||
// #nosec: Errors unhandled.
|
||||
_ = l.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) LogReader(op trace.Operation, tail int, follow bool, since int64) (io.ReadCloser, error) {
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.vm == nil {
|
||||
return nil, fmt.Errorf("vm not set")
|
||||
}
|
||||
|
||||
url, err := c.vm.VMPathNameAsURL(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s/%s", url.Path, containerLogName)
|
||||
|
||||
var via string
|
||||
|
||||
if c.state == StateRunning && c.vm.IsVC() {
|
||||
// #nosec: Errors unhandled.
|
||||
hosts, _ := c.vm.Datastore.AttachedHosts(op)
|
||||
if len(hosts) > 1 {
|
||||
// In this case, we need download from the VM host as it owns the file lock
|
||||
// #nosec: Errors unhandled.
|
||||
h, _ := c.vm.HostSystem(op)
|
||||
if h != nil {
|
||||
// get a context that embeds the host as a value
|
||||
ctx := c.vm.Datastore.HostContext(op, h)
|
||||
|
||||
// revert the govmomi returned context to the previous op
|
||||
// the op was preserved as a value in the context
|
||||
op = trace.FromContext(ctx, "LogReader")
|
||||
via = fmt.Sprintf(" via %s", h.Reference())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
op.Infof("pulling %s%s", name, via)
|
||||
|
||||
file, err := c.vm.Datastore.Open(op, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if since > 0 {
|
||||
err = file.TailFunc(tail, func(line int, message string) bool {
|
||||
if tail <= line && tail != -1 {
|
||||
return false
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(message)
|
||||
|
||||
entry, err := iolog.ParseLogEntry(buf)
|
||||
if err != nil {
|
||||
op.Errorf("Error parsing log entry: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
if entry.Timestamp.Unix() <= since {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
} else if tail >= 0 {
|
||||
err = file.Tail(tail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if follow && c.state == StateRunning {
|
||||
follower := file.Follow(time.Second)
|
||||
|
||||
c.logFollowers = append(c.logFollowers, follower)
|
||||
|
||||
return follower, nil
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// Remove removes a containerVM after detaching the disks
|
||||
func (c *Container) Remove(op trace.Operation, sess *session.Session) error {
|
||||
// op := trace.FromContext(ctx, "Remove")
|
||||
defer trace.End(trace.Begin(c.ExecConfig.ID, op))
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.vm == nil {
|
||||
return NotFoundError{}
|
||||
}
|
||||
|
||||
// check state first
|
||||
if c.state == StateRunning {
|
||||
return RemovePowerError{fmt.Errorf("Container %s is powered on", c)}
|
||||
}
|
||||
|
||||
// get existing state and set to removing
|
||||
// if there's a failure we'll revert to existing
|
||||
existingState := c.updateState(op, StateRemoving)
|
||||
|
||||
// get the folder the VM is in
|
||||
url, err := c.vm.VMPathNameAsURL(op)
|
||||
if err != nil {
|
||||
|
||||
// handle the out-of-band removal case
|
||||
if IsNotFoundError(err) {
|
||||
Containers.Remove(c.ExecConfig.ID)
|
||||
return NotFoundError{}
|
||||
}
|
||||
|
||||
op.Errorf("Failed to get datastore path for %s: %s", c, err)
|
||||
c.updateState(op, existingState)
|
||||
return err
|
||||
}
|
||||
|
||||
ds, err := sess.Finder.Datastore(op, url.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// enable Destroy
|
||||
c.vm.EnableDestroy(op)
|
||||
|
||||
concurrent := false
|
||||
// if DeleteExceptDisks succeeds on VC, it leaves the VM orphan so we need to call Unregister
|
||||
// if DeleteExceptDisks succeeds on ESXi, no further action needed
|
||||
// if DeleteExceptDisks fails, we should call Unregister and only return an error if that fails too
|
||||
// Unregister sometimes can fail with ManagedObjectNotFound so we ignore it
|
||||
_, err = c.vm.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return c.vm.DeleteExceptDisks(op)
|
||||
})
|
||||
if err != nil {
|
||||
f, ok := err.(types.HasFault)
|
||||
if !ok {
|
||||
op.Warnf("DeleteExceptDisks failed with non-fault error %s for %s.", err, c)
|
||||
|
||||
c.updateState(op, existingState)
|
||||
return err
|
||||
}
|
||||
|
||||
switch f.Fault().(type) {
|
||||
case *types.InvalidState:
|
||||
op.Warnf("container VM %s is in invalid state, unregistering", c)
|
||||
if err := c.vm.Unregister(op); err != nil {
|
||||
op.Errorf("Error while attempting to unregister container VM %s: %s", c, err)
|
||||
return err
|
||||
}
|
||||
case *types.ConcurrentAccess:
|
||||
// We are getting ConcurrentAccess errors from DeleteExceptDisks - even though we don't set ChangeVersion in that path
|
||||
// We are ignoring the error because in reality the operation finishes successfully.
|
||||
op.Warnf("DeleteExceptDisks failed with ConcurrentAccess error for %s. Ignoring it.", c)
|
||||
concurrent = true
|
||||
default:
|
||||
op.Debugf("Unhandled fault while attempting to destroy vm %s: %#v", c, f.Fault())
|
||||
|
||||
c.updateState(op, existingState)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if concurrent && c.vm.IsVC() {
|
||||
if err := c.vm.Unregister(op); err != nil {
|
||||
if !IsNotFoundError(err) {
|
||||
op.Errorf("Error while attempting to unregister container VM %s: %s", c, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove from datastore
|
||||
fm := ds.NewFileManager(sess.Datacenter, true)
|
||||
if err = fm.Delete(op, url.Path); err != nil {
|
||||
// at this phase error doesn't matter. Just log it.
|
||||
op.Debugf("Failed to delete %s, %s for %s", url, err, c)
|
||||
}
|
||||
|
||||
//remove container from cache
|
||||
Containers.Remove(c.ExecConfig.ID)
|
||||
publishContainerEvent(op, c.ExecConfig.ID, time.Now(), events.ContainerRemoved)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// eventedState will determine the target container
|
||||
// state based on the current container state and the vsphere event
|
||||
func eventedState(op trace.Operation, e events.Event, current State) State {
|
||||
switch e.String() {
|
||||
case events.ContainerPoweredOn:
|
||||
// are we in the process of starting
|
||||
if current != StateStarting {
|
||||
return StateRunning
|
||||
}
|
||||
case events.ContainerPoweredOff:
|
||||
// are we in the process of stopping or just created
|
||||
if current != StateStopping && current != StateCreated {
|
||||
return StateStopped
|
||||
}
|
||||
case events.ContainerSuspended:
|
||||
// are we in the process of suspending
|
||||
if current != StateSuspending {
|
||||
return StateSuspended
|
||||
}
|
||||
case events.ContainerRemoved:
|
||||
if current != StateRemoving {
|
||||
return StateRemoved
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
func (c *Container) OnEvent(e events.Event) {
|
||||
op := trace.NewOperation(context.Background(), "OnEvent")
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("eventID(%s) received for event: %s", e.EventID(), e.String()), op))
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.vm == nil {
|
||||
op.Warnf("Event(%s) received for %s but no VM found", e.EventID(), e.Reference())
|
||||
return
|
||||
}
|
||||
|
||||
newState := eventedState(op, e, c.state)
|
||||
c.onEvent(op, newState, e)
|
||||
}
|
||||
|
||||
// determine if the containerVM has started - this could pick up stale data in the started field for an out-of-band
|
||||
// power change such as HA or user intervention where we have not had an opportunity to reset the entry.
|
||||
func cleanStart(op trace.Operation, c *Container) bool {
|
||||
if len(c.ExecConfig.Sessions) == 0 {
|
||||
op.Warnf("Container %c has no sessions stored in in-memory config", c.ExecConfig.ID)
|
||||
// if no sessions, then nothing to wait for
|
||||
return true
|
||||
}
|
||||
|
||||
for _, session := range c.ExecConfig.Sessions {
|
||||
if session.Started != "true" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// onEvent determines what needs to be done when receiving a state update. It filters duplicate state transitions
|
||||
// and publishes container events as needed in addition to performing necessary manipulations.
|
||||
// newState - this is the new state determined by eventedState
|
||||
// e - the source event used to derive the new State and reason for the transition
|
||||
func (c *Container) onEvent(op trace.Operation, newState State, e events.Event) {
|
||||
// does local data report full start
|
||||
started := cleanStart(op, c)
|
||||
// do we need a refresh
|
||||
refresh := e.String() == events.ContainerRelocated
|
||||
// if it's a state event we've already done a refresh to end up here and dont need another
|
||||
_, stateEvent := e.(*stateevents.StateEvent)
|
||||
// the event we're going to publish - may be overridden/transformed by more context aware logic below
|
||||
// the incoming event is from the very coarse vSphere events
|
||||
publishEventType := e.String()
|
||||
|
||||
if !stateEvent {
|
||||
if (newState == StateStarting && !started) || newState == StateStopping {
|
||||
// inherently transient state. Starting with started == true is just accounting that will
|
||||
// happen below and doesn't need a refresh.
|
||||
refresh = true
|
||||
}
|
||||
|
||||
if newState == StateRunning && !started {
|
||||
// if we cannot confirm fully initialized
|
||||
refresh = true
|
||||
}
|
||||
}
|
||||
|
||||
if refresh {
|
||||
op, cancel := trace.WithTimeout(&op, constants.PropertyCollectorTimeout, "vSphere event triggered refresh")
|
||||
defer cancel()
|
||||
|
||||
if err := c.refresh(op); err != nil {
|
||||
op.Errorf("Container(%s) event driven update failed: %s", c, err)
|
||||
}
|
||||
}
|
||||
|
||||
started = cleanStart(op, c)
|
||||
// it doesn't matter how the event was translated, if we're not fully started then we're starting
|
||||
// if we are then we're running. Only exception is that we don't transition from Running->Starting
|
||||
if newState == StateRunning && !started && c.state != StateRunning {
|
||||
newState = StateStarting
|
||||
}
|
||||
if newState == StateStarting && started {
|
||||
newState = StateRunning
|
||||
}
|
||||
|
||||
if newState != c.state {
|
||||
switch newState {
|
||||
case StateRunning:
|
||||
// transform the PoweredOn event into Started
|
||||
publishEventType = events.ContainerStarted
|
||||
fallthrough
|
||||
|
||||
case StateStarting,
|
||||
StateStopping,
|
||||
StateStopped,
|
||||
StateSuspended:
|
||||
|
||||
c.updateState(op, newState)
|
||||
if newState == StateStopped {
|
||||
c.onStop()
|
||||
}
|
||||
case StateRemoved:
|
||||
if c.vm != nil && c.vm.IsFixing() {
|
||||
// is fixing vm, which will be registered back soon, so do not remove from containers cache
|
||||
op.Debugf("Container(%s) %s is being fixed - %s event ignored", c, newState)
|
||||
|
||||
// Received remove event triggered by unregister VM operation - leave
|
||||
// fixing state now. In a loaded environment, the remove event may be
|
||||
// received after vm.fixVM() has returned, at which point the container
|
||||
// should still be in fixing state to avoid removing it from the cache below.
|
||||
c.vm.LeaveFixingState()
|
||||
// since we're leaving the container in cache, just return w/o allowing
|
||||
// a container event to be propogated to subscribers
|
||||
return
|
||||
}
|
||||
op.Debugf("Container(%s) %s via event activity", c, newState)
|
||||
// if we are here the containerVM has been removed from vSphere, so lets remove it
|
||||
// from the portLayer cache
|
||||
Containers.Remove(c.ExecConfig.ID)
|
||||
c.vm = nil
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
op.Debugf("Container (%s) publishing event (state=%s, event=%s) from event %s", c, newState, publishEventType, e.String())
|
||||
// regardless of state update success or failure publish the container event
|
||||
publishContainerEvent(op, c.ExecConfig.ID, e.Created(), publishEventType)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get the containerVMs from infrastructure for this resource pool
|
||||
func infraContainers(ctx context.Context, sess *session.Session) ([]*Container, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
var rp mo.ResourcePool
|
||||
|
||||
// popluate the vm property of the vch resource pool
|
||||
if err := Config.ResourcePool.Properties(ctx, Config.ResourcePool.Reference(), []string{"vm"}, &rp); err != nil {
|
||||
name := Config.ResourcePool.Name()
|
||||
log.Errorf("List failed to get %s resource pool child vms: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
vms, err := populateVMAttributes(ctx, sess, rp.Vm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertInfraContainers(ctx, sess, vms), nil
|
||||
}
|
||||
|
||||
func instanceUUID(id string) (string, error) {
|
||||
// generate VM instance uuid, which will be used to query back VM
|
||||
u, err := sys.UUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
namespace, err := uuid.Parse(u)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to parse VCH uuid: %s", err)
|
||||
}
|
||||
return uuid.NewSHA1(namespace, []byte(id)).String(), nil
|
||||
}
|
||||
|
||||
// populate the vm attributes for the specified morefs
|
||||
func populateVMAttributes(ctx context.Context, sess *session.Session, refs []types.ManagedObjectReference) ([]mo.VirtualMachine, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("populating %d refs", len(refs))))
|
||||
var vms []mo.VirtualMachine
|
||||
|
||||
// current attributes we care about
|
||||
attrib := []string{"config", "runtime.powerState", "summary"}
|
||||
|
||||
// populate the vm properties
|
||||
err := sess.Retrieve(ctx, refs, attrib, &vms)
|
||||
return vms, err
|
||||
}
|
||||
|
||||
// convert the infra containers to a container object
|
||||
func convertInfraContainers(ctx context.Context, sess *session.Session, vms []mo.VirtualMachine) []*Container {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("converting %d containers", len(vms))))
|
||||
var cons []*Container
|
||||
|
||||
for _, v := range vms {
|
||||
vm := vm.NewVirtualMachine(ctx, sess, v.Reference())
|
||||
base := newBase(vm, v.Config, &v.Runtime)
|
||||
c := newContainer(base)
|
||||
|
||||
id := uid.Parse(c.ExecConfig.ID)
|
||||
if id == uid.NilUID {
|
||||
log.Warnf("skipping converting container VM %s: could not parse id", v.Reference())
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Summary.Storage != nil {
|
||||
c.VMUnsharedDisk = v.Summary.Storage.Unshared
|
||||
}
|
||||
|
||||
cons = append(cons, c)
|
||||
}
|
||||
|
||||
return cons
|
||||
}
|
||||
138
vendor/github.com/vmware/vic/lib/portlayer/exec/container_cache.go
generated
vendored
Normal file
138
vendor/github.com/vmware/vic/lib/portlayer/exec/container_cache.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2016-2018 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 exec
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
/*
|
||||
* ContainerCache will provide an in-memory cache of containerVMs. It will
|
||||
* be refreshed on portlayer start and updated via container lifecycle
|
||||
* operations (start, stop, rm) and well as in response to infrastructure
|
||||
* events
|
||||
*/
|
||||
type containerCache struct {
|
||||
m sync.RWMutex
|
||||
|
||||
// cache by container id
|
||||
cache map[string]*Container
|
||||
}
|
||||
|
||||
var Containers *containerCache
|
||||
|
||||
func NewContainerCache() {
|
||||
// cache by the container ID and the vsphere
|
||||
// managed object reference
|
||||
Containers = &containerCache{
|
||||
cache: make(map[string]*Container),
|
||||
}
|
||||
}
|
||||
|
||||
func (conCache *containerCache) Container(idOrRef string) *Container {
|
||||
conCache.m.RLock()
|
||||
defer conCache.m.RUnlock()
|
||||
// find by id or moref
|
||||
return conCache.cache[idOrRef]
|
||||
}
|
||||
|
||||
func (conCache *containerCache) Containers(states []State) []*Container {
|
||||
conCache.m.RLock()
|
||||
defer conCache.m.RUnlock()
|
||||
// cache contains 2 items for each container
|
||||
capacity := len(conCache.cache) / 2
|
||||
containers := make([]*Container, 0, capacity)
|
||||
|
||||
for id, con := range conCache.cache {
|
||||
// is the key a proper ID?
|
||||
if !isContainerID(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// no state filtering
|
||||
if len(states) == 0 {
|
||||
containers = append(containers, con)
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by container state
|
||||
// DO NOT use container.CurrentState as that can
|
||||
// cause cache deadlocks
|
||||
for _, state := range states {
|
||||
if state == con.State() {
|
||||
containers = append(containers, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return containers
|
||||
}
|
||||
|
||||
// puts a container in the cache and will overwrite an existing container
|
||||
func (conCache *containerCache) Put(container *Container) {
|
||||
// only add containers w/backing VMs
|
||||
if container.vm == nil {
|
||||
return
|
||||
}
|
||||
|
||||
conCache.m.Lock()
|
||||
defer conCache.m.Unlock()
|
||||
|
||||
conCache.put(container)
|
||||
}
|
||||
|
||||
func (conCache *containerCache) put(container *Container) {
|
||||
// add pointer to cache by container ID
|
||||
conCache.cache[container.ExecConfig.ID] = container
|
||||
conCache.cache[container.vm.Reference().String()] = container
|
||||
|
||||
}
|
||||
|
||||
func (conCache *containerCache) Remove(idOrRef string) {
|
||||
conCache.m.Lock()
|
||||
defer conCache.m.Unlock()
|
||||
// find by id
|
||||
container := conCache.cache[idOrRef]
|
||||
if container != nil {
|
||||
delete(conCache.cache, container.ExecConfig.ID)
|
||||
delete(conCache.cache, container.vm.Reference().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (conCache *containerCache) sync(ctx context.Context, sess *session.Session) error {
|
||||
conCache.m.Lock()
|
||||
defer conCache.m.Unlock()
|
||||
|
||||
cons, err := infraContainers(ctx, sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conCache.cache = make(map[string]*Container)
|
||||
for _, c := range cons {
|
||||
conCache.put(c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isContainerID(id string) bool {
|
||||
return uid.Parse(id) != uid.NilUID
|
||||
}
|
||||
79
vendor/github.com/vmware/vic/lib/portlayer/exec/container_cache_test.go
generated
vendored
Normal file
79
vendor/github.com/vmware/vic/lib/portlayer/exec/container_cache_test.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
func TestContainerCache(t *testing.T) {
|
||||
NewContainerCache()
|
||||
containerID := uid.New().String()
|
||||
|
||||
// create a new container
|
||||
container := newTestContainer(containerID)
|
||||
|
||||
// put it in the cache
|
||||
Containers.Put(container)
|
||||
// still shouldn't have a container because there's no vm
|
||||
assert.Equal(t, len(Containers.cache), 0)
|
||||
|
||||
// add a test vm
|
||||
addTestVM(container)
|
||||
|
||||
// put in cache
|
||||
Containers.Put(container)
|
||||
// get all containers -- should have 1
|
||||
assert.Equal(t, len(Containers.Containers(nil)), 1)
|
||||
// Get specific container
|
||||
cachedContainer := Containers.Container(containerID)
|
||||
// did we find it?
|
||||
assert.NotNil(t, cachedContainer)
|
||||
// do we have this one in the cache?
|
||||
assert.Equal(t, cachedContainer.ExecConfig.ID, containerID)
|
||||
// remove the container
|
||||
Containers.Remove(containerID)
|
||||
assert.Equal(t, len(Containers.cache), 0)
|
||||
// remove non-existent container
|
||||
Containers.Remove("blahblah")
|
||||
}
|
||||
|
||||
func TestIsContainerID(t *testing.T) {
|
||||
validID := uid.New().String()
|
||||
invalidID := "ABC-XZ_@"
|
||||
|
||||
assert.True(t, isContainerID(validID))
|
||||
assert.False(t, isContainerID(invalidID))
|
||||
}
|
||||
|
||||
// addTestVM will add a pseudo VM to the container
|
||||
func addTestVM(container *Container) {
|
||||
mo := types.ManagedObjectReference{Type: "vm", Value: "12"}
|
||||
v := object.NewVirtualMachine(nil, mo)
|
||||
container.vm = vm.NewVirtualMachineFromVM(nil, nil, v)
|
||||
}
|
||||
|
||||
func newTestContainer(id string) *Container {
|
||||
h := TestHandle(id)
|
||||
return newContainer(&h.containerBase)
|
||||
}
|
||||
58
vendor/github.com/vmware/vic/lib/portlayer/exec/container_test.go
generated
vendored
Normal file
58
vendor/github.com/vmware/vic/lib/portlayer/exec/container_test.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 exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
func TestStateStringer(t *testing.T) {
|
||||
|
||||
c := &Container{
|
||||
ContainerInfo: ContainerInfo{
|
||||
state: StateRunning,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, "Running", c.state.String())
|
||||
c.state = StateStopped
|
||||
assert.Equal(t, "Stopped", c.state.String())
|
||||
c.state = StateStopping
|
||||
assert.Equal(t, "Stopping", c.state.String())
|
||||
c.state = StateRemoving
|
||||
assert.Equal(t, "Removing", c.state.String())
|
||||
c.state = StateStarting
|
||||
assert.Equal(t, "Starting", c.state.String())
|
||||
c.state = StateCreated
|
||||
assert.Equal(t, "Created", c.state.String())
|
||||
}
|
||||
|
||||
func NewContainer(id uid.UID) *Handle {
|
||||
con := &Container{
|
||||
ContainerInfo: ContainerInfo{
|
||||
state: StateCreating,
|
||||
},
|
||||
newStateEvents: make(map[State]chan struct{}),
|
||||
}
|
||||
|
||||
h := newHandle(con)
|
||||
h.ExecConfig.ID = id.String()
|
||||
|
||||
return h
|
||||
}
|
||||
156
vendor/github.com/vmware/vic/lib/portlayer/exec/exec.go
generated
vendored
Normal file
156
vendor/github.com/vmware/vic/lib/portlayer/exec/exec.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 exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/event"
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
var (
|
||||
initializer struct {
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
)
|
||||
|
||||
func Init(ctx context.Context, sess *session.Session, source extraconfig.DataSource, _ extraconfig.DataSink) error {
|
||||
log.Info("Beginning initialization of portlayer exec component")
|
||||
initializer.once.Do(func() {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
initializer.err = err
|
||||
}
|
||||
}()
|
||||
f := find.NewFinder(sess.Vim25(), false)
|
||||
|
||||
extraconfig.Decode(source, &Config)
|
||||
|
||||
log.Debugf("Decoded VCH config for execution: %#v", Config)
|
||||
ccount := len(Config.ComputeResources)
|
||||
if ccount != 1 {
|
||||
err = fmt.Errorf("expected singular compute resource element, found %d", ccount)
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
cr := Config.ComputeResources[0]
|
||||
var r object.Reference
|
||||
r, err = f.ObjectReference(ctx, cr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get resource pool or virtual app reference from %q: %s", cr.String(), err)
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
switch o := r.(type) {
|
||||
case *object.VirtualApp:
|
||||
Config.VirtualApp = o
|
||||
Config.ResourcePool = o.ResourcePool
|
||||
case *object.ResourcePool:
|
||||
Config.ResourcePool = o
|
||||
default:
|
||||
err = fmt.Errorf("could not get resource pool or virtual app from reference %q: object type is wrong", cr.String())
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// we want to monitor the cluster, so create a vSphere Event Collector
|
||||
// The cluster managed object will either be a proper vSphere Cluster or
|
||||
// a specific host when standalone mode
|
||||
ec := vsphere.NewCollector(sess.Vim25(), sess.Cluster.Reference().String())
|
||||
|
||||
// start the collection of vsphere events
|
||||
err = ec.Start()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s failed to start: %s", ec.Name(), err)
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// instantiate the container cache now
|
||||
NewContainerCache()
|
||||
|
||||
// create the event manager & register the existing collector
|
||||
Config.EventManager = event.NewEventManager(ec)
|
||||
|
||||
// subscribe the exec layer to the event stream for Vm events
|
||||
vmSub := Config.EventManager.Subscribe(events.NewEventType(vsphere.VMEvent{}).Topic(), "exec", func(e events.Event) {
|
||||
if c := Containers.Container(e.Reference()); c != nil {
|
||||
c.OnEvent(e)
|
||||
}
|
||||
})
|
||||
|
||||
// Grab the AboutInfo about our host environment
|
||||
about := sess.Vim25().ServiceContent.About
|
||||
|
||||
vch := GetVCHstats(ctx)
|
||||
Config.VCHMhz = vch.CPULimit
|
||||
Config.VCHMemoryLimit = vch.MemoryLimit
|
||||
|
||||
Config.HostOS = about.OsType
|
||||
Config.HostOSVersion = about.Version
|
||||
Config.HostProductName = about.Name
|
||||
log.Debugf("Host - OS (%s), version (%s), name (%s)", about.OsType, about.Version, about.Name)
|
||||
log.Debugf("VCH limits - %d Mhz, %d MB", Config.VCHMhz, Config.VCHMemoryLimit)
|
||||
|
||||
// sync container cache
|
||||
vmSub.Suspend(true)
|
||||
defer vmSub.Resume()
|
||||
log.Info("Syncing container cache")
|
||||
if err = Containers.sync(ctx, sess); err != nil {
|
||||
log.Errorf("Error encountered during container cache sync during init process: %s", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
return initializer.err
|
||||
}
|
||||
|
||||
// publishContainerEvent will publish a ContainerEvent to the vic event stream
|
||||
func publishContainerEvent(op trace.Operation, id string, created time.Time, eventType string) {
|
||||
if Config.EventManager == nil || eventType == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ce := &events.ContainerEvent{
|
||||
BaseEvent: &events.BaseEvent{
|
||||
// containerEvents are a construct of vic, so lets set the
|
||||
// ID equal to the operation that created the event
|
||||
ID: op.ID(),
|
||||
Ref: id,
|
||||
CreatedTime: created,
|
||||
Event: eventType,
|
||||
Detail: fmt.Sprintf("Container %s %s", id, eventType),
|
||||
},
|
||||
}
|
||||
|
||||
Config.EventManager.Publish(ce)
|
||||
}
|
||||
180
vendor/github.com/vmware/vic/lib/portlayer/exec/exec_test.go
generated
vendored
Normal file
180
vendor/github.com/vmware/vic/lib/portlayer/exec/exec_test.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2016-2018 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 exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/event"
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var containerEvents []events.Event
|
||||
|
||||
func TestEventedState(t *testing.T) {
|
||||
id := "123439"
|
||||
container := newTestContainer(id)
|
||||
addTestVM(container)
|
||||
op := trace.NewOperation(context.Background(), "test operations")
|
||||
// poweredOn event
|
||||
event := createVMEvent(container, StateRunning)
|
||||
assert.EqualValues(t, StateStarting, eventedState(op, event, StateStarting))
|
||||
assert.EqualValues(t, StateRunning, eventedState(op, event, StateRunning))
|
||||
assert.EqualValues(t, StateRunning, eventedState(op, event, StateStopped))
|
||||
assert.EqualValues(t, StateRunning, eventedState(op, event, StateSuspended))
|
||||
|
||||
// // powerOff event
|
||||
event = createVMEvent(container, StateStopped)
|
||||
assert.EqualValues(t, StateStopping, eventedState(op, event, StateStopping))
|
||||
assert.EqualValues(t, StateStopped, eventedState(op, event, StateStopped))
|
||||
assert.EqualValues(t, StateStopped, eventedState(op, event, StateRunning))
|
||||
|
||||
// // suspended event
|
||||
event = createVMEvent(container, StateSuspended)
|
||||
assert.EqualValues(t, StateSuspending, eventedState(op, event, StateSuspending))
|
||||
assert.EqualValues(t, StateSuspended, eventedState(op, event, StateSuspended))
|
||||
assert.EqualValues(t, StateSuspended, eventedState(op, event, StateRunning))
|
||||
|
||||
// removed event
|
||||
event = createVMEvent(container, StateRemoved)
|
||||
assert.EqualValues(t, StateRemoved, eventedState(op, event, StateRemoved))
|
||||
assert.EqualValues(t, StateRemoved, eventedState(op, event, StateStopped))
|
||||
assert.EqualValues(t, StateRemoving, eventedState(op, event, StateRemoving))
|
||||
}
|
||||
|
||||
func TestPublishContainerEvent(t *testing.T) {
|
||||
|
||||
NewContainerCache()
|
||||
containerEvents = make([]events.Event, 0)
|
||||
Config = Configuration{}
|
||||
|
||||
mgr := event.NewEventManager()
|
||||
Config.EventManager = mgr
|
||||
mgr.Subscribe(events.NewEventType(events.ContainerEvent{}).Topic(), "testing", containerCallback)
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test publish event operation")
|
||||
// create new running container and place in cache
|
||||
id := "123439"
|
||||
container := newTestContainer(id)
|
||||
addTestVM(container)
|
||||
container.SetState(op, StateRunning)
|
||||
Containers.Put(container)
|
||||
|
||||
publishContainerEvent(trace.NewOperation(context.Background(), "container"), id, time.Now().UTC(), events.ContainerPoweredOff)
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
|
||||
assert.Equal(t, 1, len(containerEvents))
|
||||
assert.Equal(t, id, containerEvents[0].Reference())
|
||||
assert.Equal(t, events.ContainerPoweredOff, containerEvents[0].String())
|
||||
}
|
||||
|
||||
func TestVMRemovedEventCallback(t *testing.T) {
|
||||
|
||||
NewContainerCache()
|
||||
containerEvents = make([]events.Event, 0)
|
||||
Config = Configuration{}
|
||||
|
||||
mgr := event.NewEventManager()
|
||||
Config.EventManager = mgr
|
||||
|
||||
// subscribe the exec layer to the event stream for VM events
|
||||
mgr.Subscribe(events.NewEventType(&vsphere.VMEvent{}).Topic(), "testing", func(e events.Event) {
|
||||
if c := Containers.Container(e.Reference()); c != nil {
|
||||
c.OnEvent(e)
|
||||
}
|
||||
})
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test removed event operation")
|
||||
// create new running container and place in cache
|
||||
id := "123439"
|
||||
container := newTestContainer(id)
|
||||
addTestVM(container)
|
||||
container.SetState(op, StateRunning)
|
||||
Containers.Put(container)
|
||||
|
||||
container.vm.EnterFixingState()
|
||||
vmEvent := createVMEvent(container, StateRemoved)
|
||||
|
||||
mgr.Publish(vmEvent)
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
assertMsg := "Container should have left fixing state in VM remove event handler"
|
||||
assert.False(t, container.vm.IsFixing(), assertMsg)
|
||||
|
||||
mgr.Publish(vmEvent)
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
assertMsg = "Container should be removed now that it has left fixing state"
|
||||
assert.True(t, Containers.Container(id) == nil, assertMsg)
|
||||
}
|
||||
|
||||
func containerCallback(ee events.Event) {
|
||||
containerEvents = append(containerEvents, ee)
|
||||
}
|
||||
|
||||
func createVMEvent(container *Container, state State) *vsphere.VMEvent {
|
||||
// event to return
|
||||
var vmEvent *vsphere.VMEvent
|
||||
// basic event info
|
||||
vme := types.Event{
|
||||
CreatedTime: time.Now().UTC(),
|
||||
Key: int32(101),
|
||||
Vm: &types.VmEventArgument{
|
||||
Vm: container.vm.Reference(),
|
||||
},
|
||||
}
|
||||
|
||||
switch state {
|
||||
case StateSuspended:
|
||||
// suspended
|
||||
vmwEve := &types.VmSuspendedEvent{
|
||||
VmEvent: types.VmEvent{
|
||||
Event: vme,
|
||||
},
|
||||
}
|
||||
vmEvent = vsphere.NewVMEvent(vmwEve)
|
||||
case StateStopped:
|
||||
// poweredOff
|
||||
vmwEve := &types.VmPoweredOffEvent{
|
||||
VmEvent: types.VmEvent{
|
||||
Event: vme,
|
||||
},
|
||||
}
|
||||
vmEvent = vsphere.NewVMEvent(vmwEve)
|
||||
case StateRemoved:
|
||||
// removed
|
||||
vmwEve := &types.VmRemovedEvent{
|
||||
VmEvent: types.VmEvent{
|
||||
Event: vme,
|
||||
},
|
||||
}
|
||||
vmEvent = vsphere.NewVMEvent(vmwEve)
|
||||
default:
|
||||
// poweredOn
|
||||
vmwEve := &types.VmPoweredOnEvent{
|
||||
VmEvent: types.VmEvent{
|
||||
Event: vme,
|
||||
},
|
||||
}
|
||||
vmEvent = vsphere.NewVMEvent(vmwEve)
|
||||
}
|
||||
|
||||
return vmEvent
|
||||
}
|
||||
374
vendor/github.com/vmware/vic/lib/portlayer/exec/handle.go
generated
vendored
Normal file
374
vendor/github.com/vmware/vic/lib/portlayer/exec/handle.go
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
// Copyright 2016-2018 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 exec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/golang/groupcache/lru"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/lib/spec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// Resources describes the resource allocation for the containerVM
|
||||
type Resources struct {
|
||||
NumCPUs int64
|
||||
MemoryMB int64
|
||||
}
|
||||
|
||||
// ContainerCreateConfig defines the parameters for Create call
|
||||
type ContainerCreateConfig struct {
|
||||
Metadata *executor.ExecutorConfig
|
||||
|
||||
Resources Resources
|
||||
}
|
||||
|
||||
var handles *lru.Cache
|
||||
var handlesLock sync.Mutex
|
||||
|
||||
const (
|
||||
handleLen = 16
|
||||
lruSize = 1000
|
||||
)
|
||||
|
||||
func init() {
|
||||
handles = lru.New(lruSize)
|
||||
}
|
||||
|
||||
type Handle struct {
|
||||
// copy from container cache
|
||||
containerBase
|
||||
|
||||
// The guest used to generate specific device types
|
||||
Guest guest.Guest
|
||||
|
||||
// desired spec
|
||||
Spec *spec.VirtualMachineConfigSpec
|
||||
// desired changes to extraconfig
|
||||
changes []types.BaseOptionValue
|
||||
|
||||
// desired state
|
||||
targetState State
|
||||
|
||||
// should this change trigger a reload in the target container
|
||||
reload bool
|
||||
|
||||
// allow for passing outside of the process
|
||||
key string
|
||||
}
|
||||
|
||||
func newHandleKey() string {
|
||||
b := make([]byte, handleLen)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
panic(err) // This shouldn't happen
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// Added solely to support testing - need a better way to do this
|
||||
func TestHandle(id string) *Handle {
|
||||
defer trace.End(trace.Begin("Handle.Create"))
|
||||
|
||||
h := newHandle(&Container{})
|
||||
h.ExecConfig.ID = id
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// newHandle creates a handle for an existing container
|
||||
// con must not be nil
|
||||
func newHandle(con *Container) *Handle {
|
||||
h := &Handle{
|
||||
key: newHandleKey(),
|
||||
targetState: StateUnknown,
|
||||
containerBase: *newBase(con.vm, con.Config, con.Runtime),
|
||||
// currently every operation has a spec, because even the power operations
|
||||
// make changes to extraconfig for timestamps and session status
|
||||
Spec: &spec.VirtualMachineConfigSpec{
|
||||
VirtualMachineConfigSpec: &types.VirtualMachineConfigSpec{},
|
||||
},
|
||||
}
|
||||
|
||||
handlesLock.Lock()
|
||||
defer handlesLock.Unlock()
|
||||
|
||||
handles.Add(h.key, h)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handle) TargetState() State {
|
||||
return h.targetState
|
||||
}
|
||||
|
||||
func (h *Handle) SetTargetState(s State) {
|
||||
h.targetState = s
|
||||
}
|
||||
|
||||
func (h *Handle) Reload() {
|
||||
h.reload = true
|
||||
}
|
||||
|
||||
// Rename updates the container name in ExecConfig as well as the vSphere display name
|
||||
func (h *Handle) Rename(op trace.Operation, newName string) *Handle {
|
||||
defer trace.End(trace.Begin(newName))
|
||||
|
||||
h.ExecConfig.Name = newName
|
||||
|
||||
s := &spec.VirtualMachineConfigSpecConfig{
|
||||
ID: h.ExecConfig.ID,
|
||||
Name: newName,
|
||||
}
|
||||
|
||||
h.Spec.Spec().Name = util.DisplayName(op, s, Config.ContainerNameConvention)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// GetHandle finds and returns the handle that is referred by key
|
||||
func GetHandle(key string) *Handle {
|
||||
handlesLock.Lock()
|
||||
defer handlesLock.Unlock()
|
||||
|
||||
if h, ok := handles.Get(key); ok {
|
||||
return h.(*Handle)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleFromInterface returns the Handle
|
||||
func HandleFromInterface(key interface{}) *Handle {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if h, ok := key.(string); ok {
|
||||
return GetHandle(h)
|
||||
}
|
||||
|
||||
log.Errorf("Type assertion failed for %#+v", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReferenceFromHandle returns the reference of the given handle
|
||||
func ReferenceFromHandle(handle interface{}) interface{} {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if h, ok := handle.(*Handle); ok {
|
||||
return h.String()
|
||||
}
|
||||
|
||||
log.Errorf("Type assertion failed for %#+v", handle)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handle) String() string {
|
||||
return h.key
|
||||
}
|
||||
|
||||
func (h *Handle) Commit(op trace.Operation, sess *session.Session, waitTime *int32) error {
|
||||
|
||||
cfg := make(map[string]string)
|
||||
|
||||
// Set timestamps based on target state
|
||||
switch h.TargetState() {
|
||||
case StateRunning:
|
||||
for _, sc := range h.ExecConfig.Sessions {
|
||||
sc.StartTime = time.Now().UTC().Unix()
|
||||
sc.Started = ""
|
||||
sc.ExitStatus = 0
|
||||
}
|
||||
case StateStopped:
|
||||
for _, sc := range h.ExecConfig.Sessions {
|
||||
sc.StopTime = time.Now().UTC().Unix()
|
||||
sc.Started = ""
|
||||
}
|
||||
}
|
||||
|
||||
s := h.Spec.Spec()
|
||||
if h.Config != nil {
|
||||
s.ChangeVersion = h.Config.ChangeVersion
|
||||
}
|
||||
|
||||
// if runtime is nil, should be fresh container create
|
||||
var filter int
|
||||
if h.Runtime == nil || h.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOff || h.TargetState() == StateStopped {
|
||||
// any values set with VM powered off are inherently persistent
|
||||
filter = ^extraconfig.NonPersistent
|
||||
} else {
|
||||
filter = extraconfig.NonPersistent | extraconfig.Hidden
|
||||
}
|
||||
|
||||
extraconfig.Encode(extraconfig.ScopeFilterSink(uint(filter), extraconfig.MapSink(cfg)), h.ExecConfig)
|
||||
|
||||
// strip unmodified keys from the update
|
||||
if h.Config != nil {
|
||||
h.changes = append(s.ExtraConfig, vmomi.OptionValueUpdatesFromMap(h.Config.ExtraConfig, cfg)...)
|
||||
} else {
|
||||
h.changes = append(s.ExtraConfig, vmomi.OptionValueFromMap(cfg, true)...)
|
||||
}
|
||||
s.ExtraConfig = h.changes
|
||||
|
||||
if err := Commit(op, sess, h, waitTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// refresh is for internal use only - it's sole purpose at this time is to allow the stop path to update ChangeVersion
|
||||
// and corresponding state before performing any associated reconfigure
|
||||
func (h *Handle) refresh(op trace.Operation) {
|
||||
// update Config and Runtime to reflect current state
|
||||
h.containerBase.refresh(op)
|
||||
|
||||
// reapply extraconfig changes
|
||||
s := h.Spec.Spec()
|
||||
|
||||
s.ExtraConfig = h.changes
|
||||
s.ChangeVersion = h.Config.ChangeVersion
|
||||
}
|
||||
|
||||
func (h *Handle) Close() {
|
||||
handlesLock.Lock()
|
||||
defer handlesLock.Unlock()
|
||||
|
||||
handles.Remove(h.key)
|
||||
}
|
||||
|
||||
// Create returns a new handle that can be Committed to create a new container.
|
||||
// At this time the config is *not* deep copied so should not be changed once passed
|
||||
//
|
||||
// TODO: either deep copy the configuration, or provide an alternative means of passing the data that
|
||||
// avoids the need for the caller to unpack/repack the parameters
|
||||
func Create(ctx context.Context, vmomiSession *session.Session, config *ContainerCreateConfig) (*Handle, error) {
|
||||
op := trace.FromContext(ctx, "Handle.Create")
|
||||
defer trace.End(trace.Begin(config.Metadata.Name, op))
|
||||
|
||||
h := &Handle{
|
||||
key: newHandleKey(),
|
||||
targetState: StateCreated,
|
||||
containerBase: containerBase{
|
||||
ExecConfig: config.Metadata,
|
||||
},
|
||||
}
|
||||
|
||||
// configure with debug
|
||||
h.ExecConfig.Diagnostics.DebugLevel = Config.DebugLevel
|
||||
h.ExecConfig.Diagnostics.SysLogConfig = Config.SysLogConfig
|
||||
|
||||
// Convert the management hostname to IP
|
||||
ips, err := net.LookupIP(constants.ManagementHostName)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to look up %s during create of %s: %s", constants.ManagementHostName, config.Metadata.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
log.Errorf("No IP found for %s during create of %s", constants.ManagementHostName, config.Metadata.ID)
|
||||
return nil, fmt.Errorf("No IP found on %s", constants.ManagementHostName)
|
||||
}
|
||||
|
||||
if len(ips) > 1 {
|
||||
log.Errorf("Multiple IPs found for %s during create of %s: %v", constants.ManagementHostName, config.Metadata.ID, ips)
|
||||
return nil, fmt.Errorf("Multiple IPs found on %s: %#v", constants.ManagementHostName, ips)
|
||||
}
|
||||
|
||||
uuid, err := instanceUUID(config.Metadata.ID)
|
||||
if err != nil {
|
||||
detail := fmt.Sprintf("unable to get instance UUID: %s", err)
|
||||
log.Error(detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
specconfig := &spec.VirtualMachineConfigSpecConfig{
|
||||
NumCPUs: int32(config.Resources.NumCPUs),
|
||||
MemoryMB: config.Resources.MemoryMB,
|
||||
|
||||
ID: config.Metadata.ID,
|
||||
Name: config.Metadata.Name,
|
||||
BiosUUID: uuid,
|
||||
|
||||
// TODO: make this toggle for pod or single based on number of images joined
|
||||
BootMediaPath: Config.BootstrapImagePath,
|
||||
VMPathName: fmt.Sprintf("[%s]", vmomiSession.Datastore.Name()),
|
||||
|
||||
Metadata: config.Metadata,
|
||||
}
|
||||
|
||||
// if not vsan, set the datastore folder name to containerID
|
||||
if !vmomiSession.IsVSAN(op) {
|
||||
specconfig.VMPathName = fmt.Sprintf("[%s] %s/%s.vmx", vmomiSession.Datastore.Name(), specconfig.ID, specconfig.ID)
|
||||
}
|
||||
|
||||
specconfig.VMFullName = util.DisplayName(op, specconfig, Config.ContainerNameConvention)
|
||||
|
||||
// log only core portions
|
||||
s := specconfig
|
||||
log.Debugf("id: %s, name: %s, cpu: %d, mem: %d, parent: %s, os: %s, path: %s", s.ID, s.Name, s.NumCPUs, s.MemoryMB, s.ParentImageID, s.BootMediaPath, s.VMPathName)
|
||||
m := s.Metadata
|
||||
log.Debugf("annotations: %#v, reponame: %s", m.Annotations, m.RepoName)
|
||||
for name, sess := range m.Sessions {
|
||||
log.Debugf("session: %s, path: %s, dir: %s, runblock: %t, tty: %t, restart: %t, stdin: %t, stopsig: %s",
|
||||
name, sess.Cmd.Path, sess.Cmd.Dir, sess.RunBlock, sess.Tty, sess.Restart, sess.OpenStdin, sess.StopSignal)
|
||||
}
|
||||
|
||||
// If the debug level is high, dump everything
|
||||
// we still do the logging above for consistency so searching the logs for common strings works.
|
||||
// TODO: move this into a debug level aware structure renderer
|
||||
if Config.DebugLevel > 2 {
|
||||
log.Debugf("Config: %#v", specconfig)
|
||||
log.Debugf("Executor spec: %#v", *specconfig.Metadata)
|
||||
for _, sess := range m.Sessions {
|
||||
log.Debugf("Session spec: %#v", *sess)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a linux guest
|
||||
linux, err := guest.NewLinuxGuest(op, vmomiSession, specconfig)
|
||||
if err != nil {
|
||||
log.Errorf("Failed during linux specific spec generation during create of %s: %s", config.Metadata.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.Guest = linux
|
||||
h.Spec = linux.Spec()
|
||||
|
||||
handlesLock.Lock()
|
||||
defer handlesLock.Unlock()
|
||||
|
||||
handles.Add(h.key, h)
|
||||
|
||||
return h, nil
|
||||
}
|
||||
80
vendor/github.com/vmware/vic/lib/portlayer/exec/vchinfo.go
generated
vendored
Normal file
80
vendor/github.com/vmware/vic/lib/portlayer/exec/vchinfo.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type VCHStats struct {
|
||||
CPULimit int64 // resource pool CPU limit
|
||||
CPUUsage int64 // resource pool CPU usage in MhZ
|
||||
MemoryLimit int64 // resource pool Memory limit
|
||||
MemoryUsage int64 // resource pool Memory Usage
|
||||
}
|
||||
|
||||
func GetVCHstats(ctx context.Context, moref ...types.ManagedObjectReference) VCHStats {
|
||||
var p mo.ResourcePool
|
||||
var vch VCHStats
|
||||
|
||||
if Config.ResourcePool == nil {
|
||||
log.Errorf("Unable to retrieve VCHstats: Config.ResourcePool is nil")
|
||||
return vch
|
||||
}
|
||||
|
||||
r := Config.ResourcePool.Reference()
|
||||
if len(moref) > 0 {
|
||||
r = moref[0]
|
||||
}
|
||||
|
||||
ps := []string{"config.cpuAllocation", "config.memoryAllocation", "runtime.cpu", "runtime.memory", "parent"}
|
||||
|
||||
if err := Config.ResourcePool.Properties(ctx, r, ps, &p); err != nil {
|
||||
log.Errorf("VCH stats error: %s", err)
|
||||
return vch
|
||||
}
|
||||
|
||||
vch.CPUUsage = p.Runtime.Cpu.OverallUsage
|
||||
vch.MemoryUsage = p.Runtime.Memory.OverallUsage
|
||||
|
||||
if p.Config.CpuAllocation.Limit != nil {
|
||||
vch.CPULimit = *p.Config.CpuAllocation.Limit
|
||||
}
|
||||
|
||||
if p.Config.MemoryAllocation.Limit != nil {
|
||||
vch.MemoryLimit = *p.Config.MemoryAllocation.Limit
|
||||
}
|
||||
|
||||
stats := []int64{vch.CPULimit,
|
||||
vch.MemoryLimit,
|
||||
vch.CPUUsage,
|
||||
vch.MemoryUsage}
|
||||
|
||||
log.Debugf("The VCH stats are: %+v", stats)
|
||||
|
||||
// If any of the stats is -1, we need to get the vch stats from the parent resource pool
|
||||
for _, v := range stats {
|
||||
if v == -1 {
|
||||
return GetVCHstats(ctx, *p.Parent)
|
||||
}
|
||||
}
|
||||
|
||||
return vch
|
||||
}
|
||||
121
vendor/github.com/vmware/vic/lib/portlayer/exec2/container.go
generated
vendored
Normal file
121
vendor/github.com/vmware/vic/lib/portlayer/exec2/container.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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 exec2
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
type ID uuid.UUID
|
||||
|
||||
func GenerateID() ID {
|
||||
return ID(uuid.New())
|
||||
}
|
||||
|
||||
func ParseID(idStr string) (ID, error) {
|
||||
result, err := uuid.Parse(idStr)
|
||||
return ID(result), err
|
||||
}
|
||||
|
||||
func (id ID) String() string {
|
||||
return uuid.UUID(id).String()
|
||||
}
|
||||
|
||||
// Struct that represents the internal port-layer representation of a container
|
||||
// All data in this struct must be data that is either immutable
|
||||
// or can be relied upon without having to query either the container guest
|
||||
// or the underlying infrastructure. Some of this state will be updated by events
|
||||
type container struct {
|
||||
ConstantConfig
|
||||
|
||||
vm vm.VirtualMachine
|
||||
runState RunState
|
||||
|
||||
config Config
|
||||
mainProcess ProcessConfig // container main process
|
||||
execdProcess []ProcessConfig
|
||||
|
||||
filesToCopy []FileToCopy // cache if copy while stopped
|
||||
}
|
||||
|
||||
// config that will be applied to a container on commit
|
||||
// Needs to be public as it will be shared by net, storage etc
|
||||
type PendingCommit struct {
|
||||
ConstantConfig
|
||||
|
||||
runState RunState
|
||||
config Config
|
||||
mainProcess ProcessConfig
|
||||
filesToCopy []FileToCopy
|
||||
}
|
||||
|
||||
// config state that cannot change for the lifetime of the container
|
||||
type ConstantConfig struct {
|
||||
ContainerID ID
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
// variable container configuration state
|
||||
type Config struct {
|
||||
Name string
|
||||
Limits ResourceLimits
|
||||
}
|
||||
|
||||
// configuration state of a container process
|
||||
type ProcessConfig struct {
|
||||
ProcessID ID
|
||||
WorkDir string
|
||||
ExecPath string
|
||||
ExecArgs string
|
||||
}
|
||||
|
||||
func NewProcessConfig(workDir string, execPath string, execArgs string) ProcessConfig {
|
||||
return ProcessConfig{ProcessID: GenerateID(), WorkDir: workDir, ExecArgs: execArgs, ExecPath: execPath}
|
||||
}
|
||||
|
||||
type ProcessStatus int
|
||||
|
||||
const (
|
||||
_ ProcessStatus = iota
|
||||
Started
|
||||
Exited
|
||||
)
|
||||
|
||||
// runtime status of a container process
|
||||
type ProcessRunState struct {
|
||||
ProcessID ID
|
||||
Status ProcessStatus
|
||||
GuestPid int
|
||||
ExitCode int
|
||||
ExitMsg string
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
}
|
||||
|
||||
type FileToCopy struct {
|
||||
target url.URL
|
||||
perms int16
|
||||
data []byte
|
||||
}
|
||||
|
||||
type ResourceLimits struct {
|
||||
MemoryMb int
|
||||
CPUMhz int
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/lib/portlayer/exec2/execution.go
generated
vendored
Normal file
117
vendor/github.com/vmware/vic/lib/portlayer/exec2/execution.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 exec2
|
||||
|
||||
// ContainerLifecycle represents operations concerned with the creation, modification
|
||||
// and deletion of containers
|
||||
type ContainerLifecycle interface {
|
||||
|
||||
// CreateContainer creates a new container representation and returns a Handle
|
||||
// The Handle can be used to configure the container before its actually created
|
||||
// Calling Commit on the Handle will create the container
|
||||
CreateContainer(name string) (Handle, error)
|
||||
|
||||
// GetHandle allows for an existing container to be modified
|
||||
// The Handle can be used to reconfigure the container
|
||||
// Calling Commit on the Handle will apply the reconfiguration
|
||||
// Commit will fail if another client committed a modification after GetHandle was called
|
||||
GetHandle(cid ID) (Handle, error)
|
||||
|
||||
// CopyTo copies a file into the container represented by the handle
|
||||
// If the container is stopped, the file will be copied in when it is next started
|
||||
CopyTo(handle Handle, targetDir string, fname string, perms int16, data []byte) (Handle, error)
|
||||
|
||||
// SetEntryPoint sets the entry point for the container
|
||||
// This is the executable, the lifecycle of which is tied to the container lifecycle
|
||||
SetEntryPoint(handle Handle, workDir string, execPath string, execArgs string) (Handle, error)
|
||||
|
||||
// SetLimits sets resource limits on the container
|
||||
// A value of -1 implies a default value, not unlimited
|
||||
// New limits will be ignored if committed to a running container
|
||||
SetLimits(handle Handle, memoryMb int, cpuMhz int) (Handle, error)
|
||||
|
||||
// SetRunState allows for the running state of a container to be modified
|
||||
// Created is not a valid state and will return an error
|
||||
SetRunState(handle Handle, runState RunState) (Handle, error)
|
||||
|
||||
// Commit applies changes made to the Handle to either a new or running container
|
||||
// Commit will fail if another client committed a modification after GetHandle was called
|
||||
// Commit blocks until all changes have been committed
|
||||
Commit(handle Handle) (ID, error)
|
||||
|
||||
// DestroyContainer destroys an stopped container
|
||||
// It is up to the caller to put the container in stopped state before calling Destroy
|
||||
DestroyContainer(cid ID) error
|
||||
}
|
||||
|
||||
type ProcessLifecycle interface {
|
||||
// ExecProcess executes a process in the container
|
||||
// The lifecycle of the process is independent of the container main process
|
||||
// The ID returned is a uuid handle to the process
|
||||
ExecProcess(cid ID, workDir string, execPath string, execArgs string) (ID, error)
|
||||
|
||||
// Send a signal to the process
|
||||
// Specifying a process ID will signal an exec'd process. Specifying the container ID will signal the main process
|
||||
Signal(processID ID, signal int) error
|
||||
}
|
||||
|
||||
// ContainerQuery represents queries that can be made against a Container
|
||||
type ContainerQuery interface {
|
||||
|
||||
// ListContainers lists all container IDs for a given state
|
||||
// If forState is nil, all containers are returned
|
||||
ListContainers(forState RunState) ([]ID, error)
|
||||
|
||||
// GetConfig returns container and process config
|
||||
GetContainerConfig(cid ID) (ContainerConfig, error)
|
||||
|
||||
// GetState returns the current state of the container and its processes
|
||||
// This call will return a snapshot of the most recent state for each entity
|
||||
GetContainerState(cid ID) (ContainerState, error)
|
||||
|
||||
// CopyFrom copies file data out of a running container
|
||||
// Returns an error if the container is not running
|
||||
CopyFrom(cid ID, sourceDir string, fname string) ([]byte, error)
|
||||
}
|
||||
|
||||
// RunState represents the running state of a container
|
||||
type RunState int
|
||||
|
||||
const (
|
||||
_ RunState = iota
|
||||
Created
|
||||
Running
|
||||
Stopped
|
||||
)
|
||||
|
||||
// ContainerConfig is a type representing the configuration of a container and its processes
|
||||
type ContainerConfig struct {
|
||||
ConstantConfig
|
||||
Config
|
||||
MainProcess ProcessConfig
|
||||
ExecdProcs []ProcessConfig
|
||||
}
|
||||
|
||||
// ContainerState is a type representing the runtime state of a container and its processes
|
||||
type ContainerState struct {
|
||||
Status RunState
|
||||
MainProcess ProcessState
|
||||
ExecdProcs []ProcessState
|
||||
}
|
||||
|
||||
// ProcessState is the runtime state of a process in a container
|
||||
type ProcessState struct {
|
||||
ProcessRunState
|
||||
}
|
||||
128
vendor/github.com/vmware/vic/lib/portlayer/exec2/execution_vsphere.go
generated
vendored
Normal file
128
vendor/github.com/vmware/vic/lib/portlayer/exec2/execution_vsphere.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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 exec2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// PortLayerVsphere is a WIP implementation of the execution.go interfaces
|
||||
type PortLayerVsphere struct {
|
||||
vmomiGateway VmomiGateway
|
||||
handles HandleFactory
|
||||
containers map[ID]*container
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) getContainer(handle Handle) *container {
|
||||
return p.containers[handle.(*PendingCommit).ContainerID]
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) newHandle(cid ID) *PendingCommit {
|
||||
return p.handles.createHandle(cid).(*PendingCommit)
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) Init(gateway VmomiGateway, factory HandleFactory) error {
|
||||
p.handles = factory
|
||||
p.vmomiGateway = gateway
|
||||
p.containers = make(map[ID]*container)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) CreateContainer(name string) (Handle, error) {
|
||||
cid := GenerateID()
|
||||
handle := p.newHandle(cid)
|
||||
handle.config.Name = name
|
||||
handle.runState = Created
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) GetHandle(cid ID) (Handle, error) {
|
||||
c := p.containers[cid]
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("Invalid container ID")
|
||||
}
|
||||
return p.handles.createHandle(c.ContainerID), nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) SetEntryPoint(handle Handle, workDir string, execPath string, execArgs string) (Handle, error) {
|
||||
resolvedHandle := handle.(*PendingCommit)
|
||||
resolvedHandle.mainProcess = NewProcessConfig(workDir, execPath, execArgs)
|
||||
return p.handles.refreshHandle(handle), nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) Commit(handle Handle) (ID, error) {
|
||||
var err error
|
||||
c := p.getContainer(handle)
|
||||
if c == nil {
|
||||
c, err = p.createContainer(handle)
|
||||
} else {
|
||||
// if c.vm == nil {
|
||||
// return "", fmt.Errorf("Cannot modify container with no VM")
|
||||
// }
|
||||
err = p.modifyContainer(c.runState, handle)
|
||||
}
|
||||
// Handle will be garbage collected
|
||||
return c.ContainerID, err
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) CopyTo(handle Handle, targetDir string, fname string, perms int16, data []byte) (Handle, error) {
|
||||
var result Handle
|
||||
resolvedHandle := handle.(*PendingCommit)
|
||||
u, err := url.Parse("file://" + targetDir + "/" + fname)
|
||||
if err == nil {
|
||||
fileToCopy := FileToCopy{target: *u, perms: perms, data: data}
|
||||
resolvedHandle.filesToCopy = append(resolvedHandle.filesToCopy, fileToCopy)
|
||||
result = p.handles.refreshHandle(handle)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) SetLimits(handle Handle, memoryMb int, cpuMhz int) (Handle, error) {
|
||||
resolvedHandle := handle.(*PendingCommit)
|
||||
resolvedHandle.config.Limits = ResourceLimits{MemoryMb: memoryMb, CPUMhz: cpuMhz}
|
||||
return p.handles.refreshHandle(handle), nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) SetRunState(handle Handle, runState RunState) (Handle, error) {
|
||||
resolvedHandle := handle.(*PendingCommit)
|
||||
resolvedHandle.runState = runState
|
||||
return p.handles.refreshHandle(handle), nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) DestroyContainer(cid ID) error {
|
||||
c := p.containers[cid]
|
||||
if c == nil {
|
||||
return fmt.Errorf("Invalid container ID")
|
||||
}
|
||||
delete(p.containers, cid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) createContainer(handle Handle) (*container, error) {
|
||||
resolvedHandle := handle.(*PendingCommit)
|
||||
c := container{}
|
||||
p.containers[resolvedHandle.ContainerID] = &c
|
||||
c.ContainerID = resolvedHandle.ContainerID
|
||||
c.runState = resolvedHandle.runState
|
||||
// followed by other transfer of state from pending to container
|
||||
// fmt.Printf("Creating container for %v\n", pending)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (p *PortLayerVsphere) modifyContainer(runState RunState, handle Handle) error {
|
||||
// fmt.Printf("Modifying container for %v\n", pending)
|
||||
return nil
|
||||
}
|
||||
37
vendor/github.com/vmware/vic/lib/portlayer/exec2/handle.go
generated
vendored
Normal file
37
vendor/github.com/vmware/vic/lib/portlayer/exec2/handle.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 exec2
|
||||
|
||||
/* A Handle should be completely opaque */
|
||||
type Handle interface{}
|
||||
|
||||
type HandleFactory interface {
|
||||
createHandle(cID ID) Handle
|
||||
refreshHandle(oldHandle Handle) Handle
|
||||
}
|
||||
|
||||
type BasicHandleFactory struct {
|
||||
}
|
||||
|
||||
func (h *BasicHandleFactory) createHandle(cid ID) Handle {
|
||||
newPc := &PendingCommit{}
|
||||
newPc.ContainerID = cid
|
||||
return newPc
|
||||
}
|
||||
|
||||
// Basic handle resolver just passes back the handle passed in
|
||||
func (h *BasicHandleFactory) refreshHandle(oldHandle Handle) Handle {
|
||||
return oldHandle
|
||||
}
|
||||
126
vendor/github.com/vmware/vic/lib/portlayer/exec2/remote/portlayer_rpc_client.go
generated
vendored
Normal file
126
vendor/github.com/vmware/vic/lib/portlayer/exec2/remote/portlayer_rpc_client.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
// 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 remote
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec2"
|
||||
)
|
||||
|
||||
const serverAddress string = "localhost"
|
||||
|
||||
type PortLayerRPCClient struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) Connect() error {
|
||||
// Ignore Init args on the client - that is the server's responsibility
|
||||
var err error
|
||||
gob.Register(uuid.New())
|
||||
p.client, err = rpc.DialHTTP("tcp", serverAddress+":1234")
|
||||
return err
|
||||
}
|
||||
|
||||
type CreateArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) CreateContainer(name string) (exec2.Handle, error) {
|
||||
args := &CreateArgs{Name: name}
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.CreateContainer", args, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) GetHandle(cid exec2.ID) (exec2.Handle, error) {
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.GetHandle", cid, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
type CopyToArgs struct {
|
||||
Handle exec2.Handle
|
||||
TargetDir string
|
||||
Fname string
|
||||
Perms int16
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) CopyTo(handle exec2.Handle, targetDir string, fname string, perms int16, data []byte) (exec2.Handle, error) {
|
||||
args := &CopyToArgs{Handle: handle, TargetDir: targetDir, Fname: fname, Perms: perms, Data: data}
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.CopyTo", args, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
type SetEntryPointArgs struct {
|
||||
Handle exec2.Handle
|
||||
WorkDir string
|
||||
ExecPath string
|
||||
Args string
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) SetEntryPoint(handle exec2.Handle, workDir string, execPath string, args string) (exec2.Handle, error) {
|
||||
epArgs := &SetEntryPointArgs{Handle: handle, WorkDir: workDir, ExecPath: execPath, Args: args}
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.SetEntryPoint", epArgs, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
type SetLimitsArgs struct {
|
||||
Handle exec2.Handle
|
||||
MemoryMb int
|
||||
CPUMhz int
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) SetLimits(handle exec2.Handle, memoryMb int, cpuMhz int) (exec2.Handle, error) {
|
||||
args := &SetLimitsArgs{Handle: handle, MemoryMb: memoryMb, CPUMhz: cpuMhz}
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.SetLimits", args, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
type SetRunStateArgs struct {
|
||||
Handle exec2.Handle
|
||||
RunState exec2.RunState
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) SetRunState(handle exec2.Handle, runState exec2.RunState) (exec2.Handle, error) {
|
||||
args := &SetRunStateArgs{Handle: handle, RunState: runState}
|
||||
var reply exec2.Handle
|
||||
err := p.client.Call("PortLayerRPCServer.SetRunState", args, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
type CommitArgs struct {
|
||||
Handle exec2.Handle
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) Commit(handle exec2.Handle) (exec2.ID, error) {
|
||||
args := &CommitArgs{Handle: handle}
|
||||
var reply exec2.ID
|
||||
err := p.client.Call("PortLayerRPCServer.Commit", args, &reply)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (p *PortLayerRPCClient) DestroyContainer(cid exec2.ID) error {
|
||||
/* Ignore the reply */
|
||||
var reply exec2.ID
|
||||
return p.client.Call("PortLayerRPCServer.DestroyContainer", cid, &reply)
|
||||
}
|
||||
133
vendor/github.com/vmware/vic/lib/portlayer/exec2/remote/server/portlayer_rpc_server.go
generated
vendored
Normal file
133
vendor/github.com/vmware/vic/lib/portlayer/exec2/remote/server/portlayer_rpc_server.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec2"
|
||||
"github.com/vmware/vic/lib/portlayer/exec2/remote"
|
||||
)
|
||||
|
||||
type PortLayerRPCServer struct {
|
||||
}
|
||||
|
||||
// A Handle can be anything, so this takes advantage of that by creating a sparse handle
|
||||
// to send to the client and using that sparse handle as a key in a hashtable which points to
|
||||
// rich handles created by the HandleFactory
|
||||
type SparseHandle exec2.Handle
|
||||
|
||||
var lcTarget exec2.ContainerLifecycle
|
||||
|
||||
//var lcQuery exec2.ContainerQuery
|
||||
var handles map[SparseHandle]exec2.Handle
|
||||
|
||||
func init() {
|
||||
pl := &exec2.PortLayerVsphere{}
|
||||
pl.Init(nil, &exec2.BasicHandleFactory{})
|
||||
lcTarget = pl
|
||||
//lcQuery = lcTarget
|
||||
handles = make(map[SparseHandle]exec2.Handle)
|
||||
}
|
||||
|
||||
func main() {
|
||||
gob.Register(uuid.New())
|
||||
rpcServer := new(PortLayerRPCServer)
|
||||
rpc.Register(rpcServer)
|
||||
rpc.HandleHTTP()
|
||||
// #nosec: Binds to all network interfaces
|
||||
l, e := net.Listen("tcp", ":1234")
|
||||
if e != nil {
|
||||
log.Fatal("listen error:", e)
|
||||
}
|
||||
fmt.Println("Server listening")
|
||||
http.Serve(l, nil)
|
||||
}
|
||||
|
||||
// A sparse handle is simply a random string
|
||||
func newSparseHandle() SparseHandle {
|
||||
return SparseHandle(uuid.New())
|
||||
}
|
||||
|
||||
func createSparseHandle(handle exec2.Handle) SparseHandle {
|
||||
key := newSparseHandle()
|
||||
handles[key] = handle
|
||||
return key
|
||||
}
|
||||
|
||||
func resolveSparseHandle(handle SparseHandle) exec2.Handle {
|
||||
return handles[handle]
|
||||
}
|
||||
|
||||
func refreshHandle(result *exec2.Handle, oldHandle SparseHandle, newHandle exec2.Handle, err error) error {
|
||||
*result = createSparseHandle(newHandle)
|
||||
delete(handles, oldHandle)
|
||||
return err
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) CreateContainer(args remote.CreateArgs, result *exec2.Handle) error {
|
||||
handle, err := lcTarget.CreateContainer(args.Name)
|
||||
*result = createSparseHandle(handle)
|
||||
return err
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) GetHandle(cid exec2.ID, result *exec2.Handle) error {
|
||||
handle, err := lcTarget.GetHandle(cid)
|
||||
*result = createSparseHandle(handle)
|
||||
return err
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) CopyTo(args remote.CopyToArgs, result *exec2.Handle) error {
|
||||
handle := resolveSparseHandle(args.Handle)
|
||||
newHandle, err := lcTarget.CopyTo(handle, args.TargetDir, args.Fname, args.Perms, args.Data)
|
||||
return refreshHandle(result, handle, newHandle, err)
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) SetEntryPoint(args remote.SetEntryPointArgs, result *exec2.Handle) error {
|
||||
handle := resolveSparseHandle(args.Handle)
|
||||
newHandle, err := lcTarget.SetEntryPoint(handle, args.WorkDir, args.ExecPath, args.Args)
|
||||
return refreshHandle(result, handle, newHandle, err)
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) SetLimits(args remote.SetLimitsArgs, result *exec2.Handle) error {
|
||||
handle := resolveSparseHandle(args.Handle)
|
||||
newHandle, err := lcTarget.SetLimits(handle, args.MemoryMb, args.CPUMhz)
|
||||
return refreshHandle(result, handle, newHandle, err)
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) SetRunState(args remote.SetRunStateArgs, result *exec2.Handle) error {
|
||||
handle := resolveSparseHandle(args.Handle)
|
||||
newHandle, err := lcTarget.SetRunState(handle, args.RunState)
|
||||
return refreshHandle(result, handle, newHandle, err)
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) Commit(args remote.CommitArgs, result *exec2.ID) error {
|
||||
cid, err := lcTarget.Commit(resolveSparseHandle(args.Handle))
|
||||
*result = cid
|
||||
return err
|
||||
}
|
||||
|
||||
func (*PortLayerRPCServer) DestroyContainer(cid exec2.ID, result *exec2.ID) error {
|
||||
err := lcTarget.DestroyContainer(cid)
|
||||
*result = cid
|
||||
return err
|
||||
}
|
||||
19
vendor/github.com/vmware/vic/lib/portlayer/exec2/vmomi_gateway.go
generated
vendored
Normal file
19
vendor/github.com/vmware/vic/lib/portlayer/exec2/vmomi_gateway.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 exec2
|
||||
|
||||
// VmomiGateway represents an interface to a pre-authenticated Vmomi API
|
||||
type VmomiGateway interface {
|
||||
}
|
||||
203
vendor/github.com/vmware/vic/lib/portlayer/logging/logging.go
generated
vendored
Normal file
203
vendor/github.com/vmware/vic/lib/portlayer/logging/logging.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 logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/event/collector/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
once.Do(func() {
|
||||
// Subscribe to vm events
|
||||
exec.Config.EventManager.Subscribe(
|
||||
events.NewEventType(vsphere.VMEvent{}).Topic(),
|
||||
"logging",
|
||||
func(ie events.Event) {
|
||||
eventCallback(ie)
|
||||
})
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// listens migrated events and connects the file backed serial ports
|
||||
func eventCallback(ie events.Event) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
switch ie.String() {
|
||||
case events.ContainerMigrated,
|
||||
events.ContainerMigratedByDrs:
|
||||
op := trace.NewOperation(context.Background(), "LoggingEvent")
|
||||
op.Debugf("Logging processing eventID(%s): %s", ie.EventID(), ie)
|
||||
|
||||
// grab the container from the cache
|
||||
container := exec.Containers.Container(ie.Reference())
|
||||
if container == nil {
|
||||
op.Errorf("Container %s not found. Dropping the event %s from Logging subsystem.", ie.Reference(), ie)
|
||||
return
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
var err error
|
||||
|
||||
handle := container.NewHandle(op)
|
||||
if handle == nil {
|
||||
err = fmt.Errorf("Handle for %s cannot be created", ie.Reference())
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
// set them to true
|
||||
if handle, err = toggle(handle, true); err != nil {
|
||||
op.Errorf("Failed to toggle logging after %s event for container %s: %s", ie, ie.Reference(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = handle.Commit(op, nil, nil); err != nil {
|
||||
op.Errorf("Failed to commit handle after getting %s event for container %s: %s", ie, ie.Reference(), err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := retry.Do(operation, exec.IsConcurrentAccessError); err != nil {
|
||||
op.Errorf("Multiple attempts failed to commit handle after getting %s event for container %s: %s", ie, ie.Reference(), err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func toggle(handle *exec.Handle, connected bool) (*exec.Handle, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// get the virtual device list
|
||||
devices := object.VirtualDeviceList(handle.Config.Hardware.Device)
|
||||
|
||||
// select the virtual serial ports
|
||||
serials := devices.SelectByBackingInfo((*types.VirtualSerialPortFileBackingInfo)(nil))
|
||||
if len(serials) == 0 {
|
||||
return nil, fmt.Errorf("Unable to find a device with desired backing")
|
||||
}
|
||||
|
||||
for i := range serials {
|
||||
serial := serials[i]
|
||||
|
||||
log.Debugf("Found a device with desired backing: %#v", serial)
|
||||
|
||||
c := serial.GetVirtualDevice().Connectable
|
||||
if c.Connected == connected {
|
||||
log.Debugf("Already in the desired state (connected: %t)", connected)
|
||||
continue
|
||||
}
|
||||
log.Debugf("Setting Connected to %t", connected)
|
||||
c.Connected = connected
|
||||
|
||||
config := &types.VirtualDeviceConfigSpec{
|
||||
Device: serial,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationEdit,
|
||||
}
|
||||
handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config)
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// Join adds two file backed serial port and configures them
|
||||
func Join(h interface{}) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
|
||||
var logFilePath string
|
||||
|
||||
VMPathName := handle.Spec.VMPathName()
|
||||
VMName := handle.Spec.Spec().Name
|
||||
|
||||
logFilePath = fmt.Sprintf("%s/%s", VMPathName, VMName)
|
||||
// on non-vsan setup, VMPathName is set to "[datastore_name] containerID/containerID.vmx"
|
||||
if strings.HasSuffix(VMPathName, ".vmx") {
|
||||
idx := strings.LastIndex(VMPathName, "/")
|
||||
logFilePath = VMPathName[:idx]
|
||||
}
|
||||
|
||||
for _, logFile := range []string{"tether.debug", "output.log"} {
|
||||
filename := fmt.Sprintf("%s/%s", logFilePath, logFile)
|
||||
log.Infof("set log file name to: %s", filename)
|
||||
|
||||
// Debug and log serial ports - backed by datastore file
|
||||
serial := &types.VirtualSerialPort{
|
||||
VirtualDevice: types.VirtualDevice{
|
||||
Backing: &types.VirtualSerialPortFileBackingInfo{
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: filename,
|
||||
},
|
||||
},
|
||||
Connectable: &types.VirtualDeviceConnectInfo{
|
||||
Connected: true,
|
||||
StartConnected: true,
|
||||
AllowGuestControl: true,
|
||||
},
|
||||
},
|
||||
YieldOnPoll: true,
|
||||
}
|
||||
config := &types.VirtualDeviceConfigSpec{
|
||||
Device: serial,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
}
|
||||
handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config)
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// Bind sets the *Connected fields of the VirtualSerialPort
|
||||
func Bind(h interface{}) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
return toggle(handle, true)
|
||||
}
|
||||
|
||||
// Unbind unsets the *Connected fields of the VirtualSerialPort
|
||||
func Unbind(h interface{}) (interface{}, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
handle, ok := h.(*exec.Handle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Type assertion failed for %#+v", handle)
|
||||
}
|
||||
return toggle(handle, false)
|
||||
}
|
||||
106
vendor/github.com/vmware/vic/lib/portlayer/metrics/metrics.go
generated
vendored
Normal file
106
vendor/github.com/vmware/vic/lib/portlayer/metrics/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/performance"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
var (
|
||||
Supervisor *super
|
||||
|
||||
initializer struct {
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
)
|
||||
|
||||
// super manages the lifecycle and access to the
|
||||
// available metrics collectors
|
||||
type super struct {
|
||||
vms *performance.VMCollector
|
||||
}
|
||||
|
||||
type UnsupportedTypeError struct {
|
||||
subscriber interface{}
|
||||
}
|
||||
|
||||
func (ute UnsupportedTypeError) Error() string {
|
||||
t := reflect.TypeOf(ute.subscriber)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return fmt.Sprintf("type %s is not supported by metrics", t)
|
||||
}
|
||||
|
||||
func Init(ctx context.Context, session *session.Session) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
initializer.once.Do(func() {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
initializer.err = err
|
||||
}
|
||||
}()
|
||||
Supervisor = newSupervisor(session)
|
||||
|
||||
})
|
||||
return initializer.err
|
||||
|
||||
}
|
||||
|
||||
func newSupervisor(session *session.Session) *super {
|
||||
defer trace.End(trace.Begin(""))
|
||||
// create the vm metric collector
|
||||
v := performance.NewVMCollector(session)
|
||||
return &super{
|
||||
vms: v,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *super) Subscribe(op trace.Operation, subscriber interface{}) (chan interface{}, error) {
|
||||
switch sub := subscriber.(type) {
|
||||
case *exec.Container:
|
||||
return s.vms.Subscribe(op, sub.VMReference(), sub.String())
|
||||
}
|
||||
|
||||
err := UnsupportedTypeError{
|
||||
subscriber: subscriber,
|
||||
}
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *super) Unsubscribe(op trace.Operation, subscriber interface{}, ch chan interface{}) {
|
||||
switch sub := subscriber.(type) {
|
||||
case *exec.Container:
|
||||
s.vms.Unsubscribe(op, sub.VMReference(), ch)
|
||||
default:
|
||||
err := UnsupportedTypeError{
|
||||
subscriber: subscriber,
|
||||
}
|
||||
op.Errorf("%s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
46
vendor/github.com/vmware/vic/lib/portlayer/network/config.go
generated
vendored
Normal file
46
vendor/github.com/vmware/vic/lib/portlayer/network/config.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
source extraconfig.DataSource `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
sink extraconfig.DataSink `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// Turn on debug logging
|
||||
DebugLevel int `vic:"0.1" scope:"read-only" key:"init/diagnostics/debug"`
|
||||
|
||||
// Port Layer - network
|
||||
config.Network `vic:"0.1" scope:"read-only" key:"network"`
|
||||
|
||||
// The bridge link
|
||||
BridgeLink Link `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
|
||||
// the vsphere portgroups corresponding to container network configuration
|
||||
PortGroups map[string]object.NetworkReference `vic:"0.1" scope:"read-only" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
func (c *Configuration) Encode() {
|
||||
extraconfig.Encode(c.sink, c)
|
||||
}
|
||||
|
||||
func (c *Configuration) Decode() {
|
||||
extraconfig.Decode(c.source, c)
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/lib/portlayer/network/container.go
generated
vendored
Normal file
117
vendor/github.com/vmware/vic/lib/portlayer/network/container.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 network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
sync.Mutex
|
||||
|
||||
id uid.UID
|
||||
name string
|
||||
endpoints []*Endpoint
|
||||
}
|
||||
|
||||
func (c *Container) Endpoints() []*Endpoint {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
ret := make([]*Endpoint, len(c.endpoints))
|
||||
copy(ret, c.endpoints)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Container) ID() uid.UID {
|
||||
return c.id
|
||||
}
|
||||
|
||||
func (c *Container) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *Container) endpoint(s *Scope) *Endpoint {
|
||||
for _, e := range c.endpoints {
|
||||
if e.Scope() == s {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) Endpoint(s *Scope) *Endpoint {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
return c.endpoint(s)
|
||||
}
|
||||
|
||||
func (c *Container) Scopes() []*Scope {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
scopes := make([]*Scope, len(c.endpoints))
|
||||
i := 0
|
||||
for _, e := range c.endpoints {
|
||||
scopes[i] = e.Scope()
|
||||
i++
|
||||
}
|
||||
|
||||
return scopes
|
||||
}
|
||||
|
||||
func (c *Container) addEndpoint(e *Endpoint) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.endpoints = append(c.endpoints, e)
|
||||
}
|
||||
|
||||
func (c *Container) removeEndpoint(e *Endpoint) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.endpoints = removeEndpointHelper(e, c.endpoints)
|
||||
}
|
||||
|
||||
func (c *Container) Refresh(ctx context.Context) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
// this will "refresh" the container executor config that contains
|
||||
// the current ip addresses
|
||||
h := exec.GetContainer(ctx, c.ID())
|
||||
if h == nil {
|
||||
return fmt.Errorf("could not find container %s", c.ID())
|
||||
}
|
||||
defer h.Close()
|
||||
|
||||
for _, e := range c.endpoints {
|
||||
if err := e.refresh(h); err != nil {
|
||||
log.Warnf("could not refresh endpoint for container %s: %s", h.ExecConfig.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1396
vendor/github.com/vmware/vic/lib/portlayer/network/context.go
generated
vendored
Normal file
1396
vendor/github.com/vmware/vic/lib/portlayer/network/context.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1470
vendor/github.com/vmware/vic/lib/portlayer/network/context_test.go
generated
vendored
Normal file
1470
vendor/github.com/vmware/vic/lib/portlayer/network/context_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
229
vendor/github.com/vmware/vic/lib/portlayer/network/endpoint.go
generated
vendored
Normal file
229
vendor/github.com/vmware/vic/lib/portlayer/network/endpoint.go
generated
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
type alias struct {
|
||||
Name string
|
||||
Container string
|
||||
|
||||
ep *Endpoint
|
||||
}
|
||||
|
||||
var badAlias = alias{}
|
||||
|
||||
type Endpoint struct {
|
||||
container *Container
|
||||
scope *Scope
|
||||
ip net.IP
|
||||
static bool
|
||||
ports map[Port]interface{} // exposed ports
|
||||
aliases map[string][]alias
|
||||
gw *net.IP
|
||||
subnet *net.IPNet
|
||||
}
|
||||
|
||||
// scopeName returns the "fully qualified" name of an alias. Aliases are scoped
|
||||
// by the container and network scope they are in.
|
||||
func (a alias) scopedName() string {
|
||||
// an alias for the container itself is network scoped
|
||||
for _, al := range a.ep.getAliases("") {
|
||||
if a.Name == al.Name {
|
||||
return ScopedAliasName(a.ep.Scope().Name(), "", a.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return ScopedAliasName(a.ep.Scope().Name(), a.ep.Container().Name(), a.Name)
|
||||
}
|
||||
|
||||
// ScopedAliasName returns the fully qualified name of an alias, scoped to
|
||||
// the scope and optionally a container
|
||||
func ScopedAliasName(scope string, container string, alias string) string {
|
||||
if container != "" {
|
||||
return fmt.Sprintf("%s:%s:%s", scope, container, alias)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%s", scope, alias)
|
||||
}
|
||||
|
||||
func newEndpoint(container *Container, scope *Scope, eip *net.IP, pciSlot *int32) *Endpoint {
|
||||
e := &Endpoint{
|
||||
container: container,
|
||||
scope: scope,
|
||||
ip: net.IPv4(0, 0, 0, 0),
|
||||
static: false,
|
||||
ports: make(map[Port]interface{}),
|
||||
aliases: make(map[string][]alias),
|
||||
}
|
||||
|
||||
if eip != nil && !ip.IsUnspecifiedIP(*eip) {
|
||||
e.ip = *eip
|
||||
e.static = true
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func removeEndpointHelper(ep *Endpoint, eps []*Endpoint) []*Endpoint {
|
||||
for i, e := range eps {
|
||||
if ep != e {
|
||||
continue
|
||||
}
|
||||
|
||||
return append(eps[:i], eps[i+1:]...)
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
func (e *Endpoint) addPort(p Port) error {
|
||||
if _, ok := e.ports[p]; ok {
|
||||
return fmt.Errorf("port %s already exposed", p)
|
||||
}
|
||||
|
||||
e.ports[p] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) IP() net.IP {
|
||||
return e.ip
|
||||
}
|
||||
|
||||
func (e *Endpoint) Scope() *Scope {
|
||||
return e.scope
|
||||
}
|
||||
|
||||
func (e *Endpoint) Subnet() *net.IPNet {
|
||||
if e.subnet != nil {
|
||||
return e.subnet
|
||||
}
|
||||
|
||||
return e.Scope().Subnet()
|
||||
}
|
||||
|
||||
func (e *Endpoint) Container() *Container {
|
||||
return e.container
|
||||
}
|
||||
|
||||
func (e *Endpoint) ID() uid.UID {
|
||||
return e.container.ID()
|
||||
}
|
||||
|
||||
func (e *Endpoint) Name() string {
|
||||
return e.container.Name()
|
||||
}
|
||||
|
||||
func (e *Endpoint) Gateway() net.IP {
|
||||
if e.gw != nil {
|
||||
return *e.gw
|
||||
}
|
||||
|
||||
return e.Scope().Gateway()
|
||||
}
|
||||
|
||||
func (e *Endpoint) Ports() []Port {
|
||||
ports := make([]Port, len(e.ports))
|
||||
i := 0
|
||||
for p := range e.ports {
|
||||
ports[i] = p
|
||||
i++
|
||||
}
|
||||
|
||||
return ports
|
||||
}
|
||||
|
||||
func (e *Endpoint) addAlias(con, a string) (alias, bool) {
|
||||
if a == "" {
|
||||
return badAlias, false
|
||||
}
|
||||
|
||||
if con == "" {
|
||||
con = e.container.Name()
|
||||
}
|
||||
|
||||
aliases := e.aliases[con]
|
||||
for _, as := range aliases {
|
||||
if as.Name == a {
|
||||
// already present
|
||||
return as, true
|
||||
}
|
||||
}
|
||||
|
||||
na := alias{
|
||||
Name: a,
|
||||
Container: con,
|
||||
ep: e,
|
||||
}
|
||||
e.aliases[con] = append(aliases, na)
|
||||
return na, false
|
||||
}
|
||||
|
||||
func (e *Endpoint) getAliases(con string) []alias {
|
||||
if con == "" {
|
||||
con = e.container.Name()
|
||||
}
|
||||
|
||||
return e.aliases[con]
|
||||
}
|
||||
|
||||
func (e *Endpoint) copy() *Endpoint {
|
||||
other := *e
|
||||
other.aliases = make(map[string][]alias)
|
||||
for k, v := range e.aliases {
|
||||
a := make([]alias, len(v))
|
||||
copy(a, v)
|
||||
other.aliases[k] = a
|
||||
}
|
||||
other.ports = make(map[Port]interface{})
|
||||
for p := range e.ports {
|
||||
other.ports[p] = nil
|
||||
}
|
||||
|
||||
return &other
|
||||
}
|
||||
|
||||
func (e *Endpoint) refresh(h *exec.Handle) error {
|
||||
if !e.scope.isDynamic() {
|
||||
return nil
|
||||
}
|
||||
|
||||
s := e.scope
|
||||
ne := h.ExecConfig.Networks[s.Name()]
|
||||
if ne == nil {
|
||||
return fmt.Errorf("container config does not have info for network scope %s", s.Name())
|
||||
}
|
||||
|
||||
if ip.IsUnspecifiedSubnet(&ne.Network.Assigned.Gateway) {
|
||||
return fmt.Errorf("updating endpoint for container %s: gateway not present for scope %s", h.ExecConfig.ID, s.name)
|
||||
}
|
||||
|
||||
gw, snet, err := net.ParseCIDR(ne.Network.Assigned.Gateway.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse gateway for container %s: %s", h.ExecConfig.ID, err)
|
||||
}
|
||||
|
||||
e.ip = ne.Assigned.IP
|
||||
e.gw = &gw
|
||||
e.subnet = snet
|
||||
return nil
|
||||
}
|
||||
76
vendor/github.com/vmware/vic/lib/portlayer/network/endpoint_test.go
generated
vendored
Normal file
76
vendor/github.com/vmware/vic/lib/portlayer/network/endpoint_test.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEndpointNameID(t *testing.T) {
|
||||
c := &Container{id: "foo", name: "bar"}
|
||||
s := &Scope{
|
||||
gateway: net.ParseIP("10.10.10.1"),
|
||||
subnet: &net.IPNet{IP: net.ParseIP("10.10.10.0"), Mask: net.CIDRMask(24, 32)},
|
||||
}
|
||||
e := Endpoint{
|
||||
container: c,
|
||||
scope: s,
|
||||
ip: net.ParseIP("10.10.10.10"),
|
||||
static: true,
|
||||
ports: make(map[Port]interface{}),
|
||||
}
|
||||
|
||||
assert.Equal(t, c.ID(), e.ID())
|
||||
assert.Equal(t, c.Name(), e.Name())
|
||||
}
|
||||
|
||||
func TestEndpointCopy(t *testing.T) {
|
||||
c := &Container{id: "foo"}
|
||||
s := &Scope{
|
||||
gateway: net.ParseIP("10.10.10.1"),
|
||||
subnet: &net.IPNet{IP: net.ParseIP("10.10.10.0"), Mask: net.CIDRMask(24, 32)},
|
||||
}
|
||||
e := Endpoint{
|
||||
container: c,
|
||||
scope: s,
|
||||
ip: net.ParseIP("10.10.10.10"),
|
||||
static: true,
|
||||
ports: make(map[Port]interface{}),
|
||||
}
|
||||
|
||||
p, err := ParsePort("80/tcp")
|
||||
assert.NoError(t, err, "")
|
||||
e.ports[p] = nil
|
||||
|
||||
other := e.copy()
|
||||
|
||||
assert.Equal(t, other.ID(), e.ID())
|
||||
assert.Equal(t, other.container, c)
|
||||
assert.Equal(t, other.container, e.container)
|
||||
assert.Equal(t, other.scope, s)
|
||||
assert.Equal(t, other.scope, e.scope)
|
||||
assert.True(t, other.ip.Equal(e.ip), "other.ip (%s) != e.ip (%s)", other.ip, e.ip)
|
||||
assert.True(t, other.Gateway().Equal(e.Gateway()), "other.Gateway() (%s) != e.Gateway() (%s)", other.Gateway(), e.Gateway())
|
||||
assert.True(t, other.Subnet().IP.Equal(e.Subnet().IP), "other.Subnet() (%s) != e.Subnet() (%s)", other.Subnet(), e.Subnet())
|
||||
assert.Equal(t, other.Subnet().Mask, e.Subnet().Mask, "other.Subnet() (%s) != e.Subnet() (%s)", other.Subnet(), e.Subnet())
|
||||
assert.EqualValues(t, other.ports, e.ports)
|
||||
|
||||
// make sure .ports is a copy
|
||||
other.ports["foo"] = nil
|
||||
assert.NotContains(t, e.ports, "foo")
|
||||
}
|
||||
426
vendor/github.com/vmware/vic/lib/portlayer/network/ipam.go
generated
vendored
Normal file
426
vendor/github.com/vmware/vic/lib/portlayer/network/ipam.go
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
// 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.
|
||||
|
||||
// IP address management
|
||||
//
|
||||
// The API here just concerns itself with tracking blocks of
|
||||
// IP addresses, as well as individual IPs within the blocks.
|
||||
// The API does not have a concept of "network", in particular
|
||||
// when managing CIDR blocks, the network and broadcast address
|
||||
// are available as valid addresses. This behavior can be
|
||||
// accomplished, however, by just reserving those two addresses
|
||||
// first thing after requesting a CIDR address space, by using
|
||||
// the ReserveIP4() call.
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
)
|
||||
|
||||
// An AddressSpace is a collection of
|
||||
// IP address ranges
|
||||
type AddressSpace struct {
|
||||
Parent *AddressSpace
|
||||
Network *net.IPNet
|
||||
Pool *ip.Range
|
||||
availableRanges []*ip.Range
|
||||
}
|
||||
|
||||
// compareIPv4 compares two IPv4 addresses.
|
||||
// Returns -1 if ip1 < ip2, 0 if they are equal,
|
||||
// and 1 if ip1 > ip2
|
||||
func compareIP4(ip1 net.IP, ip2 net.IP) int {
|
||||
ip1 = ip1.To16()
|
||||
ip2 = ip2.To16()
|
||||
return bytes.Compare(ip1, ip2)
|
||||
}
|
||||
|
||||
func incrementIP4(ip net.IP) net.IP {
|
||||
if !isIP4(ip) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newIP := copyIP(ip)
|
||||
s := 0
|
||||
if len(ip) == net.IPv6len {
|
||||
s = 12
|
||||
}
|
||||
for i := len(newIP) - 1; i >= s; i-- {
|
||||
newIP[i]++
|
||||
if newIP[i] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newIP
|
||||
}
|
||||
|
||||
func decrementIP4(ip net.IP) net.IP {
|
||||
if !isIP4(ip) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newIP := copyIP(ip)
|
||||
s := 0
|
||||
if len(ip) == net.IPv6len {
|
||||
s = 12
|
||||
}
|
||||
for i := len(newIP) - 1; i >= s; i-- {
|
||||
newIP[i]--
|
||||
if newIP[i] != 0xff {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newIP
|
||||
}
|
||||
|
||||
func copyIP(ip net.IP) net.IP {
|
||||
newIP := make([]byte, len(ip))
|
||||
copy(newIP, ip)
|
||||
return newIP
|
||||
}
|
||||
|
||||
func isIP4(ip net.IP) bool {
|
||||
return ip.To4() != nil
|
||||
}
|
||||
|
||||
// lowestIP4 returns the lowest possible IP address
|
||||
// in an IP network. For example:
|
||||
//
|
||||
// lowestIP4(net.IPNet{}IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}) -> 172.16.0.0
|
||||
//
|
||||
func lowestIP4(ipRange *net.IPNet) net.IP {
|
||||
return ipRange.IP.Mask(ipRange.Mask).To16()
|
||||
}
|
||||
|
||||
// highestIP4 returns the highest possible IP address
|
||||
// in an IP network. For example:
|
||||
//
|
||||
// highestIP4(net.IPNet{}IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(16, 32)}) -> 172.16.255.255
|
||||
//
|
||||
func highestIP4(ipRange *net.IPNet) net.IP {
|
||||
if !isIP4(ipRange.IP) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newIP := net.IPv4(0, 0, 0, 0)
|
||||
ipRange.IP = ipRange.IP.To4()
|
||||
for i := 0; i < len(ipRange.Mask); i++ {
|
||||
newIP[i+12] = ipRange.IP[i] | ^ipRange.Mask[i]
|
||||
}
|
||||
|
||||
return newIP
|
||||
}
|
||||
|
||||
// NewAddressSpaceFromNetwork creates a new AddressSpace from a network specification.
|
||||
func NewAddressSpaceFromNetwork(ipRange *net.IPNet) *AddressSpace {
|
||||
s := &AddressSpace{
|
||||
Network: ipRange,
|
||||
Pool: &ip.Range{FirstIP: lowestIP4(ipRange), LastIP: highestIP4(ipRange)},
|
||||
}
|
||||
s.availableRanges = []*ip.Range{s.Pool}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// NewAddressSpaceFromRange creates a new AddressSpace from a range of IP addresses.
|
||||
func NewAddressSpaceFromRange(firstIP net.IP, lastIP net.IP) *AddressSpace {
|
||||
if compareIP4(firstIP, lastIP) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &AddressSpace{
|
||||
Pool: &ip.Range{FirstIP: firstIP, LastIP: lastIP},
|
||||
availableRanges: []*ip.Range{{FirstIP: firstIP, LastIP: lastIP}}}
|
||||
}
|
||||
|
||||
func (s *AddressSpace) NextIP4Net(mask net.IPMask) (*net.IPNet, error) {
|
||||
ones, _ := mask.Size()
|
||||
for _, r := range s.availableRanges {
|
||||
network := r.FirstIP.Mask(mask).To16()
|
||||
var firstIP net.IP
|
||||
// check if the start of the current range
|
||||
// is lower than the network boundary
|
||||
if compareIP4(network, r.FirstIP) >= 0 {
|
||||
// found the start of the range
|
||||
firstIP = network
|
||||
} else {
|
||||
// network address is lower than the first
|
||||
// ip in the range; try the next network
|
||||
// in the mask
|
||||
for i := len(network) - 1; i >= 12; i-- {
|
||||
partialByteIndex := ones/8 + 12
|
||||
var inc byte
|
||||
if i == partialByteIndex {
|
||||
// this octet may only be occupied
|
||||
// by the mask partially, e.g.
|
||||
// for a /25, the last octet has
|
||||
// only one bit in the mask
|
||||
//
|
||||
// in order to get the next network
|
||||
// we need to increment starting at
|
||||
// the last bit of the mask, e.g. 25
|
||||
// in this example, which would be
|
||||
// bit 8 in the last octet.
|
||||
inc = (byte)(1 << (uint)(8-ones%8))
|
||||
} else if i < partialByteIndex {
|
||||
// we are past the partial octets,
|
||||
// so this is portion where the mask
|
||||
// occupies the octets fully, so
|
||||
// we can just increment the last bit
|
||||
inc = 1
|
||||
}
|
||||
|
||||
if inc == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
network[i] += inc
|
||||
if network[i] > 0 {
|
||||
firstIP = network
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if firstIP != nil {
|
||||
// we found the first IP for the requested range,
|
||||
// now check if the available range can accommodate
|
||||
// the highest address given the first IP and the mask
|
||||
lastIP := highestIP4(&net.IPNet{IP: firstIP, Mask: mask})
|
||||
if compareIP4(lastIP, r.LastIP) <= 0 {
|
||||
return &net.IPNet{IP: firstIP, Mask: mask}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find IP range for mask %s", mask)
|
||||
}
|
||||
|
||||
// ReserveNextIP4Net reserves a new sub address space within the given address
|
||||
// space, given a bitmask specifying the "width" of the requested space.
|
||||
func (s *AddressSpace) ReserveNextIP4Net(mask net.IPMask) (*AddressSpace, error) {
|
||||
n, err := s.NextIP4Net(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.ReserveIP4Net(n)
|
||||
}
|
||||
|
||||
func splitRange(parentRange *ip.Range, firstIP net.IP, lastIP net.IP) (before, reserved, after *ip.Range) {
|
||||
if !firstIP.Equal(parentRange.FirstIP) {
|
||||
before = ip.NewRange(parentRange.FirstIP, decrementIP4(firstIP))
|
||||
}
|
||||
if !lastIP.Equal(parentRange.LastIP) {
|
||||
after = ip.NewRange(incrementIP4(lastIP), parentRange.LastIP)
|
||||
}
|
||||
|
||||
reserved = ip.NewRange(firstIP, lastIP)
|
||||
return
|
||||
}
|
||||
|
||||
// ReserveIP4Net reserves a new sub address space given an IP and mask.
|
||||
// Mask is required.
|
||||
// If IP is nil or "0.0.0.0", same as calling ReserveNextIP4Net
|
||||
// with the mask.
|
||||
func (s *AddressSpace) ReserveIP4Net(ipNet *net.IPNet) (*AddressSpace, error) {
|
||||
if ipNet.Mask == nil {
|
||||
return nil, fmt.Errorf("network mask not specified")
|
||||
}
|
||||
|
||||
if ipNet.IP == nil || ipNet.IP.Equal(net.ParseIP("0.0.0.0")) {
|
||||
return s.ReserveNextIP4Net(ipNet.Mask)
|
||||
}
|
||||
|
||||
sub, err := s.ReserveIP4Range(lowestIP4(ipNet), highestIP4(ipNet))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sub.Network = &net.IPNet{IP: ipNet.IP, Mask: ipNet.Mask}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (s *AddressSpace) reserveSubRange(firstIP net.IP, lastIP net.IP, index int) {
|
||||
before, _, after := splitRange(s.availableRanges[index], firstIP, lastIP)
|
||||
s.availableRanges = append(s.availableRanges[:index], s.availableRanges[index+1:]...)
|
||||
if before != nil {
|
||||
s.availableRanges = insertAddressRanges(s.availableRanges, index, before)
|
||||
index++
|
||||
}
|
||||
if after != nil {
|
||||
s.availableRanges = insertAddressRanges(s.availableRanges, index, after)
|
||||
}
|
||||
}
|
||||
|
||||
// ReserveIP4Range reserves a sub address space given a first and last IP.
|
||||
func (s *AddressSpace) ReserveIP4Range(firstIP net.IP, lastIP net.IP) (*AddressSpace, error) {
|
||||
for i, r := range s.availableRanges {
|
||||
if compareIP4(firstIP, r.FirstIP) < 0 ||
|
||||
compareIP4(lastIP, r.LastIP) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// found range
|
||||
log.Infof("Reserving IP range [%s, %s]", firstIP.String(), lastIP.String())
|
||||
s.reserveSubRange(firstIP, lastIP, i)
|
||||
subSpace := NewAddressSpaceFromRange(firstIP, lastIP)
|
||||
subSpace.Parent = s
|
||||
return subSpace, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if compareIP4(firstIP, s.Pool.FirstIP) > 0 && compareIP4(lastIP, s.Pool.LastIP) < 0 {
|
||||
// IP range is within the pool but not found available
|
||||
err = fmt.Errorf("Cannot reserve IP range %s - %s. Already in use", firstIP.String(), lastIP.String())
|
||||
} else {
|
||||
err = fmt.Errorf("Cannot reserve IP range %s - %s. Not within pool's range %s - %s",
|
||||
firstIP.String(), lastIP.String(), s.Pool.FirstIP, s.Pool.LastIP)
|
||||
}
|
||||
|
||||
log.Errorf(err.Error())
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func insertAddressRanges(r []*ip.Range, index int, ranges ...*ip.Range) []*ip.Range {
|
||||
if index == len(r) {
|
||||
return append(r, ranges...)
|
||||
}
|
||||
|
||||
for i := 0; i < len(ranges); i++ {
|
||||
r = append(r, &ip.Range{})
|
||||
}
|
||||
|
||||
copy(r[index+len(ranges):], r[index:])
|
||||
for i := 0; i < len(ranges); i++ {
|
||||
r[index+i] = ranges[i]
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ReserveNextIP4 reserves the next available IPv4 address.
|
||||
func (s *AddressSpace) ReserveNextIP4() (net.IP, error) {
|
||||
space, err := s.ReserveIP4Net(&net.IPNet{Mask: net.CIDRMask(32, 32)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return space.availableRanges[0].FirstIP, nil
|
||||
}
|
||||
|
||||
// ReserveIP4 reserves the given IPv4 address.
|
||||
func (s *AddressSpace) ReserveIP4(ip net.IP) error {
|
||||
_, err := s.ReserveIP4Range(ip, ip)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReleaseIP4Range releases a sub address space into the parent address space.
|
||||
// Sub address space has to have only a single available range.
|
||||
func (s *AddressSpace) ReleaseIP4Range(space *AddressSpace) error {
|
||||
// nothing to release
|
||||
if space == nil || len(space.availableRanges) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if space.Parent != s {
|
||||
return fmt.Errorf("cannot release subspace into another parent")
|
||||
}
|
||||
|
||||
// cannot release a range if it has more than one available sub range
|
||||
if len(space.availableRanges) > 1 {
|
||||
return fmt.Errorf("can not release an address space with more than one available range")
|
||||
}
|
||||
|
||||
firstIP := space.availableRanges[0].FirstIP
|
||||
lastIP := space.availableRanges[0].LastIP
|
||||
if compareIP4(firstIP, lastIP) > 0 {
|
||||
return fmt.Errorf("address space first ip %s is greater than last ip %s", firstIP, lastIP)
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; i < len(s.availableRanges); i++ {
|
||||
if compareIP4(lastIP, s.availableRanges[i].FirstIP) < 0 {
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if i > 0 && compareIP4(firstIP, s.availableRanges[i-1].LastIP) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i > 0 && i == len(s.availableRanges) {
|
||||
if compareIP4(firstIP, s.availableRanges[i-1].LastIP) <= 0 {
|
||||
return fmt.Errorf("Could not release IP range")
|
||||
}
|
||||
}
|
||||
|
||||
s.availableRanges = insertAddressRanges(s.availableRanges, i, space.availableRanges...)
|
||||
// #nosec: Errors unhandled.
|
||||
s.Defragment()
|
||||
|
||||
log.Infof("Released IP range [%s, %s]", firstIP, lastIP)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseIP4 releases the given IPv4 address.
|
||||
func (s *AddressSpace) ReleaseIP4(ip net.IP) error {
|
||||
tmp := NewAddressSpaceFromRange(ip, ip)
|
||||
tmp.Parent = s
|
||||
return s.ReleaseIP4Range(tmp)
|
||||
}
|
||||
|
||||
func (s *AddressSpace) Defragment() error {
|
||||
for i := 1; i < len(s.availableRanges); {
|
||||
first := s.availableRanges[i-1]
|
||||
second := s.availableRanges[i]
|
||||
if incrementIP4(first.LastIP).Equal(second.FirstIP) {
|
||||
first.LastIP = second.LastIP
|
||||
s.availableRanges = append(s.availableRanges[:i], s.availableRanges[i+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal compares two address spaces for equality.
|
||||
func (s *AddressSpace) Equal(other *AddressSpace) bool {
|
||||
if len(s.availableRanges) != len(other.availableRanges) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(s.availableRanges); i++ {
|
||||
if compareIP4(s.availableRanges[i].FirstIP, other.availableRanges[i].FirstIP) != 0 ||
|
||||
compareIP4(s.availableRanges[i].LastIP, other.availableRanges[i].LastIP) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
384
vendor/github.com/vmware/vic/lib/portlayer/network/ipam_test.go
generated
vendored
Normal file
384
vendor/github.com/vmware/vic/lib/portlayer/network/ipam_test.go
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIncrementIP4(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in net.IP
|
||||
out net.IP
|
||||
}{
|
||||
{net.IPv6loopback, nil},
|
||||
{net.ParseIP("10.10.10.255"), net.ParseIP("10.10.11.0")},
|
||||
{net.ParseIP("10.10.255.255"), net.ParseIP("10.11.0.0")},
|
||||
{net.ParseIP("10.255.255.255"), net.ParseIP("11.0.0.0")},
|
||||
{net.ParseIP("255.255.255.255"), net.ParseIP("0.0.0.0")},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
ip := incrementIP4(te.in)
|
||||
if !te.out.Equal(ip) {
|
||||
t.Errorf("got: %s, expected: %s", ip, te.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrementIP4(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in net.IP
|
||||
out net.IP
|
||||
}{
|
||||
{net.IPv6loopback, nil},
|
||||
{net.ParseIP("10.10.10.0"), net.ParseIP("10.10.9.255")},
|
||||
{net.ParseIP("10.10.0.0"), net.ParseIP("10.9.255.255")},
|
||||
{net.ParseIP("10.0.0.0"), net.ParseIP("9.255.255.255")},
|
||||
{net.ParseIP("0.0.0.0"), net.ParseIP("255.255.255.255")},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
ip := decrementIP4(te.in)
|
||||
if !te.out.Equal(ip) {
|
||||
t.Errorf("got: %s, expected: %s", ip, te.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareIP4(t *testing.T) {
|
||||
ips := []net.IP{
|
||||
net.ParseIP("10.10.10.10"),
|
||||
net.ParseIP("10.10.10.9"),
|
||||
net.ParseIP("10.10.9.9"),
|
||||
net.ParseIP("10.9.9.9"),
|
||||
net.ParseIP("9.9.9.9")}
|
||||
|
||||
for i := 0; i < len(ips)-1; i++ {
|
||||
if res := compareIP4(ips[i+1], ips[i]); res != -1 {
|
||||
t.Fatalf("comparing %s %s got: %v, expected: -1", ips[i+1], ips[i], res)
|
||||
}
|
||||
if res := compareIP4(ips[i], ips[i+1]); res != 1 {
|
||||
t.Fatalf("comparing %s %s got: %v, expected: 1", ips[i], ips[i+1], res)
|
||||
}
|
||||
if res := compareIP4(ips[i], ips[i]); res != 0 {
|
||||
t.Fatalf("comparing %s %s got: %v expected: 0", ips[i], ips[i], res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIP4(t *testing.T) {
|
||||
ip4 := net.IPv4(10, 10, 10, 10)
|
||||
if !isIP4(ip4) {
|
||||
t.Fatalf("ip %s got: false expected: true", ip4)
|
||||
}
|
||||
ip6 := net.IPv6loopback
|
||||
if isIP4(ip6) {
|
||||
t.Fatalf("ip %s got: true, expected: false", ip6)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLowestIP4(t *testing.T) {
|
||||
r := &net.IPNet{IP: net.ParseIP("10.10.10.10").To4(), Mask: net.CIDRMask(24, 32)}
|
||||
ip := net.ParseIP("10.10.10.0")
|
||||
if res := lowestIP4(r); !res.Equal(ip) {
|
||||
t.Errorf("range %s got: %s expected %s", r, res, ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHighestIP4(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in *net.IPNet
|
||||
out net.IP
|
||||
}{
|
||||
{&net.IPNet{IP: net.IPv6loopback}, nil},
|
||||
{&net.IPNet{IP: net.ParseIP("10.10.10.10").To4(), Mask: net.CIDRMask(24, 32)}, net.ParseIP("10.10.10.255")},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
if res := highestIP4(te.in); !res.Equal(te.out) {
|
||||
t.Errorf("range %s got: %s expected %s", te.in, res, te.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReserveIP4(t *testing.T) {
|
||||
space := NewAddressSpaceFromRange(net.ParseIP("10.10.10.10"),
|
||||
net.ParseIP("10.10.10.11"))
|
||||
|
||||
ip, err := space.ReserveNextIP4()
|
||||
expected := net.ParseIP("10.10.10.10")
|
||||
if err != nil || !ip.Equal(expected) {
|
||||
t.Errorf("got: %s, %s expected: %s, nil", ip, err, expected)
|
||||
}
|
||||
|
||||
ip, err = space.ReserveNextIP4()
|
||||
expected = net.ParseIP("10.10.10.11")
|
||||
if err != nil || !ip.Equal(expected) {
|
||||
t.Errorf("got: %s, %s expected: %s, nil", ip, err, expected)
|
||||
}
|
||||
|
||||
ip, err = space.ReserveNextIP4()
|
||||
if err == nil {
|
||||
t.Errorf("got: %s, %s expected: nil, error", ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseIP4(t *testing.T) {
|
||||
space := NewAddressSpaceFromRange(net.ParseIP("10.10.10.10"),
|
||||
net.ParseIP("10.10.10.11"))
|
||||
|
||||
ip, err := space.ReserveNextIP4()
|
||||
expected := net.ParseIP("10.10.10.10")
|
||||
if err != nil || !ip.Equal(expected) {
|
||||
t.Errorf("got: %s, %s expected: %s, nil", ip, err, expected)
|
||||
}
|
||||
|
||||
ip, err = space.ReserveNextIP4()
|
||||
expected = net.ParseIP("10.10.10.11")
|
||||
if err != nil || !ip.Equal(expected) {
|
||||
t.Errorf("got: %s, %s expected: %s, nil", ip, err, expected)
|
||||
}
|
||||
|
||||
ip, err = space.ReserveNextIP4()
|
||||
if err == nil {
|
||||
t.Errorf("got: %s, %s expected: nil, error", ip, err)
|
||||
}
|
||||
|
||||
err = space.ReleaseIP4(net.ParseIP("10.10.10.10"))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s expected: nil", err)
|
||||
}
|
||||
|
||||
err = space.ReleaseIP4(net.ParseIP("10.10.10.10"))
|
||||
if err == nil {
|
||||
t.Errorf("got: nil expected: error")
|
||||
}
|
||||
|
||||
err = space.ReleaseIP4(net.ParseIP("10.10.10.11"))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s expected: nil", err)
|
||||
}
|
||||
|
||||
ip, err = space.ReserveNextIP4()
|
||||
expected = net.ParseIP("10.10.10.10")
|
||||
if err != nil || !ip.Equal(expected) {
|
||||
t.Errorf("got: %s, %s expected: %s, nil", ip, err, expected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestReserveNextIP4Net(t *testing.T) {
|
||||
_, net1, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
space := NewAddressSpaceFromNetwork(net1)
|
||||
firstIP := net.IPv4(172, 16, 0, 0)
|
||||
lastIP := net.IPv4(172, 16, 255, 255)
|
||||
totalSubspaces := 0
|
||||
subspace, err := space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
for err == nil {
|
||||
totalSubspaces++
|
||||
if compareIP4(firstIP, subspace.availableRanges[0].FirstIP) != 0 {
|
||||
t.Errorf("got: %s, expected: %s", subspace.availableRanges[0].FirstIP, firstIP)
|
||||
}
|
||||
if compareIP4(lastIP, subspace.availableRanges[0].LastIP) != 0 {
|
||||
t.Errorf("got: %s, expected: %s", subspace.availableRanges[0].LastIP, lastIP)
|
||||
}
|
||||
firstIP = net.IPv4(172, firstIP[13]+1, 0, 0)
|
||||
lastIP = net.IPv4(172, lastIP[13]+1, 255, 255)
|
||||
subspace, err = space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
}
|
||||
|
||||
if totalSubspaces != 16 {
|
||||
t.Errorf("got: %d, expected: 16", totalSubspaces)
|
||||
}
|
||||
|
||||
space = NewAddressSpaceFromNetwork(net1)
|
||||
// peal off one ip from the range
|
||||
ip, err := space.ReserveNextIP4()
|
||||
if !ip.Equal(net.ParseIP("172.16.0.0")) {
|
||||
t.Errorf("got: %s, expected: 172.16.0.0", ip)
|
||||
}
|
||||
subSpace, err := space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
ip, err = subSpace.ReserveNextIP4()
|
||||
if compareIP4(ip, net.ParseIP("172.17.0.0")) != 0 {
|
||||
t.Errorf("got: %s, expected: %s", ip, net.ParseIP("172.17.0.0"))
|
||||
}
|
||||
|
||||
subSpace, err = space.ReserveNextIP4Net(net.CIDRMask(15, 32))
|
||||
ip, err = subSpace.ReserveNextIP4()
|
||||
if compareIP4(ip, net.ParseIP("172.18.0.0")) != 0 {
|
||||
t.Errorf("got: %s, expected: %s", ip, net.ParseIP("172.18.0.0"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReserveIP4Net(t *testing.T) {
|
||||
ipNet := &net.IPNet{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)}
|
||||
space := NewAddressSpaceFromNetwork(ipNet)
|
||||
// no mask
|
||||
_, err := space.ReserveIP4Net(&net.IPNet{IP: net.ParseIP("10.10.10.10")})
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
|
||||
// IP == nil, Mask != nil
|
||||
_, err = space.ReserveIP4Net(&net.IPNet{Mask: net.CIDRMask(12, 32)})
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
_, err = space.ReserveNextIP4()
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
space = NewAddressSpaceFromNetwork(ipNet)
|
||||
|
||||
// ip == "0.0.0.0", Mask != nil
|
||||
_, err = space.ReserveIP4Net(&net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: net.CIDRMask(12, 32)})
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
_, err = space.ReserveNextIP4()
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
space = NewAddressSpaceFromNetwork(ipNet)
|
||||
|
||||
// reserve the full space
|
||||
_, err = space.ReserveIP4Net(ipNet)
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
// no more ips left
|
||||
_, err = space.ReserveNextIP4()
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReserveIP4Range(t *testing.T) {
|
||||
s := NewAddressSpaceFromNetwork(&net.IPNet{IP: net.IPv4(10, 10, 10, 0), Mask: net.CIDRMask(24, 32)})
|
||||
s.ReserveNextIP4()
|
||||
// try to reserve an unavailable range
|
||||
_, err := s.ReserveIP4Range(net.IPv4(10, 10, 10, 0), net.IPv4(10, 10, 10, 255))
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseIP4Range(t *testing.T) {
|
||||
_, net1, _ := net.ParseCIDR("172.16.0.0/12")
|
||||
space := NewAddressSpaceFromNetwork(net1)
|
||||
err := space.ReleaseIP4Range(nil)
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
|
||||
// reserve the full range
|
||||
subSpaces := make([]*AddressSpace, 16)
|
||||
totalReserved := 0
|
||||
subSpaces[0], err = space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
totalReserved++
|
||||
for i := 1; i < len(subSpaces) && err == nil; i++ {
|
||||
totalReserved++
|
||||
subSpaces[i], err = space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
}
|
||||
if totalReserved != 16 {
|
||||
t.Errorf("got: %d, expected: 16", totalReserved)
|
||||
}
|
||||
|
||||
// release a range at the beginning
|
||||
err = space.ReleaseIP4Range(subSpaces[0])
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
|
||||
// try to release an already released range
|
||||
err = space.ReleaseIP4Range(subSpaces[0])
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
|
||||
// release a range in the middle
|
||||
err = space.ReleaseIP4Range(subSpaces[5])
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
|
||||
// release a range at the end
|
||||
err = space.ReleaseIP4Range(subSpaces[len(subSpaces)-1])
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
|
||||
// try to reserve a released range
|
||||
subspace, err := space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
if err != nil || !subSpaces[0].Equal(subspace) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
space = NewAddressSpaceFromNetwork(net1)
|
||||
// get a sub space
|
||||
subSpace, err := space.ReserveNextIP4Net(net.CIDRMask(16, 32))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
// fragment the sub space
|
||||
err = subSpace.ReserveIP4(net.ParseIP("172.16.0.2"))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
// try to release it; should fail
|
||||
err = space.ReleaseIP4Range(subSpace)
|
||||
if err == nil {
|
||||
t.Errorf("got: nil, expected: error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefragment(t *testing.T) {
|
||||
_, net1, _ := net.ParseCIDR("172.16.0.0/24")
|
||||
space := NewAddressSpaceFromNetwork(net1)
|
||||
ip, _ := space.ReserveNextIP4()
|
||||
if compareIP4(ip, net.ParseIP("172.16.0.0")) != 0 {
|
||||
t.Errorf("got: %s, expected: %s", ip, net.ParseIP("172.16.0.0"))
|
||||
}
|
||||
|
||||
err := space.ReserveIP4(net.ParseIP("172.16.0.24"))
|
||||
if err != nil {
|
||||
t.Errorf("got: %s, expected: nil", err)
|
||||
}
|
||||
|
||||
space.ReleaseIP4(ip)
|
||||
if len(space.availableRanges) != 2 {
|
||||
t.Errorf("got: %d, expected: 2", len(space.availableRanges))
|
||||
}
|
||||
|
||||
space.Defragment()
|
||||
if len(space.availableRanges) != 2 {
|
||||
t.Errorf("got: %d, expected: 2", len(space.availableRanges))
|
||||
}
|
||||
|
||||
space.ReleaseIP4(net.ParseIP("172.16.0.24"))
|
||||
if len(space.availableRanges) != 1 {
|
||||
t.Errorf("got: %d, expected: 1", len(space.availableRanges))
|
||||
}
|
||||
|
||||
space.Defragment()
|
||||
if len(space.availableRanges) != 1 {
|
||||
t.Errorf("got: %d, expected: 1", len(space.availableRanges))
|
||||
}
|
||||
}
|
||||
27
vendor/github.com/vmware/vic/lib/portlayer/network/link.go
generated
vendored
Normal file
27
vendor/github.com/vmware/vic/lib/portlayer/network/link.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 network
|
||||
|
||||
import "net"
|
||||
|
||||
type Link interface {
|
||||
AddrAdd(net.IPNet) error
|
||||
AddrDel(net.IPNet) error
|
||||
Attrs() *LinkAttrs
|
||||
}
|
||||
|
||||
type LinkAttrs struct {
|
||||
Name string
|
||||
}
|
||||
25
vendor/github.com/vmware/vic/lib/portlayer/network/link_darwin.go
generated
vendored
Normal file
25
vendor/github.com/vmware/vic/lib/portlayer/network/link_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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 network
|
||||
|
||||
import "fmt"
|
||||
|
||||
func LinkByName(name string) (Link, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func LinkByAlias(alias string) (Link, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
59
vendor/github.com/vmware/vic/lib/portlayer/network/link_linux.go
generated
vendored
Normal file
59
vendor/github.com/vmware/vic/lib/portlayer/network/link_linux.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 network
|
||||
|
||||
import "net"
|
||||
import "github.com/vishvananda/netlink"
|
||||
|
||||
type link struct {
|
||||
netlink.Link
|
||||
|
||||
attrs *LinkAttrs
|
||||
}
|
||||
|
||||
func newLink(l netlink.Link) Link {
|
||||
attrs := &LinkAttrs{Name: l.Attrs().Name}
|
||||
return &link{Link: l, attrs: attrs}
|
||||
}
|
||||
|
||||
func LinkByName(name string) (Link, error) {
|
||||
l, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newLink(l), nil
|
||||
}
|
||||
|
||||
func LinkByAlias(alias string) (Link, error) {
|
||||
l, err := netlink.LinkByAlias(alias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newLink(l), nil
|
||||
}
|
||||
|
||||
func (l *link) AddrAdd(addr net.IPNet) error {
|
||||
return netlink.AddrAdd(l.Link, &netlink.Addr{IPNet: &addr})
|
||||
}
|
||||
|
||||
func (l *link) AddrDel(addr net.IPNet) error {
|
||||
return netlink.AddrDel(l.Link, &netlink.Addr{IPNet: &addr})
|
||||
}
|
||||
|
||||
func (l *link) Attrs() *LinkAttrs {
|
||||
return l.attrs
|
||||
}
|
||||
25
vendor/github.com/vmware/vic/lib/portlayer/network/link_windows.go
generated
vendored
Normal file
25
vendor/github.com/vmware/vic/lib/portlayer/network/link_windows.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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 network
|
||||
|
||||
import "fmt"
|
||||
|
||||
func LinkByName(name string) (Link, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func LinkByAlias(alias string) (Link, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
246
vendor/github.com/vmware/vic/lib/portlayer/network/network.go
generated
vendored
Normal file
246
vendor/github.com/vmware/vic/lib/portlayer/network/network.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2016-2018 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 network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/event"
|
||||
"github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/store"
|
||||
"github.com/vmware/vic/pkg/kvstore"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultContext *Context
|
||||
|
||||
initializer struct {
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
)
|
||||
|
||||
type DuplicateResourceError struct {
|
||||
resID string
|
||||
}
|
||||
|
||||
type ResourceNotFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e DuplicateResourceError) Error() string {
|
||||
return fmt.Sprintf("%s already exists", e.resID)
|
||||
}
|
||||
|
||||
func Init(ctx context.Context, sess *session.Session, source extraconfig.DataSource, sink extraconfig.DataSink) error {
|
||||
trace.End(trace.Begin(""))
|
||||
|
||||
initializer.once.Do(func() {
|
||||
var err error
|
||||
defer func() {
|
||||
initializer.err = err
|
||||
}()
|
||||
|
||||
f := find.NewFinder(sess.Vim25(), false)
|
||||
|
||||
var config Configuration
|
||||
config.sink = sink
|
||||
config.source = source
|
||||
config.Decode()
|
||||
config.PortGroups = make(map[string]object.NetworkReference)
|
||||
|
||||
log.Debugf("Decoded VCH config for network: %#v", config)
|
||||
for nn, n := range config.ContainerNetworks {
|
||||
pgref := new(types.ManagedObjectReference)
|
||||
if !pgref.FromString(n.ID) {
|
||||
log.Warnf("Could not reacquire object reference from id for network %s: %s", nn, n.ID)
|
||||
}
|
||||
|
||||
var r object.Reference
|
||||
if r, err = f.ObjectReference(ctx, *pgref); err != nil {
|
||||
log.Warnf("could not get network reference for %s network: %s", nn, err)
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
|
||||
config.PortGroups[nn] = r.(object.NetworkReference)
|
||||
}
|
||||
|
||||
// make sure a NIC attached to the bridge network exists
|
||||
config.BridgeLink, err = getBridgeLink(&config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var kv kvstore.KeyValueStore
|
||||
kv, err = store.NewDatastoreKeyValue(ctx, sess, "network.contexts.default")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var netctx *Context
|
||||
if netctx, err = NewContext(&config, kv); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = engageContext(ctx, netctx, exec.Config.EventManager); err == nil {
|
||||
DefaultContext = netctx
|
||||
log.Infof("Default network context allocated")
|
||||
}
|
||||
})
|
||||
|
||||
return initializer.err
|
||||
}
|
||||
|
||||
// handleEvent processes events
|
||||
func handleEvent(netctx *Context, ie events.Event) {
|
||||
switch ie.String() {
|
||||
case events.ContainerPoweredOff:
|
||||
op := trace.NewOperation(context.Background(), fmt.Sprintf("handleEvent(%s)", ie.EventID()))
|
||||
op.Infof("Handling Event: %s", ie.EventID())
|
||||
// grab the operation from the event
|
||||
handle := exec.GetContainer(op, uid.Parse(ie.Reference()))
|
||||
if handle == nil {
|
||||
_, err := netctx.RemoveIDFromScopes(op, ie.Reference())
|
||||
if err != nil {
|
||||
op.Errorf("Failed to remove container %s scope: %s", ie.Reference(), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
if handle.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOff {
|
||||
op.Warnf("Live power state check on power off event shows %s: not unbinding network", ie.Reference(), handle.Runtime.PowerState)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := netctx.UnbindContainer(op, handle); err != nil {
|
||||
op.Warnf("Failed to unbind container %s: %s", ie.Reference(), err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := handle.Commit(op, nil, nil); err != nil {
|
||||
op.Warnf("Failed to commit handle after network unbind for container %s: %s", ie.Reference(), err)
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// engageContext connects the given network context into a vsphere environment
|
||||
// using an event manager, and a container cache. This hooks up a callback to
|
||||
// react to vsphere events, as well as populate the context with any containers
|
||||
// that are present.
|
||||
func engageContext(ctx context.Context, netctx *Context, em event.EventManager) error {
|
||||
var err error
|
||||
|
||||
// grab the context lock so that we do not unbind any containers
|
||||
// that stop out of band. this could cause, for example, for us
|
||||
// to bind a container when it has already been unbound by an
|
||||
// event callback
|
||||
netctx.Lock()
|
||||
defer netctx.Unlock()
|
||||
|
||||
// subscribe to the event stream for Vm events
|
||||
if em == nil {
|
||||
return fmt.Errorf("event manager is required for default network context")
|
||||
}
|
||||
|
||||
sub := fmt.Sprintf("%s(%p)", "netCtx", netctx)
|
||||
topic := events.NewEventType(events.ContainerEvent{}).Topic()
|
||||
s := em.Subscribe(topic, sub, func(ie events.Event) {
|
||||
handleEvent(netctx, ie)
|
||||
})
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
em.Unsubscribe(topic, sub)
|
||||
}
|
||||
}()
|
||||
|
||||
op := trace.NewOperation(context.Background(), "engageContext")
|
||||
s.Suspend(true)
|
||||
defer s.Resume()
|
||||
for _, c := range exec.Containers.Containers(nil) {
|
||||
log.Debugf("adding container %s", c)
|
||||
h := c.NewHandle(ctx)
|
||||
defer h.Close()
|
||||
|
||||
// add any user created networks that show up in container's config
|
||||
for n, ne := range h.ExecConfig.Networks {
|
||||
var s []*Scope
|
||||
s, err = netctx.findScopes(&n)
|
||||
if err != nil {
|
||||
if _, ok := err.(ResourceNotFoundError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pools := make([]string, len(ne.Network.Pools))
|
||||
for i := range ne.Network.Pools {
|
||||
pools[i] = ne.Network.Pools[i].String()
|
||||
}
|
||||
|
||||
log.Debugf("adding scope %s", n)
|
||||
|
||||
scopeData := &ScopeData{
|
||||
ScopeType: ne.Network.Type,
|
||||
Name: n,
|
||||
Subnet: &ne.Network.Gateway,
|
||||
Gateway: ne.Network.Gateway.IP,
|
||||
DNS: ne.Network.Nameservers,
|
||||
Pools: pools,
|
||||
}
|
||||
if _, err = netctx.newScope(scopeData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.CurrentState() == exec.StateRunning {
|
||||
if _, err = netctx.bindContainer(op, h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBridgeLink(config *Configuration) (Link, error) {
|
||||
// add the gateway address to the bridge interface
|
||||
link, err := LinkByName(config.BridgeNetwork)
|
||||
if err != nil {
|
||||
// lookup by alias
|
||||
return LinkByAlias(config.BridgeNetwork)
|
||||
}
|
||||
|
||||
return link, nil
|
||||
}
|
||||
76
vendor/github.com/vmware/vic/lib/portlayer/network/port.go
generated
vendored
Normal file
76
vendor/github.com/vmware/vic/lib/portlayer/network/port.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
type Port string
|
||||
|
||||
const NilPort = Port("")
|
||||
|
||||
// PortFromMapping constructs the full form of a port mapping
|
||||
// This has been added to help migrate towards consistent data returns for Endpoint structures
|
||||
func PortFromMapping(mapping nat.PortMapping) Port {
|
||||
p := fmt.Sprintf("%s:%s", mapping.Binding.HostPort, string(mapping.Port))
|
||||
return Port(p)
|
||||
}
|
||||
|
||||
func ParsePort(p string) (Port, error) {
|
||||
if _, err := Port(p).Port(); err != nil {
|
||||
return NilPort, err
|
||||
}
|
||||
proto := Port(p).Proto()
|
||||
if proto == "" {
|
||||
return NilPort, fmt.Errorf("bad port spec %s", p)
|
||||
}
|
||||
|
||||
return Port(p), nil
|
||||
}
|
||||
|
||||
func (p Port) Proto() string {
|
||||
parts := strings.Split(string(p), ":")
|
||||
proto, _ := nat.SplitProtoPort(parts[len(parts)-1])
|
||||
return proto
|
||||
}
|
||||
|
||||
func (p Port) Port() (uint16, error) {
|
||||
parts := strings.Split(string(p), ":")
|
||||
_, port := nat.SplitProtoPort(parts[len(parts)-1])
|
||||
if port == "" {
|
||||
return 0, fmt.Errorf("bad port spec %s", p)
|
||||
}
|
||||
|
||||
pout, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bad port spec %s", p)
|
||||
}
|
||||
|
||||
return uint16(pout), nil
|
||||
}
|
||||
|
||||
func (p Port) String() string {
|
||||
parts := strings.Split(string(p), ":")
|
||||
return string(parts[len(parts)-1])
|
||||
}
|
||||
|
||||
func (p Port) FullString() string {
|
||||
return string(p)
|
||||
}
|
||||
404
vendor/github.com/vmware/vic/lib/portlayer/network/scope.go
generated
vendored
Normal file
404
vendor/github.com/vmware/vic/lib/portlayer/network/scope.go
generated
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/pkg/ip"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
type Scope struct {
|
||||
sync.RWMutex
|
||||
|
||||
id uid.UID
|
||||
name string
|
||||
scopeType string
|
||||
subnet *net.IPNet
|
||||
gateway net.IP
|
||||
dns []net.IP
|
||||
trustLevel executor.TrustLevel
|
||||
containers map[uid.UID]*Container
|
||||
endpoints []*Endpoint
|
||||
spaces []*AddressSpace
|
||||
builtin bool
|
||||
network object.NetworkReference
|
||||
annotations map[string]string
|
||||
internal bool
|
||||
}
|
||||
|
||||
func newScope(id uid.UID, scopeType string, network object.NetworkReference, scopeData *ScopeData) *Scope {
|
||||
return &Scope{
|
||||
id: id,
|
||||
name: scopeData.Name,
|
||||
scopeType: scopeType,
|
||||
subnet: scopeData.Subnet,
|
||||
gateway: scopeData.Gateway,
|
||||
dns: scopeData.DNS,
|
||||
trustLevel: scopeData.TrustLevel,
|
||||
network: network,
|
||||
containers: make(map[uid.UID]*Container),
|
||||
annotations: make(map[string]string),
|
||||
internal: scopeData.Internal,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scope) Annotations() map[string]string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.annotations
|
||||
}
|
||||
|
||||
func (s *Scope) Name() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (s *Scope) ID() uid.UID {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *Scope) Type() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.scopeType
|
||||
}
|
||||
|
||||
func (s *Scope) Internal() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.internal
|
||||
}
|
||||
|
||||
func (s *Scope) Network() object.NetworkReference {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.network
|
||||
}
|
||||
|
||||
func (s *Scope) isDynamic() bool {
|
||||
return s.scopeType != constants.BridgeScopeType && len(s.spaces) == 0
|
||||
}
|
||||
|
||||
func (s *Scope) Pools() []*ip.Range {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.pools()
|
||||
}
|
||||
|
||||
func (s *Scope) TrustLevel() executor.TrustLevel {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.trustLevel
|
||||
}
|
||||
|
||||
func (s *Scope) pools() []*ip.Range {
|
||||
pools := make([]*ip.Range, len(s.spaces))
|
||||
for i := range s.spaces {
|
||||
sp := s.spaces[i]
|
||||
if sp.Network != nil {
|
||||
r := ip.ParseRange(sp.Network.String())
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
pools[i] = r
|
||||
continue
|
||||
}
|
||||
|
||||
pools[i] = sp.Pool
|
||||
}
|
||||
|
||||
return pools
|
||||
}
|
||||
|
||||
func (s *Scope) reserveEndpointIP(e *Endpoint) error {
|
||||
if s.isDynamic() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// reserve an ip address
|
||||
var err error
|
||||
for _, p := range s.spaces {
|
||||
if !ip.IsUnspecifiedIP(e.ip) {
|
||||
if err = p.ReserveIP4(e.ip); err == nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
var eip net.IP
|
||||
if eip, err = p.ReserveNextIP4(); err == nil {
|
||||
e.ip = eip
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Scope) releaseEndpointIP(e *Endpoint) error {
|
||||
if s.isDynamic() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, p := range s.spaces {
|
||||
if err := p.ReleaseIP4(e.ip); err == nil {
|
||||
if !e.static {
|
||||
e.ip = net.IPv4(0, 0, 0, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("could not release IP for endpoint")
|
||||
}
|
||||
|
||||
func (s *Scope) AddContainer(con *Container, e *Endpoint) error {
|
||||
op := trace.NewOperation(context.Background(), "Add container to the scope")
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("container is nil")
|
||||
}
|
||||
|
||||
_, ok := s.containers[con.id]
|
||||
if ok {
|
||||
return DuplicateResourceError{resID: con.id.String()}
|
||||
}
|
||||
|
||||
op.Debugf("Adding container %s to the scope %s(%s)",
|
||||
con.id, s.name, s.id)
|
||||
|
||||
if err := s.reserveEndpointIP(e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.addEndpoint(e)
|
||||
s.endpoints = append(s.endpoints, e)
|
||||
s.containers[con.id] = con
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) RemoveContainer(con *Container) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
op := trace.NewOperation(context.Background(), "Removing container from the scope")
|
||||
|
||||
c, ok := s.containers[con.id]
|
||||
if !ok || c != con {
|
||||
op.Debugf("Container %s not found in the scope %s(%s)", con.id, s.name, s.id)
|
||||
return ResourceNotFoundError{}
|
||||
}
|
||||
|
||||
e := c.Endpoint(s)
|
||||
if e == nil {
|
||||
op.Debugf("No scope endpoint for container %s in the scope %s(%s)", con.id, s.name, s.id)
|
||||
return ResourceNotFoundError{}
|
||||
}
|
||||
|
||||
if err := s.releaseEndpointIP(e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(s.containers, c.id)
|
||||
s.endpoints = removeEndpointHelper(e, s.endpoints)
|
||||
c.removeEndpoint(e)
|
||||
|
||||
op.Debugf("Container %s removed from the scope %s(%s)", con.id, s.name, s.id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) Containers() []*Container {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
containers := make([]*Container, len(s.containers))
|
||||
i := 0
|
||||
for _, c := range s.containers {
|
||||
containers[i] = c
|
||||
i++
|
||||
}
|
||||
|
||||
return containers
|
||||
}
|
||||
|
||||
func (s *Scope) Container(id uid.UID) *Container {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if c, ok := s.containers[id]; ok {
|
||||
return c
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) ContainerByAddr(addr net.IP) *Endpoint {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if addr == nil || addr.IsUnspecified() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, e := range s.endpoints {
|
||||
if addr.Equal(e.IP()) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) Endpoints() []*Endpoint {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
eps := make([]*Endpoint, len(s.endpoints))
|
||||
copy(eps, s.endpoints)
|
||||
return eps
|
||||
}
|
||||
|
||||
func (s *Scope) Subnet() *net.IPNet {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.subnet
|
||||
}
|
||||
|
||||
func (s *Scope) Gateway() net.IP {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.gateway
|
||||
}
|
||||
|
||||
func (s *Scope) DNS() []net.IP {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.dns
|
||||
}
|
||||
|
||||
type scopeJSON struct {
|
||||
ID uid.UID
|
||||
Name string
|
||||
Type string
|
||||
Subnet *net.IPNet
|
||||
Gateway net.IP
|
||||
DNS []net.IP
|
||||
Builtin bool
|
||||
Pools []*ip.Range
|
||||
Annotations map[string]string
|
||||
Internal bool
|
||||
}
|
||||
|
||||
func (s *Scope) MarshalJSON() ([]byte, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return json.Marshal(&scopeJSON{
|
||||
ID: s.id,
|
||||
Name: s.name,
|
||||
Type: s.scopeType,
|
||||
Subnet: s.subnet,
|
||||
Gateway: s.gateway,
|
||||
DNS: s.dns,
|
||||
Builtin: s.builtin,
|
||||
Pools: s.pools(),
|
||||
Annotations: s.annotations,
|
||||
Internal: s.internal,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Scope) UnmarshalJSON(data []byte) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
var sj scopeJSON
|
||||
if err := json.Unmarshal(data, &sj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns := Scope{
|
||||
containers: make(map[uid.UID]*Container),
|
||||
annotations: make(map[string]string),
|
||||
}
|
||||
ns.id = sj.ID
|
||||
ns.name = sj.Name
|
||||
ns.scopeType = sj.Type
|
||||
ns.subnet = sj.Subnet
|
||||
ns.gateway = sj.Gateway
|
||||
ns.dns = sj.DNS
|
||||
ns.builtin = sj.Builtin
|
||||
ns.spaces = make([]*AddressSpace, len(sj.Pools))
|
||||
for i := range sj.Pools {
|
||||
sp := NewAddressSpaceFromRange(sj.Pools[i].FirstIP, sj.Pools[i].LastIP)
|
||||
if sp == nil {
|
||||
return fmt.Errorf("invalid pool %s in scope %s", sj.Pools[i].String(), sj.Name)
|
||||
}
|
||||
|
||||
ns.spaces[i] = sp
|
||||
}
|
||||
|
||||
for k, v := range sj.Annotations {
|
||||
ns.annotations[k] = v
|
||||
}
|
||||
|
||||
ns.internal = sj.Internal
|
||||
|
||||
s.swap(&ns)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) swap(other *Scope) {
|
||||
s.id, other.id = other.id, s.id
|
||||
s.name, other.name = other.name, s.name
|
||||
s.scopeType, other.scopeType = other.scopeType, s.scopeType
|
||||
s.subnet, other.subnet = other.subnet, s.subnet
|
||||
s.gateway, other.gateway = other.gateway, s.gateway
|
||||
s.dns, other.dns = other.dns, s.dns
|
||||
s.builtin, other.builtin = other.builtin, s.builtin
|
||||
s.spaces, other.spaces = other.spaces, s.spaces
|
||||
s.endpoints, other.endpoints = other.endpoints, s.endpoints
|
||||
s.containers, other.containers = other.containers, s.containers
|
||||
s.network, other.network = other.network, s.network
|
||||
s.annotations, other.annotations = other.annotations, s.annotations
|
||||
s.internal, other.internal = other.internal, s.internal
|
||||
}
|
||||
195
vendor/github.com/vmware/vic/lib/portlayer/network/scope_test.go
generated
vendored
Normal file
195
vendor/github.com/vmware/vic/lib/portlayer/network/scope_test.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
func makeIP(a, b, c, d byte) *net.IP {
|
||||
i := net.IPv4(a, b, c, d)
|
||||
return &i
|
||||
}
|
||||
|
||||
var addEthernetCardOrig = addEthernetCard
|
||||
var addEthernetCardErr = func(_ *exec.Handle, _ *Scope) (types.BaseVirtualDevice, error) {
|
||||
return nil, fmt.Errorf("")
|
||||
}
|
||||
|
||||
func TestScopeAddRemoveContainer(t *testing.T) {
|
||||
var err error
|
||||
ctx, err := NewContext(testConfig(), nil)
|
||||
if err != nil {
|
||||
t.Errorf("NewContext() => (nil, %s), want (ctx, nil)", err)
|
||||
return
|
||||
}
|
||||
op := trace.NewOperation(context.Background(), "TestScopeAddRemoveContainer")
|
||||
|
||||
s := ctx.defaultScope
|
||||
|
||||
idFoo := uid.New()
|
||||
idBar := uid.New()
|
||||
|
||||
var tests1 = []struct {
|
||||
c *Container
|
||||
ip *net.IP
|
||||
out *Endpoint
|
||||
err error
|
||||
}{
|
||||
// no container
|
||||
{nil, nil, nil, fmt.Errorf("")},
|
||||
// add a new container to scope
|
||||
{&Container{id: idFoo}, nil, &Endpoint{ip: net.IPv4(172, 16, 0, 2), scope: s}, nil},
|
||||
// container already part of scope
|
||||
{&Container{id: idFoo}, nil, nil, DuplicateResourceError{}},
|
||||
// container with ip
|
||||
{&Container{id: idBar}, makeIP(172, 16, 0, 3), &Endpoint{ip: net.IPv4(172, 16, 0, 3), scope: s, static: true}, nil},
|
||||
}
|
||||
|
||||
for _, te := range tests1 {
|
||||
e := newEndpoint(te.c, s, te.ip, nil)
|
||||
err = s.AddContainer(te.c, e)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("s.AddContainer() => (_, nil), want (_, err)")
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(te.err) {
|
||||
t.Errorf("s.AddContainer() => (_, %v), want (_, %v)", reflect.TypeOf(err), reflect.TypeOf(te.err))
|
||||
continue
|
||||
}
|
||||
|
||||
if te.c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// for any other error other than DuplicateResourcError
|
||||
// verify that the container was not added
|
||||
if _, ok := err.(DuplicateResourceError); !ok {
|
||||
c := s.Container(te.c.ID())
|
||||
if c != nil {
|
||||
t.Errorf("s.Container(%s) => (%v, %v), want (nil, err)", te.c.ID(), c, err)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !e.IP().Equal(te.out.IP()) {
|
||||
t.Errorf("s.AddContainer() => e.IP() == %v, want e.IP() == %v", e.IP(), te.out.IP())
|
||||
continue
|
||||
}
|
||||
|
||||
if !e.Gateway().Equal(te.out.Gateway()) {
|
||||
t.Errorf("s.AddContainer() => e.Gateway() == %v, want e.Gateway() == %v", e.Gateway(), te.out.Gateway())
|
||||
continue
|
||||
}
|
||||
|
||||
if e.Subnet().String() != s.Subnet().String() {
|
||||
t.Errorf("s.AddContainer() => e.Subnet() == %s, want e.Subnet() == %s", e.Subnet(), s.Subnet())
|
||||
continue
|
||||
}
|
||||
|
||||
if e.static != te.out.static {
|
||||
t.Errorf("s.AddContainer() => e.static == %#v, want e.static == %#v", e.static, te.out.static)
|
||||
}
|
||||
|
||||
if e.container.ID() != te.c.ID() {
|
||||
t.Errorf("s.AddContainer() => e.container == %s, want e.container == %s", e.container.ID(), te.c.ID())
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, e1 := range s.Endpoints() {
|
||||
if e1 == e {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("s.endpoints does not contain %v", e)
|
||||
}
|
||||
|
||||
c := s.Container(te.c.id)
|
||||
if c == nil {
|
||||
t.Errorf("s.Container(%s) => nil, want %v", te.c.ID(), te.c)
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Endpoint(s) != e {
|
||||
t.Errorf("container %s does not contain %v", te.c.ID(), e)
|
||||
}
|
||||
}
|
||||
|
||||
options := &AddContainerOptions{
|
||||
Scope: ctx.defaultScope.Name(),
|
||||
}
|
||||
bound := exec.TestHandle("bound")
|
||||
ctx.AddContainer(bound, options)
|
||||
ctx.BindContainer(op, bound)
|
||||
|
||||
// test RemoveContainer
|
||||
var tests2 = []struct {
|
||||
c *Container
|
||||
err error
|
||||
}{
|
||||
// container not found
|
||||
{&Container{id: "c1"}, ResourceNotFoundError{}},
|
||||
// remove a container
|
||||
{s.Container(idFoo), nil},
|
||||
}
|
||||
|
||||
for _, te := range tests2 {
|
||||
err = s.RemoveContainer(te.c)
|
||||
if te.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("s.RemoveContainer() => nil, want %v", te.err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// container was removed, verify
|
||||
if err != nil {
|
||||
t.Errorf("s.RemoveContainer() => %s, want nil", err)
|
||||
continue
|
||||
}
|
||||
|
||||
c := s.Container(te.c.ID())
|
||||
if c != nil {
|
||||
t.Errorf("s.RemoveContainer() did not remove container %s", te.c.ID())
|
||||
continue
|
||||
}
|
||||
|
||||
for _, e := range s.endpoints {
|
||||
if e.container.ID() == te.c.ID() {
|
||||
t.Errorf("s.RemoveContainer() did not remove endpoint for container %s", te.c.ID())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
182
vendor/github.com/vmware/vic/lib/portlayer/portlayer.go
generated
vendored
Normal file
182
vendor/github.com/vmware/vic/lib/portlayer/portlayer.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2016-2018 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 portlayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/attach"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/logging"
|
||||
"github.com/vmware/vic/lib/portlayer/metrics"
|
||||
"github.com/vmware/vic/lib/portlayer/network"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/store"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Init initializes portlayer components at startup
|
||||
func Init(ctx context.Context, sess *session.Session) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
source, err := extraconfig.GuestInfoSource()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sink, err := extraconfig.GuestInfoSink()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create or restore a portlayer k/v store in the VCH's directory.
|
||||
vch, err := guest.GetSelf(ctx, sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vchvm := vm.NewVirtualMachineFromVM(ctx, sess, vch)
|
||||
vmPath, err := vchvm.VMPathName(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// vmPath is set to the vmx. Grab the directory from that.
|
||||
vmFolder, err := datastore.ToURL(path.Dir(vmPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vmParentPool, err := vchvm.ResourcePool(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = storage.Init(ctx, sess, vmParentPool, source, sink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = store.Init(ctx, sess, vmFolder); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := exec.Init(ctx, sess, source, sink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = network.Init(ctx, sess, source, sink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = logging.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = metrics.Init(ctx, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unbind containerVM serial ports configured with the old VCH IP.
|
||||
// Useful when the appliance restarts and the VCH has a different IP.
|
||||
TakeCareOfSerialPorts(sess)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TakeCareOfSerialPorts disconnects serial ports backed by network on the VCH's old IP and connects serial ports backed by file.
|
||||
// This is useful when the appliance or the portlayer restarts and the VCH has a new IP or container vms gets migrated
|
||||
// Any errors are logged and portlayer init proceeds as usual.
|
||||
func TakeCareOfSerialPorts(sess *session.Session) {
|
||||
op := trace.NewOperation(context.Background(), "SerialPorts")
|
||||
defer trace.End(trace.Begin("", op))
|
||||
// Get all running containers from the portlayer cache
|
||||
// Including starting containers here as well
|
||||
// TODO: for starting containers, if using the runblocking mechanism present as of this date, we should cause the
|
||||
// unbind change to blocking status to propagate into the container and release the process for start
|
||||
containers := exec.Containers.Containers([]exec.State{exec.StateRunning, exec.StateStarting})
|
||||
|
||||
for i := range containers {
|
||||
var containerID string
|
||||
|
||||
if containers[i].ExecConfig != nil {
|
||||
containerID = containers[i].ExecConfig.ID
|
||||
}
|
||||
op.Infof("unbinding serial port for running container %s", containerID)
|
||||
|
||||
operation := func() error {
|
||||
// Obtain a container handle
|
||||
handle := containers[i].NewHandle(op)
|
||||
if handle == nil {
|
||||
err := fmt.Errorf("unable to obtain a handle for container %s", containerID)
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Unbind the network backed VirtualSerialPort
|
||||
unbindHandle, err := attach.Unbind(handle, containerID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to unbind serial port for container %s: %s", containerID, err)
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
execHandle, ok := unbindHandle.(*exec.Handle)
|
||||
if !ok {
|
||||
err := fmt.Errorf("handle type assertion failed for container %s", containerID)
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Bind the file backed VirtualSerialPort
|
||||
bindHandle, err := logging.Bind(execHandle)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to unbind serial port for container %s: %s", containerID, err)
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
execHandle, ok = bindHandle.(*exec.Handle)
|
||||
if !ok {
|
||||
err := fmt.Errorf("handle type assertion failed for container %s", containerID)
|
||||
op.Errorf("%s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit the handle
|
||||
if err := execHandle.Commit(op, sess, nil); err != nil {
|
||||
op.Errorf("unable to commit handle for container %s: %s", containerID, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := retry.Do(operation, exec.IsConcurrentAccessError); err != nil {
|
||||
op.Errorf("Multiple attempts failed for committing the handle with %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/vmware/vic/lib/portlayer/storage/config.go
generated
vendored
Normal file
35
vendor/github.com/vmware/vic/lib/portlayer/storage/config.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi/view"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
)
|
||||
|
||||
var Config Configuration
|
||||
|
||||
// Configuration is a slice of the VCH config that is relevant to the storage part of the port layer
|
||||
type Configuration struct {
|
||||
// Turn on debug logging
|
||||
DebugLevel int `vic:"0.1" scope:"read-only" key:"init/diagnostics/debug"`
|
||||
|
||||
// Port Layer - storage
|
||||
config.Storage `vic:"0.1" scope:"read-only" key:"storage"`
|
||||
|
||||
// ContainerView
|
||||
// https://pubs.vmware.com/vsphere-6-0/index.jsp#com.vmware.wssdk.apiref.doc/vim.view.ContainerView.html
|
||||
ContainerView *view.ContainerView
|
||||
}
|
||||
177
vendor/github.com/vmware/vic/lib/portlayer/storage/container/export.go
generated
vendored
Normal file
177
vendor/github.com/vmware/vic/lib/portlayer/storage/container/export.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
func (c *ContainerStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
l, err := c.NewDataSource(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ancestor == "" {
|
||||
op.Infof("No ancestor specified so following basic export path")
|
||||
return l.Export(op, spec, data)
|
||||
}
|
||||
|
||||
// for now we assume ancetor instead of entirely generic left/right
|
||||
// this allows us to assume it's an image
|
||||
img, err := c.images.URL(op, ancestor)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to map ancestor %s to image: %s", ancestor, err)
|
||||
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
op.Debugf("Mapped ancestor %s to %s", ancestor, img.String())
|
||||
|
||||
r, err := c.newDataSource(op, img, false)
|
||||
if err != nil {
|
||||
op.Debugf("Unable to get datasource for ancestor: %s", err)
|
||||
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
closers := func() error {
|
||||
op.Debugf("Callback to io.Closer function for container export")
|
||||
|
||||
l.Close()
|
||||
r.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ls := l.Source()
|
||||
rs := r.Source()
|
||||
|
||||
fl, lok := ls.(*os.File)
|
||||
fr, rok := rs.(*os.File)
|
||||
|
||||
if !lok || !rok {
|
||||
go closers()
|
||||
return nil, errors.New("mismatched datasource types")
|
||||
}
|
||||
|
||||
// if we want data, exclude the xattrs, otherwise assume diff
|
||||
xattrs := !data
|
||||
|
||||
tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, xattrs)
|
||||
if err != nil {
|
||||
go closers()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storage.ProxyReadCloser{
|
||||
ReadCloser: tar,
|
||||
Closer: closers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewDataSource creates and returns an DataSource associated with container storage
|
||||
func (c *ContainerStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
uri, err := c.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineAttempt := 0
|
||||
offline:
|
||||
offlineAttempt++
|
||||
|
||||
// This is persistent to avoid issues with concurrent Stat/Import calls
|
||||
source, err := c.newDataSource(op, uri, true)
|
||||
if err == nil {
|
||||
return source, err
|
||||
}
|
||||
|
||||
// check for vmdk locked error here
|
||||
if !disk.IsLockedError(err) {
|
||||
op.Warnf("Unable to mount %s and do not know how to recover from error")
|
||||
// continue anyway because maybe there's an online option
|
||||
}
|
||||
|
||||
// online - Owners() should filter out the appliance VM
|
||||
// #nosec: Errors unhandled.
|
||||
owners, _ := c.Owners(op, uri, disk.LockedVMDKFilter)
|
||||
if len(owners) == 0 {
|
||||
op.Infof("No online owners were found for %s", id)
|
||||
return nil, errors.New("unable to create offline data source and no online owners found")
|
||||
}
|
||||
|
||||
for _, o := range owners {
|
||||
// sanity check to see if we are the owner - this should catch transitions
|
||||
// from container running to diff or commit for example between the offline attempt and here
|
||||
uuid, err := o.UUID(op)
|
||||
if err == nil {
|
||||
// check if the vm is appliance VM if we can successfully get its UUID
|
||||
// #nosec: Errors unhandled.
|
||||
self, _ := guest.IsSelf(op, uuid)
|
||||
if self && offlineAttempt < 2 {
|
||||
op.Infof("Appliance is owner of online vmdk - retrying offline source path")
|
||||
goto offline
|
||||
}
|
||||
}
|
||||
|
||||
online, err := c.newOnlineDataSource(op, o, id)
|
||||
if online != nil {
|
||||
return online, err
|
||||
}
|
||||
|
||||
op.Debugf("Failed to create online datasource with owner %s: %s", o.Reference(), err)
|
||||
}
|
||||
|
||||
return nil, errors.New("unable to create online or offline data source")
|
||||
}
|
||||
|
||||
func (c *ContainerStore) newDataSource(op trace.Operation, url *url.URL, persistent bool) (storage.DataSource, error) {
|
||||
mountPath, cleanFunc, err := c.Mount(op, url, persistent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data source for access to %s at %s", url, mountPath)
|
||||
return storage.NewMountDataSource(op, f, cleanFunc), nil
|
||||
}
|
||||
|
||||
func (c *ContainerStore) newOnlineDataSource(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSource, error) {
|
||||
op.Debugf("Constructing toolbox data source: %s.%s", owner.Reference(), id)
|
||||
|
||||
return &vsphere.ToolboxDataSource{
|
||||
VM: owner,
|
||||
ID: id,
|
||||
Clean: func() { return },
|
||||
}, nil
|
||||
}
|
||||
120
vendor/github.com/vmware/vic/lib/portlayer/storage/container/import.go
generated
vendored
Normal file
120
vendor/github.com/vmware/vic/lib/portlayer/storage/container/import.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
func (c *ContainerStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error {
|
||||
l, err := c.NewDataSink(op, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.Import(op, spec, tarstream)
|
||||
}
|
||||
|
||||
// NewDataSink creates and returns an DataSink associated with container storage
|
||||
func (c *ContainerStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
uri, err := c.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineAttempt := 0
|
||||
offline:
|
||||
offlineAttempt++
|
||||
|
||||
sink, err := c.newDataSink(op, uri)
|
||||
if err == nil {
|
||||
return sink, err
|
||||
}
|
||||
|
||||
// check for vmdk locked error here
|
||||
if !disk.IsLockedError(err) {
|
||||
op.Warnf("Unable to mount %s and do not know how to recover from error")
|
||||
// continue anyway because maybe there's an online option
|
||||
}
|
||||
|
||||
// online - Owners() should filter out the appliance VM
|
||||
// #nosec: Errors unhandled.
|
||||
owners, _ := c.Owners(op, uri, disk.LockedVMDKFilter)
|
||||
if len(owners) == 0 {
|
||||
op.Infof("No online owners were found for %s", id)
|
||||
return nil, errors.New("unable to create offline data sink and no online owners found")
|
||||
}
|
||||
|
||||
for _, o := range owners {
|
||||
// sanity check to see if we are the owner - this should catch transitions
|
||||
// from container running to diff or commit for example between the offline attempt and here
|
||||
uuid, err := o.UUID(op)
|
||||
if err == nil {
|
||||
// check if the vm is appliance VM if we can successfully get its UUID
|
||||
// #nosec: Errors unhandled.
|
||||
self, _ := guest.IsSelf(op, uuid)
|
||||
if self && offlineAttempt < 2 {
|
||||
op.Infof("Appliance is owner of online vmdk - retrying offline source path")
|
||||
goto offline
|
||||
}
|
||||
}
|
||||
|
||||
online, err := c.newOnlineDataSink(op, o, id)
|
||||
if online != nil {
|
||||
return online, err
|
||||
}
|
||||
|
||||
op.Debugf("Failed to create online datasink with owner %s: %s", o.Reference(), err)
|
||||
}
|
||||
|
||||
return nil, errors.New("unable to create online or offline data sink")
|
||||
}
|
||||
|
||||
func (c *ContainerStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) {
|
||||
mountPath, cleanFunc, err := c.Mount(op, url, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data sink for access to %s at %s", url, mountPath)
|
||||
return storage.NewMountDataSink(op, f, cleanFunc), nil
|
||||
}
|
||||
|
||||
func (c *ContainerStore) newOnlineDataSink(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSink, error) {
|
||||
op.Debugf("Constructing toolbox data sink: %s.%s", owner.Reference(), id)
|
||||
|
||||
return &vsphere.ToolboxDataSink{
|
||||
VM: owner,
|
||||
ID: id,
|
||||
Clean: func() { return },
|
||||
}, nil
|
||||
}
|
||||
90
vendor/github.com/vmware/vic/lib/portlayer/storage/container/store.go
generated
vendored
Normal file
90
vendor/github.com/vmware/vic/lib/portlayer/storage/container/store.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// ContainerStorer defines the interface contract expected to allow import and export
|
||||
// against containers
|
||||
type ContainerStorer interface {
|
||||
storage.Resolver
|
||||
storage.Importer
|
||||
storage.Exporter
|
||||
}
|
||||
|
||||
// ContainerStore stores container storage information
|
||||
type ContainerStore struct {
|
||||
disk.Vmdk
|
||||
|
||||
// used to resolve images when diffing
|
||||
images storage.Resolver
|
||||
}
|
||||
|
||||
// NewContainerStore creates and returns a new container store
|
||||
func NewContainerStore(op trace.Operation, s *session.Session, imageResolver storage.Resolver) (*ContainerStore, error) {
|
||||
dm, err := disk.NewDiskManager(op, s, storage.Config.ContainerView)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := &ContainerStore{
|
||||
Vmdk: disk.Vmdk{
|
||||
Manager: dm,
|
||||
//ds: ds,
|
||||
Session: s,
|
||||
},
|
||||
|
||||
images: imageResolver,
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// URL converts the id of a resource to a URL
|
||||
func (c *ContainerStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
// using diskfinder with a basic suffix match is an inefficient and potentially error prone way of doing this
|
||||
// mapping, but until the container store has a structured means of knowing this information it's at least
|
||||
// not going to be incorrect without an ID collision.
|
||||
dsPath, err := c.DiskFinder(op, func(filename string) bool {
|
||||
return strings.HasSuffix(filename, id+".vmdk")
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
Scheme: "ds",
|
||||
Path: dsPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Owners returns a list of VMs that are using the resource specified by `url`
|
||||
func (c *ContainerStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
if url.Scheme != "ds" {
|
||||
return nil, errors.New("vmdk path must be a datastore url with \"ds\" scheme")
|
||||
}
|
||||
|
||||
return c.Vmdk.Owners(op, url, filter)
|
||||
}
|
||||
32
vendor/github.com/vmware/vic/lib/portlayer/storage/image/errors.go
generated
vendored
Normal file
32
vendor/github.com/vmware/vic/lib/portlayer/storage/image/errors.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 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 image
|
||||
|
||||
type ErrImageInUse struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *ErrImageInUse) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
func IsErrImageInUse(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := err.(*ErrImageInUse)
|
||||
|
||||
return ok
|
||||
}
|
||||
202
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image.go
generated
vendored
Normal file
202
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
// 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 image
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/index"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
)
|
||||
|
||||
// ImageStorer is an interface to store images in the Image Store
|
||||
type ImageStorer interface {
|
||||
|
||||
// CreateImageStore creates a location to store images and creates a root
|
||||
// disk which serves as the parent of all layers.
|
||||
//
|
||||
// storeName - The name of the image store to be created. This must be
|
||||
// unique.
|
||||
//
|
||||
// Returns the URL of the created store
|
||||
CreateImageStore(op trace.Operation, storeName string) (*url.URL, error)
|
||||
|
||||
// DeleteImageStore is used to cleanup the image store. This can only be
|
||||
// called once there are no images left in the image store.
|
||||
DeleteImageStore(op trace.Operation, storeName string) error
|
||||
|
||||
// Gets the url to an image store via name
|
||||
GetImageStore(op trace.Operation, storeName string) (*url.URL, error)
|
||||
|
||||
// ListImageStores lists the available image stores
|
||||
ListImageStores(op trace.Operation) ([]*url.URL, error)
|
||||
|
||||
// WriteImage creates a new image layer from the given parent. Eg
|
||||
// parentImage + newLayer = new Image built from parent
|
||||
//
|
||||
// parent - The parent image to create the new image from.
|
||||
// ID - textual ID for the image to be written
|
||||
// meta - metadata associated with the image
|
||||
// sum - expected sha266 sum of the image content.
|
||||
// r - the image tar to be written
|
||||
WriteImage(op trace.Operation, parent *Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*Image, error)
|
||||
|
||||
// GetImage queries the image store for the specified image.
|
||||
//
|
||||
// store - The image store to query name - The name of the image (optional)
|
||||
// ID - textual ID for the image to be retrieved
|
||||
GetImage(op trace.Operation, store *url.URL, ID string) (*Image, error)
|
||||
|
||||
// ListImages returns a list of Images given a list of image IDs, or all
|
||||
// images in the image store if no param is passed.
|
||||
ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*Image, error)
|
||||
|
||||
// DeleteImage deletes an image from the image store. If the image is in
|
||||
// use either by way of inheritance or because it's attached to a
|
||||
// container, this will return an error.
|
||||
DeleteImage(op trace.Operation, image *Image) (*Image, error)
|
||||
|
||||
storage.Resolver
|
||||
storage.Importer
|
||||
storage.Exporter
|
||||
}
|
||||
|
||||
// Image is the handle to identify an image layer on the backing store. The
|
||||
// URI namespace used to identify the Image in the storage layer has the
|
||||
// following path scheme:
|
||||
//
|
||||
// `/storage/<image store identifier, usually the vch uuid>/<image id>`
|
||||
//
|
||||
type Image struct {
|
||||
// ID is the identifier for this layer. Usually a SHA
|
||||
ID string
|
||||
|
||||
// SelfLink is the URL for this layer. Filled in by the runtime.
|
||||
SelfLink *url.URL
|
||||
|
||||
// ParentLink is the URL for the parent. It's the VMDK this snapshot inherits from.
|
||||
ParentLink *url.URL
|
||||
|
||||
// Store is the URL for the image store the image can be found on.
|
||||
Store *url.URL
|
||||
|
||||
// Metadata associated with the image.
|
||||
Metadata map[string][]byte
|
||||
|
||||
// Disk is the underlying disk implementation
|
||||
Disk *disk.VirtualDisk
|
||||
|
||||
// DatastorePath is the dspath for actually using this image
|
||||
// NOTE: this should be replaced by structure accessors for the data and updated storage
|
||||
// interfaces that use _one_ variant of url/path for identifying images, volumes and stores.
|
||||
// URL was only suggested as an existing structure that could be leveraged when object.DatastorePath
|
||||
// was note available. The suggestion seems to have spawned monstruous unnecessary complexity.
|
||||
DatastorePath *object.DatastorePath
|
||||
}
|
||||
|
||||
func (i *Image) Copy() index.Element {
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
selflink, _ := url.Parse(i.SelfLink.String())
|
||||
// #nosec: Errors unhandled.
|
||||
store, _ := url.Parse(i.Store.String())
|
||||
|
||||
var parent *url.URL
|
||||
if i.ParentLink != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
parent, _ = url.Parse(i.ParentLink.String())
|
||||
}
|
||||
|
||||
c := &Image{
|
||||
ID: i.ID,
|
||||
SelfLink: selflink,
|
||||
ParentLink: parent,
|
||||
Store: store,
|
||||
DatastorePath: &object.DatastorePath{
|
||||
Datastore: i.DatastorePath.Datastore,
|
||||
Path: i.DatastorePath.Path,
|
||||
},
|
||||
}
|
||||
|
||||
if i.Metadata != nil {
|
||||
c.Metadata = make(map[string][]byte)
|
||||
|
||||
for k, v := range i.Metadata {
|
||||
buf := make([]byte, len(v))
|
||||
copy(buf, v)
|
||||
c.Metadata[k] = buf
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Returns the Selflink of the image
|
||||
func (i *Image) Self() string {
|
||||
return i.SelfLink.String()
|
||||
}
|
||||
|
||||
// Returns a link to the parent. Returns link to self if there is no parent
|
||||
func (i *Image) Parent() string {
|
||||
if i.ParentLink != nil {
|
||||
return i.ParentLink.String()
|
||||
}
|
||||
return i.Self()
|
||||
}
|
||||
|
||||
func Parse(u *url.URL) (*Image, error) {
|
||||
// Check the path isn't malformed.
|
||||
if !filepath.IsAbs(u.Path) {
|
||||
return nil, errors.New("invalid uri path")
|
||||
}
|
||||
|
||||
segments := strings.Split(filepath.Clean(u.Path), "/")
|
||||
if segments[0] == "" {
|
||||
segments = segments[1:]
|
||||
}
|
||||
|
||||
if segments[0] != util.StorageURLPath {
|
||||
return nil, errors.New("not a storage path")
|
||||
}
|
||||
|
||||
if len(segments) < 3 {
|
||||
return nil, errors.New("uri path mismatch")
|
||||
}
|
||||
|
||||
store, err := util.ImageStoreNameToURL(segments[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := segments[3]
|
||||
|
||||
var SelfLink url.URL
|
||||
SelfLink = *u
|
||||
|
||||
i := &Image{
|
||||
ID: id,
|
||||
SelfLink: &SelfLink,
|
||||
Store: store,
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
508
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_cache.go
generated
vendored
Normal file
508
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_cache.go
generated
vendored
Normal file
@@ -0,0 +1,508 @@
|
||||
// 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 image
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/index"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
var ErrCorruptImageStore = errors.New("Corrupt image store")
|
||||
|
||||
// NameLookupCache the global view of all of the image stores. To avoid unnecessary
|
||||
// lookups, the image cache keeps an in memory map of the store URI to the map
|
||||
// of images on disk.
|
||||
type NameLookupCache struct {
|
||||
|
||||
// The individual store locations -> Index
|
||||
storeCache map[url.URL]*index.Index
|
||||
// Guard against concurrent writes to the storeCache map
|
||||
storeCacheLock sync.Mutex
|
||||
|
||||
// The image store implementation. This mutates the actual disk images.
|
||||
DataStore ImageStorer
|
||||
}
|
||||
|
||||
func NewLookupCache(ds ImageStorer) *NameLookupCache {
|
||||
return &NameLookupCache{
|
||||
DataStore: ds,
|
||||
storeCache: make(map[url.URL]*index.Index),
|
||||
}
|
||||
}
|
||||
|
||||
// isRetry will check the error for retryability - if so reset the cache
|
||||
func (c *NameLookupCache) isRetry(op trace.Operation, err error) bool {
|
||||
if tasks.IsRetryError(op, err) {
|
||||
op.Debugf("%s is retryable, resetting store cache", err)
|
||||
c.storeCache = make(map[url.URL]*index.Index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetImageStore checks to see if a named image store exists and returns the
|
||||
// URL to it if so or error.
|
||||
func (c *NameLookupCache) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("StoreName: %s", storeName), op))
|
||||
store, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.storeCacheLock.Lock()
|
||||
defer c.storeCacheLock.Unlock()
|
||||
|
||||
// check the cache
|
||||
_, ok := c.storeCache[*store]
|
||||
|
||||
if !ok {
|
||||
op.Infof("Refreshing image cache from datastore.")
|
||||
// Store isn't in the cache. Look it up in the datastore.
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the store doesn't exist, we'll fall out here.
|
||||
_, err = c.DataStore.GetImageStore(op, storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indx := index.NewIndex()
|
||||
|
||||
c.storeCache[*store] = indx
|
||||
|
||||
// Add Scratch
|
||||
scratch, err := c.DataStore.GetImage(op, store, constants.ScratchLayerID)
|
||||
if err != nil {
|
||||
op.Errorf("ImageCache Error: looking up scratch on %s: %s", store.String(), err)
|
||||
if c.isRetry(op, err) {
|
||||
return nil, err
|
||||
}
|
||||
// potentially a recoverable error
|
||||
return nil, ErrCorruptImageStore
|
||||
}
|
||||
|
||||
if err = indx.Insert(scratch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// XXX after creating the indx and populating the map, we can put the rest in a go routine
|
||||
|
||||
images, err := c.DataStore.ListImages(op, store, nil)
|
||||
if err != nil {
|
||||
// if error is retryable we'll reset the cache
|
||||
c.isRetry(op, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Found %d images", len(images))
|
||||
|
||||
// Build image map to simplify tree traversal.
|
||||
imageMap := make(map[string]*Image, len(images))
|
||||
for _, img := range images {
|
||||
if img.ID == constants.ScratchLayerID {
|
||||
continue
|
||||
}
|
||||
imageMap[img.Self()] = img
|
||||
}
|
||||
|
||||
for k := range imageMap {
|
||||
parentTree(op, k, indx, imageMap)
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// parentTree adds images into the cache starting from the parent.
|
||||
func parentTree(op trace.Operation, imgLink string, idx *index.Index, imageMap map[string]*Image) {
|
||||
img, ok := imageMap[imgLink]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if img.Parent() != img.Self() {
|
||||
op.Debugf("Looking for parent %s for %s", img.Parent(), img.Self())
|
||||
parentTree(op, img.Parent(), idx, imageMap)
|
||||
}
|
||||
|
||||
if err := idx.Insert(img); err != nil {
|
||||
op.Errorf("Could not insert image %s: %v", imgLink, err)
|
||||
} else {
|
||||
op.Infof("Added image %s on datastore.", imgLink)
|
||||
}
|
||||
|
||||
delete(imageMap, imgLink)
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
store, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetImageStore Operation is able to be retried...
|
||||
getStore := func() error {
|
||||
// Check for existence and rehydrate the cache if it exists on disk.
|
||||
_, err = c.GetImageStore(op, storeName)
|
||||
return err
|
||||
}
|
||||
// is the error retryable
|
||||
isRetry := func(err error) bool {
|
||||
return tasks.IsRetryError(op, err)
|
||||
}
|
||||
|
||||
config := retry.NewBackoffConfig()
|
||||
config.InitialInterval = time.Second * 15
|
||||
config.MaxInterval = time.Second * 30
|
||||
config.MaxElapsedTime = time.Minute * 3
|
||||
|
||||
// attempt to get the image store
|
||||
err = retry.DoWithConfig(getStore, isRetry, config)
|
||||
if err == nil {
|
||||
// no error means that the image store exists and we can
|
||||
// safely return
|
||||
return nil, os.ErrExist
|
||||
}
|
||||
// if the image store doesn't exist or is corrupt we will continue,
|
||||
// otherwise fail here
|
||||
if err != os.ErrNotExist && err != ErrCorruptImageStore {
|
||||
op.Errorf("Error getting image store %s: %s", storeName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.storeCacheLock.Lock()
|
||||
defer c.storeCacheLock.Unlock()
|
||||
|
||||
store, err = c.DataStore.CreateImageStore(op, storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the root image
|
||||
scratch, err := c.DataStore.WriteImage(op, &Image{Store: store}, constants.ScratchLayerID, nil, "", nil)
|
||||
if err != nil {
|
||||
// if we failed here, remove the image store
|
||||
op.Infof("Removing failed image store %s", storeName)
|
||||
if e := c.DataStore.DeleteImageStore(op, storeName); e != nil {
|
||||
op.Errorf("image store cleanup failed: %s", e.Error())
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indx := index.NewIndex()
|
||||
c.storeCache[*store] = indx
|
||||
if err = indx.Insert(scratch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// ListImageStores returns a list of strings representing all existing image stores
|
||||
func (c *NameLookupCache) ListImageStores(op trace.Operation) ([]*url.URL, error) {
|
||||
c.storeCacheLock.Lock()
|
||||
defer c.storeCacheLock.Unlock()
|
||||
|
||||
stores := make([]*url.URL, 0, len(c.storeCache))
|
||||
for key := range c.storeCache {
|
||||
stores = append(stores, &key)
|
||||
}
|
||||
return stores, nil
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) WriteImage(op trace.Operation, parent *Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*Image, error) {
|
||||
// Check the parent exists (at least in the cache).
|
||||
p, err := c.GetImage(op, parent.Store, parent.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parent (%s) doesn't exist in %s: %s", parent.ID, parent.Store.String(), err)
|
||||
}
|
||||
|
||||
// Check the image doesn't already exist in the cache. A miss in this will trigger a datastore lookup.
|
||||
i, err := c.GetImage(op, p.Store, ID)
|
||||
if err == nil && i != nil {
|
||||
// TODO(FA) check sums to make sure this is the right image
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Definitely not in cache or image store, create image.
|
||||
i, err = c.DataStore.WriteImage(op, p, ID, meta, sum, r)
|
||||
if err != nil {
|
||||
op.Errorf("WriteImage of %s failed with: %s", ID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.storeCacheLock.Lock()
|
||||
indx := c.storeCache[*parent.Store]
|
||||
c.storeCacheLock.Unlock()
|
||||
|
||||
// Add the new image to the cache
|
||||
if err = indx.Insert(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) Export(op trace.Operation, store *url.URL, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
return c.DataStore.Export(op, id, ancestor, spec, data)
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) Import(op trace.Operation, store *url.URL, diskID string, spec *archive.FilterSpec, tarStream io.ReadCloser) error {
|
||||
return c.DataStore.Import(op, diskID, spec, tarStream)
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
return c.DataStore.NewDataSource(op, id)
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
return c.DataStore.URL(op, id)
|
||||
}
|
||||
|
||||
func (c *NameLookupCache) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
return c.DataStore.Owners(op, url, filter)
|
||||
}
|
||||
|
||||
// GetImage gets the specified image from the given store by retreiving it from the cache.
|
||||
func (c *NameLookupCache) GetImage(op trace.Operation, store *url.URL, ID string) (*Image, error) {
|
||||
op.Debugf("Getting image %s from %s", ID, store.String())
|
||||
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check the store exists
|
||||
if _, err = c.GetImageStore(op, storeName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.storeCacheLock.Lock()
|
||||
indx := c.storeCache[*store]
|
||||
c.storeCacheLock.Unlock()
|
||||
|
||||
imgURL, err := util.ImageURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := c.storeCache[*store].Get(imgURL.String())
|
||||
|
||||
var img *Image
|
||||
if err != nil {
|
||||
if err == index.ErrNodeNotFound {
|
||||
op.Debugf("Image %s not in cache, retreiving from datastore", ID)
|
||||
// Not in the cache. Try to load it.
|
||||
img, err = c.DataStore.GetImage(op, store, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = indx.Insert(img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
img, _ = node.(*Image)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// ListImages returns a list of Images for a list of IDs, or all if no IDs are passed
|
||||
func (c *NameLookupCache) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*Image, error) {
|
||||
// Filter the results
|
||||
imageList := make([]*Image, 0, len(IDs))
|
||||
|
||||
if len(IDs) > 0 {
|
||||
for _, id := range IDs {
|
||||
i, err := c.GetImage(op, store, id)
|
||||
if err == nil {
|
||||
imageList = append(imageList, i)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check the store exists before we start iterating it. This will populate the cache if it's empty.
|
||||
if _, err := c.GetImageStore(op, storeName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get the relevant cache
|
||||
c.storeCacheLock.Lock()
|
||||
indx := c.storeCache[*store]
|
||||
c.storeCacheLock.Unlock()
|
||||
|
||||
images, err := indx.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range images {
|
||||
img, _ := v.(*Image)
|
||||
// filter out scratch
|
||||
if img.ID == constants.ScratchLayerID {
|
||||
continue
|
||||
}
|
||||
|
||||
imageList = append(imageList, img)
|
||||
}
|
||||
}
|
||||
|
||||
return imageList, nil
|
||||
}
|
||||
|
||||
// DeleteImage deletes an image from the image store. If it is in use or is
|
||||
// being inheritted from, then this will return an error.
|
||||
func (c *NameLookupCache) DeleteImage(op trace.Operation, image *Image) (*Image, error) {
|
||||
// prevent deletes of scratch
|
||||
if image.ID == constants.ScratchLayerID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
op.Infof("DeleteImage: deleting %s", image.Self())
|
||||
|
||||
// Check the image exists. This will rehydrate the cache if necessary.
|
||||
img, err := c.GetImage(op, image.Store, image.ID)
|
||||
if err != nil {
|
||||
op.Errorf("DeleteImage: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get the relevant cache
|
||||
c.storeCacheLock.Lock()
|
||||
indx := c.storeCache[*img.Store]
|
||||
c.storeCacheLock.Unlock()
|
||||
|
||||
hasChildren, err := indx.HasChildren(img.Self())
|
||||
if err != nil {
|
||||
op.Errorf("DeleteImage: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasChildren {
|
||||
return nil, &ErrImageInUse{img.Self() + " in use by child images"}
|
||||
}
|
||||
|
||||
// The datastore will tell us if the image is attached
|
||||
if _, err = c.DataStore.DeleteImage(op, img); err != nil {
|
||||
op.Errorf("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the image from the cache
|
||||
if _, err = indx.Delete(img.Self()); err != nil {
|
||||
op.Errorf("%s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// DeleteBranch deletes a branch of images, starting from nodeID, up to the
|
||||
// first node with degree greater than 1. keepNodes is the array of images to
|
||||
// keep (and their branches).
|
||||
func (c *NameLookupCache) DeleteBranch(op trace.Operation, image *Image, keepNodes []*url.URL) ([]*Image, error) {
|
||||
op.Infof("DeleteBranch: deleting branch starting at %s", image.Self())
|
||||
|
||||
var deletedImages []*Image
|
||||
|
||||
// map of images to keep
|
||||
keep := make(map[url.URL]int)
|
||||
for _, elem := range keepNodes {
|
||||
op.Debugf("DeleteBranch: keep node %s", elem.String())
|
||||
keep[*elem] = 0
|
||||
}
|
||||
|
||||
// Check if the error is actually an error. If we deleted something,
|
||||
// then eat the error. This should really only return an error if the leaf
|
||||
// has issues.
|
||||
checkErr := func(err error, deleted []*Image) ([]*Image, error) {
|
||||
if err != nil {
|
||||
if len(deleted) == 0 {
|
||||
// we failed deleting any elements.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleted) == 0 {
|
||||
// This can't happen. deleteNode should have returned an err
|
||||
op.Debugf("No images deleted!!")
|
||||
}
|
||||
|
||||
// we deleted a section of a branch
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
for {
|
||||
if _, ok := keep[*image.SelfLink]; ok {
|
||||
return checkErr(fmt.Errorf("%s can't be deleted", image.Self()), deletedImages)
|
||||
}
|
||||
|
||||
deletedImage, err := c.DeleteImage(op, image)
|
||||
if err != nil {
|
||||
op.Debugf(err.Error())
|
||||
return checkErr(err, deletedImages)
|
||||
}
|
||||
|
||||
deletedImages = append(deletedImages, deletedImage)
|
||||
|
||||
// iterate to the parent
|
||||
parent, err := Parse(deletedImage.ParentLink)
|
||||
if err != nil {
|
||||
return deletedImages, err
|
||||
}
|
||||
|
||||
// set image to the parent
|
||||
image, err = c.GetImage(op, parent.Store, parent.ID)
|
||||
if err != nil {
|
||||
return deletedImages, err
|
||||
}
|
||||
|
||||
if image.ID == constants.ScratchLayerID {
|
||||
op.Infof("DeleteBranch: Done deleting images")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return deletedImages, nil
|
||||
}
|
||||
690
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_cache_test.go
generated
vendored
Normal file
690
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_cache_test.go
generated
vendored
Normal file
@@ -0,0 +1,690 @@
|
||||
// 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 image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
type MockDataStore struct {
|
||||
// id -> image
|
||||
db map[url.URL]map[string]*Image
|
||||
|
||||
createImageStoreError error
|
||||
writeImageError error
|
||||
}
|
||||
|
||||
func NewMockDataStore() *MockDataStore {
|
||||
m := &MockDataStore{
|
||||
db: make(map[url.URL]map[string]*Image),
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// GetImageStore checks to see if a named image store exists and returls the
|
||||
// URL to it if so or error.
|
||||
func (c *MockDataStore) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
u, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := c.db[*u]; !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
if c.createImageStoreError != nil {
|
||||
return nil, c.createImageStoreError
|
||||
}
|
||||
|
||||
u, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.db[*u] = make(map[string]*Image)
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) DeleteImageStore(op trace.Operation, storeName string) error {
|
||||
u, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.db[*u] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) ListImageStores(op trace.Operation) ([]*url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) WriteImage(op trace.Operation, parent *Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*Image, error) {
|
||||
if c.writeImageError != nil {
|
||||
op.Infof("WriteImage: returning error")
|
||||
return nil, c.writeImageError
|
||||
}
|
||||
|
||||
storeName, err := util.ImageStoreName(parent.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selflink, err := util.ImageURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parentLink *url.URL
|
||||
if parent.ID != "" {
|
||||
parentLink, err = util.ImageURL(storeName, parent.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
i := &Image{
|
||||
ID: ID,
|
||||
Store: parent.Store,
|
||||
ParentLink: parentLink,
|
||||
SelfLink: selflink,
|
||||
Metadata: meta,
|
||||
}
|
||||
|
||||
c.db[*parent.Store][ID] = i
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// GetImage gets the specified image from the given store by retreiving it from the cache.
|
||||
func (c *MockDataStore) GetImage(op trace.Operation, store *url.URL, ID string) (*Image, error) {
|
||||
i, ok := c.db[*store][ID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ListImages resturns a list of Images for a list of IDs, or all if no IDs are passed
|
||||
func (c *MockDataStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*Image, error) {
|
||||
var imageList []*Image
|
||||
for _, i := range c.db[*store] {
|
||||
imageList = append(imageList, i)
|
||||
}
|
||||
return imageList, nil
|
||||
}
|
||||
|
||||
// DeleteImage removes an image from the image store
|
||||
func (c *MockDataStore) DeleteImage(op trace.Operation, image *Image) (*Image, error) {
|
||||
delete(c.db[*image.Store], image.ID)
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *MockDataStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestListImages(t *testing.T) {
|
||||
s := NewLookupCache(NewMockDataStore())
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
storeURL, err := s.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a set of images
|
||||
images := make(map[string]*Image)
|
||||
parent := Image{
|
||||
ID: constants.ScratchLayerID,
|
||||
}
|
||||
parent.Store = storeURL
|
||||
testSum := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
for i := 1; i < 50; i++ {
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
img, werr := s.WriteImage(op, &parent, id, nil, testSum, nil)
|
||||
if !assert.NoError(t, werr) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
|
||||
images[id] = img
|
||||
}
|
||||
|
||||
// List all images
|
||||
outImages, err := s.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// check we retrieve all of the iamges
|
||||
assert.Equal(t, len(outImages), len(images))
|
||||
for _, img := range outImages {
|
||||
_, ok := images[img.ID]
|
||||
if !assert.True(t, ok) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check we can retrieve a subset
|
||||
inIDs := []string{"ID-1", "ID-2", "ID-3"}
|
||||
outImages, err = s.ListImages(op, storeURL, inIDs)
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, img := range outImages {
|
||||
reference, ok := images[img.ID]
|
||||
if !assert.True(t, ok) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, reference, img) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an image on the datastore directly and try to WriteImage via the
|
||||
// cache. The datastore should reflect the image already exists and bale out
|
||||
// without an error.
|
||||
func TestOutsideCacheWriteImage(t *testing.T) {
|
||||
s := NewLookupCache(NewMockDataStore())
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := s.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a set of images
|
||||
images := make(map[string]*Image)
|
||||
parent := Image{
|
||||
ID: constants.ScratchLayerID,
|
||||
}
|
||||
parent.Store = storeURL
|
||||
for i := 1; i < 50; i++ {
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
// Write to the datastore creating images
|
||||
img, werr := s.DataStore.WriteImage(op, &parent, id, nil, "", nil)
|
||||
if !assert.NoError(t, werr) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
|
||||
images[id] = img
|
||||
}
|
||||
|
||||
testSum := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
// Try to write the same images as above, but this time via the cache. WriteImage should return right away without any data written.
|
||||
for i := 1; i < 50; i++ {
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
// Write to the datastore creating images
|
||||
img, werr := s.WriteImage(op, &parent, id, nil, testSum, nil)
|
||||
if !assert.NoError(t, werr) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
|
||||
// assert it's the same image
|
||||
if !assert.Equal(t, images[img.ID], img) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create 2 store caches but use the same backing datastore. Create images
|
||||
// with the first cache, then get the image with the second. This simulates
|
||||
// restart since the second cache is empty and has to go to the backing store.
|
||||
func TestImageStoreRestart(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
ds := NewMockDataStore()
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
firstCache := NewLookupCache(ds)
|
||||
secondCache := NewLookupCache(ds)
|
||||
|
||||
storeURL, err := firstCache.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a set of images
|
||||
expectedImages := make(map[string]*Image)
|
||||
|
||||
parent, err := firstCache.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
testSum := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
for i := 1; i < 50; i++ {
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
img, werr := firstCache.WriteImage(op, parent, id, nil, testSum, nil)
|
||||
if !assert.NoError(t, werr) {
|
||||
return
|
||||
}
|
||||
if !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedImages[id] = img
|
||||
}
|
||||
|
||||
// get the images from the second cache to ensure it goes to the ds
|
||||
for id, expectedImg := range expectedImages {
|
||||
img, werr := secondCache.GetImage(op, storeURL, id)
|
||||
if !assert.NoError(t, werr) || !assert.Equal(t, expectedImg, img) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Nuke the second cache's datastore. All data should come from the cache.
|
||||
secondCache.DataStore = nil
|
||||
for id, expectedImg := range expectedImages {
|
||||
img, gerr := secondCache.GetImage(op, storeURL, id)
|
||||
if !assert.NoError(t, gerr) || !assert.Equal(t, expectedImg, img) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Same should happen with a third cache when image list is called
|
||||
thirdCache := NewLookupCache(ds)
|
||||
imageList, err := thirdCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(expectedImages), len(imageList)) {
|
||||
return
|
||||
}
|
||||
|
||||
// check the image data is the same
|
||||
for id, expectedImg := range expectedImages {
|
||||
img, err := thirdCache.GetImage(op, storeURL, id)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, expectedImg, img) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteImage(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
imageCache := NewLookupCache(NewMockDataStore())
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := imageCache.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
scratch, err := imageCache.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a 3 level tree with 4 branches
|
||||
branches := 4
|
||||
images := make(map[int]*Image)
|
||||
for branch := 1; branch < branches; branch++ {
|
||||
// level 1
|
||||
img, err := imageCache.WriteImage(op, scratch, strconv.Itoa(branch), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
images[branch] = img
|
||||
|
||||
// level 2
|
||||
i, err := imageCache.WriteImage(op, img, strconv.Itoa(branch*10), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, i) {
|
||||
return
|
||||
}
|
||||
images[branch*10] = i
|
||||
|
||||
// level 3
|
||||
i, err = imageCache.WriteImage(op, img, strconv.Itoa(branch*100), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, i) {
|
||||
return
|
||||
}
|
||||
images[branch*100] = i
|
||||
}
|
||||
|
||||
// Deletion of an intermediate node should fail
|
||||
_, err = imageCache.DeleteImage(op, images[1])
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
imageList, err := imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
// image list should be uneffected
|
||||
if !assert.Equal(t, len(images), len(imageList)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Deletion of leaves should be fine
|
||||
for branch := 1; branch < branches; branch++ {
|
||||
// range up the branch
|
||||
for _, img := range []*Image{images[branch*100], images[branch*10], images[branch]} {
|
||||
|
||||
_, err = imageCache.DeleteImage(op, img)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// the image should be gone
|
||||
i, err := imageCache.GetImage(op, storeURL, img.ID)
|
||||
if !assert.Error(t, err) || !assert.Nil(t, i) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List images should be empty (because we filter out scratch)
|
||||
imageList, err = imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.True(t, len(imageList) == 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBranch(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
trace.Logger.Level = logrus.DebugLevel
|
||||
|
||||
imageCache := NewLookupCache(NewMockDataStore())
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := imageCache.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
scratch, err := imageCache.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a 3 level tree with 3 branches. The third branch will have an extra node.
|
||||
// scratch
|
||||
// 1 2 3
|
||||
// 10 20 30
|
||||
// 100 200 300 301
|
||||
branches := 4
|
||||
images := make(map[int]*Image)
|
||||
for branch := 1; branch < branches; branch++ {
|
||||
// level 1
|
||||
img, err := imageCache.WriteImage(op, scratch, strconv.Itoa(branch), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
images[branch] = img
|
||||
|
||||
// level 2
|
||||
img, err = imageCache.WriteImage(op, img, strconv.Itoa(branch*10), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
images[branch*10] = img
|
||||
|
||||
// level 3
|
||||
img, err = imageCache.WriteImage(op, img, strconv.Itoa(branch*100), nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
images[branch*100] = img
|
||||
}
|
||||
|
||||
// Add an extra node to the last branch
|
||||
img, err := imageCache.WriteImage(op, images[30], "301", nil, "", nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
images[301] = img
|
||||
|
||||
//
|
||||
// Everything above here is just setup. Everything from here on is the test.
|
||||
//
|
||||
|
||||
// Deletion of an intermediate node should fail
|
||||
imagesDeleted, err := imageCache.DeleteBranch(op, images[1], nil)
|
||||
if !assert.Error(t, err) && assert.Nil(t, imagesDeleted) {
|
||||
return
|
||||
}
|
||||
|
||||
imageList, err := imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
// image list should be uneffected
|
||||
if !assert.Equal(t, len(images), len(imageList)) {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Deletion of a branch
|
||||
//
|
||||
imagesDeleted, err = imageCache.DeleteBranch(op, images[100], nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// List images should be missing a branch
|
||||
imageList, err = imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, 7, len(imageList)) || !assert.Equal(t, 3, len(imagesDeleted)) {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Deletion of the split branch should only allow deletion of a single image
|
||||
//
|
||||
imagesDeleted, err = imageCache.DeleteBranch(op, images[300], nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
imageList, err = imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
// only 300 should have been deleted
|
||||
if !assert.Equal(t, 6, len(imageList)) || !assert.Equal(t, images[300], imagesDeleted[0]) {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Test keep with our 1 remaining branch
|
||||
//
|
||||
|
||||
imagesDeleted, err = imageCache.DeleteBranch(op, images[200], []*url.URL{images[2].SelfLink})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
imageList, err = imageCache.ListImages(op, storeURL, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, imageList) {
|
||||
return
|
||||
}
|
||||
|
||||
// only 20 and 200 should have been deleted
|
||||
if !assert.Equal(t, 4, len(imageList)) || !assert.Equal(t, images[200], imagesDeleted[0]) || !assert.Equal(t, images[20], imagesDeleted[1]) {
|
||||
for _, img = range imageList {
|
||||
t.Logf("image = %#v", img)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCreateImageStoreFailureCleanup(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
trace.Logger.Level = logrus.DebugLevel
|
||||
|
||||
mds := NewMockDataStore()
|
||||
imageCache := NewLookupCache(mds)
|
||||
op := trace.NewOperation(context.Background(), "create image store error")
|
||||
mds.createImageStoreError = fmt.Errorf("foo error")
|
||||
|
||||
storeURL, err := imageCache.CreateImageStore(op, "testStore")
|
||||
if !assert.Error(t, err) || !assert.Nil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
mds.createImageStoreError = nil
|
||||
storeURL, err = imageCache.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
op = trace.NewOperation(context.Background(), "write image error")
|
||||
mds = NewMockDataStore()
|
||||
mds.writeImageError = fmt.Errorf("foo error")
|
||||
imageCache = NewLookupCache(mds)
|
||||
|
||||
storeURL, err = imageCache.CreateImageStore(op, "testStore2")
|
||||
if !assert.Error(t, err) || !assert.Nil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
mds.writeImageError = nil
|
||||
storeURL, err = imageCache.CreateImageStore(op, "testStore2")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Cache population should be happening in order starting from parent(id1) to children(id4)
|
||||
func TestPopulateCacheInExpectedOrder(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
st := NewMockDataStore()
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, _ := util.ImageStoreNameToURL("testStore")
|
||||
|
||||
storageURLStr := storeURL.String()
|
||||
|
||||
url1, _ := url.Parse(storageURLStr + "/id1")
|
||||
url2, _ := url.Parse(storageURLStr + "/id2")
|
||||
url3, _ := url.Parse(storageURLStr + "/id3")
|
||||
url4, _ := url.Parse(storageURLStr + "/id4")
|
||||
scratchURL, _ := url.Parse(storageURLStr + constants.ScratchLayerID)
|
||||
|
||||
img1 := &Image{ID: "id1", SelfLink: url1, ParentLink: scratchURL, Store: storeURL}
|
||||
img2 := &Image{ID: "id2", SelfLink: url2, ParentLink: url1, Store: storeURL}
|
||||
img3 := &Image{ID: "id3", SelfLink: url3, ParentLink: url2, Store: storeURL}
|
||||
img4 := &Image{ID: "id4", SelfLink: url4, ParentLink: url3, Store: storeURL}
|
||||
scratchImg := &Image{
|
||||
ID: constants.ScratchLayerID,
|
||||
SelfLink: scratchURL,
|
||||
ParentLink: scratchURL,
|
||||
Store: storeURL,
|
||||
}
|
||||
|
||||
// Order does matter for some reason.
|
||||
imageMap := map[string]*Image{
|
||||
img1.ID: img1,
|
||||
img4.ID: img4,
|
||||
img2.ID: img2,
|
||||
img3.ID: img3,
|
||||
scratchImg.ID: scratchImg,
|
||||
}
|
||||
|
||||
st.db[*storeURL] = imageMap
|
||||
|
||||
imageCache := NewLookupCache(st)
|
||||
imageCache.GetImageStore(op, "testStore")
|
||||
|
||||
// Check if all images are available.
|
||||
imageIds := []string{"id1", "id2", "id3", "id4"}
|
||||
for _, imageID := range imageIds {
|
||||
v, _ := imageCache.GetImage(op, storeURL, imageID)
|
||||
assert.NotNil(t, v)
|
||||
}
|
||||
}
|
||||
73
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_test.go
generated
vendored
Normal file
73
vendor/github.com/vmware/vic/lib/portlayer/storage/image/image_test.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 image
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
)
|
||||
|
||||
func TestImageCopy(t *testing.T) {
|
||||
storeName := "testStore"
|
||||
ID := "testImageID"
|
||||
|
||||
imageURL, err := util.ImageURL(storeName, ID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
parentURL, err := util.ImageURL(storeName, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
img, err := Parse(imageURL)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, img) {
|
||||
return
|
||||
}
|
||||
|
||||
storeURL, err := util.ImageStoreNameToURL(storeName)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
expected := &Image{
|
||||
ID: ID,
|
||||
SelfLink: imageURL,
|
||||
ParentLink: parentURL,
|
||||
Store: storeURL,
|
||||
Metadata: map[string][]byte{
|
||||
"1": {byte(1)},
|
||||
"2": {byte(2)},
|
||||
"3": []byte("three"),
|
||||
},
|
||||
}
|
||||
|
||||
actual := expected.Copy().(*Image)
|
||||
|
||||
if !assert.Equal(t, expected, actual) {
|
||||
return
|
||||
}
|
||||
|
||||
actual.Metadata["4"] = []byte("four")
|
||||
|
||||
if !assert.NotEqual(t, expected, actual) {
|
||||
return
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/vmware/vic/lib/portlayer/storage/image/join.go
generated
vendored
Normal file
133
vendor/github.com/vmware/vic/lib/portlayer/storage/image/join.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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 image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func Join(op trace.Operation, handle *exec.Handle, id, imgID, repoName string, img *Image) (*exec.Handle, error) {
|
||||
defer trace.End(trace.Begin(img.ID, op))
|
||||
|
||||
// if _, ok := handle.ExecConfig.Mounts[volume.ID]; ok {
|
||||
// return nil, fmt.Errorf("Volume with ID %s is already in container %s's mountspec config", volume.ID, handle.ExecConfig.ID)
|
||||
// }
|
||||
|
||||
// //constuct MountSpec for the tether
|
||||
// mountSpec := createMountSpec(volume, mountPath, diskOpts)
|
||||
// //append a device addition spec change to the container config
|
||||
// diskDevice := createVolumeVirtualDisk(volume)
|
||||
// config := createDeviceConfigSpec(diskDevice)
|
||||
// handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config)
|
||||
|
||||
// if handle.ExecConfig.Mounts == nil {
|
||||
// handle.ExecConfig.Mounts = make(map[string]executor.MountSpec)
|
||||
// }
|
||||
// handle.ExecConfig.Mounts[volume.ID] = mountSpec
|
||||
|
||||
// NOTE: from lib/spec/disk.go
|
||||
|
||||
// set the rw layer name
|
||||
// NOTE: this is a POOR assumption - I'm not clear on how it's functioning on vSAN at all in shipping code given the assumption that
|
||||
// "[ds] id/id.vmdk" is a legitimate path. Some vsphere magic path adjustment?
|
||||
rwlayer := fmt.Sprintf("%s/%s.vmdk", path.Dir(handle.Spec.VMPathName()), id)
|
||||
|
||||
disk := handle.Guest.NewDisk()
|
||||
moref := handle.Spec.Datastore.Reference()
|
||||
|
||||
// NOTE: this spec construction really should be captured in one place down in the disk layer. That code is currently biased towards
|
||||
// the appliance disk flows so couples spec creation with disk creation/attach.
|
||||
// TODO: we absolutely shouldn't be mixing the handle.Spec.Datastore (wtf does this come from) and the DatastorePath for the disk
|
||||
disk.GetVirtualDevice().Backing = &types.VirtualDiskFlatVer2BackingInfo{
|
||||
DiskMode: string(types.VirtualDiskModePersistent),
|
||||
ThinProvisioned: types.NewBool(true),
|
||||
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: rwlayer,
|
||||
Datastore: &moref,
|
||||
},
|
||||
|
||||
Parent: &types.VirtualDiskFlatVer2BackingInfo{
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: img.DatastorePath.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handle.Spec.AddVirtualDisk(disk)
|
||||
|
||||
// record the repo name and image ID that resolved to the layer in question
|
||||
// NOTE: these really shouldn't be recorded directly like this, and are 1:1 with the image, not with the ExecConfig.
|
||||
// I suspect there's some tech-debt reason they got dropped into the main configuration like this.
|
||||
// I do recall that the repoName at least was recorded because many names/tags can point to the same layer so it's the
|
||||
// point-and-time-of-use name that we're recording. I assume the same is true for the imageID whereas the layerID is actually
|
||||
// stable
|
||||
handle.ExecConfig.LayerID = img.ID
|
||||
handle.ExecConfig.ImageID = imgID
|
||||
handle.ExecConfig.RepoName = repoName
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func createVolumeVirtualDisk(volume *volume.Volume) *types.VirtualDisk {
|
||||
unitNumber := int32(-1)
|
||||
diskDevice := &types.VirtualDisk{
|
||||
CapacityInKB: 0,
|
||||
VirtualDevice: types.VirtualDevice{
|
||||
Key: -1,
|
||||
ControllerKey: 100, //FIXME: This is hardcoded for now and should be located from the config spec in the future.
|
||||
UnitNumber: &unitNumber,
|
||||
Backing: &types.VirtualDiskFlatVer2BackingInfo{
|
||||
DiskMode: string(types.VirtualDiskModeIndependent_persistent),
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: volume.Device.DiskPath().Path,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return diskDevice
|
||||
}
|
||||
|
||||
func createDeviceConfigSpec(diskDevice *types.VirtualDisk) *types.VirtualDeviceConfigSpec {
|
||||
config := &types.VirtualDeviceConfigSpec{
|
||||
Device: diskDevice,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
FileOperation: "", //blank for existing disk
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func createMountSpec(volume *volume.Volume, mountPath string, diskOpts map[string]string) executor.MountSpec {
|
||||
deviceMode := diskOpts[constants.Mode]
|
||||
newMountSpec := executor.MountSpec{
|
||||
Source: url.URL{
|
||||
Scheme: "label",
|
||||
Path: volume.Label,
|
||||
},
|
||||
Path: mountPath,
|
||||
Mode: deviceMode,
|
||||
CopyMode: volume.CopyMode,
|
||||
}
|
||||
return newMountSpec
|
||||
}
|
||||
111
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/export.go
generated
vendored
Normal file
111
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/export.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Export reads the delta between child and parent image layers, returning
|
||||
// the difference as a tar archive.
|
||||
//
|
||||
// id - must inherit from ancestor if ancestor is specified
|
||||
// ancestor - the layer up the chain against which to diff
|
||||
// spec - describes filters on paths found in the data (include, exclude, rebase, strip)
|
||||
// data - set to true to include file data in the tar archive, false to include headers only
|
||||
func (i *ImageStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
l, err := i.NewDataSource(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ancestor == "" {
|
||||
return l.Export(op, spec, data)
|
||||
}
|
||||
|
||||
// for now we assume ancestor instead of entirely generic left/right
|
||||
// this allows us to assume it's an image
|
||||
r, err := i.NewDataSource(op, ancestor)
|
||||
if err != nil {
|
||||
op.Debugf("Unable to get datasource for ancestor: %s", err)
|
||||
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
closers := func() error {
|
||||
op.Debugf("Callback to io.Closer function for image delta export")
|
||||
|
||||
l.Close()
|
||||
r.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ls := l.Source()
|
||||
rs := r.Source()
|
||||
|
||||
fl, lok := ls.(*os.File)
|
||||
fr, rok := rs.(*os.File)
|
||||
|
||||
if !lok || !rok {
|
||||
go closers()
|
||||
return nil, fmt.Errorf("mismatched datasource types: %T, %T", ls, rs)
|
||||
}
|
||||
|
||||
// if we want data, exclude the xattrs, otherwise assume diff
|
||||
tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, !data)
|
||||
if err != nil {
|
||||
go closers()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storage.ProxyReadCloser{
|
||||
ReadCloser: tar,
|
||||
Closer: closers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *ImageStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
url, err := i.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i.newDataSource(op, url)
|
||||
}
|
||||
|
||||
func (i *ImageStore) newDataSource(op trace.Operation, url *url.URL) (storage.DataSource, error) {
|
||||
mountPath, cleanFunc, err := i.Mount(op, url, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data source for access to %s at %s", url, mountPath)
|
||||
return storage.NewMountDataSource(op, f, cleanFunc), nil
|
||||
}
|
||||
65
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/import.go
generated
vendored
Normal file
65
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/import.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func (i *ImageStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error {
|
||||
l, err := i.NewDataSink(op, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.Import(op, spec, tarStream)
|
||||
}
|
||||
|
||||
// NewDataSink creates and returns an DataSource associated with image storage
|
||||
func (i *ImageStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
uri, err := i.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// there is no online fail over path for images
|
||||
// we should probably have a check in here as to whether the image is "sealed" and can no longer
|
||||
// be modified.
|
||||
return i.newDataSink(op, uri)
|
||||
}
|
||||
|
||||
func (i *ImageStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) {
|
||||
mountPath, cleanFunc, err := i.Mount(op, url, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storage.MountDataSink{
|
||||
Path: f,
|
||||
Clean: cleanFunc,
|
||||
}, nil
|
||||
}
|
||||
788
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/store.go
generated
vendored
Normal file
788
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/store.go
generated
vendored
Normal file
@@ -0,0 +1,788 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
docker "github.com/docker/docker/pkg/archive"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/image"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/lib/tether/shared"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
var (
|
||||
// Set to false for unit tests
|
||||
DetachAll = true
|
||||
|
||||
FileForMinOS = map[string]os.FileMode{
|
||||
"/etc/hostname": 0644,
|
||||
"/etc/hosts": 0644,
|
||||
"/etc/resolv.conf": 0644,
|
||||
}
|
||||
|
||||
SymLinkForMinOS = map[string]string{
|
||||
"/etc/mtab": "/proc/mounts",
|
||||
}
|
||||
|
||||
// Here the permission of .tether should be drwxrwxrwt.
|
||||
// The sticky bit 't' is added when mounting the tmpfs in bootstrap
|
||||
DirForMinOS = map[string]os.FileMode{
|
||||
"/etc": 0755,
|
||||
"/lib/modules": 0755,
|
||||
"/proc": 0555,
|
||||
"/sys": 0555,
|
||||
"/run": 0755,
|
||||
"/.tether": 0777,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
StorageImageDir = "images"
|
||||
|
||||
// scratchDiskLabel labels the root image for the disk chain
|
||||
scratchDiskLabel = "scratch"
|
||||
defaultDiskSizeInKB = 8 * 1024 * 1024
|
||||
metaDataDir = "imageMetadata"
|
||||
manifest = "manifest"
|
||||
)
|
||||
|
||||
type ImageStore struct {
|
||||
disk.Vmdk
|
||||
}
|
||||
|
||||
func NewImageStore(op trace.Operation, s *session.Session, u *url.URL) (*ImageStore, error) {
|
||||
dm, err := disk.NewDiskManager(op, s, storage.Config.ContainerView)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if DetachAll {
|
||||
if err = dm.DetachAll(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
datastores, err := s.Finder.DatastoreList(op, u.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Host returned error when trying to locate provided datastore %s: %s", u.String(), err.Error())
|
||||
}
|
||||
|
||||
if len(datastores) != 1 {
|
||||
return nil, fmt.Errorf("Found %d datastores with provided datastore path %s. Cannot create image store.", len(datastores), u)
|
||||
}
|
||||
|
||||
ds, err := datastore.NewHelper(op, s, datastores[0], path.Join(u.Path, constants.StorageParentDir))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vis := &ImageStore{
|
||||
Vmdk: disk.Vmdk{
|
||||
Manager: dm,
|
||||
Helper: ds,
|
||||
Session: s,
|
||||
},
|
||||
}
|
||||
|
||||
return vis, nil
|
||||
}
|
||||
|
||||
// Returns the path to a given image store. Currently this is the UUID of the VCH.
|
||||
// `/VIC/imageStoreName (currently the vch uuid)/images`
|
||||
func (v *ImageStore) imageStorePath(storeName string) string {
|
||||
return path.Join(storeName, StorageImageDir)
|
||||
}
|
||||
|
||||
// Returns the path to the image relative to the given
|
||||
// store. The dir structure for an image in the datastore is
|
||||
// `/VIC/imageStoreName (currently the vch uuid)/imageName/imageName.vmkd`
|
||||
func (v *ImageStore) imageDirPath(storeName, imageName string) string {
|
||||
return path.Join(v.imageStorePath(storeName), imageName)
|
||||
}
|
||||
|
||||
func (v *ImageStore) imageDiskPath(storeName, imageName string) string {
|
||||
return path.Join(v.imageDirPath(storeName, imageName), imageName+".vmdk")
|
||||
}
|
||||
|
||||
// Returns the path to the vmdk itself in datastore url format
|
||||
func (v *ImageStore) imageDiskDSPath(storeName, imageName string) *object.DatastorePath {
|
||||
return &object.DatastorePath{
|
||||
Datastore: v.Helper.RootURL.Datastore,
|
||||
Path: path.Join(v.Helper.RootURL.Path, v.imageDiskPath(storeName, imageName)),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the path to the metadata directory for an image
|
||||
func (v *ImageStore) imageMetadataDirPath(storeName, imageName string) string {
|
||||
return path.Join(v.imageDirPath(storeName, imageName), metaDataDir)
|
||||
}
|
||||
|
||||
// Returns the path to the manifest file. This file is our "done" file.
|
||||
func (v *ImageStore) manifestPath(storeName, imageName string) string {
|
||||
return path.Join(v.imageDirPath(storeName, imageName), manifest)
|
||||
}
|
||||
|
||||
func (v *ImageStore) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
// convert the store name to a port layer url.
|
||||
u, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = v.Mkdir(op, true, v.imageStorePath(storeName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// DeleteImageStore deletes the image store top level directory
|
||||
func (v *ImageStore) DeleteImageStore(op trace.Operation, storeName string) error {
|
||||
op.Infof("Cleaning up image store %s", storeName)
|
||||
return v.Rm(op, v.imageStorePath(storeName))
|
||||
}
|
||||
|
||||
// GetImageStore checks to see if the image store exists on disk and returns an
|
||||
// error or the store's URL.
|
||||
func (v *ImageStore) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
u, err := util.ImageStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := v.imageStorePath(storeName)
|
||||
info, err := v.Stat(op, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, ok := info.(*types.FolderFileInfo)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Stat error: path doesn't exist (%s)", p)
|
||||
}
|
||||
|
||||
// This is startup. Look for image directories without manifest files and
|
||||
// nuke them.
|
||||
if err := v.cleanup(op, u); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (v *ImageStore) ListImageStores(op trace.Operation) ([]*url.URL, error) {
|
||||
op.Debugf("Listing image stores under %s", v.Helper.RootURL)
|
||||
res, err := v.Ls(op, "")
|
||||
if err != nil {
|
||||
op.Errorf("Error listing image stores: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stores := []*url.URL{}
|
||||
for _, f := range res.File {
|
||||
path := f.GetFileInfo().Path
|
||||
_, ok := f.(*types.FolderFileInfo)
|
||||
if !ok {
|
||||
op.Debugf("Skipping directory element %s as it's not a folder: %T", path, f)
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := util.ImageStoreNameToURL(path)
|
||||
if err != nil {
|
||||
op.Errorf("Error converting image store name to URL: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Mapped image store name %s to %s", path, u.String())
|
||||
stores = append(stores, u)
|
||||
}
|
||||
|
||||
return stores, nil
|
||||
}
|
||||
|
||||
// WriteImage creates a new image layer from the given parent.
|
||||
// Eg parentImage + newLayer = new Image built from parent
|
||||
//
|
||||
// parent - The parent image to create the new image from.
|
||||
// ID - textual ID for the image to be written
|
||||
// meta - metadata associated with the image
|
||||
// Tag - the tag of the image to be written
|
||||
func (v *ImageStore) WriteImage(op trace.Operation, parent *image.Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*image.Image, error) {
|
||||
storeName, err := util.ImageStoreName(parent.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageURL, err := util.ImageURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dsk *disk.VirtualDisk
|
||||
// If this is scratch, then it's the root of the image store. All images
|
||||
// will be descended from this created and prepared fs.
|
||||
if ID == constants.ScratchLayerID {
|
||||
// Create the scratch layer
|
||||
if dsk, err = v.scratch(op, storeName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
|
||||
if parent.ID == "" {
|
||||
return nil, fmt.Errorf("parent ID is empty")
|
||||
}
|
||||
|
||||
dsk, err = v.writeImage(op, storeName, parent.ID, ID, meta, sum, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newImage := &image.Image{
|
||||
ID: ID,
|
||||
SelfLink: imageURL,
|
||||
ParentLink: parent.SelfLink,
|
||||
Store: parent.Store,
|
||||
Metadata: meta,
|
||||
Disk: dsk,
|
||||
DatastorePath: dsk.DatastoreURI,
|
||||
}
|
||||
|
||||
return newImage, nil
|
||||
}
|
||||
|
||||
// URL returns a url to the disk image represented by `id`
|
||||
// This is a "ds://" URL so cannot be used as input to most of the ImageStore methods that
|
||||
// take URLs.
|
||||
func (v *ImageStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
stores, err := v.ListImageStores(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(stores) < 1 {
|
||||
detail := "expected to find at least one image store available"
|
||||
op.Errorf("Listing image stores: %s", detail)
|
||||
return nil, errors.New(detail)
|
||||
}
|
||||
|
||||
storeName, err := util.ImageStoreName(stores[0])
|
||||
if err != nil {
|
||||
op.Infof("Error getting image store name for %s: %s", stores[0], err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := util.ImageDatastoreURL(v.imageDiskDSPath(storeName, id))
|
||||
if err != nil {
|
||||
op.Infof("Error getting image URL: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Mapped image %s to %s", id, url)
|
||||
return url, err
|
||||
}
|
||||
|
||||
// Owners returns a list of VMs that are using the disk specified by `url`
|
||||
func (v *ImageStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// cleanup safely on error
|
||||
func (v *ImageStore) cleanupDisk(op trace.Operation, ID, storeName string, vmdisk *disk.VirtualDisk) {
|
||||
op.Errorf("Cleaning up failed image %s", ID)
|
||||
|
||||
if vmdisk != nil {
|
||||
if vmdisk.Mounted() {
|
||||
op.Debugf("Unmounting abandoned disk")
|
||||
// #nosec: Errors unhandled.
|
||||
vmdisk.Unmount(op)
|
||||
}
|
||||
|
||||
if vmdisk.Attached() {
|
||||
op.Debugf("Detaching abandoned disk")
|
||||
// #nosec: Errors unhandled.
|
||||
v.Detach(op, vmdisk.VirtualDiskConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
v.deleteImage(op, storeName, ID)
|
||||
}
|
||||
|
||||
// Create the image directory, create a temp vmdk in this directory,
|
||||
// attach/mount the disk, unpack the tar, check the checksum. If the data
|
||||
// doesn't match the expected checksum, abort by nuking the image directory.
|
||||
// If everything matches, move the tmp vmdk to ID.vmdk. The unwind path is a
|
||||
// bit convoluted here; we need to clean up on the way out in the error case
|
||||
func (v *ImageStore) writeImage(op trace.Operation, storeName, parentID, ID string, meta map[string][]byte,
|
||||
sum string, r io.Reader) (*disk.VirtualDisk, error) {
|
||||
|
||||
// Create a temp image directory in the store.
|
||||
imageDir := v.imageDirPath(storeName, ID)
|
||||
_, err := v.Mkdir(op, true, imageDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the metadata to the datastore
|
||||
metaDataDir := v.imageMetadataDirPath(storeName, ID)
|
||||
err = vsphere.WriteMetadata(op, v.Helper, metaDataDir, meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// datastore path to the parent
|
||||
parentDiskDsURI := v.imageDiskDSPath(storeName, parentID)
|
||||
|
||||
// datastore path to the disk we're creating
|
||||
diskDsURI := v.imageDiskDSPath(storeName, ID)
|
||||
op.Infof("Creating image %s (%s)", ID, diskDsURI)
|
||||
|
||||
var vmdisk *disk.VirtualDisk
|
||||
// On error, unmount if mounted, detach if attached, and nuke the image directory
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
v.cleanupDisk(op, ID, storeName, vmdisk)
|
||||
}()
|
||||
|
||||
config := disk.NewPersistentDisk(diskDsURI).WithParent(parentDiskDsURI)
|
||||
// Create the disk
|
||||
vmdisk, err = v.CreateAndAttach(op, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vmdisk.SetLabel(op, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir, err := vmdisk.Mount(op, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
t := io.TeeReader(r, h)
|
||||
|
||||
// Untar the archive
|
||||
var n int64
|
||||
if n, err = docker.ApplyLayer(dir, t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("%s wrote %d bytes", ID, n)
|
||||
|
||||
actualSum := fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
if actualSum != sum {
|
||||
err = fmt.Errorf("Failed to validate image checksum. Expected %s, got %s", sum, actualSum)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = vmdisk.Unmount(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = v.Detach(op, vmdisk.VirtualDiskConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write our own bookkeeping manifest file to the image's directory. We
|
||||
// treat the manifest file like a done file. Its existence means this vmdk
|
||||
// is consistent. Previously we were writing the vmdk to a tmp vmdk file
|
||||
// then moving it (using the MoveDatastoreFile or MoveVirtualDisk calls).
|
||||
// However(!!) this flattens the vmdk. Also mkdir foo && ls -l foo fails
|
||||
// on VSAN (see
|
||||
// https://github.com/vmware/vic/pull/1764#issuecomment-237093424 for
|
||||
// detail). We basically can't trust any of the datastore calls to help us
|
||||
// with atomic operations. Touching an empty file seems to work well
|
||||
// enough.
|
||||
if err = v.writeManifest(op, storeName, ID, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vmdisk, nil
|
||||
}
|
||||
|
||||
func (v *ImageStore) scratch(op trace.Operation, storeName string) (*disk.VirtualDisk, error) {
|
||||
var (
|
||||
vmdisk *disk.VirtualDisk
|
||||
size int64
|
||||
err error
|
||||
)
|
||||
|
||||
// Create the image directory in the store.
|
||||
imageDir := v.imageDirPath(storeName, constants.ScratchLayerID)
|
||||
if _, err := v.Mkdir(op, false, imageDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the metadata to the datastore
|
||||
metaDataDir := v.imageMetadataDirPath(storeName, constants.ScratchLayerID)
|
||||
if err := vsphere.WriteMetadata(op, v.Helper, metaDataDir, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageDiskDsURI := v.imageDiskDSPath(storeName, constants.ScratchLayerID)
|
||||
op.Infof("Creating image %s (%s)", constants.ScratchLayerID, imageDiskDsURI)
|
||||
|
||||
size = defaultDiskSizeInKB
|
||||
if storage.Config.ScratchSize != 0 {
|
||||
size = storage.Config.ScratchSize
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
v.cleanupDisk(op, constants.ScratchLayerID, storeName, vmdisk)
|
||||
}()
|
||||
|
||||
config := disk.NewPersistentDisk(imageDiskDsURI).WithCapacity(size).WithUUID(shared.ScratchUUID)
|
||||
// Create the disk
|
||||
vmdisk, err = v.CreateAndAttach(op, config)
|
||||
if err != nil {
|
||||
op.Errorf("CreateAndAttach(%s) error: %s", imageDiskDsURI, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Scratch disk created with size %d", storage.Config.ScratchSize)
|
||||
|
||||
// Make the filesystem and set its label to defaultDiskLabel
|
||||
if err = vmdisk.Mkfs(op, scratchDiskLabel); err != nil {
|
||||
op.Errorf("Failed to create scratch filesystem: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = createBaseStructure(op, vmdisk); err != nil {
|
||||
op.Errorf("Failed to create base filesystem structure: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = v.Detach(op, vmdisk.VirtualDiskConfig); err != nil {
|
||||
op.Errorf("Failed to detach scratch image: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = v.writeManifest(op, storeName, constants.ScratchLayerID, nil); err != nil {
|
||||
op.Errorf("Failed to create manifest for scratch image: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vmdisk, nil
|
||||
}
|
||||
|
||||
func (v *ImageStore) GetImage(op trace.Operation, store *url.URL, ID string) (*image.Image, error) {
|
||||
defer trace.End(trace.Begin(store.String() + "/" + ID))
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageURL, err := util.ImageURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = v.verifyImage(op, storeName, ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get the metadata
|
||||
metaDataDir := v.imageMetadataDirPath(storeName, ID)
|
||||
meta, err := vsphere.GetMetadata(op, v.Helper, metaDataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diskDsURI := v.imageDiskDSPath(storeName, ID)
|
||||
|
||||
var s = *store
|
||||
|
||||
config := disk.NewPersistentDisk(diskDsURI)
|
||||
dsk, err := v.Get(op, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parentURL *url.URL
|
||||
if dsk.ParentDatastoreURI != nil {
|
||||
vmdk := path.Base(dsk.ParentDatastoreURI.Path)
|
||||
parentURL, err = util.ImageURL(storeName, strings.TrimSuffix(vmdk, path.Ext(vmdk)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newImage := &image.Image{
|
||||
ID: ID,
|
||||
SelfLink: imageURL,
|
||||
Store: &s,
|
||||
ParentLink: parentURL,
|
||||
Metadata: meta,
|
||||
Disk: dsk,
|
||||
DatastorePath: diskDsURI,
|
||||
}
|
||||
|
||||
op.Debugf("GetImage(%s) has parent %s", newImage.SelfLink, newImage.Parent())
|
||||
return newImage, nil
|
||||
}
|
||||
|
||||
func (v *ImageStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*image.Image, error) {
|
||||
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := v.Ls(op, v.imageStorePath(storeName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images := []*image.Image{}
|
||||
for _, f := range res.File {
|
||||
file, ok := f.(*types.FolderFileInfo)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ID := file.Path
|
||||
|
||||
// filter out scratch
|
||||
if ID == constants.ScratchLayerID {
|
||||
continue
|
||||
}
|
||||
|
||||
// GetImage verifies the image is good by calling verifyImage.
|
||||
img, err := v.GetImage(op, store, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images = append(images, img)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// DeleteImage deletes an image from the image store. If the image is in
|
||||
// use either by way of inheritance or because it's attached to a
|
||||
// container, this will return an error.
|
||||
func (v *ImageStore) DeleteImage(op trace.Operation, image *image.Image) (*image.Image, error) {
|
||||
// check if the image is in use.
|
||||
if err := imagesInUse(op, image.ID); err != nil {
|
||||
op.Errorf("ImageStore: delete image error: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeName, err := util.ImageStoreName(image.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return image, v.deleteImage(op, storeName, image.ID)
|
||||
}
|
||||
|
||||
func (v *ImageStore) deleteImage(op trace.Operation, storeName, ID string) error {
|
||||
// Delete in order of manifest (the done file), the vmdk (because VC honors
|
||||
// the deletable flag in the vmdk file), then the directory to get
|
||||
// everything else.
|
||||
paths := []string{
|
||||
v.manifestPath(storeName, ID),
|
||||
v.imageDiskPath(storeName, ID),
|
||||
v.imageDirPath(storeName, ID),
|
||||
}
|
||||
|
||||
for _, pth := range paths {
|
||||
err := v.Rm(op, pth)
|
||||
|
||||
// not exist is ok
|
||||
if err == nil || types.IsFileNotFound(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// something isn't right. bale.
|
||||
op.Errorf("ImageStore: delete image error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find any image directories without the manifest file and remove them.
|
||||
func (v *ImageStore) cleanup(op trace.Operation, store *url.URL) error {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Checking for inconsistent images on %s", store.String()), op))
|
||||
|
||||
storeName, err := util.ImageStoreName(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := v.Ls(op, v.imageStorePath(storeName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We could call v.ListImages here but that results in calling GetImage,
|
||||
// which pulls and unmarshalls the metadata. We don't need that.
|
||||
for _, f := range res.File {
|
||||
file, ok := f.(*types.FolderFileInfo)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ID := file.Path
|
||||
|
||||
if err := v.verifyImage(op, storeName, ID); err != nil {
|
||||
if ID == constants.ScratchLayerID {
|
||||
op.Errorf("Failed to verify scratch image - skipping deletion so as not to invalidate image chain but this is probably non-functional")
|
||||
continue
|
||||
}
|
||||
|
||||
if err = v.deleteImage(op, storeName, ID); err != nil {
|
||||
// deleteImage logs the error in the event there is one.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Manifest file for the image.
|
||||
func (v *ImageStore) writeManifest(op trace.Operation, storeName, ID string, r io.Reader) error {
|
||||
|
||||
if err := v.Upload(op, r, v.manifestPath(storeName, ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check for the manifest file AND the vmdk
|
||||
func (v *ImageStore) verifyImage(op trace.Operation, storeName, ID string) error {
|
||||
|
||||
// Check for the manifiest file and the vmdk
|
||||
for _, p := range []string{v.manifestPath(storeName, ID), v.imageDiskPath(storeName, ID)} {
|
||||
if _, err := v.Stat(op, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX TODO This should be tied to an interface so we don't have to import exec
|
||||
// here (or wherever the cache lives).
|
||||
func imagesInUse(op trace.Operation, ID string) error {
|
||||
// XXX Why doesnt this ever return an error? Strange.
|
||||
// Gather all containers
|
||||
conts := exec.Containers.Containers(nil)
|
||||
if len(conts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cont := range conts {
|
||||
layerID := cont.ExecConfig.LayerID
|
||||
|
||||
if layerID == ID {
|
||||
return &image.ErrImageInUse{
|
||||
Msg: fmt.Sprintf("image %s in use by %s", ID, cont.ExecConfig.ID),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populate the scratch with minimum OS structure defined in FileForMinOS and DirForMinOS
|
||||
func createBaseStructure(op trace.Operation, vmdisk *disk.VirtualDisk) (err error) {
|
||||
dir, err := vmdisk.Mount(op, nil)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to mount device %s to dir %s", vmdisk.DevicePath, dir)
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
e2 := vmdisk.Unmount(op)
|
||||
if e2 != nil {
|
||||
op.Errorf("Failed to unmount device: %s", e2)
|
||||
if err == nil {
|
||||
err = e2
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for dname, dmode := range DirForMinOS {
|
||||
dirPath := path.Join(dir, dname)
|
||||
if err = os.MkdirAll(dirPath, dmode); err != nil {
|
||||
op.Errorf("Failed to create directory %s: %s", dirPath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
op.Infof("Creating base file structure on disk")
|
||||
// The directory has to exist before creating the new file
|
||||
for fname, fmode := range FileForMinOS {
|
||||
filePath := path.Join(dir, fname)
|
||||
f, err := os.OpenFile(filePath, os.O_CREATE, fmode)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to open file %s: %s", filePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
op.Errorf("Failed to close file %s: %s", filePath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for fname, target := range SymLinkForMinOS {
|
||||
filePath := path.Join(dir, fname)
|
||||
err := syscall.Symlink(target, filePath)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to create symlink %s->%s: %s", filePath, target, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
625
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/store_test.go
generated
vendored
Normal file
625
vendor/github.com/vmware/vic/lib/portlayer/storage/image/vsphere/store_test.go
generated
vendored
Normal file
@@ -0,0 +1,625 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/image"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) (*image.NameLookupCache, *session.Session, string, error) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
trace.Logger.Level = logrus.DebugLevel
|
||||
DetachAll = false
|
||||
|
||||
client := datastore.Session(context.TODO(), t)
|
||||
if client == nil {
|
||||
return nil, nil, "", fmt.Errorf("skip")
|
||||
}
|
||||
|
||||
storeURL := &url.URL{
|
||||
Path: datastore.TestName("imageTests"),
|
||||
Host: client.DatastorePath}
|
||||
|
||||
op := trace.NewOperation(context.Background(), "setup")
|
||||
vsImageStore, err := NewImageStore(op, client, storeURL)
|
||||
if err != nil {
|
||||
if err.Error() == "can't find the hosting vm" {
|
||||
t.Skip("Skipping: test must be run in a VM")
|
||||
} else {
|
||||
t.Log(err.Error())
|
||||
}
|
||||
return nil, nil, "", err
|
||||
}
|
||||
|
||||
s := image.NewLookupCache(vsImageStore)
|
||||
|
||||
return s, client, storeURL.Path, nil
|
||||
}
|
||||
|
||||
func TestRestartImageStore(t *testing.T) {
|
||||
t.Skip("this test needs TLC")
|
||||
|
||||
// Start the image store once
|
||||
cacheStore, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
origVsStore := cacheStore.DataStore.(*ImageStore)
|
||||
defer cleanup(t, client, origVsStore, parentPath)
|
||||
|
||||
storeName := "bogusStoreName"
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
origStore, err := cacheStore.CreateImageStore(op, storeName)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, origStore) {
|
||||
return
|
||||
}
|
||||
|
||||
imageStoreURL := &url.URL{
|
||||
Path: constants.StorageParentDir,
|
||||
Host: client.DatastorePath}
|
||||
|
||||
// now start it again
|
||||
restartedVsStore, err := NewImageStore(op, client, imageStoreURL)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, restartedVsStore) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check we didn't create a new UUID directory (relevant if vsan)
|
||||
if !assert.Equal(t, origVsStore.RootURL, restartedVsStore.RootURL) {
|
||||
return
|
||||
}
|
||||
|
||||
restartedStore, err := restartedVsStore.GetImageStore(op, storeName)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, restartedStore) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, origStore.String(), restartedStore.String()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create an image store then test it exists
|
||||
func TestCreateAndGetImageStore(t *testing.T) {
|
||||
vsis, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the parent image store directory
|
||||
defer rm(t, client, client.Datastore.Path(parentPath))
|
||||
|
||||
storeName := "bogusStoreName"
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
u, err := vsis.CreateImageStore(op, storeName)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, u) {
|
||||
return
|
||||
}
|
||||
|
||||
u, err = vsis.GetImageStore(op, storeName)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, u) {
|
||||
return
|
||||
}
|
||||
|
||||
// Negative test. Check for a dir that doesn't exist
|
||||
u, err = vsis.GetImageStore(op, storeName+"garbage")
|
||||
if !assert.Error(t, err) || !assert.Nil(t, u) {
|
||||
return
|
||||
}
|
||||
|
||||
// Test for a store that already exists
|
||||
u, err = vsis.CreateImageStore(op, storeName)
|
||||
if !assert.Error(t, err) || !assert.Nil(t, u) || !assert.Equal(t, err, os.ErrExist) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImageStore(t *testing.T) {
|
||||
vsis, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the parent image store directory
|
||||
defer rm(t, client, client.Datastore.Path(parentPath))
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
count := 3
|
||||
for i := 0; i < count; i++ {
|
||||
storeName := fmt.Sprintf("storeName%d", i)
|
||||
u, err := vsis.CreateImageStore(op, storeName)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, u) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
images, err := vsis.ListImageStores(op)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, len(images), count) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a tar archive in memory for each layer and uses this to test image creation of layers
|
||||
func TestCreateImageLayers(t *testing.T) {
|
||||
numLayers := 4
|
||||
|
||||
cacheStore, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
vsStore := cacheStore.DataStore.(*ImageStore)
|
||||
defer cleanup(t, client, vsStore, parentPath)
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := cacheStore.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get an image that doesn't exist and check for error
|
||||
grbg, err := cacheStore.GetImage(op, storeURL, "garbage")
|
||||
if !assert.Error(t, err) || !assert.Nil(t, grbg) {
|
||||
return
|
||||
}
|
||||
|
||||
// base this image off scratch
|
||||
parent, err := cacheStore.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Keep a list of all files we're extracting via layers so we can verify
|
||||
// they exist in the leaf layer. Ext adds lost+found, so add it here.
|
||||
expectedFilesOnDisk := []string{"lost+found"}
|
||||
|
||||
// Keep a list of images we created
|
||||
expectedImages := make(map[string]*image.Image)
|
||||
expectedImages[parent.ID] = parent
|
||||
|
||||
for layer := 0; layer < numLayers; layer++ {
|
||||
|
||||
dirName := fmt.Sprintf("dir%d", layer)
|
||||
// Add some files to the archive.
|
||||
var files = []tarFile{
|
||||
{dirName, tar.TypeDir, ""},
|
||||
{dirName + "/readme.txt", tar.TypeReg, "This archive contains some text files."},
|
||||
{dirName + "/gopher.txt", tar.TypeReg, "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{dirName + "/todo.txt", tar.TypeReg, "Get animal handling license."},
|
||||
}
|
||||
|
||||
for _, i := range files {
|
||||
expectedFilesOnDisk = append(expectedFilesOnDisk, i.Name)
|
||||
}
|
||||
|
||||
// meta for the image
|
||||
meta := make(map[string][]byte)
|
||||
meta[dirName+"_meta"] = []byte("Some Meta")
|
||||
meta[dirName+"_moreMeta"] = []byte("Some More Meta")
|
||||
meta[dirName+"_scorpions"] = []byte("Here I am, rock you like a hurricane")
|
||||
|
||||
// Tar the files
|
||||
buf, terr := tarFiles(files)
|
||||
if !assert.NoError(t, terr) {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate the checksum
|
||||
h := sha256.New()
|
||||
h.Write(buf.Bytes())
|
||||
sum := fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
|
||||
// Write the image via the cache (which writes to the vsphere impl)
|
||||
writtenImage, terr := cacheStore.WriteImage(op, parent, dirName, meta, sum, buf)
|
||||
if !assert.NoError(t, terr) || !assert.NotNil(t, writtenImage) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedImages[dirName] = writtenImage
|
||||
|
||||
// Get the image directly via the vsphere image store impl.
|
||||
vsImage, terr := vsStore.GetImage(op, parent.Store, dirName)
|
||||
if !assert.NoError(t, terr) || !assert.NotNil(t, vsImage) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, writtenImage, vsImage)
|
||||
|
||||
// make the next image a child of the one we just created
|
||||
parent = writtenImage
|
||||
}
|
||||
|
||||
// Test list images on the datastore
|
||||
listedImages, err := vsStore.ListImages(op, parent.Store, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, listedImages) {
|
||||
return
|
||||
}
|
||||
for _, img := range listedImages {
|
||||
if !assert.Equal(t, expectedImages[img.ID].Store.String(), img.Store.String()) {
|
||||
return
|
||||
}
|
||||
if !assert.Equal(t, expectedImages[img.ID].SelfLink.String(), img.SelfLink.String()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// verify the disk's data by attaching the last layer rdonly
|
||||
roDisk, err := mountLayerRO(vsStore, parent)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
p, err := roDisk.MountPath()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rodiskcleanupfunc := func() {
|
||||
if roDisk != nil {
|
||||
if roDisk.Mounted() {
|
||||
roDisk.Unmount(op)
|
||||
}
|
||||
if roDisk.Attached() {
|
||||
vsStore.Detach(op, roDisk.VirtualDiskConfig)
|
||||
}
|
||||
}
|
||||
os.RemoveAll(p)
|
||||
}
|
||||
|
||||
filesFoundOnDisk := []string{}
|
||||
// Diff the contents of the RO file of the last child (with all of the contents)
|
||||
err = filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := path[len(p):]
|
||||
if f != "" {
|
||||
// strip the slash
|
||||
filesFoundOnDisk = append(filesFoundOnDisk, f[1:])
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
rodiskcleanupfunc()
|
||||
sort.Strings(filesFoundOnDisk)
|
||||
sort.Strings(expectedFilesOnDisk)
|
||||
|
||||
if !assert.Equal(t, expectedFilesOnDisk, filesFoundOnDisk) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to delete an intermediate image (should fail)
|
||||
exec.NewContainerCache()
|
||||
_, err = cacheStore.DeleteImage(op, expectedImages["dir1"])
|
||||
if !assert.Error(t, err) || !assert.True(t, image.IsErrImageInUse(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to delete a leaf (should pass)
|
||||
leaf := expectedImages["dir"+strconv.Itoa(numLayers-1)]
|
||||
_, err = cacheStore.DeleteImage(op, leaf)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the delete image directly via the vsphere image store impl.
|
||||
deletedImage, err := vsStore.GetImage(op, parent.Store, leaf.ID)
|
||||
if !assert.Error(t, err) || !assert.Nil(t, deletedImage) || !assert.True(t, os.IsNotExist(err)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrokenPull(t *testing.T) {
|
||||
|
||||
cacheStore, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
vsStore := cacheStore.DataStore.(*ImageStore)
|
||||
|
||||
defer cleanup(t, client, vsStore, parentPath)
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := cacheStore.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// base this image off scratch
|
||||
parent, err := cacheStore.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
imageID := "dir0"
|
||||
|
||||
// Add some files to the archive.
|
||||
var files = []tarFile{
|
||||
{imageID, tar.TypeDir, ""},
|
||||
{imageID + "/readme.txt", tar.TypeReg, "This archive contains some text files."},
|
||||
{imageID + "/gopher.txt", tar.TypeReg, "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{imageID + "/todo.txt", tar.TypeReg, "Get animal handling license."},
|
||||
}
|
||||
|
||||
// meta for the image
|
||||
meta := make(map[string][]byte)
|
||||
meta[imageID+"_meta"] = []byte("Some Meta")
|
||||
meta[imageID+"_moreMeta"] = []byte("Some More Meta")
|
||||
meta[imageID+"_scorpions"] = []byte("Here I am, rock you like a hurricane")
|
||||
|
||||
// Tar the files
|
||||
buf, terr := tarFiles(files)
|
||||
if !assert.NoError(t, terr) {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate the checksum
|
||||
h := sha256.New()
|
||||
h.Write(buf.Bytes())
|
||||
actualsum := fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
|
||||
// Write the image via the cache (which writes to the vsphere impl). We're passing a bogus sum so the image should fail to save.
|
||||
writtenImage, err := cacheStore.WriteImage(op, parent, imageID, meta, "bogusSum", new(bytes.Buffer))
|
||||
if !assert.Error(t, err) || !assert.Nil(t, writtenImage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Now try again with the right sum and there shouldn't be an error.
|
||||
writtenImage, err = cacheStore.WriteImage(op, parent, imageID, meta, actualsum, buf)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, writtenImage) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Creates numLayers layers in parallel using the same parent to exercise parallel reconfigures
|
||||
func TestParallel(t *testing.T) {
|
||||
numLayers := 10
|
||||
|
||||
cacheStore, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
vsStore := cacheStore.DataStore.(*ImageStore)
|
||||
defer cleanup(t, client, vsStore, parentPath)
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
storeURL, err := cacheStore.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// base this image off scratch
|
||||
parent, err := cacheStore.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(numLayers)
|
||||
for i := 0; i < numLayers; i++ {
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
|
||||
imageID := fmt.Sprintf("testStore-%d", idx)
|
||||
|
||||
op := trace.NewOperation(context.Background(), imageID)
|
||||
// Write the image via the cache (which writes to the vsphere impl). We're passing a bogus sum so the image should fail to save.
|
||||
writtenImage, err := cacheStore.WriteImage(op, parent, imageID, nil, "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", new(bytes.Buffer))
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, writtenImage) {
|
||||
t.FailNow()
|
||||
return
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestInProgressCleanup(t *testing.T) {
|
||||
|
||||
cacheStore, client, parentPath, err := setup(t)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
vsStore := cacheStore.DataStore.(*ImageStore)
|
||||
|
||||
defer cleanup(t, client, vsStore, parentPath)
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
storeURL, err := cacheStore.CreateImageStore(op, "testStore")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// base this image off scratch
|
||||
parent, err := cacheStore.GetImage(op, storeURL, constants.ScratchLayerID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a test image
|
||||
imageID := "testImage"
|
||||
|
||||
// meta for the image
|
||||
meta := make(map[string][]byte)
|
||||
meta[imageID+"_meta"] = []byte("Some Meta")
|
||||
|
||||
// Tar the files
|
||||
buf, err := tarFiles([]tarFile{})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate the checksum
|
||||
h := sha256.New()
|
||||
h.Write(buf.Bytes())
|
||||
sum := fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
|
||||
writtenImage, err := cacheStore.WriteImage(op, parent, imageID, meta, sum, buf)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, writtenImage) {
|
||||
return
|
||||
}
|
||||
|
||||
// nuke the done file.
|
||||
rm(t, client, path.Join(vsStore.RootURL.String(), vsStore.imageDirPath("testStore", imageID), manifest))
|
||||
|
||||
// ensure GetImage doesn't find this image now
|
||||
if _, err = vsStore.GetImage(op, storeURL, imageID); !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// call cleanup
|
||||
if err = vsStore.cleanup(op, storeURL); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure list is now empty.
|
||||
listedImages, err := vsStore.ListImages(op, parent.Store, nil)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, len(listedImages), 1) || !assert.Equal(t, listedImages[0].ID, constants.ScratchLayerID) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type tarFile struct {
|
||||
Name string
|
||||
Type byte
|
||||
Body string
|
||||
}
|
||||
|
||||
func tarFiles(files []tarFile) (*bytes.Buffer, error) {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new tar archive.
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
// Write data to the tar as if it came from the hub
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Mode: 0777,
|
||||
Typeflag: file.Type,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if file.Type == tar.TypeDir {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func mountLayerRO(v *ImageStore, parent *image.Image) (*disk.VirtualDisk, error) {
|
||||
roName := v.imageDiskDSPath("testStore", parent.ID)
|
||||
roName.Path = roName.Path + "-ro.vmdk"
|
||||
|
||||
parentDsURI := v.imageDiskDSPath("testStore", parent.ID)
|
||||
|
||||
op := trace.NewOperation(context.TODO(), "ro")
|
||||
|
||||
config := disk.NewNonPersistentDisk(roName).WithParent(parentDsURI)
|
||||
roDisk, err := v.CreateAndAttach(op, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = roDisk.Mount(op, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roDisk, nil
|
||||
}
|
||||
|
||||
func rm(t *testing.T, client *session.Session, name string) {
|
||||
t.Logf("deleting %s", name)
|
||||
fm := object.NewFileManager(client.Vim25())
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), name, client.Datacenter)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, _ = task.WaitForResult(context.TODO(), nil)
|
||||
}
|
||||
|
||||
// Nuke the files and then the parent dir. Unfortunately, because this is
|
||||
// vsan, we need to delete the files in the directories first (maybe
|
||||
// because they're linked vmkds) before we can delete the parent directory.
|
||||
func cleanup(t *testing.T, client *session.Session, vsStore *ImageStore, parentPath string) {
|
||||
res, err := vsStore.LsDirs(context.TODO(), "")
|
||||
if err != nil {
|
||||
t.Logf("error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, dir := range res.HostDatastoreBrowserSearchResults {
|
||||
for _, f := range dir.File {
|
||||
fpath := f.GetFileInfo().Path
|
||||
|
||||
rm(t, client, path.Join(dir.FolderPath, fpath))
|
||||
}
|
||||
rm(t, client, dir.FolderPath)
|
||||
}
|
||||
|
||||
rm(t, client, client.Datastore.Path(parentPath))
|
||||
}
|
||||
92
vendor/github.com/vmware/vic/lib/portlayer/storage/mount_datasink.go
generated
vendored
Normal file
92
vendor/github.com/vmware/vic/lib/portlayer/storage/mount_datasink.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// MountDataSink implements the DataSink interface for mounted devices
|
||||
// This is a single use mechanism and will be tidied up on exit from MountDataSink.Import
|
||||
type MountDataSink struct {
|
||||
Path *os.File
|
||||
Clean func()
|
||||
cleanOp trace.Operation
|
||||
}
|
||||
|
||||
// NewMountDataSink creates a new data sink associated with a specific mount, with the mount
|
||||
// point being the path argument.
|
||||
// The cleanup function is invoked once the import is complete.
|
||||
func NewMountDataSink(op trace.Operation, path *os.File, cleanup func()) *MountDataSink {
|
||||
if path == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data sink at %s", path.Name())
|
||||
|
||||
return &MountDataSink{
|
||||
Path: path,
|
||||
Clean: cleanup,
|
||||
cleanOp: trace.FromOperation(op, "clean up from new mount sink"),
|
||||
}
|
||||
}
|
||||
|
||||
// Sink returns the data source associated with the DataSink
|
||||
func (m *MountDataSink) Sink() interface{} {
|
||||
return m.Path
|
||||
}
|
||||
|
||||
// Import writes `data` to the data source associated with this DataSource
|
||||
// This will call MountDataSink.Close on exit, irrespective of success or error
|
||||
func (m *MountDataSink) Import(op trace.Operation, spec *archive.FilterSpec, data io.ReadCloser) error {
|
||||
// reparent cleanup to Export operation
|
||||
m.cleanOp = trace.FromOperation(op, "clean up from export")
|
||||
// ensure that mounts are tidied up - a data sink is a single use mechanism.
|
||||
defer m.Close()
|
||||
|
||||
name := m.Path.Name()
|
||||
|
||||
fi, err := m.Path.Stat()
|
||||
if err != nil {
|
||||
op.Errorf("Unable to stat mount path %s for data sink: %s", name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return errors.New("Path must be a directory")
|
||||
}
|
||||
|
||||
// This assumes that m.Path was opened with a useful path (i.e. absolute) as that argument is what's
|
||||
// returned by Name.
|
||||
op.Infof("Importing supplied data stream to %s", name)
|
||||
return archive.Unpack(op, data, spec, name)
|
||||
}
|
||||
|
||||
func (m *MountDataSink) Close() error {
|
||||
m.cleanOp.Infof("cleaning up after import")
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
m.Path.Close()
|
||||
if m.Clean != nil {
|
||||
m.Clean()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
138
vendor/github.com/vmware/vic/lib/portlayer/storage/mount_datasource.go
generated
vendored
Normal file
138
vendor/github.com/vmware/vic/lib/portlayer/storage/mount_datasource.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// MountDataSource implements the DataSource interface for mounted devices
|
||||
type MountDataSource struct {
|
||||
Path *os.File
|
||||
Clean func()
|
||||
cleanOp trace.Operation
|
||||
cancel func()
|
||||
done sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewMountDataSource creates a new data source associated with a specific mount, with the mount
|
||||
// point being the path argument.
|
||||
// The cleanup function is invoked with the Close of the ReadCloser from Export, or explicitly
|
||||
func NewMountDataSource(op trace.Operation, path *os.File, cleanup func()) *MountDataSource {
|
||||
if path == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data source at %s", path.Name())
|
||||
|
||||
return &MountDataSource{
|
||||
Path: path,
|
||||
Clean: cleanup,
|
||||
cleanOp: trace.FromOperation(op, "clean up from new mount source"),
|
||||
}
|
||||
}
|
||||
|
||||
// Source returns the data source associated with the DataSource
|
||||
func (m *MountDataSource) Source() interface{} {
|
||||
return m.Path
|
||||
}
|
||||
|
||||
// Export reads data from the associated data source and returns it as a tar archive
|
||||
func (m *MountDataSource) Export(op trace.Operation, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
// reparent cleanup to Export operation
|
||||
m.cleanOp = trace.FromOperation(op, "clean up from export")
|
||||
notifyOp := trace.WithValue(&op, archive.CancelNotifyKey{}, &m.done, "with cancel notifier")
|
||||
cop, cancel := trace.WithCancel(¬ifyOp, "cancellable export from mount")
|
||||
m.cancel = cancel
|
||||
|
||||
name := m.Path.Name()
|
||||
fi, err := m.Path.Stat()
|
||||
if err != nil {
|
||||
op.Errorf("Unable to stat mount path %s for data source: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return nil, errors.New("path must be a directory")
|
||||
}
|
||||
|
||||
op.Infof("Exporting data from %s", name)
|
||||
// Diff is supplied "" to indicate that we are performing a read against a single target.
|
||||
rc, err := archive.Diff(cop, name, "", spec, data, false)
|
||||
|
||||
// return the proxy regardless of error so that Close can be called
|
||||
return &ProxyReadCloser{
|
||||
rc,
|
||||
m.Close,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Stat stats the filesystem target indicated by the last entry in the given Filterspecs inclusion map
|
||||
func (m *MountDataSource) Stat(op trace.Operation, spec *archive.FilterSpec) (*FileStat, error) {
|
||||
// retrieve relative path
|
||||
|
||||
var targetPath string
|
||||
for path := range spec.Inclusions {
|
||||
targetPath = path
|
||||
}
|
||||
|
||||
filePath := filepath.Join(m.Path.Name(), targetPath)
|
||||
fileInfo, err := os.Lstat(filePath)
|
||||
if err != nil {
|
||||
// Does not exist is an expected result so no errors logged
|
||||
if !os.IsNotExist(err) {
|
||||
op.Errorf("failed to stat file: %s", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var linkTarget string
|
||||
// check for symlink
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
linkTarget, err = os.Readlink(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &FileStat{linkTarget, uint32(fileInfo.Mode()), fileInfo.Name(), fileInfo.Size(), fileInfo.ModTime()}, nil
|
||||
}
|
||||
|
||||
func (m *MountDataSource) Close() error {
|
||||
m.cleanOp.Infof("cleaning up after export - waiting for cancelation completion if necessary")
|
||||
|
||||
// trigger cancelation of any ongoing operations
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
|
||||
// wait for cancelation to take effect
|
||||
m.done.Wait()
|
||||
|
||||
m.Path.Close()
|
||||
if m.Clean != nil {
|
||||
m.cleanOp.Debugf("calling specified cleaner function")
|
||||
m.Clean()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
vendor/github.com/vmware/vic/lib/portlayer/storage/proxy_readcloser.go
generated
vendored
Normal file
33
vendor/github.com/vmware/vic/lib/portlayer/storage/proxy_readcloser.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// ProxyReadCloser is a read closer that provides for wrapping the Close with
|
||||
// a custom Close call. The original ReadCloser.Close function will be invoked
|
||||
// after the custom call. Errors from the custom call with be ignored.
|
||||
type ProxyReadCloser struct {
|
||||
io.ReadCloser
|
||||
Closer func() error
|
||||
}
|
||||
|
||||
func (p *ProxyReadCloser) Close() error {
|
||||
/* #nosec - no useful way to handle this error */
|
||||
p.Closer()
|
||||
return p.ReadCloser.Close()
|
||||
}
|
||||
207
vendor/github.com/vmware/vic/lib/portlayer/storage/storage.go
generated
vendored
Normal file
207
vendor/github.com/vmware/vic/lib/portlayer/storage/storage.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/view"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
|
||||
importers map[string]Importer
|
||||
exporters map[string]Exporter
|
||||
)
|
||||
|
||||
type FileStat struct {
|
||||
LinkTarget string
|
||||
Mode uint32
|
||||
Name string
|
||||
Size int64
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
func init() {
|
||||
importers = make(map[string]Importer)
|
||||
exporters = make(map[string]Exporter)
|
||||
}
|
||||
|
||||
func create(ctx context.Context, session *session.Session, pool *object.ResourcePool) error {
|
||||
var err error
|
||||
|
||||
mngr := view.NewManager(session.Vim25())
|
||||
|
||||
// Create view of VirtualMachine objects under the VCH's resource pool
|
||||
Config.ContainerView, err = mngr.CreateContainerView(ctx, pool.Reference(), []string{"VirtualMachine"}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init performs basic initialization, including population of storage.Config
|
||||
func Init(ctx context.Context, session *session.Session, pool *object.ResourcePool, source extraconfig.DataSource, _ extraconfig.DataSink) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var err error
|
||||
|
||||
once.Do(func() {
|
||||
// Grab the storage layer config blobs from extra config
|
||||
extraconfig.Decode(source, &Config)
|
||||
log.Debugf("Decoded VCH config for storage: %#v", Config)
|
||||
|
||||
err = create(ctx, session, pool)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterImporter registers the specified importer against the provided store for later retrieval.
|
||||
func RegisterImporter(op trace.Operation, store string, i Importer) {
|
||||
op.Infof("Registering importer: %s => %T", store, i)
|
||||
|
||||
importers[store] = i
|
||||
}
|
||||
|
||||
// RegisterExporter registers the specified exporter against the provided store for later retrieval.
|
||||
func RegisterExporter(op trace.Operation, store string, e Exporter) {
|
||||
op.Infof("Registering exporter: %s => %T", store, e)
|
||||
|
||||
exporters[store] = e
|
||||
}
|
||||
|
||||
// GetImporter retrieves an importer registered with the provided store.
|
||||
// Will return nil, false if the store is not found.
|
||||
func GetImporter(store string) (Importer, bool) {
|
||||
i, ok := importers[store]
|
||||
return i, ok
|
||||
}
|
||||
|
||||
// GetExporter retrieves an exporter registered with the provided store.
|
||||
// Will return nil, false if the store is not found.
|
||||
func GetExporter(store string) (Exporter, bool) {
|
||||
e, ok := exporters[store]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
// GetImporters returns the set of known importers.
|
||||
func GetImporters() []string {
|
||||
keys := make([]string, 0, len(importers))
|
||||
for key := range importers {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// GetExporters returns the set of known importers.
|
||||
func GetExporters() []string {
|
||||
keys := make([]string, 0, len(exporters))
|
||||
for key := range exporters {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// Resolver defines methods for mapping ids to URLS, and urls to owners of that device
|
||||
type Resolver interface {
|
||||
// URL returns a url to the data source representing `id`
|
||||
// For historic reasons this is not the same URL that other parts of the storage component use, but an actual
|
||||
// URL suited for locating the storage element without having additional precursor knowledge.
|
||||
URL(op trace.Operation, id string) (*url.URL, error)
|
||||
|
||||
// Owners returns a list of VMs that are using the resource specified by `url`
|
||||
Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error)
|
||||
}
|
||||
|
||||
// DataSource defines the methods for exporting data from a specific storage element as a tar stream
|
||||
type DataSource interface {
|
||||
// Close releases all resources associated with this source. Shared resources should be reference counted.
|
||||
io.Closer
|
||||
|
||||
// Export performs an export of the specified files, returning the data as a tar stream. This is single use; once
|
||||
// the export has completed it should not be assumed that the source remains functional.
|
||||
//
|
||||
// spec: specifies which files will be included/excluded in the export and allows for path rebasing/stripping
|
||||
// data: if true the actual file data is included, if false only the file headers are present
|
||||
Export(op trace.Operation, spec *archive.FilterSpec, data bool) (io.ReadCloser, error)
|
||||
|
||||
// Source returns the mechanism by which the data source is accessed
|
||||
// Examples:
|
||||
// vmdk mounted locally: *os.File
|
||||
// nfs volume: XDR-client
|
||||
// via guesttools: toolbox client
|
||||
Source() interface{}
|
||||
|
||||
// Stat stats the filesystem target indicated by the last entry in the given Filterspecs inclusion map
|
||||
Stat(op trace.Operation, spec *archive.FilterSpec) (*FileStat, error)
|
||||
}
|
||||
|
||||
// DataSink defines the methods for importing data to a specific storage element from a tar stream
|
||||
type DataSink interface {
|
||||
// Close releases all resources associated with this sink. Shared resources should be reference counted.
|
||||
io.Closer
|
||||
|
||||
// Import performs an import of the tar stream to the source held by this DataSink. This is single use; once
|
||||
// the export has completed it should not be assumed that the sink remains functional.
|
||||
//
|
||||
// spec: specifies which files will be included/excluded in the import and allows for path rebasing/stripping
|
||||
// tarStream: the tar stream to from which to import data
|
||||
Import(op trace.Operation, spec *archive.FilterSpec, tarStream io.ReadCloser) error
|
||||
|
||||
// Sink returns the mechanism by which the data sink is accessed
|
||||
// Examples:
|
||||
// vmdk mounted locally: *os.File
|
||||
// nfs volume: XDR-client
|
||||
// via guesttools: toolbox client
|
||||
Sink() interface{}
|
||||
}
|
||||
|
||||
// Importer defines the methods needed to write data into a storage element. This should be implemented by the various
|
||||
// store types.
|
||||
type Importer interface {
|
||||
// Import allows direct construction and invocation of a data sink for the specified ID.
|
||||
Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error
|
||||
|
||||
// NewDataSink constructs a data sink for the specified ID within the context of the Importer. This is a single
|
||||
// use sink which may hold resources until Closed.
|
||||
NewDataSink(op trace.Operation, id string) (DataSink, error)
|
||||
}
|
||||
|
||||
// Exporter defines the methods needed to read data from a storage element, optionally diff with an ancestor. This
|
||||
// shoiuld be implemented by the various store types.
|
||||
type Exporter interface {
|
||||
// Export allows direct construction and invocation of a data source for the specified ID.
|
||||
Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error)
|
||||
|
||||
// NewDataSource constructs a data source for the specified ID within the context of the Exporter. This is a single
|
||||
// use source which may hold resources until Closed.
|
||||
NewDataSource(op trace.Operation, id string) (DataSource, error)
|
||||
}
|
||||
50
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/errors.go
generated
vendored
Normal file
50
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/errors.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2016-2018 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 volume
|
||||
|
||||
type ErrVolumeInUse struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *ErrVolumeInUse) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
func IsErrVolumeInUse(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := err.(*ErrVolumeInUse)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// VolumeStoreNotFoundError : custom error type for when we fail to find a target volume store
|
||||
type VolumeStoreNotFoundError struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e VolumeStoreNotFoundError) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
// VolumeExistsError : custom error type for when a create operation targets and already occupied ID
|
||||
type VolumeExistsError struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e VolumeExistsError) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
57
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/disk.go
generated
vendored
Normal file
57
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/disk.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Volume identifies an NFS based volume
|
||||
type Volume struct {
|
||||
|
||||
// VS Host + Path to the actual volume
|
||||
Host *url.URL
|
||||
|
||||
// Path of the volume from the volumestore target
|
||||
Path string
|
||||
}
|
||||
|
||||
func NewVolume(host *url.URL, NFSPath string) Volume {
|
||||
volumeLocation := &url.URL{
|
||||
Scheme: host.Scheme,
|
||||
Host: host.Host,
|
||||
Path: path.Join(host.Path, NFSPath),
|
||||
}
|
||||
|
||||
v := Volume{
|
||||
Host: volumeLocation,
|
||||
Path: NFSPath,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Volume) MountPath() (string, error) {
|
||||
return v.Path, nil
|
||||
}
|
||||
|
||||
// DiskPath includes the url to the nfs directory for the container to mount,
|
||||
func (v Volume) DiskPath() url.URL {
|
||||
if v.Host == nil {
|
||||
return url.URL{}
|
||||
}
|
||||
|
||||
return *v.Host
|
||||
}
|
||||
71
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/join.go
generated
vendored
Normal file
71
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/join.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2017-2018 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 nfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
//Below is a list of options included in the mount options and a brief reason why.
|
||||
// rw : on by default, for ro the syscall.MS_READONLY flag must be set instead of putting it in the options pointer of the mount call.
|
||||
// "noatime" : this option prevents read's from triggering a write to update the accesstime of an inode. Helps with efficient reads. (Specified as a flag during the tether operation.)
|
||||
// "vers=3" : we want to use NFSv3 it is simpler to implement and we do not need all the features of NFSv4 at this time
|
||||
// "rsize=131072" : indicates the maximum read size for data on the NFS server. If the rsize is too big for either the client or server a negotion will occur to determine a supportable size. the value chosen is a default for /bin/mount for ubuntu 16.04
|
||||
// "wsize=131072" : indicates the maximum write size for data on the NFS server. Like rsize it is negotiated if the value is too large. the value chosen is a default for /bin/mount for ubuntu 16.04
|
||||
// "hard" : implies that we will retry indefinitely upon a failed transmission of data. this was agreed upon to indicate that problems have occurred with the mount if a hang on a write occurs.
|
||||
// "proto=tcp" : tcp is a realiable protocol. The client used by the VCH also uses TCP as it's protocol. meaning we gain consistency in the communication between the tether->nfs and portlayer->nfs
|
||||
// "timeo=600" : 600 deciseconds. This means a 60 second timout. With "hard" enabled this option likely does not matter.
|
||||
// "sec=sys" : this means the NFS client uses the AUTH_SYS security flavor for all NFS requests on this mount point. This requires UID and GID of the user for permissions. also allows squashing permissions
|
||||
// "mountvers=3" : this is listed as the RPC bind version. However, it is listed as a default by /bin/mount even when RPC is not the protocol used.
|
||||
// "mountProto=TCP" : since the VCH uses TCP we should be using it as well here on the tether. Additionally, the mountProto does effect the initial protocol used for interacting with an nfs server. Keeping everything as the same protocol makes protocol issues easier to detect.
|
||||
nfsMountOptions = "vers=3,rsize=131072,wsize=131072,hard,proto=tcp,timeo=600,sec=sys,mountvers=3,mountproto=tcp,nolock"
|
||||
)
|
||||
|
||||
func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *volume.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("handle.ID(%s), volume(%s), mountPath(%s), diskPath(%#v)", handle.ExecConfig.ID, volume.ID, mountPath, volume.Device.DiskPath())))
|
||||
|
||||
if _, ok := handle.ExecConfig.Mounts[volume.ID]; ok {
|
||||
return nil, fmt.Errorf("Volume with ID %s is already in container %s's mountspec config", volume.ID, handle.ExecConfig.ID)
|
||||
}
|
||||
|
||||
// construct MountSpec for the tether
|
||||
mountSpec := createMountSpec(volume, mountPath, diskOpts)
|
||||
if handle.ExecConfig.Mounts == nil {
|
||||
handle.ExecConfig.Mounts = make(map[string]executor.MountSpec)
|
||||
}
|
||||
handle.ExecConfig.Mounts[volume.ID] = *mountSpec
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func createMountSpec(volume *volume.Volume, mountPath string, diskOpts map[string]string) *executor.MountSpec {
|
||||
host := volume.Device.DiskPath()
|
||||
deviceMode := nfsMountOptions + ",addr=" + host.Host
|
||||
|
||||
// Note: rw mode is not specified in the device node since the syscall.Mount defaults to rw. Additional, "noatime" must be indicated with the flag syscall.MS_NOATIME
|
||||
newMountSpec := executor.MountSpec{
|
||||
Source: host,
|
||||
Path: mountPath,
|
||||
Mode: deviceMode,
|
||||
CopyMode: volume.CopyMode,
|
||||
}
|
||||
return &newMountSpec
|
||||
}
|
||||
320
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/store.go
generated
vendored
Normal file
320
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/store.go
generated
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
// The directory created in the NFS VolumeStore which we create volumes under
|
||||
volumesDir = "volumes"
|
||||
|
||||
// path that namespaces the metadata for a specific volume. It lives beside the Volumes Directory.
|
||||
metadataDir = "volumes_metadata"
|
||||
|
||||
// Stock permissions that are set, In the future we may pass these in.
|
||||
defaultPermissions = 0755
|
||||
|
||||
DefaultUID = 1000
|
||||
|
||||
nfsFilesystemTypeString = "nfs"
|
||||
)
|
||||
|
||||
// VolumeStore this is nfs related volume store definition
|
||||
type VolumeStore struct {
|
||||
// volume store name
|
||||
Name string
|
||||
|
||||
// Service is the interface to the nfs target.
|
||||
Service MountServer
|
||||
|
||||
// Service selflink to volume store.
|
||||
SelfLink *url.URL
|
||||
|
||||
// Archiver defines WriteArchive and Export interface methods
|
||||
archive.Archiver
|
||||
}
|
||||
|
||||
func NewVolumeStore(op trace.Operation, storeName string, mount MountServer) (*VolumeStore, error) {
|
||||
// #nosec: Errors unhandled.
|
||||
u, _ := mount.URL()
|
||||
op.Infof("Creating nfs volumestore %s on %s", storeName, u.String())
|
||||
|
||||
target, err := mount.Mount(op)
|
||||
if err != nil {
|
||||
op.Errorf("error occurred while attempting to mount volumestore (%s). err: (%s)", storeName, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
defer mount.Unmount(op)
|
||||
|
||||
selfLink, err := util.VolumeStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to construct URL for volume store %s: %s", storeName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &VolumeStore{
|
||||
Name: storeName,
|
||||
Service: mount,
|
||||
SelfLink: selfLink,
|
||||
}
|
||||
|
||||
// we assume that nfsTargetURL.path already exists.
|
||||
// make volumes directory
|
||||
if _, err := target.Mkdir(volumesDir, defaultPermissions); err != nil && !os.IsExist(err) {
|
||||
op.Errorf("Failed to create volumes directory '%s': %s", volumesDir, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make metadata directory
|
||||
if _, err := target.Mkdir(metadataDir, defaultPermissions); err != nil && !os.IsExist(err) {
|
||||
op.Errorf("Failed to create metadata directory '%s': %s", metadataDir, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Returns the path to the vol relative to the given store. The dir structure
|
||||
// for a vol in a nfs store is `<configured nfs server path>/volumes/<vol ID>/<volume contents>`.
|
||||
func (v *VolumeStore) volDirPath(ID string) string {
|
||||
return path.Join(volumesDir, ID)
|
||||
}
|
||||
|
||||
// Returns the path to the metadata directory for a volume
|
||||
func (v *VolumeStore) volMetadataDirPath(ID string) string {
|
||||
return path.Join(metadataDir, ID)
|
||||
}
|
||||
|
||||
// Creates a volume directory and volume object for NFS based volumes
|
||||
func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) {
|
||||
target, err := v.Service.Mount(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer v.Service.Unmount(op)
|
||||
|
||||
dir := v.volDirPath(ID)
|
||||
if _, err := target.Mkdir(dir, defaultPermissions); err != nil {
|
||||
op.Errorf("Failed to create volume path '%s' on store %s: %s", dir, v.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
u, _ := v.Service.URL()
|
||||
if u.Scheme != nfsFilesystemTypeString {
|
||||
op.Errorf("URL from nfs mount target had scheme (%s) instead of nfs for volume store (%s)", u.Scheme, v.Name)
|
||||
return nil, fmt.Errorf("Unexpected scheme (%s) for volume store (%s)", u.Scheme, v.Name)
|
||||
}
|
||||
|
||||
vol, err := volume.NewVolume(v.SelfLink, ID, info, NewVolume(u, v.volDirPath(ID)), executor.CopyNew)
|
||||
if err != nil {
|
||||
op.Errorf("Created volume directory but failed to create volume: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := v.writeMetadata(op, ID, info, target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Infof("nfs volume (%s) successfully created on volume store (%s)", ID, v.Name)
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
// VolumeDestroy Removes a volume and all of its contents from the nfs store. We already know via the cache if it is in use.
|
||||
func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error {
|
||||
target, err := v.Service.Mount(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer v.Service.Unmount(op)
|
||||
|
||||
op.Infof("Attempting to remove volume (%s) and its metadata from volume store (%s)", vol.ID, v.Name)
|
||||
|
||||
// remove volume directory and children
|
||||
if err := target.RemoveAll(v.volDirPath(vol.ID)); err != nil {
|
||||
op.Errorf("failed to remove volume (%s) on volume store (%s) due to error (%s)", vol.ID, v.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// remove volume metadata directory and children
|
||||
if err := target.RemoveAll(v.volMetadataDirPath(vol.ID)); err != nil {
|
||||
op.Errorf("failed to remove metadata for volume (%s) at path (%q) on volume store (%s)", vol.ID, v.volDirPath(vol.ID), v.Name)
|
||||
}
|
||||
op.Infof("Successfully removed volume (%s) from volumestore (%s)", vol.ID, v.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) {
|
||||
|
||||
target, err := v.Service.Mount(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer v.Service.Unmount(op)
|
||||
|
||||
volFileInfo, err := target.ReadDir(volumesDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var volumes []*volume.Volume
|
||||
var fetchErr error
|
||||
|
||||
for _, fileInfo := range volFileInfo {
|
||||
|
||||
if fileInfo.Name() == "." || fileInfo.Name() == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
volMetadata, err := v.getMetadata(op, fileInfo.Name(), target)
|
||||
if err != nil {
|
||||
op.Errorf("getting metadata for %s: %s", fileInfo.Name(), err.Error())
|
||||
fetchErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled.
|
||||
u, _ := v.Service.URL()
|
||||
vol, err := volume.NewVolume(v.SelfLink, fileInfo.Name(), volMetadata, NewVolume(u, v.volDirPath(fileInfo.Name())), executor.CopyNew)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to create volume struct from volume directory (%s)", fileInfo.Name())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumes = append(volumes, vol)
|
||||
}
|
||||
|
||||
if fetchErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
// Import takes a tar archive stream and extracts it into the target volume
|
||||
func (v *VolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error {
|
||||
return fmt.Errorf("Write for nfs volumes is not Implemented")
|
||||
}
|
||||
|
||||
// Export creates and returns a tar archive containing data found between an nfs layer one or all of its ancestors
|
||||
func (v *VolumeStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("vSphere Integrated Containers does not yet implement Export for nfs volumes")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
return nil, errors.New("NFS VolumeStore does not yet implement NewDataSource")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
return nil, errors.New("NFS VolumeStore does not yet implement NewDataSink")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
return nil, errors.New("NFS VolumeStore does not yet implement URL")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
return nil, errors.New("NFS VolumeStore does not yet implement Owners")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) writeMetadata(op trace.Operation, ID string, info map[string][]byte, target Target) error {
|
||||
// write metadata into the metadata directory by key (filename) / value
|
||||
// (data), namespaced by volume id
|
||||
//
|
||||
// <root>/volume_matadata/<id>/<key>
|
||||
metadataPath := v.volMetadataDirPath(ID)
|
||||
|
||||
_, err := target.Mkdir(metadataPath, defaultPermissions)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to create metadata directory '%s': %s", metadataPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
op.Infof("Writing metadata to (%s)", metadataPath)
|
||||
for fileName, data := range info {
|
||||
targetPath := path.Join(metadataPath, fileName)
|
||||
blobFile, err := target.OpenFile(targetPath, defaultPermissions)
|
||||
if err != nil {
|
||||
op.Errorf("openning file %s: %s", targetPath, err.Error())
|
||||
return err
|
||||
}
|
||||
defer blobFile.Close()
|
||||
|
||||
_, err = blobFile.Write(data)
|
||||
if err != nil {
|
||||
op.Errorf("failed to write metadata to '%s': %s", targetPath, err.Error())
|
||||
return err
|
||||
}
|
||||
defer blobFile.Close()
|
||||
}
|
||||
op.Infof("Successfully wrote metadata to (%s)", metadataPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) getMetadata(op trace.Operation, ID string, target Target) (map[string][]byte, error) {
|
||||
metadataPath := v.volMetadataDirPath(ID)
|
||||
op.Debugf("Attempting to retrieve volume metadata for (%s) at (%s)", ID, metadataPath)
|
||||
|
||||
dataKeys, err := target.ReadDir(metadataPath)
|
||||
if err != nil {
|
||||
op.Errorf("readdir(%s): %s", metadataPath, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := make(map[string][]byte)
|
||||
for _, keyFile := range dataKeys {
|
||||
|
||||
if keyFile.Name() == "." || keyFile.Name() == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
pth := path.Join(metadataPath, keyFile.Name())
|
||||
|
||||
f, err := target.Open(pth)
|
||||
if err != nil {
|
||||
op.Errorf("open(%s): %s", pth, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dataBlob, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
op.Errorf("readall(%s): %s", pth, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info[keyFile.Name()] = dataBlob
|
||||
}
|
||||
|
||||
op.Infof("Successfully read volume metadata at (%s)", metadataPath)
|
||||
return info, nil
|
||||
}
|
||||
489
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/store_test.go
generated
vendored
Normal file
489
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/store_test.go
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
nfsTestDir = "NFSVolumeStoreTests"
|
||||
)
|
||||
|
||||
type MockMount struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (m MockMount) Mount(op trace.Operation) (Target, error) {
|
||||
return NewMocktarget(m.Path), nil
|
||||
}
|
||||
|
||||
func (m MockMount) Unmount(op trace.Operation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MockMount) URL() (*url.URL, error) {
|
||||
return url.Parse("nfs://localhost/some/interesting/dir")
|
||||
}
|
||||
|
||||
type MockTarget struct {
|
||||
dirPath string
|
||||
}
|
||||
|
||||
func NewMocktarget(pth string) MockTarget {
|
||||
return MockTarget{dirPath: pth}
|
||||
}
|
||||
|
||||
func (v MockTarget) Open(pth string) (io.ReadCloser, error) {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("open(%s)", pth)
|
||||
return os.Open(pth)
|
||||
}
|
||||
|
||||
func (v MockTarget) OpenFile(pth string, mode os.FileMode) (io.ReadWriteCloser, error) {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("openfile(%s)", pth)
|
||||
return os.OpenFile(pth, os.O_RDWR|os.O_CREATE, mode)
|
||||
}
|
||||
|
||||
func (v MockTarget) Mkdir(pth string, perm os.FileMode) ([]byte, error) {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("mkdir(%s)", pth)
|
||||
return nil, os.Mkdir(pth, perm)
|
||||
}
|
||||
|
||||
func (v MockTarget) RemoveAll(pth string) error {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("RemoveAll(%s)", pth)
|
||||
return os.RemoveAll(pth)
|
||||
}
|
||||
|
||||
func (v MockTarget) ReadDir(pth string) ([]os.FileInfo, error) {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("readdir(%s)", pth)
|
||||
dir, err := os.Open(pth)
|
||||
defer dir.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dir.Readdir(0)
|
||||
}
|
||||
|
||||
func (v MockTarget) Lookup(pth string) (os.FileInfo, []byte, error) {
|
||||
pth = path.Join(v.dirPath, pth)
|
||||
logrus.Infof("stat(%s)", pth)
|
||||
info, err := os.Stat(pth)
|
||||
return info, nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
expected Target
|
||||
mnt MountServer
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
nfsurl := os.Getenv("NFS_TEST_URL")
|
||||
if nfsurl != "" {
|
||||
u, err := url.Parse(nfsurl)
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
logrus.Infof("testing nfs against %#v", u)
|
||||
|
||||
mnt = NewMount(u, "hasselhoff", 1001, 10001)
|
||||
expected, err = mnt.Mount(trace.NewOperation(context.TODO(), "mount"))
|
||||
if err != nil {
|
||||
logrus.Errorf("error mounting %s: %s", u.String(), err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
testdir, err := ioutil.TempDir(os.TempDir(), nfsTestDir)
|
||||
if err != nil {
|
||||
logrus.Errorf("error creating tmpdir: %s", err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
os.Mkdir(testdir, 0755)
|
||||
defer os.RemoveAll(testdir)
|
||||
|
||||
// We can twiddle the target directly via expected and use it to verify the
|
||||
// right things are happening.
|
||||
mnt = &MockMount{testdir}
|
||||
expected = &MockTarget{testdir}
|
||||
}
|
||||
|
||||
result := m.Run()
|
||||
os.Exit(result)
|
||||
}
|
||||
|
||||
func TestSimpleVolumeStoreOperations(t *testing.T) {
|
||||
op := trace.NewOperation(context.TODO(), "TestOp")
|
||||
|
||||
// Create a Volume Store
|
||||
vs, err := NewVolumeStore(op, "testStore", mnt)
|
||||
if !assert.NoError(t, err, "Failed during call to NewVolumeStore with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = expected.Lookup(volumesDir)
|
||||
if !assert.NoError(t, err, "Could not find the initial volume store directory after creation of volume store. err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NotNil(t, vs, "Volume Store created with nil err, but return is also nil") {
|
||||
return
|
||||
}
|
||||
|
||||
info := make(map[string][]byte)
|
||||
testInfoKey := "junk"
|
||||
info[testInfoKey] = make([]byte, 20)
|
||||
|
||||
// Create a Volume
|
||||
testVolName := "testVolume"
|
||||
|
||||
vol, err := vs.VolumeCreate(op, testVolName, vs.SelfLink, 0 /*we do not use this*/, info)
|
||||
if !assert.NoError(t, err, "Failed during call to VolumeCreate with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, testVolName, vol.ID, "expected volume ID (%s) got ID (%s)", testVolName, vol.ID) {
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := vol.Info[testInfoKey]
|
||||
if !assert.True(t, ok, "TestInfoKey did not exist in the return metadata map") {
|
||||
return
|
||||
}
|
||||
|
||||
// Check Metadata Pathing
|
||||
metaDirEntries, err := expected.ReadDir(metadataDir)
|
||||
if !assert.NoError(t, err, "Failed to read the metadata directory with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Len(t, metaDirEntries, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
volumeDirEntries, err := expected.ReadDir(volumesDir)
|
||||
if !assert.NoError(t, err, "Failed to read the volume data directory with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Len(t, volumeDirEntries, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the Volume
|
||||
err = vs.VolumeDestroy(op, vol)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumeDestroy with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
// should throw an error since the directory got nuked
|
||||
metaDirEntries, err = expected.ReadDir(path.Join(metadataDir, vol.ID))
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(metaDirEntries), 0, "expected metadata directory to have 1 entry and it had (%s)", len(metaDirEntries)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Should throw an error on the volume directory
|
||||
volumeDirEntries, err = expected.ReadDir(path.Join(volumesDir, vol.ID))
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(volumeDirEntries), 0, "expected metadata directory to have 1 entry and it had (%s)", len(volumeDirEntries)) {
|
||||
return
|
||||
}
|
||||
|
||||
volToCheck, err := vs.VolumeCreate(op, testVolName, vs.SelfLink, 0, info)
|
||||
if !assert.NoError(t, err, "Failed during call to VolumeCreate with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
volumeList, err := vs.VolumesList(op)
|
||||
if !assert.NoError(t, err, "Failed during call to VolumesList with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, 1, len(volumeList)) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, volumeList[0].ID, volToCheck.ID, "Failed due to VolumeList returning an unexpected volume %#v when volume %#v was expected.", volumeList[0], volToCheck) {
|
||||
return
|
||||
}
|
||||
|
||||
RetrievedInfo := volumeList[0].Info
|
||||
CreatedInfo := volToCheck.Info
|
||||
|
||||
if !assert.Equal(t, len(RetrievedInfo), len(CreatedInfo), "Length mismatch between the created volume(%s) and the volume returned from VolumeList(%s)", len(CreatedInfo), len(RetrievedInfo)) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, RetrievedInfo[testInfoKey], CreatedInfo[testInfoKey], "Failed due to mismatch in metadata between the content of the Created volume(%s) and the volume return from VolumesList", CreatedInfo[testInfoKey], RetrievedInfo[testInfoKey]) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vs.VolumeDestroy(op, volToCheck)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumeDestroy with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
volumeList, err = vs.VolumesList(op)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumesListwith err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(volumeList), 0, "Expected %s volumes, VolumesList returned %s", 0, len(volumeList)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleVolumes(t *testing.T) {
|
||||
op := trace.NewOperation(context.TODO(), "TestOp")
|
||||
|
||||
//Create a Volume Store
|
||||
vs, err := NewVolumeStore(op, "testStore", mnt)
|
||||
if !assert.NoError(t, err, "Failed during call to NewVolumeStore with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NotNil(t, vs, "Volume Store created with nil err, but return is also nil") {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = expected.Lookup(volumesDir)
|
||||
if !assert.NoError(t, err, "Could not find the initial volume store directory after creation of volume store. err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
// setup volume inputs
|
||||
testVolNameOne := "test1"
|
||||
infoOne := make(map[string][]byte)
|
||||
testOneInfoKey := "junk"
|
||||
infoOne[testOneInfoKey] = make([]byte, 20)
|
||||
|
||||
testVolNameTwo := "test2"
|
||||
testTwoInfoKey := "important"
|
||||
infoTwo := make(map[string][]byte)
|
||||
infoTwo[testTwoInfoKey] = []byte("42")
|
||||
testTwoInfoKeyTwo := "lessImportant"
|
||||
infoTwo[testTwoInfoKeyTwo] = []byte("41")
|
||||
|
||||
testVolNameThree := "test3"
|
||||
infoThree := make(map[string][]byte)
|
||||
testThreeInfoKey := "lotsOfStuff"
|
||||
infoThree[testThreeInfoKey] = []byte("importantData")
|
||||
testThreeInfoKeyTwo := "someMoreStuff"
|
||||
infoThree[testThreeInfoKeyTwo] = []byte("maybeSomeLabels")
|
||||
|
||||
//make volume one
|
||||
volOne, err := vs.VolumeCreate(op, testVolNameOne, vs.SelfLink, 0 /*we do not use this*/, infoOne)
|
||||
|
||||
if !assert.NoError(t, err, "Failed during call to VolumeCreate with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, testVolNameOne, volOne.ID, "expected volume ID (%s) got ID (%s)", testVolNameOne, volOne.ID) {
|
||||
return
|
||||
}
|
||||
|
||||
valOne, ok := volOne.Info[testOneInfoKey]
|
||||
if !assert.True(t, ok, "TestInfoKey did not exist in the return metadata map") {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, valOne, volOne.Info[testOneInfoKey], "TestVolOne expected to have data (%s) and (%s) was found", infoOne, valOne) {
|
||||
return
|
||||
}
|
||||
|
||||
// make volume two
|
||||
volTwo, err := vs.VolumeCreate(op, testVolNameTwo, vs.SelfLink, 0 /*we do not use this*/, infoTwo)
|
||||
|
||||
if !assert.NoError(t, err, "Failed during call to VolumeCreate with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, testVolNameTwo, volTwo.ID, "expected volume ID (%s) got ID (%s)", testVolNameTwo, volTwo.ID) {
|
||||
return
|
||||
}
|
||||
|
||||
valOne, ok = volTwo.Info[testTwoInfoKey]
|
||||
if !assert.True(t, ok, "TestInfoKey did not exist in the return metadata map") {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, []byte("42"), valOne, "TestVolTwo expected to have data (%s) and (%s) was found", []byte("42"), valOne) {
|
||||
return
|
||||
}
|
||||
|
||||
valTwo, ok := volTwo.Info[testTwoInfoKeyTwo]
|
||||
if !assert.True(t, ok, "TestTwoInfoKeyTwo did not exist in the return metadata map for volTwo") {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, []byte("41"), valTwo, "TestVolTwo expected to have data (%s) and (%s) was found", []byte("41"), valTwo) {
|
||||
return
|
||||
}
|
||||
|
||||
// make volume three
|
||||
volThree, err := vs.VolumeCreate(op, testVolNameThree, vs.SelfLink, 0 /*we do not use this*/, infoThree)
|
||||
|
||||
if !assert.NoError(t, err, "Failed during call to VolumeCreate with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, testVolNameThree, volThree.ID, "expected volume ID (%s) got ID (%s)", testVolNameThree, volThree.ID) {
|
||||
return
|
||||
}
|
||||
|
||||
valOne, ok = volThree.Info[testThreeInfoKey]
|
||||
if !assert.True(t, ok, "TestInfoKey did not exist in the return metadata map") {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, []byte("importantData"), valOne, "TestVolThree expected to have data (%s) and (%s) was found", []byte("importantData"), valOne) {
|
||||
return
|
||||
}
|
||||
|
||||
valTwo, ok = volThree.Info[testThreeInfoKeyTwo]
|
||||
if !assert.True(t, ok, "TestThreeInfoKeyTwo did not exist in the return metadata map for volThree") {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, []byte("maybeSomeLabels"), valTwo, "TestVolThree expected to have data (%s) and (%s) was found", []byte("maybeSomeLabels"), valTwo) {
|
||||
return
|
||||
}
|
||||
|
||||
// list volumes
|
||||
volumes, err := vs.VolumesList(op)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumesList with err (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
volCount := len(volumes)
|
||||
if !assert.Equal(t, volCount, 3, "VolumesList returned unexpected volume count. expected (%s), but received (%s) ", 3, volCount) {
|
||||
return
|
||||
}
|
||||
|
||||
// check metadatas
|
||||
metaDirEntries, err := expected.ReadDir(metadataDir)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(metaDirEntries), 3, "expected metadata directory to have 1 entry and it had (%s)", len(metaDirEntries)) {
|
||||
return
|
||||
}
|
||||
|
||||
volumeDirEntries, err := expected.ReadDir(volumesDir)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(volumeDirEntries), 3, "expected metadata directory to have 1 entry and it had (%s)", len(volumeDirEntries)) {
|
||||
return
|
||||
}
|
||||
|
||||
verify := func(vols map[string]int) error {
|
||||
for name, num := range vols {
|
||||
metadataFiles, err := expected.ReadDir(path.Join(metadataDir, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(metadataFiles) != num {
|
||||
return fmt.Errorf("len metadata %d != %d", len(metadataFiles), num)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// check and individual metadata dir
|
||||
volmap := map[string]int{
|
||||
testVolNameThree: 2,
|
||||
testVolNameTwo: 2,
|
||||
testVolNameOne: 1,
|
||||
}
|
||||
if !assert.NoError(t, verify(volmap)) {
|
||||
return
|
||||
}
|
||||
|
||||
// remove volume one
|
||||
err = vs.VolumeDestroy(op, volOne)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumeDestroy with error (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
// assert it's gone
|
||||
_, _, err = expected.Lookup(path.Join(metadataDir, volOne.ID))
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = expected.Lookup(path.Join(volumesDir, volOne.ID))
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// check that volume two and three exist with appropriate metadata
|
||||
delete(volmap, testVolNameOne)
|
||||
if !assert.NoError(t, verify(volmap)) {
|
||||
return
|
||||
}
|
||||
|
||||
// remove the rest of the volumes
|
||||
err = vs.VolumeDestroy(op, volTwo)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumeDestroy with error (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vs.VolumeDestroy(op, volThree)
|
||||
if !assert.NoError(t, err, "Failed during a call to VolumeDestroy with error (%s)", err) {
|
||||
return
|
||||
}
|
||||
|
||||
// verify they're gone
|
||||
vols, err := vs.VolumesList(op)
|
||||
if !assert.Len(t, vols, 0) {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
152
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/target.go
generated
vendored
Normal file
152
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/nfs/target.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
nfsClient "github.com/vmware/go-nfs-client/nfs"
|
||||
"github.com/vmware/go-nfs-client/nfs/rpc"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// MountServer is an interface used to communicate with network attached storage.
|
||||
type MountServer interface {
|
||||
// Mount executes the mount program on the Target.
|
||||
Mount(op trace.Operation) (Target, error)
|
||||
|
||||
// Unmount terminates the Mount on the Target.
|
||||
Unmount(op trace.Operation) error
|
||||
|
||||
URL() (*url.URL, error)
|
||||
}
|
||||
|
||||
// Target is the filesystem interface for performing actions against attached storage.
|
||||
type Target interface {
|
||||
// Open opens a file on the Target in RD_ONLY
|
||||
Open(path string) (io.ReadCloser, error)
|
||||
|
||||
// OpenFile opens a file on the Target with the given mode
|
||||
OpenFile(path string, perm os.FileMode) (io.ReadWriteCloser, error)
|
||||
|
||||
// Mkdir creates a directory at the given path
|
||||
Mkdir(path string, perm os.FileMode) ([]byte, error)
|
||||
|
||||
// RemoveAll deletes Directory recursively
|
||||
RemoveAll(Path string) error
|
||||
|
||||
// ReadDir reads the dirents in the given directory
|
||||
ReadDir(path string) ([]os.FileInfo, error)
|
||||
|
||||
// Lookup reads os.FileInfo for the given path
|
||||
Lookup(path string) (os.FileInfo, []byte, error)
|
||||
}
|
||||
|
||||
// NfsMount is used to wrap a MountServer to do the Mount()/Unmount() and Close()
|
||||
type NfsMount struct {
|
||||
// Hostname is the name to authenticate with to the target as
|
||||
Hostname string
|
||||
|
||||
// UID and GID are the user id and group id to authenticate with the target
|
||||
UID, GID uint32
|
||||
|
||||
// The URL (host + path) of the NFS server and target path
|
||||
TargetURL *url.URL
|
||||
|
||||
s *nfsClient.Mount
|
||||
}
|
||||
|
||||
func NewMount(t *url.URL, hostname string, uid, gid uint32) *NfsMount {
|
||||
return &NfsMount{
|
||||
Hostname: hostname,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
TargetURL: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NfsMount) Mount(op trace.Operation) (Target, error) {
|
||||
op.Debugf("Mounting %s", m.TargetURL.String())
|
||||
s, err := nfsClient.DialMount(m.TargetURL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.s = s
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
m.s.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
auth := rpc.NewAuthUnix(m.Hostname, m.UID, m.GID)
|
||||
mnt, err := s.Mount(m.TargetURL.Path, auth.Auth())
|
||||
if err != nil {
|
||||
op.Errorf("unable to mount volume: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Infof("Mounted %s", m.TargetURL.String())
|
||||
return &target{mnt}, nil
|
||||
}
|
||||
|
||||
func (m *NfsMount) Unmount(op trace.Operation) error {
|
||||
op.Debugf("Unmounting %s", m.TargetURL.String())
|
||||
if err := m.s.Unmount(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.s.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Debugf("Unmounted %s", m.TargetURL.String())
|
||||
m.s = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NfsMount) URL() (*url.URL, error) {
|
||||
return m.TargetURL, nil
|
||||
}
|
||||
|
||||
// wrap ReadDir to return a slice of os.FileInfo
|
||||
type target struct {
|
||||
*nfsClient.Target
|
||||
}
|
||||
|
||||
func (t *target) ReadDir(path string) ([]os.FileInfo, error) {
|
||||
entries, err := t.ReadDirPlus(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var e []os.FileInfo
|
||||
for i := 0; i < len(entries); i++ {
|
||||
|
||||
// filter out . and ..
|
||||
name := entries[i].Name()
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
e = append(e, os.FileInfo(entries[i]))
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
150
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume.go
generated
vendored
Normal file
150
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2016-2018 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 volume
|
||||
|
||||
import (
|
||||
"crypto/md5" // #nosec: Use of weak cryptographic primitive
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type Disk interface {
|
||||
// Path to this disk on the VCH
|
||||
MountPath() (string, error)
|
||||
|
||||
// Path to the disk on the datastore
|
||||
DiskPath() url.URL
|
||||
}
|
||||
|
||||
// VolumeStorer is an interface to create, remove, enumerate, and get Volumes.
|
||||
type VolumeStorer interface {
|
||||
// Creates a volume on the given volume store, of the given size, with the given metadata.
|
||||
VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*Volume, error)
|
||||
|
||||
// Destroys a volume
|
||||
VolumeDestroy(op trace.Operation, vol *Volume) error
|
||||
|
||||
// Lists all volumes
|
||||
VolumesList(op trace.Operation) ([]*Volume, error)
|
||||
|
||||
// The interfaces necessary for Import and Export
|
||||
storage.Resolver
|
||||
storage.Importer
|
||||
storage.Exporter
|
||||
}
|
||||
|
||||
// Volume is the handle to identify a volume on the backing store. The URI
|
||||
// namespace used to identify the Volume in the storage layer has the following
|
||||
// path scheme:
|
||||
//
|
||||
// `/storage/volumes/<volume store identifier, usually the vch uuid>/<volume id>`
|
||||
//
|
||||
type Volume struct {
|
||||
// Identifies the volume
|
||||
ID string
|
||||
|
||||
// Label is the computed label of the Volume. This is set by the runtime.
|
||||
Label string
|
||||
|
||||
// The volumestore the volume lives on. (e.g the datastore + vch + configured vol directory)
|
||||
Store *url.URL
|
||||
|
||||
// Metadata the volume is included with. Is persisted along side the volume vmdk.
|
||||
Info map[string][]byte
|
||||
|
||||
// Namespace in the storage layer to look up this volume.
|
||||
SelfLink *url.URL
|
||||
|
||||
// Backing device
|
||||
Device Disk
|
||||
|
||||
CopyMode executor.CopyMode
|
||||
}
|
||||
|
||||
// NewVolume creates a Volume
|
||||
func NewVolume(store *url.URL, ID string, info map[string][]byte, device Disk, copyMode executor.CopyMode) (*Volume, error) {
|
||||
storeName, err := util.VolumeStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selflink, err := util.VolumeURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the label to the md5 of the ID
|
||||
|
||||
vol := &Volume{
|
||||
ID: ID,
|
||||
Label: Label(ID),
|
||||
Store: store,
|
||||
SelfLink: selflink,
|
||||
Device: device,
|
||||
Info: info,
|
||||
CopyMode: copyMode,
|
||||
}
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
// given an ID, compute the volume's label
|
||||
func Label(ID string) string {
|
||||
|
||||
// e2label's manpage says the label size is 16 chars
|
||||
// #nosec: Use of weak cryptographic primitive
|
||||
m := md5.Sum([]byte(ID))
|
||||
return fmt.Sprintf("%x", m)[:16]
|
||||
}
|
||||
|
||||
func (v *Volume) Parse(u *url.URL) error {
|
||||
// Check the path isn't malformed.
|
||||
if !filepath.IsAbs(u.Path) {
|
||||
return errors.New("invalid uri path")
|
||||
}
|
||||
|
||||
segments := strings.Split(filepath.Clean(u.Path), "/")[1:]
|
||||
|
||||
if segments[0] != util.StorageURLPath {
|
||||
return errors.New("not a storage path")
|
||||
}
|
||||
|
||||
if len(segments) < 3 {
|
||||
return errors.New("uri path mismatch")
|
||||
}
|
||||
|
||||
store, err := util.VolumeStoreNameToURL(segments[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := segments[3]
|
||||
|
||||
var SelfLink url.URL
|
||||
SelfLink = *u
|
||||
|
||||
v.ID = id
|
||||
v.SelfLink = &SelfLink
|
||||
v.Store = store
|
||||
|
||||
return nil
|
||||
}
|
||||
307
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_cache.go
generated
vendored
Normal file
307
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_cache.go
generated
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
// Copyright 2016-2018 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 volume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// VolumeLookupCache caches Volume references to volumes in the system.
|
||||
type VolumeLookupCache struct {
|
||||
|
||||
// Maps IDs to Volumes.
|
||||
//
|
||||
// id -> Volume
|
||||
vlc map[string]Volume
|
||||
vlcLock sync.RWMutex
|
||||
|
||||
// Maps the service url of the volume store to the underlying data storage implementation
|
||||
volumeStores map[string]VolumeStorer
|
||||
}
|
||||
|
||||
func NewVolumeLookupCache(op trace.Operation) *VolumeLookupCache {
|
||||
v := &VolumeLookupCache{
|
||||
vlc: make(map[string]Volume),
|
||||
volumeStores: make(map[string]VolumeStorer),
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) GetVolumeStore(op trace.Operation, storeName string) (*url.URL, error) {
|
||||
u, err := util.VolumeStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// AddStore adds a volumestore by name. The url returned is the service url to the volume store.
|
||||
func (v *VolumeLookupCache) AddStore(op trace.Operation, storeName string, vs VolumeStorer) (*url.URL, error) {
|
||||
v.vlcLock.Lock()
|
||||
defer v.vlcLock.Unlock()
|
||||
|
||||
// get the service url
|
||||
u, err := util.VolumeStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeURLStr := u.String()
|
||||
if _, ok := v.volumeStores[storeURLStr]; ok {
|
||||
return nil, fmt.Errorf("volumestore (%s) already added", storeURLStr)
|
||||
}
|
||||
|
||||
v.volumeStores[storeURLStr] = vs
|
||||
return u, v.addVolumesToCache(op, storeURLStr, vs)
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) volumeStore(store *url.URL) (VolumeStorer, error) {
|
||||
|
||||
// find the datastore
|
||||
vs, ok := v.volumeStores[store.String()]
|
||||
if !ok {
|
||||
err := VolumeStoreNotFoundError{
|
||||
Msg: fmt.Sprintf("volume store (%s) not found", store.String()),
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
// VolumeStoresList returns a list of volume store names
|
||||
func (v *VolumeLookupCache) VolumeStoresList(op trace.Operation) ([]string, error) {
|
||||
v.vlcLock.RLock()
|
||||
defer v.vlcLock.RUnlock()
|
||||
|
||||
stores := make([]string, 0, len(v.volumeStores))
|
||||
for u := range v.volumeStores {
|
||||
|
||||
// from the storage url, get the store name
|
||||
storeURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeName, err := util.VolumeStoreName(storeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stores = append(stores, storeName)
|
||||
}
|
||||
|
||||
return stores, nil
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*Volume, error) {
|
||||
v.vlcLock.Lock()
|
||||
defer v.vlcLock.Unlock()
|
||||
|
||||
// check if it exists
|
||||
_, ok := v.vlc[ID]
|
||||
if ok {
|
||||
return nil, os.ErrExist
|
||||
}
|
||||
|
||||
vs, err := v.volumeStore(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vol, err := vs.VolumeCreate(op, ID, store, capacityKB, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add it to the cache.
|
||||
v.vlc[vol.ID] = *vol
|
||||
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) VolumeDestroy(op trace.Operation, ID string) error {
|
||||
v.vlcLock.Lock()
|
||||
defer v.vlcLock.Unlock()
|
||||
|
||||
// Check if it exists
|
||||
vol, ok := v.vlc[ID]
|
||||
if !ok {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
if err := volumeInUse(vol.ID); err != nil {
|
||||
op.Errorf("VolumeStore: delete error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
vs, err := v.volumeStore(vol.Store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove it from the volumestore
|
||||
if err := vs.VolumeDestroy(op, &vol); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(v.vlc, vol.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) VolumeGet(op trace.Operation, ID string) (*Volume, error) {
|
||||
v.vlcLock.RLock()
|
||||
defer v.vlcLock.RUnlock()
|
||||
|
||||
// look in the cache
|
||||
vol, ok := v.vlc[ID]
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return &vol, nil
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) VolumesList(op trace.Operation) ([]*Volume, error) {
|
||||
v.vlcLock.RLock()
|
||||
defer v.vlcLock.RUnlock()
|
||||
|
||||
// look in the cache, return the list
|
||||
l := make([]*Volume, 0, len(v.vlc))
|
||||
for _, vol := range v.vlc {
|
||||
// this is idiotic
|
||||
var e Volume
|
||||
e = vol
|
||||
l = append(l, &e)
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// addVolumesToCache finds the volumes in the input volume store and adds them to the cache.
|
||||
func (v *VolumeLookupCache) addVolumesToCache(op trace.Operation, storeURLStr string, vs VolumeStorer) error {
|
||||
op.Infof("Adding volumes in volume store %s to volume cache", storeURLStr)
|
||||
|
||||
vols, err := vs.VolumesList(op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, vol := range vols {
|
||||
log.Infof("Volumestore: Found vol %s on store %s", vol.ID, vol.Store)
|
||||
// Add it to the cache.
|
||||
v.vlc[vol.ID] = *vol
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func volumeInUse(ID string) error {
|
||||
conts := exec.Containers.Containers(nil)
|
||||
if len(conts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cont := range conts {
|
||||
|
||||
if cont.ExecConfig.Mounts == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, mounted := cont.ExecConfig.Mounts[ID]; mounted {
|
||||
return &ErrVolumeInUse{
|
||||
Msg: fmt.Sprintf("volume %s in use by %s", ID, cont.ExecConfig.ID),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Import is a fake store import so that we can do a late lookup of the actual store - this is a work around for the fact that the store
|
||||
// URL isn't available in useful form outside of the volumeCache
|
||||
func (v *VolumeLookupCache) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error {
|
||||
volume, err := v.VolumeGet(op, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store, err := v.volumeStore(volume.Store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// relay call to actual store
|
||||
return store.Import(op, id, spec, tarStream)
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
volume, err := v.VolumeGet(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := v.volumeStore(volume.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// relay call to actual store
|
||||
return store.NewDataSink(op, id)
|
||||
}
|
||||
|
||||
// Export is a fake store export so that we can do a late lookup of the actual store - this is a work around for the fact that the store
|
||||
// URL isn't available in useful form outside of the volumeCache
|
||||
func (v *VolumeLookupCache) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
volume, err := v.VolumeGet(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := v.volumeStore(volume.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// relay call to actual store
|
||||
return store.Export(op, id, ancestor, spec, data)
|
||||
}
|
||||
|
||||
func (v *VolumeLookupCache) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
volume, err := v.VolumeGet(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := v.volumeStore(volume.Store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// relay call to actual store
|
||||
return store.NewDataSource(op, id)
|
||||
}
|
||||
297
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_cache_test.go
generated
vendored
Normal file
297
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_cache_test.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
// Copyright 2016-2018 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 volume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
type MockVolumeStore struct {
|
||||
// id -> volume
|
||||
db map[string]*Volume
|
||||
}
|
||||
|
||||
func NewMockVolumeStore() *MockVolumeStore {
|
||||
m := &MockVolumeStore{
|
||||
db: make(map[string]*Volume),
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) VolumeStoresList(op trace.Operation) (map[string]url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Creates a volume on the given volume store, of the given size, with the given metadata.
|
||||
func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*Volume, error) {
|
||||
storeName, err := util.VolumeStoreName(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selfLink, err := util.VolumeURL(storeName, ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vol := &Volume{
|
||||
ID: ID,
|
||||
Store: store,
|
||||
SelfLink: selfLink,
|
||||
}
|
||||
|
||||
m.db[ID] = vol
|
||||
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
// Get an existing volume via it's ID and volume store.
|
||||
func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*Volume, error) {
|
||||
vol, ok := m.db[ID]
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
// Destroys a volume
|
||||
func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *Volume) error {
|
||||
if _, ok := m.db[vol.ID]; !ok {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
delete(m.db, vol.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VolumesList lists all volumes on the given volume store.
|
||||
func (m *MockVolumeStore) VolumesList(op trace.Operation) ([]*Volume, error) {
|
||||
var i int
|
||||
list := make([]*Volume, len(m.db))
|
||||
for _, v := range m.db {
|
||||
t := *v
|
||||
list[i] = &t
|
||||
i++
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockVolumeStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestVolumeCreateGetListAndDelete(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
exec.NewContainerCache()
|
||||
|
||||
mvs := NewMockVolumeStore()
|
||||
v := NewVolumeLookupCache(op)
|
||||
storeURL, err := v.AddStore(op, "testStore", mvs)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
inVols := make(map[string]*Volume)
|
||||
inVolsM := &sync.Mutex{}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
createFn := func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
// Write to the datastore
|
||||
vol, err := v.VolumeCreate(op, id, storeURL, 0, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vol) {
|
||||
return
|
||||
}
|
||||
|
||||
inVolsM.Lock()
|
||||
inVols[id] = vol
|
||||
inVolsM.Unlock()
|
||||
}
|
||||
|
||||
// Create a set of volumes
|
||||
numVolumes := 5
|
||||
wg.Add(numVolumes)
|
||||
for i := 0; i < numVolumes; i++ {
|
||||
go createFn(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
getFn := func(inVol *Volume) {
|
||||
vol, err := v.VolumeGet(op, inVol.ID)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vol) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, inVol, vol) {
|
||||
return
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
wg.Add(numVolumes)
|
||||
for _, inVol := range inVols {
|
||||
getFn(inVol)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
volumeList, err := v.VolumesList(op)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, numVolumes, len(volumeList)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Test that the list returned by VolumeList matches our inVols list. Then
|
||||
// delete each vol via the cache, then check the datastore to ensure it's
|
||||
// empty
|
||||
for _, outVol := range volumeList {
|
||||
if !assert.Equal(t, inVols[outVol.ID], outVol) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = v.VolumeDestroy(op, outVol.ID); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check the datastore is empty.
|
||||
if !assert.Empty(t, mvs.db) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// createVolumes is a test helper that creates a set of num volumes on the input volume cache and volume store.
|
||||
func createVolumes(t *testing.T, op trace.Operation, v *VolumeLookupCache, storeURL *url.URL, num int) map[string]*Volume {
|
||||
vols := make(map[string]*Volume)
|
||||
for i := 1; i <= num; i++ {
|
||||
id := fmt.Sprintf("ID-%d", i)
|
||||
|
||||
// Write to the datastore
|
||||
vol, err := v.VolumeCreate(op, id, storeURL, 0, nil)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vol) {
|
||||
return nil
|
||||
}
|
||||
|
||||
vols[id] = vol
|
||||
}
|
||||
|
||||
return vols
|
||||
}
|
||||
|
||||
func TestAddVolumesToCache(t *testing.T) {
|
||||
mvs1 := NewMockVolumeStore()
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
v := NewVolumeLookupCache(op)
|
||||
|
||||
storeURL, err := util.VolumeStoreNameToURL("testStore")
|
||||
assert.NotNil(t, storeURL)
|
||||
storeURLStr := storeURL.String()
|
||||
v.volumeStores[storeURLStr] = mvs1
|
||||
|
||||
// Create 50 volumes on the volume store.
|
||||
vols := createVolumes(t, op, v, storeURL, 50)
|
||||
|
||||
// Clear the volume map after it has been filled during volume creation.
|
||||
v.vlc = make(map[string]Volume)
|
||||
|
||||
err = v.addVolumesToCache(op, storeURLStr, mvs1)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check that the volume map is intact again in the cache.
|
||||
for _, expectedVol := range vols {
|
||||
vol, err := v.VolumeGet(op, expectedVol.ID)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, expectedVol, vol) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create 2 store caches but use the same backing datastore. Create images
|
||||
// with the first cache, then get the image with the second. This simulates
|
||||
// restart since the second cache is empty and has to go to the backing store.
|
||||
func TestVolumeCacheRestart(t *testing.T) {
|
||||
mvs := NewMockVolumeStore()
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
firstCache := NewVolumeLookupCache(op)
|
||||
storeURL, err := firstCache.AddStore(op, "testStore", mvs)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a set of 50 volumes.
|
||||
inVols := createVolumes(t, op, firstCache, storeURL, 50)
|
||||
|
||||
secondCache := NewVolumeLookupCache(op)
|
||||
if !assert.NotNil(t, secondCache) {
|
||||
return
|
||||
}
|
||||
|
||||
storeURL, err = secondCache.AddStore(op, "testStore", mvs)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// get the vols from the second cache to ensure it goes to the ds
|
||||
for _, expectedVol := range inVols {
|
||||
vol, err := secondCache.VolumeGet(op, expectedVol.ID)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, expectedVol, vol) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
54
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_test.go
generated
vendored
Normal file
54
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/volume_test.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2016-2018 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 volume
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
)
|
||||
|
||||
func TestVolumeParseURL(t *testing.T) {
|
||||
util.DefaultHost, _ = url.Parse("http://foo.com/")
|
||||
|
||||
in, _ := url.Parse(util.DefaultHost.String())
|
||||
in.Path = "/" + path.Join("storage", "volumes", "volStore", "volName")
|
||||
|
||||
v := &Volume{}
|
||||
err := v.Parse(in)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, "volName", v.ID) {
|
||||
return
|
||||
}
|
||||
|
||||
volStore, _ := url.Parse(util.DefaultHost.String())
|
||||
util.AppendDir(volStore, "/storage/volumes/volStore")
|
||||
if !assert.Equal(t, volStore.String(), v.Store.String()) {
|
||||
return
|
||||
}
|
||||
|
||||
util.AppendDir(volStore, "volName")
|
||||
if !assert.Equal(t, volStore.String(), v.SelfLink.String()) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
136
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/export.go
generated
vendored
Normal file
136
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/export.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 vsphere
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// Export reads the delta between child and parent volume layers, returning
|
||||
// the difference as a tar archive.
|
||||
//
|
||||
// store - the volume store containing the two layers
|
||||
// id - must inherit from ancestor if ancestor is specified
|
||||
// ancestor - the volume layer up the chain against which to diff
|
||||
// spec - describes filters on paths found in the data (include, exclude, strip)
|
||||
// data - set to true to include file data in the tar archive, false to include headers only
|
||||
func (v *VolumeStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
if ancestor != "" {
|
||||
return nil, fmt.Errorf("volume diff is not supported in this volume store: %s", v.SelfLink.String())
|
||||
}
|
||||
|
||||
l, err := v.NewDataSource(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.Export(op, spec, data)
|
||||
}
|
||||
|
||||
// NewDataSource creates and returns an DataSource associated with container storage
|
||||
func (v *VolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) {
|
||||
uri, err := v.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineAttempt := 0
|
||||
offline:
|
||||
offlineAttempt++
|
||||
|
||||
// offline disk attempt
|
||||
source, err := v.newDataSource(op, uri)
|
||||
if err == nil {
|
||||
return source, err
|
||||
}
|
||||
|
||||
// check for vmdk locked error here
|
||||
if !disk.IsLockedError(err) {
|
||||
op.Warnf("Unable to mount %s and do not know how to recover from error")
|
||||
// continue anyway because maybe there's an online option
|
||||
}
|
||||
|
||||
// online - Owners() should filter out the appliance VM
|
||||
// #nosec: Errors unhandled.
|
||||
owners, _ := v.Owners(op, uri, disk.LockedVMDKFilter)
|
||||
if len(owners) == 0 {
|
||||
op.Infof("No online owners were found for %s", id)
|
||||
return nil, errors.New("unable to create offline data source and no online owners found")
|
||||
}
|
||||
|
||||
for _, o := range owners {
|
||||
// sanity check to see if we are the owner - this should catch transitions
|
||||
// from container running to diff or commit for example between the offline attempt and here
|
||||
uuid, err := o.UUID(op)
|
||||
if err == nil {
|
||||
// check if the vm is appliance VM if we can successfully get its UUID
|
||||
// #nosec: Errors unhandled.
|
||||
self, _ := guest.IsSelf(op, uuid)
|
||||
if self && offlineAttempt < 2 {
|
||||
op.Infof("Appliance is owner of online vmdk - retrying offline source path")
|
||||
goto offline
|
||||
}
|
||||
}
|
||||
|
||||
online, err := v.newOnlineDataSource(op, o, id)
|
||||
if online != nil {
|
||||
return online, err
|
||||
}
|
||||
|
||||
op.Debugf("Failed to create online datasource with owner %s: %s", o.Reference(), err)
|
||||
}
|
||||
|
||||
return nil, errors.New("unable to create online or offline data source")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) newDataSource(op trace.Operation, url *url.URL) (storage.DataSource, error) {
|
||||
// This is persistent to avoid issues with concurrent Stat/Import calls
|
||||
mountPath, cleanFunc, err := v.Mount(op, url, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data source for access to %s at %s", url, mountPath)
|
||||
return storage.NewMountDataSource(op, f, cleanFunc), nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) newOnlineDataSource(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSource, error) {
|
||||
op.Debugf("Constructing toolbox data source: %s.%s", owner.Reference(), id)
|
||||
|
||||
return &vsphere.ToolboxDataSource{
|
||||
VM: owner,
|
||||
ID: volume.Label(id),
|
||||
Clean: func() { return },
|
||||
}, nil
|
||||
}
|
||||
121
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/import.go
generated
vendored
Normal file
121
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/import.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
func (v *VolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error {
|
||||
l, err := v.NewDataSink(op, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.Import(op, spec, tarstream)
|
||||
}
|
||||
|
||||
// NewDataSource creates and returns an DataSource associated with container storage
|
||||
func (v *VolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) {
|
||||
uri, err := v.URL(op, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineAttempt := 0
|
||||
offline:
|
||||
offlineAttempt++
|
||||
|
||||
source, err := v.newDataSink(op, uri)
|
||||
if err == nil {
|
||||
return source, err
|
||||
}
|
||||
|
||||
// check for vmdk locked error here
|
||||
if !disk.IsLockedError(err) {
|
||||
op.Warnf("Unable to mount %s and do not know how to recover from error")
|
||||
// continue anyway because maybe there's an online option
|
||||
}
|
||||
|
||||
// online - Owners() should filter out the appliance VM
|
||||
// #nosec: Errors unhandled.
|
||||
owners, _ := v.Owners(op, uri, disk.LockedVMDKFilter)
|
||||
if len(owners) == 0 {
|
||||
op.Infof("No online owners were found for %s", id)
|
||||
return nil, errors.New("unable to create offline data sink and no online owners found")
|
||||
}
|
||||
|
||||
for _, o := range owners {
|
||||
// sanity check to see if we are the owner - this should catch transitions
|
||||
// from container running to diff or commit for example between the offline attempt and here
|
||||
uuid, err := o.UUID(op)
|
||||
if err == nil {
|
||||
// check if the vm is appliance VM if we can successfully get its UUID
|
||||
// #nosec: Errors unhandled.
|
||||
self, _ := guest.IsSelf(op, uuid)
|
||||
if self && offlineAttempt < 2 {
|
||||
op.Infof("Appliance is owner of online vmdk - retrying offline source path")
|
||||
goto offline
|
||||
}
|
||||
}
|
||||
|
||||
online, err := v.newOnlineDataSink(op, o, id)
|
||||
if online != nil {
|
||||
return online, err
|
||||
}
|
||||
|
||||
op.Debugf("Failed to create online sink with owner %s: %s", o.Reference(), err)
|
||||
}
|
||||
|
||||
return nil, errors.New("unable to create online or offline data sink")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) {
|
||||
mountPath, cleanFunc, err := v.Mount(op, url, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(mountPath)
|
||||
if err != nil {
|
||||
cleanFunc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Debugf("Created mount data sink for access to %s at %s", url, mountPath)
|
||||
return storage.NewMountDataSink(op, f, cleanFunc), nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) newOnlineDataSink(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSink, error) {
|
||||
op.Debugf("Constructing toolbox data sink: %s.%s", owner.Reference(), id)
|
||||
|
||||
return &vsphere.ToolboxDataSink{
|
||||
VM: owner,
|
||||
ID: volume.Label(id),
|
||||
Clean: func() { return },
|
||||
}, nil
|
||||
}
|
||||
79
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/join.go
generated
vendored
Normal file
79
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/join.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/exec"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *volume.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) {
|
||||
defer trace.End(trace.Begin("vsphere.VolumeJoin", op))
|
||||
|
||||
if _, ok := handle.ExecConfig.Mounts[volume.ID]; ok {
|
||||
return nil, fmt.Errorf("Volume with ID %s is already in container %s's mountspec config", volume.ID, handle.ExecConfig.ID)
|
||||
}
|
||||
|
||||
if handle.ExecConfig.Mounts == nil {
|
||||
handle.ExecConfig.Mounts = make(map[string]executor.MountSpec)
|
||||
}
|
||||
|
||||
//constuct MountSpec for the tether
|
||||
mountSpec := createMountSpec(volume, mountPath, diskOpts)
|
||||
handle.ExecConfig.Mounts[volume.ID] = mountSpec
|
||||
|
||||
//append a device addition spec change to the container config
|
||||
disk := handle.Guest.NewDisk()
|
||||
configureVolumeVirtualDisk(disk, volume)
|
||||
|
||||
handle.Spec.AddVirtualDevice(disk)
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func configureVolumeVirtualDisk(disk *types.VirtualDisk, volume *volume.Volume) {
|
||||
// the unit number hack may no longer be needed
|
||||
unitNumber := int32(-1)
|
||||
|
||||
disk.CapacityInKB = 0
|
||||
disk.UnitNumber = &unitNumber
|
||||
disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{
|
||||
DiskMode: string(types.VirtualDiskModeIndependent_persistent),
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: volume.Device.DiskPath().Path,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createMountSpec(volume *volume.Volume, mountPath string, diskOpts map[string]string) executor.MountSpec {
|
||||
deviceMode := diskOpts[constants.Mode]
|
||||
newMountSpec := executor.MountSpec{
|
||||
Source: url.URL{
|
||||
Scheme: "label",
|
||||
Path: volume.Label,
|
||||
},
|
||||
Path: mountPath,
|
||||
Mode: deviceMode,
|
||||
CopyMode: volume.CopyMode,
|
||||
}
|
||||
return newMountSpec
|
||||
}
|
||||
224
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/store.go
generated
vendored
Normal file
224
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/store.go
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/vsphere"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/disk"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: this was shared with image store hence the disjoint naming. Should be updated
|
||||
// but migration/upgrade implications are unclear
|
||||
metaDataDir = "imageMetadata"
|
||||
)
|
||||
|
||||
var (
|
||||
// Set to false for unit tests
|
||||
DetachAll = true
|
||||
)
|
||||
|
||||
// VolumeStore caches Volume references to volumes in the system.
|
||||
type VolumeStore struct {
|
||||
disk.Vmdk
|
||||
|
||||
// Service url to this VolumeStore
|
||||
SelfLink *url.URL
|
||||
}
|
||||
|
||||
func NewVolumeStore(op trace.Operation, storeName string, s *session.Session, ds *datastore.Helper) (*VolumeStore, error) {
|
||||
// Create the volume dir if it doesn't already exist
|
||||
if _, err := ds.Mkdir(op, true, constants.VolumesDir); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dm, err := disk.NewDiskManager(op, s, storage.Config.ContainerView)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if DetachAll {
|
||||
if err = dm.DetachAll(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
u, err := util.VolumeStoreNameToURL(storeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &VolumeStore{
|
||||
Vmdk: disk.Vmdk{
|
||||
Manager: dm,
|
||||
Helper: ds,
|
||||
Session: s,
|
||||
},
|
||||
SelfLink: u,
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Returns the path to the vol relative to the given store. The dir structure
|
||||
// for a vol in the datastore is `<configured datastore path>/volumes/<vol ID>/<vol ID>.vmkd`.
|
||||
// Everything up to "volumes" is taken care of by the datastore wrapper.
|
||||
func (v *VolumeStore) volDirPath(ID string) string {
|
||||
return path.Join(constants.VolumesDir, ID)
|
||||
}
|
||||
|
||||
// Returns the path to the metadata directory for a volume
|
||||
func (v *VolumeStore) volMetadataDirPath(ID string) string {
|
||||
return path.Join(v.volDirPath(ID), metaDataDir)
|
||||
}
|
||||
|
||||
// Returns the path to the vmdk itself (in datastore URL format)
|
||||
func (v *VolumeStore) volDiskDSPath(ID string) *object.DatastorePath {
|
||||
return &object.DatastorePath{
|
||||
Datastore: v.Helper.RootURL.Datastore,
|
||||
Path: path.Join(v.Helper.RootURL.Path, v.volDirPath(ID), ID+".vmdk"),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) {
|
||||
|
||||
// Create the volume directory in the store.
|
||||
if _, err := v.Mkdir(op, false, v.volDirPath(ID)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the path to the disk in datastore uri format
|
||||
volDiskDSPath := v.volDiskDSPath(ID)
|
||||
|
||||
config := disk.NewPersistentDisk(volDiskDSPath).WithCapacity(int64(capacityKB))
|
||||
// Create the disk
|
||||
vmdisk, err := v.CreateAndAttach(op, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer v.Detach(op, vmdisk.VirtualDiskConfig)
|
||||
vol, err := volume.NewVolume(store, ID, info, vmdisk, executor.CopyNew)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make the filesystem and set its label
|
||||
if err = vmdisk.Mkfs(op, vol.Label); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mask lost+found from containerVM
|
||||
opts := []string{"noatime"}
|
||||
path, err := vmdisk.Mount(op, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer vmdisk.Unmount(op)
|
||||
|
||||
// #nosec
|
||||
err = os.Mkdir(filepath.Join(path, disk.VolumeDataDir), 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Persist the metadata
|
||||
metaDataDir := v.volMetadataDirPath(ID)
|
||||
if err = vsphere.WriteMetadata(op, v.Helper, metaDataDir, info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op.Infof("volumestore: %s (%s)", ID, vol.SelfLink)
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error {
|
||||
volDir := v.volDirPath(vol.ID)
|
||||
|
||||
op.Infof("VolumeStore: Deleting %s", volDir)
|
||||
if err := v.Rm(op, volDir); err != nil {
|
||||
op.Errorf("VolumeStore: delete error: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) VolumeGet(op trace.Operation, ID string) (*volume.Volume, error) {
|
||||
// We can't get the volume directly without looking up what datastore it's on.
|
||||
return nil, fmt.Errorf("not supported: use VolumesList")
|
||||
}
|
||||
|
||||
func (v *VolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) {
|
||||
volumes := []*volume.Volume{}
|
||||
|
||||
res, err := v.Ls(op, constants.VolumesDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing vols: %s", err)
|
||||
}
|
||||
|
||||
for _, f := range res.File {
|
||||
ID := f.GetFileInfo().Path
|
||||
|
||||
// Get the path to the disk in datastore uri format
|
||||
volDiskDSPath := v.volDiskDSPath(ID)
|
||||
|
||||
config := disk.NewPersistentDisk(volDiskDSPath)
|
||||
dev, err := disk.NewVirtualDisk(op, config, v.Manager.Disks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metaDataDir := v.volMetadataDirPath(ID)
|
||||
meta, err := vsphere.GetMetadata(op, v.Helper, metaDataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vol, err := volume.NewVolume(v.SelfLink, ID, meta, dev, executor.CopyNew)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumes = append(volumes, vol)
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
func (v *VolumeStore) URL(op trace.Operation, id string) (*url.URL, error) {
|
||||
path := v.volDiskDSPath(id).String()
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("unable to translate %s into datastore path", id)
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
Scheme: "ds",
|
||||
Path: path,
|
||||
}, nil
|
||||
}
|
||||
151
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/store_test.go
generated
vendored
Normal file
151
vendor/github.com/vmware/vic/lib/portlayer/storage/volume/vsphere/store_test.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/portlayer/storage/volume"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
)
|
||||
|
||||
func TestVolumeCreateListAndRestart(t *testing.T) {
|
||||
client := datastore.Session(context.TODO(), t)
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
|
||||
// Root our datastore
|
||||
testStorePath := datastore.TestName("voltest")
|
||||
ds, err := datastore.NewHelper(op, client, client.Datastore, testStorePath)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, ds) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the backing store on vsphere
|
||||
DetachAll = false
|
||||
vsVolumeStore, err := NewVolumeStore(op, "testStoreName", client, ds)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vsVolumeStore) {
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up the mess
|
||||
defer func() {
|
||||
fm := object.NewFileManager(client.Vim25())
|
||||
tasks.WaitForResult(context.TODO(), func(ctx context.Context) (tasks.Task, error) {
|
||||
return fm.DeleteDatastoreFile(ctx, client.Datastore.Path(testStorePath), client.Datacenter)
|
||||
})
|
||||
}()
|
||||
|
||||
// Create the cache
|
||||
cache := volume.NewVolumeLookupCache(op)
|
||||
if !assert.NotNil(t, cache) {
|
||||
return
|
||||
}
|
||||
|
||||
// add the vs to the cache and assert the url matches
|
||||
storeURL, err := cache.AddStore(op, "testStoreName", vsVolumeStore)
|
||||
if !assert.NoError(t, err) || !assert.Equal(t, vsVolumeStore.SelfLink, storeURL) {
|
||||
return
|
||||
}
|
||||
|
||||
// test we can list it
|
||||
m, err := cache.VolumeStoresList(op)
|
||||
if !assert.NoError(t, err) || !assert.Len(t, m, 1) || !assert.Equal(t, m[0], "testStoreName") {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the volumes (in parallel)
|
||||
numVols := 5
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(numVols)
|
||||
volumes := make(map[string]*volume.Volume)
|
||||
for i := 0; i < numVols; i++ {
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
ID := fmt.Sprintf("testvolume-%d", idx)
|
||||
|
||||
// add some metadata if i is even
|
||||
var info map[string][]byte
|
||||
|
||||
if idx%2 == 0 {
|
||||
info = make(map[string][]byte)
|
||||
info[ID] = []byte(ID)
|
||||
}
|
||||
|
||||
outVol, err := cache.VolumeCreate(op, ID, storeURL, 10240, info)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, outVol) {
|
||||
return
|
||||
}
|
||||
|
||||
volumes[ID] = outVol
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// list using the datastore (skipping the cache)
|
||||
outVols, err := vsVolumeStore.VolumesList(op)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, outVols) || !assert.Equal(t, numVols, len(outVols)) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, outVol := range outVols {
|
||||
if !assert.Equal(t, volumes[outVol.ID], outVol) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Test restart
|
||||
|
||||
// Create a new vs and cache to the same datastore (simulating restart) and compare
|
||||
secondVStore, err := NewVolumeStore(op, "testStoreName", client, ds)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vsVolumeStore) {
|
||||
return
|
||||
}
|
||||
|
||||
secondCache := volume.NewVolumeLookupCache(op)
|
||||
if !assert.NotNil(t, secondCache) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = secondCache.AddStore(op, "testStore", secondVStore)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
secondOutVols, err := secondCache.VolumesList(op)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, secondOutVols) || !assert.Equal(t, numVols, len(secondOutVols)) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, outVol := range secondOutVols {
|
||||
// XXX we could compare the Volumes, but the paths are different the
|
||||
// second time around on vsan since the vsan UUID is not included.
|
||||
if !assert.NotEmpty(t, volumes[outVol.ID].Device.DiskPath()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
91
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/metadata.go
generated
vendored
Normal file
91
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
)
|
||||
|
||||
// Write the opaque metadata blobs (by name).
|
||||
// Each blob in the metadata map is written to a file with the corresponding
|
||||
// name. Likewise, when we read it back (on restart) we populate the map
|
||||
// accordingly.
|
||||
func WriteMetadata(op trace.Operation, ds *datastore.Helper, dir string, meta map[string][]byte) error {
|
||||
// XXX this should be done via disklib so this meta follows the disk in
|
||||
// case of motion.
|
||||
|
||||
if meta != nil && len(meta) != 0 {
|
||||
for name, value := range meta {
|
||||
r := bytes.NewReader(value)
|
||||
pth := path.Join(dir, name)
|
||||
op.Infof("Writing metadata %s", pth)
|
||||
if err := ds.Upload(op, r, pth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := ds.Mkdir(op, false, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the metadata from the given dir
|
||||
func GetMetadata(op trace.Operation, ds *datastore.Helper, dir string) (map[string][]byte, error) {
|
||||
|
||||
res, err := ds.Ls(op, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.File) == 0 {
|
||||
op.Infof("No meta found for %s", dir)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
meta := make(map[string][]byte)
|
||||
for _, f := range res.File {
|
||||
// we're only interested in files, not folders
|
||||
finfo, ok := f.(*types.FileInfo)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
p := path.Join(dir, finfo.Path)
|
||||
op.Infof("Getting metadata %s", p)
|
||||
rc, err := ds.Download(op, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
buf, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta[finfo.Path] = buf
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
158
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_common.go
generated
vendored
Normal file
158
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_common.go
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/guest"
|
||||
"github.com/vmware/govmomi/guest/toolbox"
|
||||
"github.com/vmware/govmomi/task"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/tether/shared"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
VixEToolsNotRunning = "(3016, 0)"
|
||||
)
|
||||
|
||||
var (
|
||||
toolboxRetryConf *retry.BackoffConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
toolboxRetryConf = retry.NewBackoffConfig()
|
||||
|
||||
// These numbers are somewhat arbitrary best guesses
|
||||
toolboxRetryConf.MaxElapsedTime = time.Second * 30
|
||||
toolboxRetryConf.InitialInterval = time.Millisecond * 500
|
||||
toolboxRetryConf.MaxInterval = time.Second * 5
|
||||
}
|
||||
|
||||
// Parse Archive builds an archive url with disklabel, filtersec, recursive, and data booleans.
|
||||
func BuildArchiveURL(op trace.Operation, disklabel, target string, fs *archive.FilterSpec, recurse, data bool) (string, error) {
|
||||
encodedSpec, err := archive.EncodeFilterSpec(op, fs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
target = path.Join("/archive:/", target)
|
||||
|
||||
// if diskLabel is longer than 16 characters, then the function was passed a containerID
|
||||
// use containerfs as the diskLabel
|
||||
if len(disklabel) > 16 {
|
||||
disklabel = "containerfs"
|
||||
}
|
||||
|
||||
// note that the query parameters a SkipX for recurse and data so values are inverted
|
||||
target += "?" + (url.Values{
|
||||
shared.DiskLabelQueryName: []string{disklabel},
|
||||
shared.FilterSpecQueryName: []string{*encodedSpec},
|
||||
shared.SkipRecurseQueryName: []string{strconv.FormatBool(!recurse)},
|
||||
shared.SkipDataQueryName: []string{strconv.FormatBool(!data)},
|
||||
}).Encode()
|
||||
|
||||
op.Debugf("OnlineData* Url: %s", target)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// GetToolboxClient returns a toolbox client given a vm and id
|
||||
func GetToolboxClient(op trace.Operation, vm *vm.VirtualMachine, id string) (*toolbox.Client, error) {
|
||||
opmgr := guest.NewOperationsManager(vm.Session.Client.Client, vm.Reference())
|
||||
pm, err := opmgr.ProcessManager(op)
|
||||
if err != nil {
|
||||
op.Debugf("Failed to create new process manager ")
|
||||
return nil, err
|
||||
}
|
||||
fm, err := opmgr.FileManager(op)
|
||||
if err != nil {
|
||||
op.Debugf("Failed to create new file manager ")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &toolbox.Client{
|
||||
ProcessManager: pm,
|
||||
FileManager: fm,
|
||||
Authentication: &types.NamePasswordAuthentication{
|
||||
Username: id,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isInvalidStateError is used to identify whether the supplied error is an InvalidState fault
|
||||
func isInvalidStateError(err error) bool {
|
||||
if soap.IsSoapFault(err) {
|
||||
switch soap.ToSoapFault(err).VimFault().(type) {
|
||||
case types.InvalidState:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if soap.IsVimFault(err) {
|
||||
switch soap.ToVimFault(err).(type) {
|
||||
case *types.InvalidState:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch err := err.(type) {
|
||||
case task.Error:
|
||||
switch err.Fault().(type) {
|
||||
case *types.InvalidState:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsToolBoxConflictErr checks for conflictError for online import
|
||||
func IsToolBoxStateChangeErr(err error) bool {
|
||||
// check if error has to do with toolbox state changes
|
||||
if soap.IsSoapFault(err) {
|
||||
switch soap.ToSoapFault(err).VimFault().(type) {
|
||||
case types.InvalidState:
|
||||
return true
|
||||
case types.InvalidPowerState:
|
||||
return true
|
||||
case types.GuestOperationsUnavailable:
|
||||
return true
|
||||
case types.SystemError:
|
||||
return strings.Contains(err.Error(), VixEToolsNotRunning)
|
||||
}
|
||||
}
|
||||
|
||||
switch err.(type) {
|
||||
case *url.Error:
|
||||
err = err.(*url.Error).Err
|
||||
switch err.(type) {
|
||||
case *net.OpError:
|
||||
// can check for error message as well
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: on certain failures toolbox only returns 500 which can be caused by state change in the middle
|
||||
// but can also be caused by invalid command. There is no way to tell unless toolbox returns more information.
|
||||
return false
|
||||
}
|
||||
89
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_datasink.go
generated
vendored
Normal file
89
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_datasink.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// ToolboxDataSink implements the DataSink interface for mounted devices
|
||||
type ToolboxDataSink struct {
|
||||
ID string
|
||||
VM *vm.VirtualMachine
|
||||
Clean func()
|
||||
}
|
||||
|
||||
// Sink returns the data sink associated with the DataSink
|
||||
func (t *ToolboxDataSink) Sink() interface{} {
|
||||
return t.VM
|
||||
}
|
||||
|
||||
// Import writes `data` to the data sink associated with this DataSink
|
||||
func (t *ToolboxDataSink) Import(op trace.Operation, spec *archive.FilterSpec, data io.ReadCloser) error {
|
||||
defer trace.End(trace.Begin("toolbox import"))
|
||||
|
||||
client, err := GetToolboxClient(op, t.VM, t.ID)
|
||||
if err != nil {
|
||||
op.Debugf("Cannot get toolbox client: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := BuildArchiveURL(op, t.ID, spec.RebasePath, spec, true, true)
|
||||
if err != nil {
|
||||
op.Debugf("Cannot build archive url: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// buffer the data - needed to allow retry or the Upload drains the reader before the failure
|
||||
// and we lose the data
|
||||
// TODO: should look into chunking so that we can support copy of very large files.
|
||||
// NOW: need a check that size doesn't exceed available memory - and error recommending offline
|
||||
// copy as alternative
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.Copy(buf, data)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to buffer archive data for upload")
|
||||
return err
|
||||
}
|
||||
|
||||
// upload the gzip archive.
|
||||
p := soap.DefaultUpload
|
||||
|
||||
retryFunc := func() error {
|
||||
return client.Upload(op, buf, target, p, &types.GuestPosixFileAttributes{}, true)
|
||||
}
|
||||
|
||||
err = retry.DoWithConfig(retryFunc, isInvalidStateError, toolboxRetryConf)
|
||||
|
||||
if err != nil {
|
||||
op.Debugf("Upload error: %s", err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *ToolboxDataSink) Close() error {
|
||||
t.Clean()
|
||||
|
||||
return nil
|
||||
}
|
||||
156
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_datasource.go
generated
vendored
Normal file
156
vendor/github.com/vmware/vic/lib/portlayer/storage/vsphere/toolbox_datasource.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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 vsphere
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/portlayer/storage"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// ToolboxDataSource implements the DataSource interface for mounted devices
|
||||
type ToolboxDataSource struct {
|
||||
ID string
|
||||
VM *vm.VirtualMachine
|
||||
Clean func()
|
||||
}
|
||||
|
||||
// Source returns the data source associated with the DataSource
|
||||
func (t *ToolboxDataSource) Source() interface{} {
|
||||
return t.VM
|
||||
}
|
||||
|
||||
// Export reads data from the associated data source and returns it as a tar archive
|
||||
func (t *ToolboxDataSource) Export(op trace.Operation, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) {
|
||||
defer trace.End(trace.Begin("toolbox export"))
|
||||
|
||||
client, err := GetToolboxClient(op, t.VM, t.ID)
|
||||
if err != nil {
|
||||
op.Errorf("Cannot get toolbox client: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var readers []io.Reader
|
||||
for inclusion := range spec.Inclusions {
|
||||
// build a proper target
|
||||
target, err := BuildArchiveURL(op, t.ID, inclusion, spec, true, true)
|
||||
if err != nil {
|
||||
op.Errorf("Cannot build archive url: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var tar io.ReadCloser
|
||||
var contentLength int64
|
||||
|
||||
retryFunc := func() error {
|
||||
var retryErr error
|
||||
tar, contentLength, retryErr = client.Download(op, target)
|
||||
return retryErr
|
||||
}
|
||||
|
||||
err = retry.DoWithConfig(retryFunc, isInvalidStateError, toolboxRetryConf)
|
||||
if err != nil {
|
||||
op.Errorf("Download error: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
op.Debugf("Downloaded from %s with size %d", target, contentLength)
|
||||
readers = append(readers, tar)
|
||||
|
||||
}
|
||||
return ioutil.NopCloser(io.MultiReader(readers...)), nil
|
||||
}
|
||||
|
||||
// Stat returns file stats of the destination header determined but the filterspec inclusion path
|
||||
func (t *ToolboxDataSource) Stat(op trace.Operation, spec *archive.FilterSpec) (*storage.FileStat, error) {
|
||||
defer trace.End(trace.Begin("toolbox stat"))
|
||||
|
||||
client, err := GetToolboxClient(op, t.VM, t.ID)
|
||||
if err != nil {
|
||||
op.Errorf("Cannot get toolbox client: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// should only find a single path to stat, but make sure here.
|
||||
if len(spec.Inclusions) != 1 {
|
||||
op.Errorf("Stat called on more than one path: %+v", spec.Inclusions)
|
||||
}
|
||||
|
||||
var statPath string
|
||||
inclusions := len(spec.Inclusions)
|
||||
if inclusions == 0 {
|
||||
op.Debugf("filter spec for stat operation has no inclusion specified : %#v", *spec)
|
||||
}
|
||||
|
||||
if inclusions > 1 {
|
||||
op.Debugf("filter spec for stat operation had multiple inclusion paths : %#v", *spec)
|
||||
}
|
||||
|
||||
for inclusion := range spec.Inclusions {
|
||||
statPath = inclusion
|
||||
}
|
||||
|
||||
target, err := BuildArchiveURL(op, t.ID, statPath, spec, false, false)
|
||||
if err != nil {
|
||||
op.Errorf("Cannot build archive url: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var statTar io.ReadCloser
|
||||
|
||||
retryFunc := func() error {
|
||||
var retryErr error
|
||||
statTar, _, retryErr = client.Download(op, target)
|
||||
return retryErr
|
||||
}
|
||||
|
||||
err = retry.DoWithConfig(retryFunc, isInvalidStateError, toolboxRetryConf)
|
||||
if err != nil {
|
||||
op.Errorf("Download error: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
defer statTar.Close()
|
||||
|
||||
// decode from guest tools
|
||||
header, err := tar.NewReader(statTar).Next()
|
||||
if err == io.EOF {
|
||||
// special case - unable to get a single header translates to Not Found
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stat := &storage.FileStat{
|
||||
Mode: uint32(header.FileInfo().Mode()),
|
||||
Name: header.Name,
|
||||
Size: header.Size,
|
||||
ModTime: header.ModTime,
|
||||
LinkTarget: header.Linkname,
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func (t *ToolboxDataSource) Close() error {
|
||||
t.Clean()
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user