Files
kubesphere/vendor/github.com/projectcalico/libcalico-go/lib/clientv3/client.go
2019-08-17 15:34:02 +08:00

340 lines
11 KiB
Go

// Copyright (c) 2017-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 clientv3
import (
"context"
log "github.com/sirupsen/logrus"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/satori/go.uuid"
"github.com/projectcalico/libcalico-go/lib/apiconfig"
"github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/backend"
bapi "github.com/projectcalico/libcalico-go/lib/backend/api"
cerrors "github.com/projectcalico/libcalico-go/lib/errors"
"github.com/projectcalico/libcalico-go/lib/ipam"
"github.com/projectcalico/libcalico-go/lib/net"
"github.com/projectcalico/libcalico-go/lib/options"
"github.com/projectcalico/libcalico-go/lib/set"
)
// client implements the client.Interface.
type client struct {
// The config we were created with.
config apiconfig.CalicoAPIConfig
// The backend client.
backend bapi.Client
// The resources client used internally.
resources resourceInterface
}
// New returns a connected client. The ClientConfig can either be created explicitly,
// or can be loaded from a config file or environment variables using the LoadClientConfig() function.
func New(config apiconfig.CalicoAPIConfig) (Interface, error) {
be, err := backend.NewClient(config)
if err != nil {
return nil, err
}
return client{
config: config,
backend: be,
resources: &resources{backend: be},
}, nil
}
// NewFromEnv loads the config from ENV variables and returns a connected client.
func NewFromEnv() (Interface, error) {
config, err := apiconfig.LoadClientConfigFromEnvironment()
if err != nil {
return nil, err
}
return New(*config)
}
// Nodes returns an interface for managing node resources.
func (c client) Nodes() NodeInterface {
return nodes{client: c}
}
// NetworkPolicies returns an interface for managing policy resources.
func (c client) NetworkPolicies() NetworkPolicyInterface {
return networkPolicies{client: c}
}
// GlobalNetworkPolicies returns an interface for managing policy resources.
func (c client) GlobalNetworkPolicies() GlobalNetworkPolicyInterface {
return globalNetworkPolicies{client: c}
}
// IPPools returns an interface for managing IP pool resources.
func (c client) IPPools() IPPoolInterface {
return ipPools{client: c}
}
// Profiles returns an interface for managing profile resources.
func (c client) Profiles() ProfileInterface {
return profiles{client: c}
}
// GlobalNetworkSets returns an interface for managing host endpoint resources.
func (c client) GlobalNetworkSets() GlobalNetworkSetInterface {
return globalNetworkSets{client: c}
}
// NetworkSets returns an interface for managing host endpoint resources.
func (c client) NetworkSets() NetworkSetInterface {
return networkSets{client: c}
}
// HostEndpoints returns an interface for managing host endpoint resources.
func (c client) HostEndpoints() HostEndpointInterface {
return hostEndpoints{client: c}
}
// WorkloadEndpoints returns an interface for managing workload endpoint resources.
func (c client) WorkloadEndpoints() WorkloadEndpointInterface {
return workloadEndpoints{client: c}
}
// BGPPeers returns an interface for managing BGP peer resources.
func (c client) BGPPeers() BGPPeerInterface {
return bgpPeers{client: c}
}
// IPAM returns an interface for managing IP address assignment and releasing.
func (c client) IPAM() ipam.Interface {
return ipam.NewIPAMClient(c.backend, poolAccessor{client: &c})
}
// BGPConfigurations returns an interface for managing the BGP configuration resources.
func (c client) BGPConfigurations() BGPConfigurationInterface {
return bgpConfigurations{client: c}
}
// FelixConfigurations returns an interface for managing the Felix configuration resources.
func (c client) FelixConfigurations() FelixConfigurationInterface {
return felixConfigurations{client: c}
}
// ClusterInformation returns an interface for managing the cluster information resource.
func (c client) ClusterInformation() ClusterInformationInterface {
return clusterInformation{client: c}
}
type poolAccessor struct {
client *client
}
func (p poolAccessor) GetEnabledPools(ipVersion int) ([]v3.IPPool, error) {
return p.getPools(func(pool *v3.IPPool) bool {
if pool.Spec.Disabled {
log.Debugf("Skipping disabled IP pool (%s)", pool.Name)
return false
}
if _, cidr, err := net.ParseCIDR(pool.Spec.CIDR); err == nil && cidr.Version() == ipVersion {
log.Debugf("Adding pool (%s) to the IPPool list", cidr.String())
return true
} else if err != nil {
log.Warnf("Failed to parse the IPPool: %s. Ignoring that IPPool", pool.Spec.CIDR)
} else {
log.Debugf("Ignoring IPPool: %s. IP version is different.", pool.Spec.CIDR)
}
return false
})
}
func (p poolAccessor) getPools(filter func(pool *v3.IPPool) bool) ([]v3.IPPool, error) {
pools, err := p.client.IPPools().List(context.Background(), options.ListOptions{})
if err != nil {
return nil, err
}
log.Debugf("Got list of all IPPools: %v", pools)
var filtered []v3.IPPool
for _, pool := range pools.Items {
if filter(&pool) {
filtered = append(filtered, pool)
}
}
return filtered, nil
}
func (p poolAccessor) GetAllPools() ([]v3.IPPool, error) {
return p.getPools(func(pool *v3.IPPool) bool {
return true
})
}
// EnsureInitialized is used to ensure the backend datastore is correctly
// initialized for use by Calico. This method may be called multiple times, and
// will have no effect if the datastore is already correctly initialized.
//
// Most Calico deployment scenarios will automatically implicitly invoke this
// method and so a general consumer of this API can assume that the datastore
// is already initialized.
func (c client) EnsureInitialized(ctx context.Context, calicoVersion, clusterType string) error {
// Perform datastore specific initialization first.
if err := c.backend.EnsureInitialized(); err != nil {
return err
}
if err := c.ensureClusterInformation(ctx, calicoVersion, clusterType); err != nil {
return err
}
return nil
}
const globalClusterInfoName = "default"
// ensureClusterInformation ensures that the ClusterInformation fields i.e. ClusterType,
// CalicoVersion and ClusterGUID are set. It creates/updates the ClusterInformation as needed.
func (c client) ensureClusterInformation(ctx context.Context, calicoVersion, clusterType string) error {
// Append "kdd" last if the datastoreType is 'kubernetes'.
if c.config.Spec.DatastoreType == apiconfig.Kubernetes {
// If clusterType is already set then append ",kdd" at the end.
if clusterType != "" {
// Trim the trailing ",", if any.
clusterType = strings.TrimSuffix(clusterType, ",")
// Append "kdd" very last thing in the list.
clusterType = fmt.Sprintf("%s,%s", clusterType, "kdd")
} else {
clusterType = "kdd"
}
}
for {
if ctx.Err() != nil {
return ctx.Err()
}
clusterInfo, err := c.ClusterInformation().Get(ctx, globalClusterInfoName, options.GetOptions{})
if err != nil {
// Create the default config if it doesn't already exist.
if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok {
newClusterInfo := v3.NewClusterInformation()
newClusterInfo.Name = globalClusterInfoName
newClusterInfo.Spec.CalicoVersion = calicoVersion
newClusterInfo.Spec.ClusterType = clusterType
newClusterInfo.Spec.ClusterGUID = fmt.Sprintf("%s", hex.EncodeToString(uuid.NewV4().Bytes()))
datastoreReady := true
newClusterInfo.Spec.DatastoreReady = &datastoreReady
_, err = c.ClusterInformation().Create(ctx, newClusterInfo, options.SetOptions{})
if err != nil {
if _, ok := err.(cerrors.ErrorResourceAlreadyExists); ok {
log.Info("Failed to create global ClusterInformation; another node got there first.")
time.Sleep(1 * time.Second)
continue
}
log.WithError(err).WithField("ClusterInformation", newClusterInfo).Errorf("Error creating cluster information config")
return err
}
} else {
log.WithError(err).WithField("ClusterInformation", globalClusterInfoName).Errorf("Error getting cluster information config")
return err
}
break
}
updateNeeded := false
if calicoVersion != "" {
// Only update the version if it's different from what we have.
if clusterInfo.Spec.CalicoVersion != calicoVersion {
clusterInfo.Spec.CalicoVersion = calicoVersion
updateNeeded = true
} else {
log.WithField("CalicoVersion", clusterInfo.Spec.CalicoVersion).Debug("Calico version value already assigned")
}
}
if clusterInfo.Spec.ClusterGUID == "" {
clusterInfo.Spec.ClusterGUID = fmt.Sprintf("%s", hex.EncodeToString(uuid.NewV4().Bytes()))
updateNeeded = true
} else {
log.WithField("ClusterGUID", clusterInfo.Spec.ClusterGUID).Debug("Cluster GUID value already set")
}
if clusterInfo.Spec.DatastoreReady == nil {
// If the ready flag is nil, default it to true (but if it's explicitly false, leave
// it as-is).
datastoreReady := true
clusterInfo.Spec.DatastoreReady = &datastoreReady
updateNeeded = true
} else {
log.WithField("DatastoreReady", clusterInfo.Spec.DatastoreReady).Debug("DatastoreReady value already set")
}
if clusterType != "" {
if clusterInfo.Spec.ClusterType == "" {
clusterInfo.Spec.ClusterType = clusterType
updateNeeded = true
} else {
allClusterTypes := strings.Split(clusterInfo.Spec.ClusterType, ",")
existingClusterTypes := set.FromArray(allClusterTypes)
localClusterTypes := strings.Split(clusterType, ",")
clusterTypeUpdateNeeded := false
for _, lct := range localClusterTypes {
if existingClusterTypes.Contains(lct) {
continue
}
clusterTypeUpdateNeeded = true
allClusterTypes = append(allClusterTypes, lct)
}
if clusterTypeUpdateNeeded {
clusterInfo.Spec.ClusterType = strings.Join(allClusterTypes, ",")
updateNeeded = true
}
}
}
if updateNeeded {
_, err = c.ClusterInformation().Update(ctx, clusterInfo, options.SetOptions{})
if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok {
log.WithError(err).WithField("ClusterInformation", clusterInfo).Warning(
"Conflict while updating cluster information, may retry")
time.Sleep(1 * time.Second)
continue
} else if err != nil {
log.WithError(err).WithField("ClusterInformation", clusterInfo).Errorf(
"Error updating cluster information")
return err
}
}
break
}
return nil
}
// Backend returns the backend client used by the v3 client. Not exposed on the main
// client API, but available publicly for consumers that require access to the backend
// client (e.g. for syncer support).
func (c client) Backend() bapi.Client {
return c.backend
}