temp commit

This commit is contained in:
magicsong
2019-08-14 20:45:43 +08:00
parent 90fa38851f
commit 7314064e83
635 changed files with 116500 additions and 494 deletions

View File

@@ -0,0 +1,141 @@
// Copyright (c) 2017 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
cerrors "github.com/projectcalico/libcalico-go/lib/errors"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// BGPConfigurationInterface has methods to work with BGPConfiguration resources.
type BGPConfigurationInterface interface {
Create(ctx context.Context, res *apiv3.BGPConfiguration, opts options.SetOptions) (*apiv3.BGPConfiguration, error)
Update(ctx context.Context, res *apiv3.BGPConfiguration, opts options.SetOptions) (*apiv3.BGPConfiguration, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.BGPConfiguration, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.BGPConfiguration, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.BGPConfigurationList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// bgpConfigurations implements BGPConfigurationInterface
type bgpConfigurations struct {
client client
}
// Create takes the representation of a BGPConfiguration and creates it.
// Returns the stored representation of the BGPConfiguration, and an error
// if there is any.
func (r bgpConfigurations) Create(ctx context.Context, res *apiv3.BGPConfiguration, opts options.SetOptions) (*apiv3.BGPConfiguration, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
if err := r.ValidateDefaultOnlyFields(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindBGPConfiguration, res)
if out != nil {
return out.(*apiv3.BGPConfiguration), err
}
return nil, err
}
// Update takes the representation of a BGPConfiguration and updates it.
// Returns the stored representation of the BGPConfiguration, and an error
// if there is any.
func (r bgpConfigurations) Update(ctx context.Context, res *apiv3.BGPConfiguration, opts options.SetOptions) (*apiv3.BGPConfiguration, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
// Check that NodeToNodeMeshEnabled and ASNumber are set. Can only be set on "default".
if err := r.ValidateDefaultOnlyFields(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindBGPConfiguration, res)
if out != nil {
return out.(*apiv3.BGPConfiguration), err
}
return nil, err
}
// Delete takes name of the BGPConfiguration and deletes it. Returns an
// error if one occurs.
func (r bgpConfigurations) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.BGPConfiguration, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindBGPConfiguration, noNamespace, name)
if out != nil {
return out.(*apiv3.BGPConfiguration), err
}
return nil, err
}
// Get takes name of the BGPConfiguration, and returns the corresponding
// BGPConfiguration object, and an error if there is any.
func (r bgpConfigurations) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.BGPConfiguration, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindBGPConfiguration, noNamespace, name)
if out != nil {
return out.(*apiv3.BGPConfiguration), err
}
return nil, err
}
// List returns the list of BGPConfiguration objects that match the supplied options.
func (r bgpConfigurations) List(ctx context.Context, opts options.ListOptions) (*apiv3.BGPConfigurationList, error) {
res := &apiv3.BGPConfigurationList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindBGPConfiguration, apiv3.KindBGPConfigurationList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the BGPConfiguration that
// match the supplied options.
func (r bgpConfigurations) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindBGPConfiguration, nil)
}
func (r bgpConfigurations) ValidateDefaultOnlyFields(res *apiv3.BGPConfiguration) error {
errFields := []cerrors.ErroredField{}
if res.ObjectMeta.GetName() != "default" {
if res.Spec.NodeToNodeMeshEnabled != nil {
errFields = append(errFields, cerrors.ErroredField{
Name: "BGPConfiguration.Spec.NodeToNodeMeshEnabled",
Reason: "Cannot set nodeToNodeMeshEnabled on a non default BGP Configuration.",
})
}
if res.Spec.ASNumber != nil {
errFields = append(errFields, cerrors.ErroredField{
Name: "BGPConfiguration.Spec.ASNumber",
Reason: "Cannot set ASNumber on a non default BGP Configuration.",
})
}
}
if len(errFields) > 0 {
return cerrors.ErrorValidation{
ErroredFields: errFields,
}
}
return nil
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2017 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// BGPPeerInterface has methods to work with BGPPeer resources.
type BGPPeerInterface interface {
Create(ctx context.Context, res *apiv3.BGPPeer, opts options.SetOptions) (*apiv3.BGPPeer, error)
Update(ctx context.Context, res *apiv3.BGPPeer, opts options.SetOptions) (*apiv3.BGPPeer, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.BGPPeer, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.BGPPeer, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.BGPPeerList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// bgpPeers implements BGPPeerInterface
type bgpPeers struct {
client client
}
// Create takes the representation of a BGPPeer and creates it. Returns the stored
// representation of the BGPPeer, and an error, if there is any.
func (r bgpPeers) Create(ctx context.Context, res *apiv3.BGPPeer, opts options.SetOptions) (*apiv3.BGPPeer, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindBGPPeer, res)
if out != nil {
return out.(*apiv3.BGPPeer), err
}
return nil, err
}
// Update takes the representation of a BGPPeer and updates it. Returns the stored
// representation of the BGPPeer, and an error, if there is any.
func (r bgpPeers) Update(ctx context.Context, res *apiv3.BGPPeer, opts options.SetOptions) (*apiv3.BGPPeer, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindBGPPeer, res)
if out != nil {
return out.(*apiv3.BGPPeer), err
}
return nil, err
}
// Delete takes name of the BGPPeer and deletes it. Returns an error if one occurs.
func (r bgpPeers) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.BGPPeer, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindBGPPeer, noNamespace, name)
if out != nil {
return out.(*apiv3.BGPPeer), err
}
return nil, err
}
// Get takes name of the BGPPeer, and returns the corresponding BGPPeer object,
// and an error if there is any.
func (r bgpPeers) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.BGPPeer, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindBGPPeer, noNamespace, name)
if out != nil {
return out.(*apiv3.BGPPeer), err
}
return nil, err
}
// List returns the list of BGPPeer objects that match the supplied options.
func (r bgpPeers) List(ctx context.Context, opts options.ListOptions) (*apiv3.BGPPeerList, error) {
res := &apiv3.BGPPeerList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindBGPPeer, apiv3.KindBGPPeerList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the BGPPeers that match the
// supplied options.
func (r bgpPeers) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindBGPPeer, nil)
}

View File

@@ -0,0 +1,339 @@
// 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
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) 2017 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"
"errors"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// ClusterInformationInterface has methods to work with ClusterInformation resources.
type ClusterInformationInterface interface {
Create(ctx context.Context, res *apiv3.ClusterInformation, opts options.SetOptions) (*apiv3.ClusterInformation, error)
Update(ctx context.Context, res *apiv3.ClusterInformation, opts options.SetOptions) (*apiv3.ClusterInformation, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.ClusterInformation, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.ClusterInformation, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.ClusterInformationList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// clusterInformation implements ClusterInformationInterface
type clusterInformation struct {
client client
}
// Create takes the representation of a ClusterInformation and creates it.
// Returns the stored representation of the ClusterInformation, and an error
// if there is any.
func (r clusterInformation) Create(ctx context.Context, res *apiv3.ClusterInformation, opts options.SetOptions) (*apiv3.ClusterInformation, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
if res.ObjectMeta.GetName() != "default" {
return nil, errors.New("Cannot create a Cluster Information resource with a name other than \"default\"")
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindClusterInformation, res)
if out != nil {
return out.(*apiv3.ClusterInformation), err
}
return nil, err
}
// Update takes the representation of a ClusterInformation and updates it.
// Returns the stored representation of the ClusterInformation, and an error
// if there is any.
func (r clusterInformation) Update(ctx context.Context, res *apiv3.ClusterInformation, opts options.SetOptions) (*apiv3.ClusterInformation, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindClusterInformation, res)
if out != nil {
return out.(*apiv3.ClusterInformation), err
}
return nil, err
}
// Delete takes name of the ClusterInformation and deletes it. Returns an
// error if one occurs.
func (r clusterInformation) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.ClusterInformation, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindClusterInformation, noNamespace, name)
if out != nil {
return out.(*apiv3.ClusterInformation), err
}
return nil, err
}
// Get takes name of the ClusterInformation, and returns the corresponding
// ClusterInformation object, and an error if there is any.
func (r clusterInformation) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.ClusterInformation, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindClusterInformation, noNamespace, name)
if out != nil {
return out.(*apiv3.ClusterInformation), err
}
return nil, err
}
// List returns the list of ClusterInformation objects that match the supplied options.
func (r clusterInformation) List(ctx context.Context, opts options.ListOptions) (*apiv3.ClusterInformationList, error) {
res := &apiv3.ClusterInformationList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindClusterInformation, apiv3.KindClusterInformationList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the ClusterInformation that
// match the supplied options.
func (r clusterInformation) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindClusterInformation, nil)
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2018 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 client implements the northbound client used to manage Calico configuration.
This client is the main entry point for applications that are managing or querying
Calico configuration.
This client provides a typed interface for managing different resource types. The
definitions for each resource type are defined in the following package:
github.com/projectcalico/libcalico-go/lib/api
The client has a number of methods that return interfaces for managing:
- BGP Peer resources
- Policy resources
- IP Pool resources
- Global network sets resources
- Host endpoint resources
- Workload endpoint resources
- Profile resources
- IP Address Management (IPAM)
See [resource definitions](http://docs.projectcalico.org/latest/reference/calicoctl/resources/) for details about the set of management commands for each
resource type.
*/
package clientv3

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2017 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// FelixConfigurationInterface has methods to work with FelixConfiguration resources.
type FelixConfigurationInterface interface {
Create(ctx context.Context, res *apiv3.FelixConfiguration, opts options.SetOptions) (*apiv3.FelixConfiguration, error)
Update(ctx context.Context, res *apiv3.FelixConfiguration, opts options.SetOptions) (*apiv3.FelixConfiguration, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.FelixConfiguration, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.FelixConfiguration, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.FelixConfigurationList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// felixConfigurations implements FelixConfigurationInterface
type felixConfigurations struct {
client client
}
// Create takes the representation of a FelixConfiguration and creates it.
// Returns the stored representation of the FelixConfiguration, and an error
// if there is any.
func (r felixConfigurations) Create(ctx context.Context, res *apiv3.FelixConfiguration, opts options.SetOptions) (*apiv3.FelixConfiguration, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindFelixConfiguration, res)
if out != nil {
return out.(*apiv3.FelixConfiguration), err
}
return nil, err
}
// Update takes the representation of a FelixConfiguration and updates it.
// Returns the stored representation of the FelixConfiguration, and an error
// if there is any.
func (r felixConfigurations) Update(ctx context.Context, res *apiv3.FelixConfiguration, opts options.SetOptions) (*apiv3.FelixConfiguration, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindFelixConfiguration, res)
if out != nil {
return out.(*apiv3.FelixConfiguration), err
}
return nil, err
}
// Delete takes name of the FelixConfiguration and deletes it. Returns an
// error if one occurs.
func (r felixConfigurations) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.FelixConfiguration, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindFelixConfiguration, noNamespace, name)
if out != nil {
return out.(*apiv3.FelixConfiguration), err
}
return nil, err
}
// Get takes name of the FelixConfiguration, and returns the corresponding
// FelixConfiguration object, and an error if there is any.
func (r felixConfigurations) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.FelixConfiguration, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindFelixConfiguration, noNamespace, name)
if out != nil {
return out.(*apiv3.FelixConfiguration), err
}
return nil, err
}
// List returns the list of FelixConfiguration objects that match the supplied options.
func (r felixConfigurations) List(ctx context.Context, opts options.ListOptions) (*apiv3.FelixConfigurationList, error) {
res := &apiv3.FelixConfigurationList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindFelixConfiguration, apiv3.KindFelixConfigurationList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the FelixConfiguration that
// match the supplied options.
func (r felixConfigurations) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindFelixConfiguration, nil)
}

