Files
virtual-kubelet/vendor/github.com/vmware/vic/lib/imagec/imagec.go
Loc Nguyen 513cebe7b7 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
2018-06-04 15:41:32 -07:00

649 lines
18 KiB
Go

// 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 imagec
import (
"crypto/sha256"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"golang.org/x/net/context"
"github.com/docker/distribution/manifest/schema2"
docker "github.com/docker/docker/image"
dockerLayer "github.com/docker/docker/layer"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/reference"
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
"github.com/vmware/vic/lib/apiservers/portlayer/models"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/metadata"
urlfetcher "github.com/vmware/vic/pkg/fetcher"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/vsphere/sys"
)
// ImageC is responsible for pulling docker images from a repository
type ImageC struct {
Options
// https://raw.githubusercontent.com/docker/docker/master/distribution/pull_v2.go
sf *streamformatter.StreamFormatter
progressOutput progress.Output
// ImageLayers are sourced from the manifest file
ImageLayers []*ImageWithMeta
// ImageID is the docker ImageID calculated during download
ImageID string
}
// NewImageC returns a new instance of ImageC
func NewImageC(options Options, strfmtr *streamformatter.StreamFormatter) *ImageC {
return &ImageC{
Options: options,
sf: strfmtr,
progressOutput: strfmtr.NewProgressOutput(options.Outstream, false),
}
}
// Options contain all options for a single instance of imagec
type Options struct {
Reference reference.Named
Registry string
Image string
Tag string
Destination string
Host string
Storename string
Username string
Password string
Token *urlfetcher.Token
Timeout time.Duration
Outstream io.Writer
InsecureSkipVerify bool
InsecureAllowHTTP bool
// Get both schema 1 and schema 2 manifests. Schema 1 is used to get history
// and was imageC implementation predated schema 2. Schema 2 is used to
// calculate digest.
ImageManifestSchema1 *Manifest
ImageManifestSchema2 *schema2.DeserializedManifest
//Digest of manifest schema 2 or schema 1; therefore, it may not match hash
//of the above manifest.
ManifestDigest string
// RegistryCAs will not be modified by imagec
RegistryCAs *x509.CertPool
// If true, do not bother portlayer or persona
Standalone bool
// image store name or url
ImageStore string
}
// ImageWithMeta wraps the models.Image with some additional metadata
type ImageWithMeta struct {
*models.Image
DiffID string
Layer FSLayer
Meta string
Size int64
Downloading bool
}
func (i *ImageWithMeta) String() string {
return stringid.TruncateID(i.Layer.BlobSum)
}
var (
ldm *LayerDownloader
)
const (
// DefaultDockerURL holds the URL of Docker registry
DefaultDockerURL = "registry.hub.docker.com"
// DefaultDestination specifies the default directory to use
DefaultDestination = "images"
// DefaultPortLayerHost specifies the default port layer server
DefaultPortLayerHost = "localhost:2377"
// DefaultLogfile specifies the default log file name
DefaultLogfile = "imagec.log"
// DefaultHTTPTimeout specifies the default HTTP timeout
DefaultHTTPTimeout = 3600 * time.Second
// attribute update actions
Add = iota + 1
)
func init() {
ldm = NewLayerDownloader()
}
// ParseReference parses the -reference parameter and populate options struct
func (ic *ImageC) ParseReference() {
if reference.IsNameOnly(ic.Reference) {
ic.Tag = reference.DefaultTag
} else {
if tagged, isTagged := ic.Reference.(reference.NamedTagged); isTagged {
ic.Tag = tagged.Tag()
}
}
ic.Registry = ic.Reference.Hostname()
if ic.Registry == reference.DefaultHostname {
ic.Registry = DefaultDockerURL
}
ic.Image = ic.Reference.RemoteName()
}
// DestinationDirectory returns the path of the output directory
func DestinationDirectory(options Options) string {
// #nosec: Errors unhandled.
u, _ := url.Parse(options.Registry)
// Use a hierarchy like following so that we can support multiple schemes, registries and versions
/*
https/
├── 192.168.218.5:5000
│ └── v2
│ └── busybox
│ └── latest
...
│ ├── fef924a0204a00b3ec67318e2ed337b189c99ea19e2bf10ed30a13b87c5e17ab
│ │ ├── fef924a0204a00b3ec67318e2ed337b189c99ea19e2bf10ed30a13b87c5e17ab.json
│ │ └── fef924a0204a00b3ec67318e2ed337b189c99ea19e2bf10ed30a13b87c5e17ab.tar
│ └── manifest.json
└── registry-1.docker.io
└── v2
└── library
└── golang
└── latest
...
├── f61ebe2817bb4e6a7f0a4cf249a5316223f7ecc886feac24b9887a490feaed57
│ ├── f61ebe2817bb4e6a7f0a4cf249a5316223f7ecc886feac24b9887a490feaed57.json
│ └── f61ebe2817bb4e6a7f0a4cf249a5316223f7ecc886feac24b9887a490feaed57.tar
└── manifest.json
*/
if u.Scheme == "" && u.Host == "" && u.Path == "" {
return path.Join(
options.Destination,
"localhost",
options.Image,
options.Tag,
)
}
return path.Join(
options.Destination,
u.Scheme,
u.Host,
u.Path,
options.Image,
options.Tag,
)
}
// LayersToDownload creates a slice of ImageWithMeta for the layers that need to be downloaded
func (ic *ImageC) LayersToDownload() ([]*ImageWithMeta, error) {
images := make([]*ImageWithMeta, len(ic.ImageManifestSchema1.FSLayers))
manifest := ic.ImageManifestSchema1
v1 := docker.V1Image{}
// iterate from parent to children
for i := len(ic.ImageManifestSchema1.History) - 1; i >= 0; i-- {
history := manifest.History[i]
layer := manifest.FSLayers[i]
// unmarshall V1Compatibility to get the image ID
if err := json.Unmarshal([]byte(history.V1Compatibility), &v1); err != nil {
return nil, fmt.Errorf("Failed to unmarshall image history: %s", err)
}
// if parent is empty set it to scratch
parent := constants.ScratchLayerID
if v1.Parent != "" {
parent = v1.Parent
}
// add image to ImageWithMeta list
images[i] = &ImageWithMeta{
Image: &models.Image{
ID: v1.ID,
Parent: parent,
Store: ic.Storename,
},
Meta: history.V1Compatibility,
Layer: layer,
}
// populate manifest layer with existing cached data
if layer, err := LayerCache().Get(images[i].ID); err == nil {
if !layer.Downloading { // possibly unnecessary but won't hurt anything
images[i] = layer
}
}
}
return images, nil
}
// UpdateRepositoryCache will update the repository cache
// that resides in the docker persona. This will add image tag,
// digest and layer information.
func UpdateRepoCache(ic *ImageC) error {
// if standalone then no persona, so exit
if ic.Standalone {
return nil
}
// LayerID for the image layer
imageLayerID := ic.ImageLayers[0].ID
// get the repoCache
repoCache := cache.RepositoryCache()
// In the case that we don't have the ImageID, then we need
// to go to the RepositoryCache to get it.
if ic.ImageID == "" {
// call to repository cache for the imageID for this layer
ic.ImageID = repoCache.GetImageID(imageLayerID)
// if we still don't have an imageID we can't continue
if ic.ImageID == "" {
return fmt.Errorf("ImageID not found by LayerID(%s) in RepositoryCache", imageLayerID)
}
}
// AddReference will add the repo:tag to the repositoryCache and save to the portLayer
err := repoCache.AddReference(ic.Reference, ic.ImageID, true, imageLayerID, true)
if err != nil {
return fmt.Errorf("Unable to Add Image Reference(%s): %s", ic.Reference.String(), err.Error())
}
dig, err := reference.ParseNamed(fmt.Sprintf("%s@%s", ic.Reference.Name(), ic.ManifestDigest))
if err != nil {
return fmt.Errorf("Unable to parse digest: %s", err.Error())
}
// AddReference will add the digest and persist to the portLayer
err = repoCache.AddReference(dig, ic.ImageID, true, imageLayerID, true)
if err != nil {
return fmt.Errorf("Unable to Add Image Digest(%s): %s", dig.String(), err.Error())
}
return nil
}
// WriteImageBlob writes the image blob to the storage layer
func (ic *ImageC) WriteImageBlob(image *ImageWithMeta, progressOutput progress.Output, cleanup bool) error {
defer trace.End(trace.Begin(image.Image.ID))
destination := DestinationDirectory(ic.Options)
id := image.Image.ID
filePath := path.Join(destination, id, id+".tar")
log.Infof("Path: %s", filePath)
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("Failed to open file: %s", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return fmt.Errorf("Failed to stat file: %s", err)
}
in := progress.NewProgressReader(
ioutils.NewCancelReadCloser(context.Background(), f),
progressOutput,
fi.Size(),
image.String(),
"Extracting",
)
defer in.Close()
if !ic.Standalone {
// Write the image
err = WriteImage(ic.Host, image, in)
if err != nil {
return fmt.Errorf("Failed to write to image store: %s", err)
}
} else {
// If standalone, write to a local directory
cleanup = false
}
progress.Update(progressOutput, image.String(), "Pull complete")
if cleanup {
if err := os.RemoveAll(destination); err != nil {
return fmt.Errorf("Failed to remove download directory: %s", err)
}
}
return nil
}
// CreateImageConfig constructs the image metadata from layers that compose the image
func (ic *ImageC) CreateImageConfig(images []*ImageWithMeta) (metadata.ImageConfig, error) {
imageLayer := images[0] // the layer that represents the actual image
// if we already have an imageID associated with this layerID, we don't need
// to calculate imageID and can just grab the image config from the cache
id := cache.RepositoryCache().GetImageID(imageLayer.ID)
if image, err := cache.ImageCache().Get(id); err == nil {
return *image, nil
}
manifest := ic.ImageManifestSchema1
image := docker.V1Image{}
rootFS := docker.NewRootFS()
history := make([]docker.History, 0, len(images))
diffIDs := make(map[string]string)
var size int64
// step through layers to get command history and diffID from oldest to newest
for i := len(images) - 1; i >= 0; i-- {
layer := images[i]
if err := json.Unmarshal([]byte(layer.Meta), &image); err != nil {
return metadata.ImageConfig{}, fmt.Errorf("Failed to unmarshall layer history: %s", err)
}
h := docker.History{
Created: image.Created,
Author: image.Author,
CreatedBy: strings.Join(image.ContainerConfig.Cmd, " "),
Comment: image.Comment,
}
// is this an empty layer?
if layer.DiffID == dockerLayer.DigestSHA256EmptyTar.String() {
h.EmptyLayer = true
} else {
// if not empty, add diffID to rootFS
rootFS.DiffIDs = append(rootFS.DiffIDs, dockerLayer.DiffID(layer.DiffID))
}
history = append(history, h)
size += layer.Size
}
// result is constructed without unused fields
result := docker.Image{
V1Image: docker.V1Image{
Comment: image.Comment,
Created: image.Created,
Container: image.Container,
ContainerConfig: image.ContainerConfig,
DockerVersion: image.DockerVersion,
Author: image.Author,
Config: image.Config,
Architecture: image.Architecture,
OS: image.OS,
},
RootFS: rootFS,
History: history,
}
imageConfigBytes, err := result.MarshalJSON()
if err != nil {
return metadata.ImageConfig{}, fmt.Errorf("Failed to marshall image metadata: %s", err)
}
// calculate image ID
sum := fmt.Sprintf("%x", sha256.Sum256(imageConfigBytes))
log.Infof("Image ID: sha256:%s", sum)
// prepare metadata
result.V1Image.Parent = image.Parent
result.Size = size
result.V1Image.ID = imageLayer.ID
imageConfig := metadata.ImageConfig{
V1Image: result.V1Image,
ImageID: sum,
DiffIDs: diffIDs,
History: history,
}
if ic.Tag != "" {
imageConfig.Tags = []string{ic.Tag}
}
if manifest != nil {
imageConfig.Name = manifest.Name
}
if ic.Reference != nil {
imageConfig.Reference = ic.Reference.String()
}
if _, ok := ic.Reference.(reference.Canonical); ok {
log.Debugf("Populating digest in imageConfig for image: %s", ic.Reference.String())
imageConfig.Digests = []string{ic.ManifestDigest}
}
return imageConfig, nil
}
// PullImage pulls an image from docker hub
func (ic *ImageC) PullImage() error {
ctx, cancel := context.WithTimeout(ctx, ic.Options.Timeout)
defer cancel()
// Authenticate, get URL, get token
if err := ic.prepareTransfer(ctx); err != nil {
return err
}
// Output message
tagOrDigest := tagOrDigest(ic.Reference, ic.Tag)
progress.Message(ic.progressOutput, "", tagOrDigest+": Pulling from "+ic.Image)
// Pull the image manifest
if err := ic.pullManifest(ctx); err != nil {
return err
}
log.Infof("Manifest for image = %#v", ic.ImageManifestSchema1)
// Get layers to download from manifest
layers, err := ic.LayersToDownload()
if err != nil {
return err
}
ic.ImageLayers = layers
// Download all the layers
if err := ldm.DownloadLayers(ctx, ic); err != nil {
return err
}
return nil
}
// ListLayer prints out the layers for an image to progress. This is used by imagec standalone binary
// for debug/validation.
func (ic *ImageC) ListLayers() error {
defer trace.End(trace.Begin(""))
ctx, cancel := context.WithTimeout(ctx, ic.Options.Timeout)
defer cancel()
// Authenticate, get URL, get token
if err := ic.prepareTransfer(ctx); err != nil {
return err
}
// Output message
tagOrDigest := tagOrDigest(ic.Reference, ic.Tag)
progress.Message(ic.progressOutput, "", tagOrDigest+": Fetching layers from "+ic.Image)
// Pull the image manifest
if err := ic.pullManifest(ctx); err != nil {
return err
}
// Get layers to download from manifest
layers, err := ic.LayersToDownload()
if err != nil {
return err
}
progress.Message(ic.progressOutput, "", constants.ScratchLayerID)
for i := len(layers) - 1; i >= 0; i-- {
progress.Message(ic.progressOutput, "", layers[i].ID)
}
return nil
}
// prepareTransfer Looks up URLs and fetch auth token
func (ic *ImageC) prepareTransfer(ctx context.Context) error {
// Parse the -reference parameter
ic.ParseReference()
// Host is either the host's UUID (if run on vsphere) or the hostname of
// the system (if run standalone)
host, err := sys.UUID()
if err != nil {
log.Errorf("Failed to return host name: %s", err)
return err
}
if host != "" {
log.Infof("Using UUID (%s) for imagestore name", host)
} else if ic.Standalone {
host, err = os.Hostname()
log.Infof("Using host (%s) for imagestore name", host)
}
ic.Storename = host
if !ic.Standalone {
log.Debugf("Running with portlayer")
// Ping the server to ensure it's at least running
ok, err := PingPortLayer(ic.Host)
if err != nil || !ok {
log.Errorf("Failed to ping portlayer: %s", err)
return err
}
} else {
log.Debugf("Running standalone")
}
// Calculate (and overwrite) the registry URL and make sure that it responds to requests
ic.Registry, err = LearnRegistryURL(&ic.Options)
if err != nil {
log.Errorf("Error while pulling image: %s", err)
return err
}
// Get the URL of the OAuth endpoint
url, err := LearnAuthURL(ic.Options)
if err != nil {
log.Warnf("LearnAuthURL returned %s", err.Error())
switch err := err.(type) {
case urlfetcher.ImageNotFoundError:
return fmt.Errorf("Error: image %s not found", ic.Reference)
default:
return fmt.Errorf("Failed to obtain OAuth endpoint: %s", err)
}
}
// Get the OAuth token - if only we have a URL
if url != nil {
token, err := FetchToken(ctx, ic.Options, url, ic.progressOutput)
if err != nil {
log.Errorf("Failed to fetch OAuth token: %s", err)
return err
}
ic.Token = token
}
return nil
}
// pullManifest attempts to pull manifest for an image. Attempts to get schema 2 but will fall back to schema 1.
func (ic *ImageC) pullManifest(ctx context.Context) error {
// Get the schema1 manifest
manifest, digest, err := FetchImageManifest(ctx, ic.Options, 1, ic.progressOutput)
if err != nil {
log.Infof(err.Error())
switch err := err.(type) {
case urlfetcher.ImageNotFoundError:
return fmt.Errorf("Error: image %s not found", ic.Image)
case urlfetcher.TagNotFoundError:
return fmt.Errorf("Tag %s not found in repository %s", ic.Tag, ic.Image)
default:
return fmt.Errorf("Error while pulling image manifest: %s", err)
}
}
schema1, ok := manifest.(*Manifest)
if !ok {
return fmt.Errorf("Error pulling manifest schema 1")
}
ic.ImageManifestSchema1 = schema1
ic.ManifestDigest = digest
// Attempt to get schema2 manifest
manifest, digest, err = FetchImageManifest(ctx, ic.Options, 2, ic.progressOutput)
if err == nil {
if schema2, ok := manifest.(*schema2.DeserializedManifest); ok {
if schema2 != nil {
log.Infof("pullManifest - schema 2: %#v", schema2)
}
ic.ImageManifestSchema2 = schema2
// Override the manifest digest as Docker uses schema 2, unless the image
// is pulled by digest since we only support pull-by-digest for schema 1.
// TODO(anchal): this check should be removed once issue #5187 is implemented.
if _, ok := ic.Reference.(reference.Canonical); !ok {
ic.ManifestDigest = digest
}
}
}
return nil
}