* 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
511 lines
12 KiB
Go
511 lines
12 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 extraconfig
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
"github.com/vmware/vic/pkg/log"
|
|
)
|
|
|
|
const (
|
|
// DefaultTagName value
|
|
DefaultTagName = "vic"
|
|
// DefaultPrefix value
|
|
DefaultPrefix = ""
|
|
// DefaultGuestInfoPrefix value
|
|
DefaultGuestInfoPrefix = "guestinfo.vice."
|
|
//Separator for slice values and map keys
|
|
Separator = "|"
|
|
|
|
// suffix separator character
|
|
suffixSeparator = "@"
|
|
// secret suffix
|
|
secretSuffix = "secret"
|
|
// non-persistent suffix
|
|
nonpersistentSuffix = "non-persistent"
|
|
)
|
|
|
|
const (
|
|
// Invalid value
|
|
Invalid = 1 << iota
|
|
// Hidden value
|
|
Hidden
|
|
// ReadOnly value
|
|
ReadOnly
|
|
// ReadWrite value
|
|
ReadWrite
|
|
// NonPersistent value
|
|
NonPersistent
|
|
// Volatile value
|
|
Volatile
|
|
// Secret value
|
|
Secret
|
|
)
|
|
|
|
type recursion struct {
|
|
// depth is a recursion depth, 0 equating to skip field
|
|
depth int
|
|
// follow controls whether we follow pointers
|
|
follow bool
|
|
}
|
|
|
|
// Unbounded is the value used for unbounded recursion
|
|
var Unbounded = recursion{depth: -1, follow: true}
|
|
|
|
var logger = &logrus.Logger{
|
|
Out: os.Stderr,
|
|
// We're using our own text formatter to skip the \n and \t escaping logrus
|
|
// was doing on non TTY Out (we redirect to a file) descriptors.
|
|
Formatter: log.NewTextFormatter(),
|
|
Hooks: make(logrus.LevelHooks),
|
|
Level: logrus.InfoLevel,
|
|
}
|
|
|
|
// SetLogLevel for the extraconfig package
|
|
func SetLogLevel(level logrus.Level) {
|
|
logger.Level = level
|
|
}
|
|
|
|
// calculateScope returns the uint representation of scope tag
|
|
func calculateScope(scopes []string) uint {
|
|
var scope uint
|
|
|
|
empty := true
|
|
for i := range scopes {
|
|
if scopes[i] != "" {
|
|
empty = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if empty {
|
|
return Hidden | ReadOnly
|
|
}
|
|
|
|
for _, v := range scopes {
|
|
switch v {
|
|
case "hidden":
|
|
scope |= Hidden
|
|
case "read-only":
|
|
scope |= ReadOnly
|
|
case "read-write":
|
|
scope |= ReadWrite
|
|
case nonpersistentSuffix:
|
|
scope |= NonPersistent
|
|
case "volatile":
|
|
scope |= Volatile
|
|
case secretSuffix:
|
|
scope |= Secret | ReadOnly
|
|
default:
|
|
return Invalid
|
|
}
|
|
}
|
|
return scope
|
|
}
|
|
|
|
func isSecret(key string) bool {
|
|
suffix := strings.Split(key, suffixSeparator)
|
|
if len(suffix) < 2 {
|
|
// no @ separator
|
|
return false
|
|
}
|
|
|
|
for i := range suffix[1:] {
|
|
if suffix[i+1] == secretSuffix {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isNonPersistent(key string) bool {
|
|
suffix := strings.Split(key, suffixSeparator)
|
|
if len(suffix) < 2 {
|
|
// no @ separator
|
|
return false
|
|
}
|
|
|
|
for i := range suffix[1:] {
|
|
if suffix[i+1] == nonpersistentSuffix {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func calculateScopeFromKey(key string) []string {
|
|
scopes := []string{}
|
|
|
|
if !strings.HasPrefix(key, DefaultGuestInfoPrefix) {
|
|
scopes = append(scopes, "hidden")
|
|
}
|
|
|
|
if strings.Contains(key, "/") {
|
|
scopes = append(scopes, "read-only")
|
|
} else {
|
|
scopes = append(scopes, "read-write")
|
|
}
|
|
|
|
if isSecret(key) {
|
|
scopes = append(scopes, secretSuffix)
|
|
}
|
|
|
|
if isNonPersistent(key) {
|
|
scopes = append(scopes, nonpersistentSuffix)
|
|
}
|
|
|
|
return scopes
|
|
}
|
|
|
|
func calculateKeyFromField(field reflect.StructField, prefix string, depth recursion) (string, recursion) {
|
|
skip := recursion{}
|
|
//skip unexported fields
|
|
if field.PkgPath != "" {
|
|
logger.Debugf("Skipping %s (not exported)", field.Name)
|
|
return "", skip
|
|
}
|
|
|
|
// get the annotations
|
|
tags := field.Tag
|
|
logger.Debugf("Tags: %#v", tags)
|
|
|
|
var key string
|
|
var scopes []string
|
|
var scope uint
|
|
|
|
fdepth := depth
|
|
|
|
prefixScopes := calculateScopeFromKey(prefix)
|
|
prefixScope := calculateScope(prefixScopes)
|
|
|
|
// do we have DefaultTagName?
|
|
if tags.Get(DefaultTagName) != "" {
|
|
// get the scopes
|
|
scopes = strings.Split(tags.Get("scope"), ",")
|
|
logger.Debugf("Scopes: %#v", scopes)
|
|
|
|
// get the keys and split properties from it
|
|
key = tags.Get("key")
|
|
logger.Debugf("Key specified: %s", key)
|
|
|
|
// get the keys and split properties from it
|
|
recurse := tags.Get("recurse")
|
|
if recurse != "" {
|
|
props := strings.Split(recurse, ",")
|
|
// process properties
|
|
for _, prop := range props {
|
|
// determine recursion depth
|
|
if strings.HasPrefix(prop, "depth") {
|
|
parts := strings.Split(prop, "=")
|
|
if len(parts) != 2 {
|
|
logger.Warnf("Skipping field with incorrect recurse property: %s", prop)
|
|
return "", skip
|
|
}
|
|
|
|
val, err := strconv.ParseInt(parts[1], 10, 64)
|
|
if err != nil {
|
|
logger.Warnf("Skipping field with incorrect recurse value: %s", parts[1])
|
|
return "", skip
|
|
}
|
|
fdepth.depth = int(val)
|
|
} else if prop == "nofollow" {
|
|
fdepth.follow = false
|
|
} else if prop == "follow" {
|
|
fdepth.follow = true
|
|
} else {
|
|
logger.Warnf("Ignoring unknown recurse property %s (%s)", key, prop)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
logger.Debugf("%s not tagged - inheriting parent scope", field.Name)
|
|
scopes = prefixScopes
|
|
}
|
|
|
|
if key == "" {
|
|
logger.Debugf("%s does not specify key - defaulting to fieldname", field.Name)
|
|
key = field.Name
|
|
}
|
|
|
|
scope = calculateScope(scopes)
|
|
|
|
// non-persistent is inherited, even if other scopes are specified
|
|
if prefixScope&NonPersistent != 0 {
|
|
scope |= NonPersistent
|
|
}
|
|
|
|
// re-calculate the key based on the scope and prefix
|
|
if key = calculateKey(scope, prefix, key); key == "" {
|
|
logger.Debugf("Skipping %s (unknown scope %s)", field.Name, scopes)
|
|
return "", skip
|
|
}
|
|
|
|
return key, fdepth
|
|
}
|
|
|
|
// calculateKey calculates the key based on the scope and current prefix
|
|
func calculateKey(scope uint, prefix string, key string) string {
|
|
if scope&Invalid != 0 {
|
|
logger.Debugf("invalid scope")
|
|
return ""
|
|
}
|
|
|
|
newSep := "/"
|
|
oldSep := "."
|
|
key = strings.TrimSpace(key)
|
|
|
|
hide := scope&Hidden != 0
|
|
write := scope&ReadWrite != 0
|
|
visible := strings.HasPrefix(prefix, DefaultGuestInfoPrefix)
|
|
|
|
if !hide && write {
|
|
oldSep = "/"
|
|
newSep = "."
|
|
}
|
|
|
|
// strip any existing suffix from the prefix - it'll be re-added if still applicable
|
|
suffix := strings.Index(prefix, suffixSeparator)
|
|
if suffix != -1 {
|
|
prefix = prefix[:suffix]
|
|
}
|
|
|
|
// assemble the actual keypath with appropriate separators
|
|
out := key
|
|
if prefix != "" {
|
|
out = strings.Join([]string{prefix, key}, newSep)
|
|
}
|
|
|
|
if scope&Secret != 0 {
|
|
out += suffixSeparator + secretSuffix
|
|
}
|
|
|
|
if scope&NonPersistent != 0 {
|
|
if hide {
|
|
logger.Debugf("Unable to combine non-persistent and hidden scopes")
|
|
return ""
|
|
}
|
|
out += suffixSeparator + nonpersistentSuffix
|
|
}
|
|
|
|
// we don't care about existing separators when hiden
|
|
if hide {
|
|
if !visible {
|
|
return out
|
|
}
|
|
|
|
// strip the prefix and the leading r/w signifier
|
|
return out[len(DefaultGuestInfoPrefix)+1:]
|
|
}
|
|
|
|
// ensure that separators are correct
|
|
out = strings.Replace(out, oldSep, newSep, -1)
|
|
|
|
// Assemble the base that controls key publishing in guest
|
|
if !visible {
|
|
return DefaultGuestInfoPrefix + newSep + out
|
|
}
|
|
|
|
// prefix will have been mangled by strings.Replace
|
|
return DefaultGuestInfoPrefix + out[len(DefaultGuestInfoPrefix):]
|
|
}
|
|
|
|
// utility function to allow adding of arbitrary prefix into key
|
|
// header is a leading segment that is preserved, prefix is injected after that
|
|
func addPrefixToKey(header, prefix, key string) string {
|
|
if prefix == "" {
|
|
return key
|
|
}
|
|
|
|
base := strings.TrimPrefix(key, header)
|
|
separator := base[0]
|
|
|
|
var modifiedPrefix string
|
|
if separator == '.' {
|
|
modifiedPrefix = strings.Replace(prefix, "/", ".", -1)
|
|
} else {
|
|
modifiedPrefix = strings.Replace(prefix, ".", "/", -1)
|
|
}
|
|
|
|
// we assume (given usage comment for WithPrefix) that there's no leading or trailing separator
|
|
// on the prefix. base has a leading separator
|
|
// guestinfoPrefix is const so adding it to the format string directly
|
|
return fmt.Sprintf(header+"%c%s%s", separator, modifiedPrefix, base)
|
|
}
|
|
|
|
// appendToPrefix will join the value to the prefix with the separator (if any) while ensuring that
|
|
// any suffixes are moved to the end of the key
|
|
func appendToPrefix(prefix, separator, value string) string {
|
|
// strip any existing suffix from the prefix - it'll be re-added if still applicable
|
|
index := strings.Index(prefix, suffixSeparator)
|
|
suffix := ""
|
|
if index != -1 {
|
|
suffix = prefix[index:]
|
|
prefix = prefix[:index]
|
|
}
|
|
|
|
// suffix wil still include the suffix separator if present
|
|
key := fmt.Sprintf("%s%s%s%s", prefix, separator, value, suffix)
|
|
|
|
return key
|
|
}
|
|
|
|
func calculateKeys(v reflect.Value, field string, prefix string) []string {
|
|
logger.Debugf("v=%#v, field=%#v, prefix=%#v", v, field, prefix)
|
|
if v.Kind() == reflect.Ptr {
|
|
return calculateKeys(v.Elem(), field, prefix)
|
|
}
|
|
|
|
if field == "" {
|
|
return []string{prefix}
|
|
}
|
|
|
|
s := strings.SplitN(field, ".", 2)
|
|
field = ""
|
|
iterate := false
|
|
if s[0] == "*" {
|
|
iterate = true
|
|
}
|
|
|
|
if len(s) > 1 {
|
|
field = s[1]
|
|
}
|
|
|
|
if !iterate {
|
|
switch v.Kind() {
|
|
case reflect.Map:
|
|
found := false
|
|
for _, k := range v.MapKeys() {
|
|
sk := k.Convert(reflect.TypeOf(""))
|
|
if sk.String() == s[0] {
|
|
v = v.MapIndex(k)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
panic(fmt.Sprintf("could not find map key %s", s[0]))
|
|
}
|
|
prefix = appendToPrefix(prefix, Separator, s[0])
|
|
case reflect.Array, reflect.Slice:
|
|
i, err := strconv.Atoi(s[0])
|
|
if err != nil {
|
|
panic(fmt.Sprintf("bad array index %s: %s", s[0], err))
|
|
}
|
|
switch v.Type().Elem().Kind() {
|
|
case reflect.Struct:
|
|
prefix = appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
|
|
case reflect.Uint8:
|
|
return []string{prefix}
|
|
default:
|
|
prefix = appendToPrefix(prefix, "", "~")
|
|
}
|
|
v = v.Index(i)
|
|
case reflect.Struct:
|
|
f, found := v.Type().FieldByName(s[0])
|
|
if !found {
|
|
panic(fmt.Sprintf("could not find field %s", s[0]))
|
|
}
|
|
prefix, _ = calculateKeyFromField(f, prefix, recursion{})
|
|
v = v.FieldByIndex(f.Index)
|
|
default:
|
|
panic(fmt.Sprintf("cannot get field from type %s", v.Type()))
|
|
}
|
|
|
|
return calculateKeys(v, field, prefix)
|
|
}
|
|
|
|
var out []string
|
|
switch v.Kind() {
|
|
case reflect.Map:
|
|
for _, k := range v.MapKeys() {
|
|
sk := k.Convert(reflect.TypeOf(""))
|
|
prefix := appendToPrefix(prefix, Separator, sk.String())
|
|
out = append(out, calculateKeys(v.MapIndex(k), field, prefix)...)
|
|
}
|
|
case reflect.Array, reflect.Slice:
|
|
switch v.Type().Elem().Kind() {
|
|
case reflect.Struct:
|
|
for i := 0; i < v.Len(); i++ {
|
|
prefix := appendToPrefix(prefix, Separator, fmt.Sprintf("%d", i))
|
|
out = append(out, calculateKeys(v.Index(i), field, prefix)...)
|
|
}
|
|
case reflect.Uint8:
|
|
return []string{prefix}
|
|
default:
|
|
return []string{appendToPrefix(prefix, "", "~")}
|
|
}
|
|
case reflect.Struct:
|
|
for i := 0; i < v.NumField(); i++ {
|
|
prefix, _ := calculateKeyFromField(v.Type().Field(i), prefix, recursion{})
|
|
out = append(out, calculateKeys(v.Field(i), field, prefix)...)
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("can't iterate type %s", v.Type().String()))
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
// CalculateKeys gets the keys in extraconfig corresponding to the field
|
|
// specification passed in for obj. Examples:
|
|
//
|
|
// type struct A {
|
|
// I int `vic:"0.1" scope:"read-only" key:"i"`
|
|
// Str string `vic:"0.1" scope:"read-only" key:"str"`
|
|
// }
|
|
//
|
|
// type struct B {
|
|
// A A `vic:"0.1" scope:"read-only" key:"a"`
|
|
// Array []A `vic:"0.1" scope:"read-only" key:"array"`
|
|
// Map map[string]string `vic:"0.1" scope:"read-only" key:"map"`
|
|
// }
|
|
//
|
|
// b := B{}
|
|
// b.Array = []A{A{}}
|
|
// b.Map = map[string]string{"foo": "", "bar": ""}
|
|
// // returns []string{"a/str"}
|
|
// CalculateKeys(b, "A.Str", "")
|
|
//
|
|
// // returns []string{"array|0"}
|
|
// CalculateKeys(b, "Array.0", "")
|
|
//
|
|
// // returns []string{"array|0"}
|
|
// CalculateKeys(b, "Array.*", "")
|
|
//
|
|
// // returns []string{"map|foo", "map|bar"}
|
|
// CalculateKeys(b, "Map.*", "")
|
|
//
|
|
// // returns []string{"map|foo"}
|
|
// CalculateKeys(b, "Map.foo", "")
|
|
//
|
|
// // returns []string{"map|foo/str"}
|
|
// CalculateKeys(b, "Map.foo.str", "")
|
|
//
|
|
func CalculateKeys(obj interface{}, field string, prefix string) []string {
|
|
return calculateKeys(reflect.ValueOf(obj), field, prefix)
|
|
}
|