View File

@@ -0,0 +1,206 @@
// Copyright (c) 2017-2018 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"
"strings"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// GlobalNetworkPolicyInterface has methods to work with GlobalNetworkPolicy resources.
type GlobalNetworkPolicyInterface interface {
Create(ctx context.Context, res *apiv3.GlobalNetworkPolicy, opts options.SetOptions) (*apiv3.GlobalNetworkPolicy, error)
Update(ctx context.Context, res *apiv3.GlobalNetworkPolicy, opts options.SetOptions) (*apiv3.GlobalNetworkPolicy, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.GlobalNetworkPolicy, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.GlobalNetworkPolicy, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.GlobalNetworkPolicyList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// globalNetworkPolicies implements GlobalNetworkPolicyInterface
type globalNetworkPolicies struct {
client client
}
// Create takes the representation of a GlobalNetworkPolicy and creates it. Returns the stored
// representation of the GlobalNetworkPolicy, and an error, if there is any.
func (r globalNetworkPolicies) Create(ctx context.Context, res *apiv3.GlobalNetworkPolicy, opts options.SetOptions) (*apiv3.GlobalNetworkPolicy, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types)
if err := validator.Validate(res); err != nil {
return nil, err
}
// Properly prefix the name
res.GetObjectMeta().SetName(convertPolicyNameForStorage(res.GetObjectMeta().GetName()))
out, err := r.client.resources.Create(ctx, opts, apiv3.KindGlobalNetworkPolicy, res)
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.GlobalNetworkPolicy), err
}
// Remove the prefix out of the returned policy name.
res.GetObjectMeta().SetName(convertPolicyNameFromStorage(res.GetObjectMeta().GetName()))
return nil, err
}
// Update takes the representation of a GlobalNetworkPolicy and updates it. Returns the stored
// representation of the GlobalNetworkPolicy, and an error, if there is any.
func (r globalNetworkPolicies) Update(ctx context.Context, res *apiv3.GlobalNetworkPolicy, opts options.SetOptions) (*apiv3.GlobalNetworkPolicy, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types)
if err := validator.Validate(res); err != nil {
return nil, err
}
// Properly prefix the name
res.GetObjectMeta().SetName(convertPolicyNameForStorage(res.GetObjectMeta().GetName()))
out, err := r.client.resources.Update(ctx, opts, apiv3.KindGlobalNetworkPolicy, res)
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.GlobalNetworkPolicy), err
}
// Remove the prefix out of the returned policy name.
res.GetObjectMeta().SetName(convertPolicyNameFromStorage(res.GetObjectMeta().GetName()))
return nil, err
}
// Delete takes name of the GlobalNetworkPolicy and deletes it. Returns an error if one occurs.
func (r globalNetworkPolicies) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.GlobalNetworkPolicy, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindGlobalNetworkPolicy, noNamespace, convertPolicyNameForStorage(name))
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.GlobalNetworkPolicy), err
}
return nil, err
}
// Get takes name of the GlobalNetworkPolicy, and returns the corresponding GlobalNetworkPolicy object,
// and an error if there is any.
func (r globalNetworkPolicies) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.GlobalNetworkPolicy, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindGlobalNetworkPolicy, noNamespace, convertPolicyNameForStorage(name))
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.GlobalNetworkPolicy), err
}
return nil, err
}
// List returns the list of GlobalNetworkPolicy objects that match the supplied options.
func (r globalNetworkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.GlobalNetworkPolicyList, error) {
res := &apiv3.GlobalNetworkPolicyList{}
// Add the name prefix if name is provided
if opts.Name != "" {
opts.Name = convertPolicyNameForStorage(opts.Name)
}
if err := r.client.resources.List(ctx, opts, apiv3.KindGlobalNetworkPolicy, apiv3.KindGlobalNetworkPolicyList, res); err != nil {
return nil, err
}
// Remove the prefix off of each policy name
for i, _ := range res.Items {
name := res.Items[i].GetObjectMeta().GetName()
res.Items[i].GetObjectMeta().SetName(convertPolicyNameFromStorage(name))
}
return res, nil
}
// Watch returns a watch.Interface that watches the globalNetworkPolicies that match the
// supplied options.
func (r globalNetworkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
// Add the name prefix if name is provided
if opts.Name != "" {
opts.Name = convertPolicyNameForStorage(opts.Name)
}
return r.client.resources.Watch(ctx, opts, apiv3.KindGlobalNetworkPolicy, &policyConverter{})
}
func defaultPolicyTypesField(ingressRules, egressRules []apiv3.Rule, types *[]apiv3.PolicyType) {
if len(*types) == 0 {
// Default the Types field according to what inbound and outbound rules are present
// in the policy.
if len(egressRules) == 0 {
// Policy has no egress rules, so apply this policy to ingress only. (Note:
// intentionally including the case where the policy also has no ingress
// rules.)
*types = []apiv3.PolicyType{apiv3.PolicyTypeIngress}
} else if len(ingressRules) == 0 {
// Policy has egress rules but no ingress rules, so apply this policy to
// egress only.
*types = []apiv3.PolicyType{apiv3.PolicyTypeEgress}
} else {
// Policy has both ingress and egress rules, so apply this policy to both
// ingress and egress.
*types = []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}
}
}
}
func convertPolicyNameForStorage(name string) string {
// Do nothing on names prefixed with "knp."
if strings.HasPrefix(name, "knp.") {
return name
}
// Similarly for "ossg."
if strings.HasPrefix(name, "ossg.") {
return name
}
return "default." + name
}
func convertPolicyNameFromStorage(name string) string {
// Do nothing on names prefixed with "knp."
if strings.HasPrefix(name, "knp.") {
return name
}
// Similarly for "ossg."
if strings.HasPrefix(name, "ossg.") {
return name
}
parts := strings.SplitN(name, ".", 2)
return parts[len(parts)-1]
}
type policyConverter struct{}
func (pc *policyConverter) Convert(r resource) resource {
r.GetObjectMeta().SetName(convertPolicyNameFromStorage(r.GetObjectMeta().GetName()))
return r
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2018 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// GlobalNetworkSetInterface has methods to work with GlobalNetworkSet resources.
type GlobalNetworkSetInterface interface {
Create(ctx context.Context, res *apiv3.GlobalNetworkSet, opts options.SetOptions) (*apiv3.GlobalNetworkSet, error)
Update(ctx context.Context, res *apiv3.GlobalNetworkSet, opts options.SetOptions) (*apiv3.GlobalNetworkSet, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.GlobalNetworkSet, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.GlobalNetworkSet, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.GlobalNetworkSetList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// globalNetworkSets implements GlobalNetworkSetInterface
type globalNetworkSets struct {
client client
}
// Create takes the representation of a GlobalNetworkSet and creates it. Returns the stored
// representation of the GlobalNetworkSet, and an error, if there is any.
func (r globalNetworkSets) Create(ctx context.Context, res *apiv3.GlobalNetworkSet, opts options.SetOptions) (*apiv3.GlobalNetworkSet, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindGlobalNetworkSet, res)
if out != nil {
return out.(*apiv3.GlobalNetworkSet), err
}
return nil, err
}
// Update takes the representation of a GlobalNetworkSet and updates it. Returns the stored
// representation of the GlobalNetworkSet, and an error, if there is any.
func (r globalNetworkSets) Update(ctx context.Context, res *apiv3.GlobalNetworkSet, opts options.SetOptions) (*apiv3.GlobalNetworkSet, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindGlobalNetworkSet, res)
if out != nil {
return out.(*apiv3.GlobalNetworkSet), err
}
return nil, err
}
// Delete takes name of the GlobalNetworkSet and deletes it. Returns an error if one occurs.
func (r globalNetworkSets) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.GlobalNetworkSet, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindGlobalNetworkSet, noNamespace, name)
if out != nil {
return out.(*apiv3.GlobalNetworkSet), err
}
return nil, err
}
// Get takes name of the GlobalNetworkSet, and returns the corresponding GlobalNetworkSet object,
// and an error if there is any.
func (r globalNetworkSets) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.GlobalNetworkSet, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindGlobalNetworkSet, noNamespace, name)
if out != nil {
return out.(*apiv3.GlobalNetworkSet), err
}
return nil, err
}
// List returns the list of GlobalNetworkSet objects that match the supplied options.
func (r globalNetworkSets) List(ctx context.Context, opts options.ListOptions) (*apiv3.GlobalNetworkSetList, error) {
res := &apiv3.GlobalNetworkSetList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindGlobalNetworkSet, apiv3.KindGlobalNetworkSetList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the GlobalNetworkSets that match the
// supplied options.
func (r globalNetworkSets) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindGlobalNetworkSet, nil)
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2017 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// HostEndpointInterface has methods to work with HostEndpoint resources.
type HostEndpointInterface interface {
Create(ctx context.Context, res *apiv3.HostEndpoint, opts options.SetOptions) (*apiv3.HostEndpoint, error)
Update(ctx context.Context, res *apiv3.HostEndpoint, opts options.SetOptions) (*apiv3.HostEndpoint, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.HostEndpoint, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.HostEndpoint, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.HostEndpointList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// hostEndpoints implements HostEndpointInterface
type hostEndpoints struct {
client client
}
// Create takes the representation of a HostEndpoint and creates it. Returns the stored
// representation of the HostEndpoint, and an error, if there is any.
func (r hostEndpoints) Create(ctx context.Context, res *apiv3.HostEndpoint, opts options.SetOptions) (*apiv3.HostEndpoint, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindHostEndpoint, res)
if out != nil {
return out.(*apiv3.HostEndpoint), err
}
return nil, err
}
// Update takes the representation of a HostEndpoint and updates it. Returns the stored
// representation of the HostEndpoint, and an error, if there is any.
func (r hostEndpoints) Update(ctx context.Context, res *apiv3.HostEndpoint, opts options.SetOptions) (*apiv3.HostEndpoint, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindHostEndpoint, res)
if out != nil {
return out.(*apiv3.HostEndpoint), err
}
return nil, err
}
// Delete takes name of the HostEndpoint and deletes it. Returns an error if one occurs.
func (r hostEndpoints) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.HostEndpoint, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindHostEndpoint, noNamespace, name)
if out != nil {
return out.(*apiv3.HostEndpoint), err
}
return nil, err
}
// Get takes name of the HostEndpoint, and returns the corresponding HostEndpoint object,
// and an error if there is any.
func (r hostEndpoints) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.HostEndpoint, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindHostEndpoint, noNamespace, name)
if out != nil {
return out.(*apiv3.HostEndpoint), err
}
return nil, err
}
// List returns the list of HostEndpoint objects that match the supplied options.
func (r hostEndpoints) List(ctx context.Context, opts options.ListOptions) (*apiv3.HostEndpointList, error) {
res := &apiv3.HostEndpointList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindHostEndpoint, apiv3.KindHostEndpointList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the HostEndpoints that match the
// supplied options.
func (r hostEndpoints) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindHostEndpoint, nil)
}

