Files
kubesphere/vendor/github.com/projectcalico/libcalico-go/lib/backend/model/keys.go
zryfish ea88c8803d use istio client-go library instead of knative (#1661)
use istio client-go library instead of knative
bump kubernetes dependency version
change code coverage to codecov
2019-12-13 11:26:18 +08:00

366 lines
13 KiB
Go

// Copyright (c) 2016-2019 Tigera, 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 model
import (
"bytes"
"encoding/json"
"fmt"
net2 "net"
"reflect"
"strings"
"time"
v3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/net"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
)
// RawString is used a value type to indicate that the value is a bare non-JSON string
type rawString string
type rawBool bool
type rawIP net.IP
var rawStringType = reflect.TypeOf(rawString(""))
var rawBoolType = reflect.TypeOf(rawBool(true))
var rawIPType = reflect.TypeOf(rawIP{})
// Key represents a parsed datastore key.
type Key interface {
// defaultPath() returns a common path representation of the object used by
// etcdv3 and other datastores.
defaultPath() (string, error)
// defaultDeletePath() returns a common path representation used by etcdv3
// and other datastores to delete the object.
defaultDeletePath() (string, error)
// defaultDeleteParentPaths() returns an ordered slice of paths that should
// be removed after deleting the primary path (given by defaultDeletePath),
// provided there are no child entries associated with those paths. This is
// only used by directory based KV stores (such as etcdv3). With a directory
// based KV store, creation of a resource may also create parent directory entries
// that could be shared by multiple resources, and therefore the parent directories
// can only be removed when there are no more resources under them. The list of
// parent paths is ordered, and directories should be removed in the order supplied
// in the slice and only if the directory is empty.
defaultDeleteParentPaths() ([]string, error)
// valueType returns the object type associated with this key.
valueType() (reflect.Type, error)
// String returns a unique string representation of this key. The string
// returned by this method must uniquely identify this Key.
String() string
}
// Interface used to perform datastore lookups.
type ListInterface interface {
// defaultPathRoot() returns a default stringified root path, i.e. path
// to the directory containing all the keys to be listed.
defaultPathRoot() string
// BUG(smc) I think we should remove this and use the package KeyFromDefaultPath function.
// KeyFromDefaultPath parses the default path representation of the
// Key type for this list. It returns nil if passed a different kind
// of path.
KeyFromDefaultPath(key string) Key
}
// KVPair holds a typed key and value object as well as datastore specific
// revision information.
//
// The Value is dependent on the Key, but in general will be on of the following
// types:
// - A pointer to a struct
// - A slice or map
// - A bare string, boolean value or IP address (i.e. without quotes, so not
// JSON format).
type KVPair struct {
Key Key
Value interface{}
Revision string
UID *types.UID
TTL time.Duration // For writes, if non-zero, key has a TTL.
}
// KVPairList hosts a slice of KVPair structs and a Revision, returned from a Ls
type KVPairList struct {
KVPairs []*KVPair
Revision string
}
// KeyToDefaultPath converts one of the Keys from this package into a unique
// '/'-delimited path, which is suitable for use as the key when storing the
// value in a hierarchical (i.e. one with directories and leaves) key/value
// datastore such as etcd v3.
//
// Each unique key returns a unique path.
//
// Keys with a hierarchical relationship share a common prefix. However, in
// order to support datastores that do not support storing data at non-leaf
// nodes in the hierarchy (such as etcd v3), the path returned for a "parent"
// key, is not a direct ancestor of its children.
func KeyToDefaultPath(key Key) (string, error) {
return key.defaultPath()
}
// KeyToDefaultDeletePath converts one of the Keys from this package into a
// unique '/'-delimited path, which is suitable for use as the key when
// (recursively) deleting the value from a hierarchical (i.e. one with
// directories and leaves) key/value datastore such as etcd v3.
//
// KeyToDefaultDeletePath returns a different path to KeyToDefaultPath when
// it is a passed a Key that represents a non-leaf which, for example, has its
// own metadata but also contains other resource types as children.
//
// KeyToDefaultDeletePath returns the common prefix of the non-leaf key and
// its children so that a recursive delete of that key would delete the
// object itself and any children it has.
func KeyToDefaultDeletePath(key Key) (string, error) {
return key.defaultDeletePath()
}
func KeyToValueType(key Key) (reflect.Type, error) {
return key.valueType()
}
// KeyToDefaultDeleteParentPaths returns a slice of '/'-delimited
// paths which are used to delete parent entries that may be auto-created
// by directory-based KV stores (e.g. etcd v3). These paths should also be
// removed provided they have no more child entries.
//
// The list of parent paths is ordered, and directories should be removed
// in the order supplied in the slice and only if the directory is empty.
//
// For example,
// KeyToDefaultDeletePaths(WorkloadEndpointKey{
// Nodename: "h",
// OrchestratorID: "o",
// WorkloadID: "w",
// EndpointID: "e",
// })
// returns
//
// ["/calico/v1/host/h/workload/o/w/endpoint",
// "/calico/v1/host/h/workload/o/w"]
//
// indicating that these paths should also be deleted when they are empty.
// In this example it is equivalent to deleting the workload when there are
// no more endpoints in the workload.
func KeyToDefaultDeleteParentPaths(key Key) ([]string, error) {
return key.defaultDeleteParentPaths()
}
// ListOptionsToDefaultPathRoot converts list options struct into a
// common-prefix path suitable for querying a datastore that uses the paths
// returned by KeyToDefaultPath.
func ListOptionsToDefaultPathRoot(listOptions ListInterface) string {
return listOptions.defaultPathRoot()
}
// ListOptionsIsFullyQualified returns true if the options actually specify a fully
// qualified resource rather than a partial match.
func ListOptionsIsFullyQualified(listOptions ListInterface) bool {
// Construct the path prefix and then check to see if that actually corresponds to
// the path of a resource instance.
return listOptions.KeyFromDefaultPath(listOptions.defaultPathRoot()) != nil
}
// IsListOptionsLastSegmentPrefix returns true if the final segment of the default path
// root is a name prefix rather than the full name.
func IsListOptionsLastSegmentPrefix(listOptions ListInterface) bool {
// Only supported for ResourceListOptions.
rl, ok := listOptions.(ResourceListOptions)
return ok && rl.IsLastSegmentIsPrefix()
}
// KeyFromDefaultPath parses the default path representation of a key into one
// of our <Type>Key structs. Returns nil if the string doesn't match one of
// our key types.
func KeyFromDefaultPath(path string) Key {
if m := matchWorkloadEndpoint.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a workload endpoint: %v", path)
return WorkloadEndpointKey{
Hostname: m[1],
OrchestratorID: unescapeName(m[2]),
WorkloadID: unescapeName(m[3]),
EndpointID: unescapeName(m[4]),
}
} else if m := matchHostEndpoint.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host endpoint: %v", path)
return HostEndpointKey{
Hostname: m[1],
EndpointID: unescapeName(m[2]),
}
} else if m := matchNetworkSet.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a network set: %v", path)
return NetworkSetKey{
Name: unescapeName(m[1]),
}
} else if m := matchPolicy.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a policy: %v", path)
return PolicyKey{
Name: unescapeName(m[2]),
}
} else if m := matchProfile.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a profile: %v (%v)", path, m[2])
pk := ProfileKey{unescapeName(m[1])}
switch m[2] {
case "tags":
log.Debugf("Profile tags")
return ProfileTagsKey{ProfileKey: pk}
case "rules":
log.Debugf("Profile rules")
return ProfileRulesKey{ProfileKey: pk}
case "labels":
log.Debugf("Profile labels")
return ProfileLabelsKey{ProfileKey: pk}
}
return nil
} else if m := matchHostIp.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host ID: %v", path)
return HostIPKey{Hostname: m[1]}
} else if m := matchIPPool.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a pool: %v", path)
mungedCIDR := m[1]
cidr := strings.Replace(mungedCIDR, "-", "/", 1)
_, c, err := net.ParseCIDR(cidr)
if err != nil {
log.WithError(err).Warningf("Failed to parse CIDR %s", cidr)
} else {
return IPPoolKey{CIDR: *c}
}
} else if m := matchGlobalConfig.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a global felix config: %v", path)
return GlobalConfigKey{Name: m[1]}
} else if m := matchHostConfig.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host config: %v", path)
return HostConfigKey{Hostname: m[1], Name: m[2]}
} else if matchReadyFlag.MatchString(path) {
log.Debugf("Path is a ready flag: %v", path)
return ReadyFlagKey{}
} else if k := (NodeBGPConfigListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (GlobalBGPConfigListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (BlockAffinityListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (BlockListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ResourceListOptions{Kind: v3.KindNode}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ResourceListOptions{Kind: v3.KindBGPPeer}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ResourceListOptions{Kind: v3.KindNetworkPolicy}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ResourceListOptions{Kind: v3.KindIPPool}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (HostEndpointStatusListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (WorkloadEndpointStatusListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ActiveStatusReportListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (LastStatusReportListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else {
log.Debugf("Path is unknown: %v", path)
}
// Not a key we know about.
return nil
}
// ParseValue parses the default JSON representation of our data into one of
// our value structs, according to the type of key. I.e. if passed a
// PolicyKey as the first parameter, it will try to parse rawData into a
// Policy struct.
func ParseValue(key Key, rawData []byte) (interface{}, error) {
valueType, err := key.valueType()
if err != nil {
return nil, err
}
if valueType == rawStringType {
return string(rawData), nil
}
if valueType == rawBoolType {
return string(rawData) == "true", nil
}
if valueType == rawIPType {
ip := net2.ParseIP(string(rawData))
if ip == nil {
return nil, nil
}
return &net.IP{ip}, nil
}
value := reflect.New(valueType)
elem := value.Elem()
if elem.Kind() == reflect.Struct && elem.NumField() > 0 {
if elem.Field(0).Type() == reflect.ValueOf(key).Type() {
elem.Field(0).Set(reflect.ValueOf(key))
}
}
iface := value.Interface()
err = json.Unmarshal(rawData, iface)
if err != nil {
// This is a special case to address backwards compatibility from the time when we had no state information as block affinity value.
// example:
// Key: "/calico/ipam/v2/host/myhost.io/ipv4/block/172.29.82.0-26"
// Value: ""
// In 3.0.7 we added block affinity state as the value, so old "" value is no longer a valid JSON, so for that
// particular case we replace the "" with a "{}" so it can be parsed and we don't leak blocks after upgrade to Calico 3.0.7
// See: https://github.com/projectcalico/calico/issues/1956
if bytes.Equal(rawData, []byte(``)) && valueType == typeBlockAff {
rawData = []byte(`{}`)
if err = json.Unmarshal(rawData, iface); err != nil {
return nil, err
}
} else {
log.Warningf("Failed to unmarshal %#v into value %#v",
string(rawData), value)
return nil, err
}
}
if elem.Kind() != reflect.Struct {
// Pointer to a map or slice, unwrap.
iface = elem.Interface()
}
return iface, nil
}
// Serialize a value in the model to a []byte to stored in the datastore. This
// performs the opposite processing to ParseValue()
func SerializeValue(d *KVPair) ([]byte, error) {
valueType, err := d.Key.valueType()
if err != nil {
return nil, err
}
if d.Value == nil {
return json.Marshal(nil)
}
if valueType == rawStringType {
return []byte(d.Value.(string)), nil
}
if valueType == rawBoolType {
return []byte(fmt.Sprint(d.Value)), nil
}
if valueType == rawIPType {
return []byte(fmt.Sprint(d.Value)), nil
}
return json.Marshal(d.Value)
}