VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

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

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

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

* Cleanup and readme file

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

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

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

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

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

* Vendored packages for the VIC provider

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

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

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

View File

@@ -0,0 +1,130 @@
// 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 util
import (
"errors"
"fmt"
"net/url"
"path"
"path/filepath"
"strings"
"github.com/vmware/govmomi/object"
)
const (
StorageURLPath = "storage"
ImageURLPath = StorageURLPath + "/images"
VolumeURLPath = StorageURLPath + "/volumes"
)
// ImageStoreNameToURL parses the image URL in the form /storage/images/<image store>/<image name>
func ImageStoreNameToURL(storeName string) (*url.URL, error) {
a := ServiceURL(ImageURLPath)
AppendDir(a, storeName)
return a, nil
}
func ImageStoreName(u *url.URL) (string, 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 len(segments) < 3 ||
segments[0] != filepath.Clean(StorageURLPath) {
return "", errors.New("not a storage path")
}
if segments[1] != "images" {
return "", errors.New("not an imagestore path")
}
if len(segments) < 2 {
return "", errors.New("uri path mismatch")
}
return segments[2], nil
}
// ImageURL converts a store and image name into an URL that is an internal imagestore representation
// NOTE: this is NOT a datastore URL and cannot be used in any calls that expect a ds:// scheme
func ImageURL(storeName, imageName string) (*url.URL, error) {
if imageName == "" {
return nil, fmt.Errorf("image ID missing")
}
u, err := ImageStoreNameToURL(storeName)
if err != nil {
return nil, err
}
AppendDir(u, imageName)
return u, nil
}
// ImageDatastoreURL takes a datastore path object and converts it into a stable URL for with a "ds" scheme
func ImageDatastoreURL(path *object.DatastorePath) *url.URL {
return &url.URL{
Scheme: "ds",
Path: path.String(),
}
}
// VolumeStoreNameToURL parses the volume URL in the form /storage/volumes/<volume store>/<volume name>
func VolumeStoreNameToURL(storeName string) (*url.URL, error) {
a := ServiceURL(VolumeURLPath)
AppendDir(a, storeName)
return a, nil
}
func VolumeStoreName(u *url.URL) (string, 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 len(segments) < 3 ||
segments[0] != filepath.Clean(StorageURLPath) {
return "", errors.New("not a storage path")
}
if segments[1] != "volumes" {
return "", errors.New("not an volumestore path")
}
if len(segments) < 2 {
return "", errors.New("uri path mismatch")
}
return segments[2], nil
}
func VolumeURL(storeName, volumeName string) (*url.URL, error) {
u, err := VolumeStoreNameToURL(storeName)
if err != nil {
return nil, err
}
AppendDir(u, volumeName)
return u, nil
}
func AppendDir(u *url.URL, dir string) {
u.Path = path.Join(u.Path, dir)
}

View File