View File

@@ -0,0 +1,62 @@
// 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"
"github.com/projectcalico/libcalico-go/lib/ipam"
)
type Interface interface {
// Nodes returns an interface for managing node resources.
Nodes() NodeInterface
// GlobalNetworkPolicies returns an interface for managing global network policy resources.
GlobalNetworkPolicies() GlobalNetworkPolicyInterface
// NetworkPolicies returns an interface for managing namespaced network policy resources.
NetworkPolicies() NetworkPolicyInterface
// IPPools returns an interface for managing IP pool resources.
IPPools() IPPoolInterface
// Profiles returns an interface for managing profile resources.
Profiles() ProfileInterface
// GlobalNetworkSets returns an interface for managing global network sets resources.
GlobalNetworkSets() GlobalNetworkSetInterface
// NetworkSets returns an interface for managing network sets resources.
NetworkSets() NetworkSetInterface
// HostEndpoints returns an interface for managing host endpoint resources.
HostEndpoints() HostEndpointInterface
// WorkloadEndpoints returns an interface for managing workload endpoint resources.
WorkloadEndpoints() WorkloadEndpointInterface
// BGPPeers returns an interface for managing BGP peer resources.
BGPPeers() BGPPeerInterface
// IPAM returns an interface for managing IP address assignment and releasing.
IPAM() ipam.Interface
// BGPConfigurations returns an interface for managing the BGP configuration resources.
BGPConfigurations() BGPConfigurationInterface
// FelixConfigurations returns an interface for managing the Felix configuration resources.
FelixConfigurations() FelixConfigurationInterface
// ClusterInformation returns an interface for managing the cluster information resource.
ClusterInformation() ClusterInformationInterface
// 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.
EnsureInitialized(ctx context.Context, calicoVersion, clusterType string) error
}
// Compile-time assertion that our client implements its interface.
var _ Interface = (*client)(nil)

View File

