340 lines
11 KiB
Go
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
|
|
}
|