@@ -0,0 +1,124 @@
// 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 util
import (
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
func TestImageStoreName(t *testing.T) {
u, _ := url.Parse("/storage/images/imgstore/image")
store, err := ImageStoreName(u)
if !assert.NoError(t, err) {
return
}
expectedStore := "imgstore"
if !assert.Equal(t, expectedStore, store) {
return
}
}
func TestImageStoreNameErrors(t *testing.T) {
u, _ := url.Parse("fail")
_, err := ImageStoreName(u)
expectedError := "invalid uri path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
u, _ = url.Parse("/storage:123")
_, err = ImageStoreName(u)
expectedError = "not a storage path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
u, _ = url.Parse("/storage")
_, err = ImageStoreName(u)
expectedError = "not a storage path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
}
func TestImageURL(t *testing.T) {
DefaultHost, _ = url.Parse("http://foo.com/")
storeName := "storeName"
imageName := "imageName"
u, err := ImageURL(storeName, imageName)
if err != nil {
t.Errorf("ImageURL failed %v", err)
}
expectedURL := "http://foo.com/storage/images/storeName/imageName"
if !assert.Equal(t, expectedURL, u.String()) {
return
}
}
func TestVolumeStoreName(t *testing.T) {
u, _ := url.Parse("/storage/volumes/volstore/volume")
store, err := VolumeStoreName(u)
if !assert.NoError(t, err) {
return
}
expectedStore := "volstore"
if !assert.Equal(t, expectedStore, store) {
return
}
}
func TestVolumeStoreNameErrors(t *testing.T) {
u, _ := url.Parse("fail")
_, err := VolumeStoreName(u)
expectedError := "invalid uri path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
u, _ = url.Parse("/storage:123")
_, err = VolumeStoreName(u)
expectedError = "not a storage path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
u, _ = url.Parse("/storage")
_, err = VolumeStoreName(u)
expectedError = "not a storage path"
if err.Error() != expectedError {
t.Errorf("Got: %s Expected: %s", err, expectedError)
}
}
func TestVolumeURL(t *testing.T) {
DefaultHost, _ = url.Parse("http://foo.com/")
storeName := "storeName"
volumeName := "volumeName"
u, err := VolumeURL(storeName, volumeName)
if err != nil {
t.Errorf("VolumeURL failed %v", err)
}
expectedURL := "http://foo.com/storage/volumes/storeName/volumeName"
if !assert.Equal(t, expectedURL, u.String()) {
return
}
}

166
vendor/github.com/vmware/vic/lib/portlayer/util/util.go generated vendored Normal file
View File

@@ -0,0 +1,166 @@
// 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 util
import (
"fmt"
"net"
"net/url"
"os"
"strings"
"sync"
log "github.com/Sirupsen/logrus"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/spec"
"github.com/vmware/vic/pkg/trace"
)
const (
// XXX leaving this as http for now. We probably want to make this unix://
scheme = "http://"
)
var (
DefaultHost = Host()
nameTemplateOnce sync.Once
nameTemplateInitial string
nameTemplate string
nameAvailableCapacity int
)
func Host() *url.URL {
name, err := os.Hostname()
if err != nil {
log.Fatal(err)
}
thisHost, err := url.Parse(scheme + name)
if err != nil {
log.Fatal(err)
}
return thisHost
}
// ServiceURL returns the URL for a given service relative to this host
func ServiceURL(serviceName string) *url.URL {
s, err := DefaultHost.Parse(serviceName)
if err != nil {
log.Fatal(err)
}
return s
}
// prepTemplate takes a template string, determines it's suitability for use and adjusts it if necessary
// returns:
// adjusted template
// available length for insertions
func prepTemplate(op trace.Operation, template string) (string, int) {
if template == "" {
template = config.DefaultNamePattern
}
withoutName := strings.Replace(template, config.NameToken.String(), "", 1)
withoutEither := strings.Replace(withoutName, config.IDToken.String(), "", 1)
availableLen := constants.MaxVMNameLength - len(withoutEither)
// TODO: initialization time check that template actually contains a token or we have a static string
// TODO: initialization time check that template is of usable length
// if there's zero space for replacement then there is no room for parameterization to avoid collisons
if availableLen > 0 {
return template, availableLen
}
op.Error("Falling back to default name convention as custom convention overflows name capacity")
template, availableLen = prepTemplate(op, config.DefaultNamePattern)
if availableLen > 0 {
return template, availableLen
}
// return sane fallback - has bounded length and probably few collisions
op.Error("Falling back to raw ID name convention as default convention overflows name capacity")
return config.IDToken.String(), constants.MaxVMNameLength
}
// cachedPrepTemplate provides basic single value memoization caching wrapping prepTemplate
func cachedPrepTemplate(op trace.Operation, template string) (string, int) {
// cache these values for the first template
// can move to full memoization if/when we allow dynamic choice or dynamic config
nameTemplateOnce.Do(func() {
nameTemplateInitial = template
nameTemplate, nameAvailableCapacity = prepTemplate(op, template)
op.Infof("Cached processed name convention template %q with insertion capacity of %d", nameTemplate, nameAvailableCapacity)
})
if template == nameTemplateInitial {
return nameTemplate, nameAvailableCapacity
}
return prepTemplate(op, template)
}
// replaceToken will replace the first occurrence only of the specific PatternToken in the template.
// Returns:
// updated template with value inserted
// renaming available capacity for insertions
func replaceToken(template string, token config.PatternToken, value string, availableLen int) (string, int) {
if strings.Contains(template, token.String()) {
trunc := value
if len(value) > availableLen {
trunc = value[:availableLen]
}
return strings.Replace(template, token.String(), trunc, 1), availableLen - len(trunc)
}
return template, availableLen
}
// Update the VM display name on vSphere UI
func DisplayName(op trace.Operation, cfg *spec.VirtualMachineConfigSpecConfig, namingConvention string) string {
shortID := cfg.ID[:constants.ShortIDLen]
prettyName := cfg.Name
// determine length of template without tokens
name, availableLen := cachedPrepTemplate(op, namingConvention)
name, availableLen = replaceToken(name, config.IDToken, shortID, availableLen)
name, availableLen = replaceToken(name, config.NameToken, prettyName, availableLen)
op.Infof("Applied naming convention: %s resulting %s", namingConvention, name)
return name
}
func ClientIP() (net.IP, error) {
ips, err := net.LookupIP(constants.ClientHostName)
if err != nil {
return nil, err
}
if len(ips) == 0 {
return nil, fmt.Errorf("No IP found on %s", constants.ClientHostName)
}
if len(ips) > 1 {
return nil, fmt.Errorf("Multiple IPs found on %s: %#v", constants.ClientHostName, ips)
}
return ips[0], nil
}

View File

@@ -0,0 +1,332 @@
// 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 util
import (
"context"
"fmt"
"net/url"
"path"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vmware/vic/lib/config"
"github.com/vmware/vic/lib/constants"
"github.com/vmware/vic/lib/spec"
"github.com/vmware/vic/pkg/trace"
)
// testName allows easy embedding of the test function name in the test body as string data
func testName(t *testing.T) string {
pc, _, _, ok := runtime.Caller(1)
require.True(t, ok, "unable to determine test name")
// lets only return the func name from the repo (vic)
// down - i.e. vic/lib/etc vs. github.com/vmware/vic/lib/etc
// if github.com/vmware doesn't match then the original is returned
pkgAndFunc := path.Base(runtime.FuncForPC(pc).Name())
parts := strings.Split(pkgAndFunc, ".")
return parts[len(parts)-1]
}
func TestServiceUrl(t *testing.T) {
DefaultHost, _ = url.Parse("http://foo.com/")
u := ServiceURL(StorageURLPath)
if !assert.Equal(t, "http://foo.com/storage", u.String()) {
return
}
}
func TestNameConventionDefault(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s-%s"
template := fmt.Sprintf(formatString, config.NameToken.String(), config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name, shortID), formatted, "display name not as expected")
second := DisplayName(op, cfg, template)
assert.Equal(t, formatted, second, "second call should return the same output")
}
func TestNameConventionUnspecified(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s-%s"
template := ""
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name, shortID), formatted, "display name not as expected")
}
func TestNameConventionNameInsertOnly(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s"
template := fmt.Sprintf(formatString, config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name), formatted, "display name not as expected")
}
func TestNameConventionIdInsertOnly(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s"
template := fmt.Sprintf(formatString, config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, shortID), formatted, "display name not as expected")
}
func TestNameConventionNamePre(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s"
template := fmt.Sprintf(formatString, config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name), formatted, "display name not as expected")
}
func TestNameConventionNamePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s-post"
template := fmt.Sprintf(formatString, config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name), formatted, "display name not as expected")
}
func TestNameConventionIDPre(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s"
template := fmt.Sprintf(formatString, config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, shortID), formatted, "display name not as expected")
}
func TestNameConventionIDPost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, shortID), formatted, "display name not as expected")
}
func TestNameConventionNameBoth(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s-post"
template := fmt.Sprintf(formatString, config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name), formatted, "display name not as expected")
}
func TestNameConventionIDBoth(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, shortID), formatted, "display name not as expected")
}
func TestNameConventionNameAndIDWithPrePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s-%s-post"
template := fmt.Sprintf(formatString, config.NameToken.String(), config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, cfg.Name, shortID), formatted, "display name not as expected")
}
func TestNameConventionIDAndNameWithPrePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "pre-%s-%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String(), config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, fmt.Sprintf(formatString, shortID, cfg.Name), formatted, "display name not as expected")
}
func TestNameConventionNameOverflowWithPrePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "this-is-a-really-long-pre-segment-in-order-to-test-name-truncation-%s-post"
template := fmt.Sprintf(formatString, config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, constants.MaxVMNameLength, len(formatted), "name was expected to be the max vm name length")
}
func TestNameConventionIDOverflowWithPrePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "this-is-a-really-long-pre-segment-in-order-to-test-name-truncation-%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, constants.MaxVMNameLength, len(formatted), "name was expected to be the max vm name length")
}
func TestNameConventionBothOverflowWithPrePost(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "this-is-a-really-long-pre-segment-in-order-to-test-name-truncation-%s-%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String(), config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
_ = cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
assert.Equal(t, constants.MaxVMNameLength, len(formatted), "name was expected to be the max vm name length")
}
func TestTemplateOverflow(t *testing.T) {
op := trace.NewOperation(context.Background(), testName(t))
formatString := "this-is-a-really-long-pre-segment-in-order-to-test-name-truncation-that-overflows-without-any-%s-post"
template := fmt.Sprintf(formatString, config.IDToken.String(), config.NameToken.String())
cfg := &spec.VirtualMachineConfigSpecConfig{
ID: "abcdefg0123456789hijklmnopqrstu",
Name: testName(t),
}
shortID := cfg.ID[:constants.ShortIDLen]
formatted := DisplayName(op, cfg, template)
// behaviour of template overflow is to revert to default template
assert.Equal(t, fmt.Sprintf("%s-%s", cfg.Name, shortID), formatted, "display name not as expected")
}