@@ -0,0 +1,620 @@
// 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"
"fmt"
"net"
"time"
log "github.com/sirupsen/logrus"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/backend/model"
cerrors "github.com/projectcalico/libcalico-go/lib/errors"
cnet "github.com/projectcalico/libcalico-go/lib/net"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// IPPoolInterface has methods to work with IPPool resources.
type IPPoolInterface interface {
Create(ctx context.Context, res *apiv3.IPPool, opts options.SetOptions) (*apiv3.IPPool, error)
Update(ctx context.Context, res *apiv3.IPPool, opts options.SetOptions) (*apiv3.IPPool, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.IPPool, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.IPPool, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.IPPoolList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// ipPools implements IPPoolInterface
type ipPools struct {
client client
}
// Create takes the representation of a IPPool and creates it. Returns the stored
// representation of the IPPool, and an error, if there is any.
func (r ipPools) Create(ctx context.Context, res *apiv3.IPPool, opts options.SetOptions) (*apiv3.IPPool, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
// Validate the IPPool before creating the resource.
if err := r.validateAndSetDefaults(ctx, res, nil); err != nil {
return nil, err
}
if err := validator.Validate(res); err != nil {
return nil, err
}
// Check that there are no existing blocks in the pool range that have a different block size.
poolBlockSize := res.Spec.BlockSize
poolIP, poolCIDR, err := net.ParseCIDR(res.Spec.CIDR)
if err != nil {
return nil, cerrors.ErrorParsingDatastoreEntry{
RawKey: "CIDR",
RawValue: string(res.Spec.CIDR),
Err: err,
}
}
ipVersion := 4
if poolIP.To4() == nil {
ipVersion = 6
}
blocks, err := r.client.backend.List(ctx, model.BlockListOptions{IPVersion: ipVersion}, "")
if _, ok := err.(cerrors.ErrorOperationNotSupported); !ok && err != nil {
// There was an error and it wasn't OperationNotSupported - return it.
return nil, err
} else if err == nil {
// Skip the block check if the error is OperationUnsupported - listing blocks is not
// supported with host-local IPAM on KDD.
for _, b := range blocks.KVPairs {
k := b.Key.(model.BlockKey)
ones, _ := k.CIDR.Mask.Size()
// Check if this block has a different size to the pool, and that it overlaps with the pool.
if ones != poolBlockSize && k.CIDR.IsNetOverlap(*poolCIDR) {
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "IPPool.Spec.BlockSize",
Reason: "IPPool blocksSize conflicts with existing allocations that use a different blockSize",
Value: res.Spec.BlockSize,
}},
}
}
}
}
// Enable IPIP or VXLAN globally if required. Do this before the Create so if it fails the user
// can retry the same command.
err = r.maybeEnableIPIP(ctx, res)
if err != nil {
return nil, err
}
err = r.maybeEnableVXLAN(ctx, res)
if err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindIPPool, res)
if out != nil {
return out.(*apiv3.IPPool), err
}
return nil, err
}
// Update takes the representation of a IPPool and updates it. Returns the stored
// representation of the IPPool, and an error, if there is any.
func (r ipPools) Update(ctx context.Context, res *apiv3.IPPool, opts options.SetOptions) (*apiv3.IPPool, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
// Get the existing settings, so that we can validate the CIDR and block size have not changed.
old, err := r.Get(ctx, res.Name, options.GetOptions{})
if err != nil {
return nil, err
}
// Validate the IPPool updating the resource.
if err := r.validateAndSetDefaults(ctx, res, old); err != nil {
return nil, err
}
if err := validator.Validate(res); err != nil {
return nil, err
}
// Enable IPIP globally if required. Do this before the Update so if it fails the user
// can retry the same command.
err = r.maybeEnableIPIP(ctx, res)
if err != nil {
return nil, err
}
err = r.maybeEnableVXLAN(ctx, res)
if err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindIPPool, res)
if out != nil {
return out.(*apiv3.IPPool), err
}
return nil, err
}
// Delete takes name of the IPPool and deletes it. Returns an error if one occurs.
func (r ipPools) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.IPPool, error) {
// Deleting a pool requires a little care because of existing endpoints
// using IP addresses allocated in the pool. We do the deletion in
// the following steps:
// - disable the pool so no more IPs are assigned from it
// - remove all affinities associated with the pool
// - delete the pool
// Get the pool so that we can find the CIDR associated with it.
pool, err := r.Get(ctx, name, options.GetOptions{})
if err != nil {
return nil, err
}
logCxt := log.WithFields(log.Fields{
"CIDR": pool.Spec.CIDR,
"Name": name,
})
// If the pool is active, set the disabled flag to ensure we stop allocating from this pool.
if !pool.Spec.Disabled {
logCxt.Info("Disabling pool to release affinities")
pool.Spec.Disabled = true
// If the Delete has been called with a ResourceVersion then use that to perform the
// update - that way we'll catch update conflicts (we could actually check here, but
// the most likely scenario is there isn't one - so just pass it through and let the
// Update handle any conflicts).
if opts.ResourceVersion != "" {
pool.ResourceVersion = opts.ResourceVersion
}
if _, err := r.Update(ctx, pool, options.SetOptions{}); err != nil {
return nil, err
}
// Reset the resource version before the actual delete since the version of that resource
// will now have been updated.
opts.ResourceVersion = ""
}
// Release affinities associated with this pool. We do this even if the pool was disabled
// (since it may have been enabled at one time, and if there are no affine blocks created
// then this will be a no-op). We've already validated the CIDR so we know it will parse.
if _, cidrNet, err := cnet.ParseCIDR(pool.Spec.CIDR); err == nil {
logCxt.Info("Releasing pool affinities")
// Pause for a short period before releasing the affinities - this gives any in-progress
// allocations an opportunity to finish.
time.Sleep(500 * time.Millisecond)
err = r.client.IPAM().ReleasePoolAffinities(ctx, *cidrNet)
// Depending on the datastore, IPAM may not be supported. If we get a not supported
// error, then continue. Any other error, fail.
if _, ok := err.(cerrors.ErrorOperationNotSupported); !ok && err != nil {
return nil, err
}
}
// And finally, delete the pool.
logCxt.Info("Deleting pool")
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindIPPool, noNamespace, name)
if out != nil {
return out.(*apiv3.IPPool), err
}
return nil, err
}
// Get takes name of the IPPool, and returns the corresponding IPPool object,
// and an error if there is any.
func (r ipPools) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.IPPool, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindIPPool, noNamespace, name)
if out != nil {
convertIpPoolFromStorage(out.(*apiv3.IPPool))
return out.(*apiv3.IPPool), err
}
return nil, err
}
// List returns the list of IPPool objects that match the supplied options.
func (r ipPools) List(ctx context.Context, opts options.ListOptions) (*apiv3.IPPoolList, error) {
res := &apiv3.IPPoolList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindIPPool, apiv3.KindIPPoolList, res); err != nil {
return nil, err
}
// Default values when reading from backend.
for i := range res.Items {
convertIpPoolFromStorage(&res.Items[i])
}
return res, nil
}
// Default pool values when reading from storage
func convertIpPoolFromStorage(pool *apiv3.IPPool) error {
// Default the blockSize if it wasn't previously set
if pool.Spec.BlockSize == 0 {
// Get the IP address of the CIDR to find the IP version
ipAddr, _, err := cnet.ParseCIDR(pool.Spec.CIDR)
if err != nil {
return cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR must be a valid subnet",
Value: pool.Spec.CIDR,
}},
}
}
if ipAddr.Version() == 4 {
pool.Spec.BlockSize = 26
} else {
pool.Spec.BlockSize = 122
}
}
// Default the nodeSelector if it wasn't previously set.
if pool.Spec.NodeSelector == "" {
pool.Spec.NodeSelector = "all()"
}
return nil
}
// Watch returns a watch.Interface that watches the IPPools that match the
// supplied options.
func (r ipPools) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindIPPool, nil)
}
// validateAndSetDefaults validates IPPool fields and sets default values that are
// not assigned.
// The old pool will be unassigned for a Create.
func (r ipPools) validateAndSetDefaults(ctx context.Context, new, old *apiv3.IPPool) error {
errFields := []cerrors.ErroredField{}
// Spec.CIDR field must not be empty.
if new.Spec.CIDR == "" {
return cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR must be specified",
}},
}
}
// Make sure the CIDR is parsable.
ipAddr, cidr, err := cnet.ParseCIDR(new.Spec.CIDR)
if err != nil {
return cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR must be a valid subnet",
Value: new.Spec.CIDR,
}},
}
}
// Normalize the CIDR before persisting.
new.Spec.CIDR = cidr.String()
// If a nodeSelector is not specified, then this IP pool selects all nodes.
if new.Spec.NodeSelector == "" {
new.Spec.NodeSelector = "all()"
}
// If there was a previous pool then this must be an Update, validate that the
// CIDR has not changed. Since we are using normalized CIDRs we can just do a
// simple string comparison.
if old != nil && old.Spec.CIDR != new.Spec.CIDR {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR cannot be modified",
Value: new.Spec.CIDR,
})
}
// Default the blockSize
if new.Spec.BlockSize == 0 {
if ipAddr.Version() == 4 {
new.Spec.BlockSize = 26
} else {
new.Spec.BlockSize = 122
}
}
// Check that the blockSize hasn't changed since updates are not supported.
if old != nil && old.Spec.BlockSize != new.Spec.BlockSize {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.BlockSize",
Reason: "IPPool BlockSize cannot be modified",
Value: new.Spec.BlockSize,
})
}
if ipAddr.Version() == 4 {
if new.Spec.BlockSize > 32 || new.Spec.BlockSize < 20 {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.BlockSize",
Reason: "IPv4 block size must be between 20 and 32",
Value: new.Spec.BlockSize,
})
}
} else {
if new.Spec.BlockSize > 128 || new.Spec.BlockSize < 116 {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.BlockSize",
Reason: "IPv6 block size must be between 116 and 128",
Value: new.Spec.BlockSize,
})
}
}
// The Calico IPAM places restrictions on the minimum IP pool size. If
// the ippool is enabled, check that the pool is at least the minimum size.
if !new.Spec.Disabled {
ones, _ := cidr.Mask.Size()
log.Debugf("Pool CIDR: %s, mask: %d, blockSize: %d", cidr.String(), ones, new.Spec.BlockSize)
if ones > new.Spec.BlockSize {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: "IP pool size is too small for use with Calico IPAM. It must be equal to or greater than the block size.",
Value: new.Spec.CIDR,
})
}
}
// If there was no previous pool then this must be a Create. Check that the CIDR
// does not overlap with any other pool CIDRs.
if old == nil {
allPools, err := r.List(ctx, options.ListOptions{})
if err != nil {
return err
}
for _, otherPool := range allPools.Items {
// It's possible that Create is called for a pre-existing pool, so skip our own
// pool and let the generic processing handle the pre-existing resource error case.
if otherPool.Name == new.Name {
continue
}
_, otherCIDR, err := cnet.ParseCIDR(otherPool.Spec.CIDR)
if err != nil {
log.WithField("Name", otherPool.Name).WithError(err).Error("IPPool is configured with an invalid CIDR")
continue
}
if otherCIDR.IsNetOverlap(cidr.IPNet) {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: fmt.Sprintf("IPPool(%s) CIDR overlaps with IPPool(%s) CIDR %s", new.Name, otherPool.Name, otherPool.Spec.CIDR),
Value: new.Spec.CIDR,
})
}
}
}
// Make sure IPIPMode is defaulted to "Never".
if len(new.Spec.IPIPMode) == 0 {
new.Spec.IPIPMode = apiv3.IPIPModeNever
}
// Make sure VXLANMode is defaulted to "Never".
if len(new.Spec.VXLANMode) == 0 {
new.Spec.VXLANMode = apiv3.VXLANModeNever
}
// Make sure only one of VXLAN and IPIP is enabled.
if new.Spec.VXLANMode != apiv3.VXLANModeNever && new.Spec.IPIPMode != apiv3.IPIPModeNever {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.VXLANMode",
Reason: "Cannot enable both VXLAN and IPIP on the same IPPool",
Value: new.Spec.VXLANMode,
})
}
// IPIP cannot be enabled for IPv6.
if cidr.Version() == 6 && new.Spec.IPIPMode != apiv3.IPIPModeNever {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.IPIPMode",
Reason: "IPIP is not supported on an IPv6 IP pool",
Value: new.Spec.IPIPMode,
})
}
// The Calico CIDR should be strictly masked
log.Debugf("IPPool CIDR: %s, Masked IP: %d", new.Spec.CIDR, cidr.IP)
if cidr.IP.String() != ipAddr.String() {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR is not strictly masked",
Value: new.Spec.CIDR,
})
}
// IPv4 link local subnet.
ipv4LinkLocalNet := net.IPNet{
IP: net.ParseIP("169.254.0.0"),
Mask: net.CIDRMask(16, 32),
}
// IPv6 link local subnet.
ipv6LinkLocalNet := net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(10, 128),
}
// IP Pool CIDR cannot overlap with IPv4 or IPv6 link local address range.
if cidr.Version() == 4 && cidr.IsNetOverlap(ipv4LinkLocalNet) {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR overlaps with IPv4 Link Local range 169.254.0.0/16",
Value: new.Spec.CIDR,
})
}
if cidr.Version() == 6 && cidr.IsNetOverlap(ipv6LinkLocalNet) {
errFields = append(errFields, cerrors.ErroredField{
Name: "IPPool.Spec.CIDR",
Reason: "IPPool CIDR overlaps with IPv6 Link Local range fe80::/10",
Value: new.Spec.CIDR,
})
}
// Return the errors if we have one or more validation errors.
if len(errFields) > 0 {
return cerrors.ErrorValidation{
ErroredFields: errFields,
}
}
return nil
}
// maybeEnableIPIP enables global IPIP if a default setting is not already configured
// and the pool has IPIP enabled.
func (c ipPools) maybeEnableIPIP(ctx context.Context, pool *apiv3.IPPool) error {
if pool.Spec.IPIPMode == apiv3.IPIPModeNever {
log.Debug("IPIP is not enabled for this pool - no need to check global setting")
return nil
}
var err error
ipEnabled := true
for i := 0; i < maxApplyRetries; i++ {
log.WithField("Retry", i).Debug("Checking global IPIP setting")
res, err := c.client.FelixConfigurations().Get(ctx, "default", options.GetOptions{})
if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok && err != nil {
log.WithError(err).Debug("Error getting current FelixConfiguration resource")
return err
}
if res == nil {
log.Debug("Global FelixConfiguration does not exist - creating")
res = apiv3.NewFelixConfiguration()
res.Name = "default"
} else if res.Spec.IPIPEnabled != nil {
// A value for the default config is set so leave unchanged. It may be set to false,
// so log the actual value - but we shouldn't update it if someone has explicitly
// disabled it globally.
log.WithField("IPIPEnabled", res.Spec.IPIPEnabled).Debug("Global IPIPEnabled setting is already configured")
return nil
}
// Enable IpInIp and do the Create or Update.
res.Spec.IPIPEnabled = &ipEnabled
if res.ResourceVersion == "" {
res, err = c.client.FelixConfigurations().Create(ctx, res, options.SetOptions{})
if _, ok := err.(cerrors.ErrorResourceAlreadyExists); ok {
log.Debug("FelixConfiguration already exists - retry update")
continue
}
} else {
res, err = c.client.FelixConfigurations().Update(ctx, res, options.SetOptions{})
if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok {
log.Debug("FelixConfiguration update conflict - retry update")
continue
}
}
if err == nil {
log.Debug("FelixConfiguration updated successfully")
return nil
}
log.WithError(err).Debug("Error updating FelixConfiguration to enable IPIP")
return err
}
// Return the error from the final Update.
log.WithError(err).Info("Too many conflict failures attempting to update FelixConfiguration to enable IPIP")
return err
}
// maybeEnableVXLAN enables global VXLAN if a default setting is not already configured
// and the pool has VXLAN enabled.
func (c ipPools) maybeEnableVXLAN(ctx context.Context, pool *apiv3.IPPool) error {
if pool.Spec.VXLANMode == apiv3.VXLANModeNever {
log.Debug("VXLAN is not enabled for this pool - no need to check global setting")
return nil
}
var err error
ipEnabled := true
for i := 0; i < maxApplyRetries; i++ {
log.WithField("Retry", i).Debug("Checking global VXLAN setting")
res, err := c.client.FelixConfigurations().Get(ctx, "default", options.GetOptions{})
if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok && err != nil {
log.WithError(err).Debug("Error getting current FelixConfiguration resource")
return err
}
if res == nil {
log.Debug("Global FelixConfiguration does not exist - creating")
res = apiv3.NewFelixConfiguration()
res.Name = "default"
} else if res.Spec.VXLANEnabled != nil {
// A value for the default config is set so leave unchanged. It may be set to false,
// so log the actual value - but we shouldn't update it if someone has explicitly
// disabled it globally.
log.WithField("VXLANEnabled", res.Spec.VXLANEnabled).Debug("Global VXLANEnabled setting is already configured")
return nil
}
// Enable IpInIp and do the Create or Update.
res.Spec.VXLANEnabled = &ipEnabled
if res.ResourceVersion == "" {
res, err = c.client.FelixConfigurations().Create(ctx, res, options.SetOptions{})
if _, ok := err.(cerrors.ErrorResourceAlreadyExists); ok {
log.Debug("FelixConfiguration already exists - retry update")
continue
}
} else {
res, err = c.client.FelixConfigurations().Update(ctx, res, options.SetOptions{})
if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok {
log.Debug("FelixConfiguration update conflict - retry update")
continue
}
}
if err == nil {
log.Debug("FelixConfiguration updated successfully")
return nil
}
log.WithError(err).Debug("Error updating FelixConfiguration to enable VXLAN")
return err
}
// Return the error from the final Update.
log.WithError(err).Info("Too many conflict failures attempting to update FelixConfiguration to enable VXLAN")
return err
}

View File

@@ -0,0 +1,152 @@
// Copyright (c) 2017-2018 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// NetworkPolicyInterface has methods to work with NetworkPolicy resources.
type NetworkPolicyInterface interface {
Create(ctx context.Context, res *apiv3.NetworkPolicy, opts options.SetOptions) (*apiv3.NetworkPolicy, error)
Update(ctx context.Context, res *apiv3.NetworkPolicy, opts options.SetOptions) (*apiv3.NetworkPolicy, error)
Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.NetworkPolicy, error)
Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.NetworkPolicy, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.NetworkPolicyList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// networkPolicies implements NetworkPolicyInterface
type networkPolicies struct {
client client
}
// Create takes the representation of a NetworkPolicy and creates it. Returns the stored
// representation of the NetworkPolicy, and an error, if there is any.
func (r networkPolicies) Create(ctx context.Context, res *apiv3.NetworkPolicy, opts options.SetOptions) (*apiv3.NetworkPolicy, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types)
if err := validator.Validate(res); err != nil {
return nil, err
}
// Properly prefix the name
res.GetObjectMeta().SetName(convertPolicyNameForStorage(res.GetObjectMeta().GetName()))
out, err := r.client.resources.Create(ctx, opts, apiv3.KindNetworkPolicy, res)
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.NetworkPolicy), err
}
// Remove the prefix out of the returned policy name.
res.GetObjectMeta().SetName(convertPolicyNameFromStorage(res.GetObjectMeta().GetName()))
return nil, err
}
// Update takes the representation of a NetworkPolicy and updates it. Returns the stored
// representation of the NetworkPolicy, and an error, if there is any.
func (r networkPolicies) Update(ctx context.Context, res *apiv3.NetworkPolicy, opts options.SetOptions) (*apiv3.NetworkPolicy, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types)
if err := validator.Validate(res); err != nil {
return nil, err
}
// Properly prefix the name
res.GetObjectMeta().SetName(convertPolicyNameForStorage(res.GetObjectMeta().GetName()))
out, err := r.client.resources.Update(ctx, opts, apiv3.KindNetworkPolicy, res)
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.NetworkPolicy), err
}
// Remove the prefix out of the returned policy name.
res.GetObjectMeta().SetName(convertPolicyNameFromStorage(res.GetObjectMeta().GetName()))
return nil, err
}
// Delete takes name of the NetworkPolicy and deletes it. Returns an error if one occurs.
func (r networkPolicies) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.NetworkPolicy, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindNetworkPolicy, namespace, convertPolicyNameForStorage(name))
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.NetworkPolicy), err
}
return nil, err
}
// Get takes name of the NetworkPolicy, and returns the corresponding NetworkPolicy object,
// and an error if there is any.
func (r networkPolicies) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.NetworkPolicy, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindNetworkPolicy, namespace, convertPolicyNameForStorage(name))
if out != nil {
// Remove the prefix out of the returned policy name.
out.GetObjectMeta().SetName(convertPolicyNameFromStorage(out.GetObjectMeta().GetName()))
return out.(*apiv3.NetworkPolicy), err
}
return nil, err
}
// List returns the list of NetworkPolicy objects that match the supplied options.
func (r networkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.NetworkPolicyList, error) {
res := &apiv3.NetworkPolicyList{}
// Add the name prefix if name is provided
if opts.Name != "" {
opts.Name = convertPolicyNameForStorage(opts.Name)
}
if err := r.client.resources.List(ctx, opts, apiv3.KindNetworkPolicy, apiv3.KindNetworkPolicyList, res); err != nil {
return nil, err
}
// Remove the prefix off of each policy name
for i, _ := range res.Items {
name := res.Items[i].GetObjectMeta().GetName()
res.Items[i].GetObjectMeta().SetName(convertPolicyNameFromStorage(name))
}
return res, nil
}
// Watch returns a watch.Interface that watches the NetworkPolicies that match the
// supplied options.
func (r networkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
// Add the name prefix if name is provided
if opts.Name != "" {
opts.Name = convertPolicyNameForStorage(opts.Name)
}
return r.client.resources.Watch(ctx, opts, apiv3.KindNetworkPolicy, &policyConverter{})
}

View File

@@ -0,0 +1,99 @@
// Copyright (c) 2018-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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// NetworkSetInterface has methods to work with NetworkSet resources.
type NetworkSetInterface interface {
Create(ctx context.Context, res *apiv3.NetworkSet, opts options.SetOptions) (*apiv3.NetworkSet, error)
Update(ctx context.Context, res *apiv3.NetworkSet, opts options.SetOptions) (*apiv3.NetworkSet, error)
Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.NetworkSet, error)
Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.NetworkSet, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.NetworkSetList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// networkSets implements NetworkSetInterface
type networkSets struct {
client client
}
// Create takes the representation of a NetworkSet and creates it. Returns the stored
// representation of the NetworkSet, and an error, if there is any.
func (r networkSets) Create(ctx context.Context, res *apiv3.NetworkSet, opts options.SetOptions) (*apiv3.NetworkSet, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindNetworkSet, res)
if out != nil {
return out.(*apiv3.NetworkSet), err
}
return nil, err
}
// Update takes the representation of a NetworkSet and updates it. Returns the stored
// representation of the NetworkSet, and an error, if there is any.
func (r networkSets) Update(ctx context.Context, res *apiv3.NetworkSet, opts options.SetOptions) (*apiv3.NetworkSet, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindNetworkSet, res)
if out != nil {
return out.(*apiv3.NetworkSet), err
}
return nil, err
}
// Delete takes name of the NetworkSet and deletes it. Returns an error if one occurs.
func (r networkSets) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.NetworkSet, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindNetworkSet, namespace, name)
if out != nil {
return out.(*apiv3.NetworkSet), err
}
return nil, err
}
// Get takes name of the NetworkSet, and returns the corresponding NetworkSet object,
// and an error if there is any.
func (r networkSets) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.NetworkSet, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindNetworkSet, namespace, name)
if out != nil {
return out.(*apiv3.NetworkSet), err
}
return nil, err
}
// List returns the list of NetworkSet objects that match the supplied options.
func (r networkSets) List(ctx context.Context, opts options.ListOptions) (*apiv3.NetworkSetList, error) {
res := &apiv3.NetworkSetList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindNetworkSet, apiv3.KindNetworkSetList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the NetworkSets that match the
// supplied options.
func (r networkSets) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindNetworkSet, nil)
}

View File

@@ -0,0 +1,212 @@
// Copyright (c) 2017 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"
"fmt"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/errors"
"github.com/projectcalico/libcalico-go/lib/names"
"github.com/projectcalico/libcalico-go/lib/net"
cnet "github.com/projectcalico/libcalico-go/lib/net"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
log "github.com/sirupsen/logrus"
)
// NodeInterface has methods to work with Node resources.
type NodeInterface interface {
Create(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error)
Update(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Node, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.Node, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.NodeList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// nodes implements NodeInterface
type nodes struct {
client client
}
// Create takes the representation of a Node and creates it. Returns the stored
// representation of the Node, and an error, if there is any.
func (r nodes) Create(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
// For host-protection only clusters, we instruct the user to create a Node as the first
// operation. Piggy-back the datastore initialisation on that to ensure the Ready flag gets
// set. Since we're likely being called from calicoctl, we don't know the Calico version.
err := r.client.EnsureInitialized(ctx, "", "")
if err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindNode, res)
if out != nil {
return out.(*apiv3.Node), err
}
return nil, err
}
// Update takes the representation of a Node and updates it. Returns the stored
// representation of the Node, and an error, if there is any.
func (r nodes) Update(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindNode, res)
if out != nil {
return out.(*apiv3.Node), err
}
return nil, err
}
// Delete takes name of the Node and deletes it. Returns an error if one occurs.
func (r nodes) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Node, error) {
pname, err := names.WorkloadEndpointIdentifiers{Node: name}.CalculateWorkloadEndpointName(true)
if err != nil {
return nil, err
}
// Get all weps belonging to the node
weps, err := r.client.WorkloadEndpoints().List(ctx, options.ListOptions{
Prefix: true,
Name: pname,
})
if err != nil {
return nil, err
}
// Collate all IPs across all endpoints, and then release those IPs.
ips := []net.IP{}
for _, wep := range weps.Items {
// The prefix match is unfortunately not a perfect match on the Node (since it is theoretically possible for
// another node to match the prefix (e.g. a node name of the format <thisnode>-foobar would also match a prefix
// search of the node <thisnode>). Therefore, we will also need to check that the Spec.Node field matches the Node.
if wep.Spec.Node != name {
continue
}
for _, ip := range wep.Spec.IPNetworks {
ipAddr, _, err := cnet.ParseCIDROrIP(ip)
if err == nil {
ips = append(ips, *ipAddr)
} else {
// Validation for wep insists upon CIDR, so we should always succeed
log.WithError(err).Warnf("Failed to parse CIDR: %s", ip)
}
}
}
_, err = r.client.IPAM().ReleaseIPs(context.Background(), ips)
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
// Delete the weps.
for _, wep := range weps.Items {
if wep.Spec.Node != name {
continue
}
_, err = r.client.WorkloadEndpoints().Delete(ctx, wep.Namespace, wep.Name, options.DeleteOptions{})
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
}
// Remove the node from the IPAM data if it exists.
err = r.client.IPAM().RemoveIPAMHost(ctx, name)
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
// Remove BGPPeers.
bgpPeers, err := r.client.BGPPeers().List(ctx, options.ListOptions{})
if err != nil {
return nil, err
}
for _, peer := range bgpPeers.Items {
if peer.Spec.Node != name {
continue
}
_, err = r.client.BGPPeers().Delete(ctx, peer.Name, options.DeleteOptions{})
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
}
// Delete felix configuration
nodeConfName := fmt.Sprintf("node.%s", name)
_, err = r.client.FelixConfigurations().Delete(ctx, nodeConfName, options.DeleteOptions{})
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
// Delete bgp configuration
_, err = r.client.BGPConfigurations().Delete(ctx, nodeConfName, options.DeleteOptions{})
switch err.(type) {
case nil, errors.ErrorResourceDoesNotExist, errors.ErrorOperationNotSupported:
default:
return nil, err
}
// Delete the node.
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindNode, noNamespace, name)
if out != nil {
return out.(*apiv3.Node), err
}
return nil, err
}
// Get takes name of the Node, and returns the corresponding Node object,
// and an error if there is any.
func (r nodes) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.Node, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindNode, noNamespace, name)
if out != nil {
return out.(*apiv3.Node), err
}
return nil, err
}
// List returns the list of Node objects that match the supplied options.
func (r nodes) List(ctx context.Context, opts options.ListOptions) (*apiv3.NodeList, error) {
res := &apiv3.NodeList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindNode, apiv3.KindNodeList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the Nodes that match the
// supplied options.
func (r nodes) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindNode, nil)
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2017 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"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// ProfileInterface has methods to work with Profile resources.
type ProfileInterface interface {
Create(ctx context.Context, res *apiv3.Profile, opts options.SetOptions) (*apiv3.Profile, error)
Update(ctx context.Context, res *apiv3.Profile, opts options.SetOptions) (*apiv3.Profile, error)
Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Profile, error)
Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.Profile, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.ProfileList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// profiles implements ProfileInterface
type profiles struct {
client client
}
// Create takes the representation of a Profile and creates it. Returns the stored
// representation of the Profile, and an error, if there is any.
func (r profiles) Create(ctx context.Context, res *apiv3.Profile, opts options.SetOptions) (*apiv3.Profile, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Create(ctx, opts, apiv3.KindProfile, res)
if out != nil {
return out.(*apiv3.Profile), err
}
return nil, err
}
// Update takes the representation of a Profile and updates it. Returns the stored
// representation of the Profile, and an error, if there is any.
func (r profiles) Update(ctx context.Context, res *apiv3.Profile, opts options.SetOptions) (*apiv3.Profile, error) {
if err := validator.Validate(res); err != nil {
return nil, err
}
out, err := r.client.resources.Update(ctx, opts, apiv3.KindProfile, res)
if out != nil {
return out.(*apiv3.Profile), err
}
return nil, err
}
// Delete takes name of the Profile and deletes it. Returns an error if one occurs.
func (r profiles) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Profile, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindProfile, noNamespace, name)
if out != nil {
return out.(*apiv3.Profile), err
}
return nil, err
}
// Get takes name of the Profile, and returns the corresponding Profile object,
// and an error if there is any.
func (r profiles) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.Profile, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindProfile, noNamespace, name)
if out != nil {
return out.(*apiv3.Profile), err
}
return nil, err
}
// List returns the list of Profile objects that match the supplied options.
func (r profiles) List(ctx context.Context, opts options.ListOptions) (*apiv3.ProfileList, error) {
res := &apiv3.ProfileList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindProfile, apiv3.KindProfileList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the Profiles that match the
// supplied options.
func (r profiles) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindProfile, nil)
}

View File

@@ -0,0 +1,434 @@
// Copyright (c) 2017 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"
"sync/atomic"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/uuid"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
bapi "github.com/projectcalico/libcalico-go/lib/backend/api"
"github.com/projectcalico/libcalico-go/lib/backend/model"
cerrors "github.com/projectcalico/libcalico-go/lib/errors"
"github.com/projectcalico/libcalico-go/lib/namespace"
"github.com/projectcalico/libcalico-go/lib/options"
"github.com/projectcalico/libcalico-go/lib/watch"
)
const (
noNamespace = ""
defaultNamespace = "default"
maxApplyRetries = 10
)
// All Calico resources implement the resource interface.
type resource interface {
runtime.Object
v1.ObjectMetaAccessor
}
// All Calico resource lists implement the resourceList interface.
type resourceList interface {
runtime.Object
v1.ListMetaAccessor
}
// resourceInterface has methods to work with generic resource types.
type resourceInterface interface {
Create(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error)
Update(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error)
Delete(ctx context.Context, opts options.DeleteOptions, kind, ns, name string) (resource, error)
Get(ctx context.Context, opts options.GetOptions, kind, ns, name string) (resource, error)
List(ctx context.Context, opts options.ListOptions, kind, listkind string, inout resourceList) error
Watch(ctx context.Context, opts options.ListOptions, kind string, converter watcherConverter) (watch.Interface, error)
}
// resources implements resourceInterface.
type resources struct {
backend bapi.Client
}
// Create creates a resource in the backend datastore.
func (c *resources) Create(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) {
// Resource must have a Name. Currently we do not support GenerateName.
if len(in.GetObjectMeta().GetName()) == 0 {
var generateNameMessage string
if len(in.GetObjectMeta().GetGenerateName()) != 0 {
generateNameMessage = " (GenerateName is not supported)"
}
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.Name",
Reason: "field must be set for a Create request" + generateNameMessage,
Value: in.GetObjectMeta().GetName(),
}},
}
}
// A ResourceVersion should never be specified on a Create.
if len(in.GetObjectMeta().GetResourceVersion()) != 0 {
logWithResource(in).Info("Rejecting Create request with non-empty resource version")
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.ResourceVersion",
Reason: "field must not be set for a Create request",
Value: in.GetObjectMeta().GetResourceVersion(),
}},
}
}
if err := c.checkNamespace(in.GetObjectMeta().GetNamespace(), kind); err != nil {
return nil, err
}
// Add in the UID and creation timestamp for the resource if needed.
creationTimestamp := in.GetObjectMeta().GetCreationTimestamp()
if creationTimestamp.IsZero() {
in.GetObjectMeta().SetCreationTimestamp(v1.Now())
}
if in.GetObjectMeta().GetUID() == "" {
in.GetObjectMeta().SetUID(uuid.NewUUID())
}
// Convert the resource to a KVPair and pass that to the backend datastore, converting
// the response (if we get one) back to a resource.
kvp, err := c.backend.Create(ctx, c.resourceToKVPair(opts, kind, in))
if kvp != nil {
return c.kvPairToResource(kvp), err
}
return nil, err
}
// Update updates a resource in the backend datastore.
func (c *resources) Update(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) {
// A ResourceVersion should always be specified on an Update.
if len(in.GetObjectMeta().GetResourceVersion()) == 0 {
logWithResource(in).Info("Rejecting Update request with empty resource version")
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.ResourceVersion",
Reason: "field must be set for an Update request",
Value: in.GetObjectMeta().GetResourceVersion(),
}},
}
}
if err := c.checkNamespace(in.GetObjectMeta().GetNamespace(), kind); err != nil {
return nil, err
}
creationTimestamp := in.GetObjectMeta().GetCreationTimestamp()
if creationTimestamp.IsZero() {
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.CreationTimestamp",
Reason: "field must be set for an Update request",
Value: in.GetObjectMeta().GetCreationTimestamp(),
}},
}
}
if in.GetObjectMeta().GetUID() == "" {
return nil, cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.UID",
Reason: "field must be set for an Update request",
Value: in.GetObjectMeta().GetUID(),
}},
}
}
// Convert the resource to a KVPair and pass that to the backend datastore, converting
// the response (if we get one) back to a resource.
kvp, err := c.backend.Update(ctx, c.resourceToKVPair(opts, kind, in))
if kvp != nil {
return c.kvPairToResource(kvp), err
}
return nil, err
}
// Delete deletes a resource from the backend datastore.
func (c *resources) Delete(ctx context.Context, opts options.DeleteOptions, kind, ns, name string) (resource, error) {
if err := c.checkNamespace(ns, kind); err != nil {
return nil, err
}
// Create a ResourceKey and pass that to the backend datastore.
key := model.ResourceKey{
Kind: kind,
Name: name,
Namespace: ns,
}
kvp, err := c.backend.Delete(ctx, key, opts.ResourceVersion)
if kvp != nil {
return c.kvPairToResource(kvp), err
}
return nil, err
}
// Get gets a resource from the backend datastore.
func (c *resources) Get(ctx context.Context, opts options.GetOptions, kind, ns, name string) (resource, error) {
if err := c.checkNamespace(ns, kind); err != nil {
return nil, err
}
key := model.ResourceKey{
Kind: kind,
Name: name,
Namespace: ns,
}
kvp, err := c.backend.Get(ctx, key, opts.ResourceVersion)
if err != nil {
return nil, err
}
out := c.kvPairToResource(kvp)
return out, nil
}
// List lists a resource from the backend datastore.
func (c *resources) List(ctx context.Context, opts options.ListOptions, kind, listKind string, listObj resourceList) error {
list := model.ResourceListOptions{
Kind: kind,
Name: opts.Name,
Namespace: opts.Namespace,
Prefix: opts.Prefix,
}
// Query the backend.
kvps, err := c.backend.List(ctx, list, opts.ResourceVersion)
if err != nil {
return err
}
// Convert the slice of KVPairs to a slice of Objects.
resources := []runtime.Object{}
for _, kvp := range kvps.KVPairs {
resources = append(resources, c.kvPairToResource(kvp))
}
err = meta.SetList(listObj, resources)
if err != nil {
return err
}
// Finally, set the resource version and api group version of the list object.
listObj.GetListMeta().SetResourceVersion(kvps.Revision)
listObj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
Group: apiv3.Group,
Version: apiv3.VersionCurrent,
Kind: listKind,
})
return nil
}
// Watch watches a specific resource or resource type.
func (c *resources) Watch(ctx context.Context, opts options.ListOptions, kind string, converter watcherConverter) (watch.Interface, error) {
list := model.ResourceListOptions{
Kind: kind,
Name: opts.Name,
Namespace: opts.Namespace,
}
// Create the backend watcher. We need to process the results to add revision data etc.
ctx, cancel := context.WithCancel(ctx)
backend, err := c.backend.Watch(ctx, list, opts.ResourceVersion)
if err != nil {
return nil, err
}
w := &watcher{
results: make(chan watch.Event, 100),
client: c,
cancel: cancel,
context: ctx,
backend: backend,
converter: converter,
}
go w.run()
return w, nil
}
// resourceToKVPair converts the resource to a KVPair that can be consumed by the
// backend datastore client.
func (c *resources) resourceToKVPair(opts options.SetOptions, kind string, in resource) *model.KVPair {
// Prepare the resource to remove non-persisted fields.
rv := in.GetObjectMeta().GetResourceVersion()
in.GetObjectMeta().SetResourceVersion("")
in.GetObjectMeta().SetSelfLink("")
// Make sure the kind and version are set before storing.
in.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
Group: apiv3.Group,
Version: apiv3.VersionCurrent,
Kind: kind,
})
// Create a KVPair using the "generic" resource Key, and the actual object as
// the value.
return &model.KVPair{
TTL: opts.TTL,
Value: in,
Key: model.ResourceKey{
Kind: kind,
Name: in.GetObjectMeta().GetName(),
Namespace: in.GetObjectMeta().GetNamespace(),
},
Revision: rv,
}
}
// kvPairToResource converts a KVPair returned by the backend datastore client to a
// resource.
func (c *resources) kvPairToResource(kvp *model.KVPair) resource {
// Extract the resource from the returned value - the backend will already have
// decoded it.
out := kvp.Value.(resource)
// Remove the SelfLink which Calico does not use, and set the ResourceVersion from the
// value returned from the backend datastore.
out.GetObjectMeta().SetSelfLink("")
out.GetObjectMeta().SetResourceVersion(kvp.Revision)
return out
}
// checkNamespace checks that the namespace is supplied on a namespaced resource type.
func (c *resources) checkNamespace(ns, kind string) error {
if namespace.IsNamespaced(kind) && len(ns) == 0 {
return cerrors.ErrorValidation{
ErroredFields: []cerrors.ErroredField{{
Name: "Metadata.Namespace",
Reason: "namespace is not specified on namespaced resource",
}},
}
}
return nil
}
// watcher implements the watch.Interface.
type watcher struct {
backend bapi.WatchInterface
context context.Context
cancel context.CancelFunc
results chan watch.Event
client *resources
terminated uint32
converter watcherConverter
}
func (w *watcher) Stop() {
w.cancel()
}
func (w *watcher) ResultChan() <-chan watch.Event {
return w.results
}
// run is the main watch loop, pulling events from the backend watcher and sending
// down the results channel.
func (w *watcher) run() {
log.Info("Main client watcher loop")
// Make sure we terminate resources if we exit.
defer w.terminate()
for {
select {
case event, ok := <-w.backend.ResultChan():
if !ok {
log.Debug("Watcher results channel closed by remote")
return
}
e := w.convertEvent(event)
select {
case w.results <- e:
case <-w.context.Done():
log.Info("Process backend watcher done event during watch event in main client")
return
}
case <-w.context.Done(): // user cancel
log.Info("Process backend watcher done event in main client")
return
}
}
}
// terminate all resources associated with this watcher.
func (w *watcher) terminate() {
log.Info("Terminating main client watcher loop")
w.cancel()
close(w.results)
atomic.AddUint32(&w.terminated, 1)
}
// convertEvent converts a backend watch event into a client watch event.
func (w *watcher) convertEvent(backendEvent bapi.WatchEvent) watch.Event {
apiEvent := watch.Event{
Error: backendEvent.Error,
}
switch backendEvent.Type {
case bapi.WatchError:
apiEvent.Type = watch.Error
case bapi.WatchAdded:
apiEvent.Type = watch.Added
case bapi.WatchDeleted:
apiEvent.Type = watch.Deleted
case bapi.WatchModified:
apiEvent.Type = watch.Modified
}
if backendEvent.Old != nil {
res := w.client.kvPairToResource(backendEvent.Old)
if w.converter != nil {
res = w.converter.Convert(res)
}
apiEvent.Previous = res
}
if backendEvent.New != nil {
res := w.client.kvPairToResource(backendEvent.New)
if w.converter != nil {
apiEvent.Object = w.converter.Convert(res)
}
apiEvent.Object = res
}
return apiEvent
}
// hasTerminated returns true if the watcher has terminated, release all resources.
// Used for test purposes.
func (w *watcher) hasTerminated() bool {
t := atomic.LoadUint32(&w.terminated) != 0
bt := w.backend.HasTerminated()
log.Infof("hasTerminated() terminated=%v; backend-terminated=%v", t, bt)
return t && bt
}
// logWithResource returns a logrus entry with key resource attributes included.
func logWithResource(res resource) *log.Entry {
return log.WithFields(log.Fields{
"Kind": res.GetObjectKind().GroupVersionKind(),
"Name": res.GetObjectMeta().GetName(),
"Namespace": res.GetObjectMeta().GetNamespace(),
"ResourceVersion": res.GetObjectMeta().GetResourceVersion(),
})
}
// watcherConverter represents a formatter for calico resources returned by Watch.
type watcherConverter interface {
// Convert the internal representation of a resource to a readable format.
Convert(resource) resource
}

View File

@@ -0,0 +1,166 @@
// Copyright (c) 2017-2018 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"
"fmt"
apiv3 "github.com/projectcalico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/libcalico-go/lib/errors"
"github.com/projectcalico/libcalico-go/lib/names"
"github.com/projectcalico/libcalico-go/lib/options"
validator "github.com/projectcalico/libcalico-go/lib/validator/v3"
"github.com/projectcalico/libcalico-go/lib/watch"
)
// WorkloadEndpointInterface has methods to work with WorkloadEndpoint resources.
type WorkloadEndpointInterface interface {
Create(ctx context.Context, res *apiv3.WorkloadEndpoint, opts options.SetOptions) (*apiv3.WorkloadEndpoint, error)
Update(ctx context.Context, res *apiv3.WorkloadEndpoint, opts options.SetOptions) (*apiv3.WorkloadEndpoint, error)
Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.WorkloadEndpoint, error)
Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.WorkloadEndpoint, error)
List(ctx context.Context, opts options.ListOptions) (*apiv3.WorkloadEndpointList, error)
Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error)
}
// workloadEndpoints implements WorkloadEndpointInterface
type workloadEndpoints struct {
client client
}
// Create takes the representation of a WorkloadEndpoint and creates it. Returns the stored
// representation of the WorkloadEndpoint, and an error, if there is any.
func (r workloadEndpoints) Create(ctx context.Context, res *apiv3.WorkloadEndpoint, opts options.SetOptions) (*apiv3.WorkloadEndpoint, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
if err := r.assignOrValidateName(res); err != nil {
return nil, err
} else if err := validator.Validate(res); err != nil {
return nil, err
}
r.updateLabelsForStorage(res)
out, err := r.client.resources.Create(ctx, opts, apiv3.KindWorkloadEndpoint, res)
if out != nil {
return out.(*apiv3.WorkloadEndpoint), err
}
return nil, err
}
// Update takes the representation of a WorkloadEndpoint and updates it. Returns the stored
// representation of the WorkloadEndpoint, and an error, if there is any.
func (r workloadEndpoints) Update(ctx context.Context, res *apiv3.WorkloadEndpoint, opts options.SetOptions) (*apiv3.WorkloadEndpoint, error) {
if res != nil {
// Since we're about to default some fields, take a (shallow) copy of the input data
// before we do so.
resCopy := *res
res = &resCopy
}
if err := r.assignOrValidateName(res); err != nil {
return nil, err
} else if err := validator.Validate(res); err != nil {
return nil, err
}
r.updateLabelsForStorage(res)
out, err := r.client.resources.Update(ctx, opts, apiv3.KindWorkloadEndpoint, res)
if out != nil {
return out.(*apiv3.WorkloadEndpoint), err
}
return nil, err
}
// Delete takes name of the WorkloadEndpoint and deletes it. Returns an error if one occurs.
func (r workloadEndpoints) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.WorkloadEndpoint, error) {
out, err := r.client.resources.Delete(ctx, opts, apiv3.KindWorkloadEndpoint, namespace, name)
if out != nil {
return out.(*apiv3.WorkloadEndpoint), err
}
return nil, err
}
// Get takes name of the WorkloadEndpoint, and returns the corresponding WorkloadEndpoint object,
// and an error if there is any.
func (r workloadEndpoints) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*apiv3.WorkloadEndpoint, error) {
out, err := r.client.resources.Get(ctx, opts, apiv3.KindWorkloadEndpoint, namespace, name)
if out != nil {
return out.(*apiv3.WorkloadEndpoint), err
}
return nil, err
}
// List returns the list of WorkloadEndpoint objects that match the supplied options.
func (r workloadEndpoints) List(ctx context.Context, opts options.ListOptions) (*apiv3.WorkloadEndpointList, error) {
res := &apiv3.WorkloadEndpointList{}
if err := r.client.resources.List(ctx, opts, apiv3.KindWorkloadEndpoint, apiv3.KindWorkloadEndpointList, res); err != nil {
return nil, err
}
return res, nil
}
// Watch returns a watch.Interface that watches the NetworkPolicies that match the
// supplied options.
func (r workloadEndpoints) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) {
return r.client.resources.Watch(ctx, opts, apiv3.KindWorkloadEndpoint, nil)
}
// assignOrValidateName either assigns the name calculated from the Spec fields, or validates
// the name against the spec fields.
func (r workloadEndpoints) assignOrValidateName(res *apiv3.WorkloadEndpoint) error {
// Validate the workload endpoint indices and the name match.
wepids := names.WorkloadEndpointIdentifiers{
Node: res.Spec.Node,
Orchestrator: res.Spec.Orchestrator,
Endpoint: res.Spec.Endpoint,
Workload: res.Spec.Workload,
Pod: res.Spec.Pod,
ContainerID: res.Spec.ContainerID,
}
expectedName, err := wepids.CalculateWorkloadEndpointName(false)
if err != nil {
return err
}
if len(res.Name) == 0 {
// If a name was not specified then we will calculate it on behalf of the caller.
res.Name = expectedName
return nil
}
if res.Name != expectedName {
return errors.ErrorValidation{
ErroredFields: []errors.ErroredField{{
Name: "Name",
Value: res.Name,
Reason: fmt.Sprintf("the WorkloadEndpoint name does not match the primary identifiers assigned in the Spec: expected name %s", expectedName),
}},
}
}
return nil
}
// updateLabelsForStorage updates the set of labels that we persist. It adds/overrides
// the Namespace and Orchestrator labels which must be set to the correct values and are
// not user configurable.
func (r workloadEndpoints) updateLabelsForStorage(res *apiv3.WorkloadEndpoint) {
labelsCopy := make(map[string]string, len(res.GetLabels())+2)
for k, v := range res.GetLabels() {
labelsCopy[k] = v
}
labelsCopy[apiv3.LabelNamespace] = res.Namespace
labelsCopy[apiv3.LabelOrchestrator] = res.Spec.Orchestrator
res.SetLabels(labelsCopy)
}