gomod: change projectcalico/calico to kubesphere/calico (#5557)

* chore(calico): update calico to 3.25.0

* chore(calico): replace projectcalico/calico to kubesphere/calico

Signed-off-by: root <renyunkang@kubesphere.io>

---------

Signed-off-by: root <renyunkang@kubesphere.io>
This commit is contained in:
Yunkang Ren
2023-02-28 17:03:36 +08:00
committed by GitHub
parent dc28a0917a
commit a3a6a1cd98
146 changed files with 11189 additions and 4663 deletions

View File

@@ -0,0 +1,18 @@
// Copyright (c) 2016 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 encap implements a field type that represent different encap modes.
*/
package encap

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2016 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 encap
type Mode string
const (
Undefined Mode = ""
Always = "always"
CrossSubnet = "cross-subnet"
)
const DefaultMode = Always

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2017-2021 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 conversion
const (
NamespaceLabelPrefix = "pcns."
NamespaceProfileNamePrefix = "kns."
K8sNetworkPolicyNamePrefix = "knp.default."
ServiceAccountLabelPrefix = "pcsa."
ServiceAccountProfileNamePrefix = "ksa."
// AnnotationPodIP is an annotation we apply to pods when assigning them an IP. It
// duplicates the value of the Pod.Status.PodIP field, which is set by kubelet but,
// since we write it ourselves, we can make sure that it is written synchronously
// and quickly.
//
// We set this annotation to the empty string when the WEP is deleted by the CNI plugin.
// That signals that the IP no longer belongs to this pod.
AnnotationPodIP = "cni.projectcalico.org/podIP"
// AnnotationPodIPs is similar for the plural PodIPs field.
AnnotationPodIPs = "cni.projectcalico.org/podIPs"
// AnnotationPodIPs is the annotation set by the Amazon VPC CNI plugin.
AnnotationAWSPodIPs = "vpc.amazonaws.com/pod-ips"
// AnnotationContainerID stores the container ID of the pod. This allows us to disambiguate different pods
// that have the same name and namespace. For example, stateful set pod that is restarted. May be missing
// on older Pods.
AnnotationContainerID = "cni.projectcalico.org/containerID"
// NameLabel is a label that can be used to match a serviceaccount or namespace
// name exactly.
NameLabel = "projectcalico.org/name"
)

View File

@@ -0,0 +1,811 @@
// Copyright (c) 2016-2021 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 conversion
import (
"fmt"
"sort"
"strings"
log "github.com/sirupsen/logrus"
kapiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
discovery "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/util/intstr"
apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
"github.com/projectcalico/api/pkg/lib/numorstring"
"github.com/projectcalico/calico/libcalico-go/lib/backend/model"
cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/names"
cnet "github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
protoTCP = kapiv1.ProtocolTCP
)
type selectorType int8
const (
SelectorNamespace selectorType = iota
SelectorPod
)
type Converter interface {
WorkloadEndpointConverter
ParseWorkloadEndpointName(workloadName string) (names.WorkloadEndpointIdentifiers, error)
NamespaceToProfile(ns *kapiv1.Namespace) (*model.KVPair, error)
IsValidCalicoWorkloadEndpoint(pod *kapiv1.Pod) bool
IsReadyCalicoPod(pod *kapiv1.Pod) bool
IsScheduled(pod *kapiv1.Pod) bool
IsHostNetworked(pod *kapiv1.Pod) bool
HasIPAddress(pod *kapiv1.Pod) bool
StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string
K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error)
EndpointSliceToKVP(svc *discovery.EndpointSlice) (*model.KVPair, error)
ServiceToKVP(service *kapiv1.Service) (*model.KVPair, error)
ProfileNameToNamespace(profileName string) (string, error)
ServiceAccountToProfile(sa *kapiv1.ServiceAccount) (*model.KVPair, error)
ProfileNameToServiceAccount(profileName string) (ns, sa string, err error)
JoinProfileRevisions(nsRev, saRev string) string
SplitProfileRevision(rev string) (nsRev string, saRev string, err error)
}
type converter struct {
WorkloadEndpointConverter
}
func NewConverter() Converter {
return &converter{
WorkloadEndpointConverter: NewWorkloadEndpointConverter(),
}
}
// ParseWorkloadName extracts the Node name, Orchestrator, Pod name and endpoint from the
// given WorkloadEndpoint name.
// The expected format for k8s is <node>-k8s-<pod>-<endpoint>
func (c converter) ParseWorkloadEndpointName(workloadName string) (names.WorkloadEndpointIdentifiers, error) {
return names.ParseWorkloadEndpointName(workloadName)
}
// NamespaceToProfile converts a Namespace to a Calico Profile. The Profile stores
// labels from the Namespace which are inherited by the WorkloadEndpoints within
// the Profile. This Profile also has the default ingress and egress rules, which are both 'allow'.
func (c converter) NamespaceToProfile(ns *kapiv1.Namespace) (*model.KVPair, error) {
// Generate the labels to apply to the profile, using a special prefix
// to indicate that these are the labels from the parent Kubernetes Namespace.
labels := map[string]string{}
for k, v := range ns.Labels {
labels[NamespaceLabelPrefix+k] = v
}
// Add a label for the namespace's name. This allows exact namespace matching
// based on name within the namespaceSelector.
labels[NamespaceLabelPrefix+NameLabel] = ns.Name
// Create the profile object.
name := NamespaceProfileNamePrefix + ns.Name
profile := apiv3.NewProfile()
profile.ObjectMeta = metav1.ObjectMeta{
Name: name,
CreationTimestamp: ns.CreationTimestamp,
UID: ns.UID,
}
profile.Spec = apiv3.ProfileSpec{
Ingress: []apiv3.Rule{{Action: apiv3.Allow}},
Egress: []apiv3.Rule{{Action: apiv3.Allow}},
LabelsToApply: labels,
}
// Embed the profile in a KVPair.
kvp := model.KVPair{
Key: model.ResourceKey{
Name: name,
Kind: apiv3.KindProfile,
},
Value: profile,
Revision: c.JoinProfileRevisions(ns.ResourceVersion, ""),
}
return &kvp, nil
}
// IsValidCalicoWorkloadEndpoint returns true if the pod should be shown as a workloadEndpoint
// in the Calico API and false otherwise. Note: since we completely ignore notifications for
// invalid Pods, it is important that pods can only transition from not-valid to valid and not
// the other way. If they transition from valid to invalid, we'll fail to emit a deletion
// event in the watcher.
func (c converter) IsValidCalicoWorkloadEndpoint(pod *kapiv1.Pod) bool {
if c.IsHostNetworked(pod) {
log.WithField("pod", pod.Name).Debug("Pod is host networked.")
return false
} else if !c.IsScheduled(pod) {
log.WithField("pod", pod.Name).Debug("Pod is not scheduled.")
return false
}
return true
}
// IsReadyCalicoPod returns true if the pod is a valid Calico WorkloadEndpoint and has
// an IP address assigned (i.e. it's ready for Calico networking).
func (c converter) IsReadyCalicoPod(pod *kapiv1.Pod) bool {
if !c.IsValidCalicoWorkloadEndpoint(pod) {
return false
} else if !c.HasIPAddress(pod) {
log.WithField("pod", pod.Name).Debug("Pod does not have an IP address.")
return false
}
return true
}
const (
// Completed is documented but doesn't seem to be in the API, it should be safe to include.
// Maybe it's in an older version of the API?
podCompleted kapiv1.PodPhase = "Completed"
)
func IsFinished(pod *kapiv1.Pod) bool {
if pod.DeletionTimestamp != nil {
// Pod is being deleted but it may still be in its termination grace period. If Calico CNI
// was used, then we use AnnotationPodIP to signal the moment that the pod actually loses its
// IP by setting the annotation to "". (Otherwise, just fall back on the status of the pod.)
if ip, ok := pod.Annotations[AnnotationPodIP]; ok && ip == "" {
// AnnotationPodIP is explicitly set to empty string, Calico CNI has removed the network
// from the pod.
log.Debug("Pod is being deleted and IPs have been removed by Calico CNI.")
return true
} else if ips, ok := pod.Annotations[AnnotationAWSPodIPs]; ok && ips == "" {
// AnnotationAWSPodIPs is explicitly set to empty string, AWS CNI has removed the network
// from the pod.
log.Debug("Pod is being deleted and IPs have been removed by AWS CNI.")
return true
}
}
switch pod.Status.Phase {
case kapiv1.PodFailed, kapiv1.PodSucceeded, podCompleted:
log.Debug("Pod phase is failed/succeeded/completed.")
return true
}
return false
}
func (c converter) IsScheduled(pod *kapiv1.Pod) bool {
return pod.Spec.NodeName != ""
}
func (c converter) IsHostNetworked(pod *kapiv1.Pod) bool {
return pod.Spec.HostNetwork
}
func (c converter) HasIPAddress(pod *kapiv1.Pod) bool {
return pod.Status.PodIP != "" || pod.Annotations[AnnotationPodIP] != "" || pod.Annotations[AnnotationAWSPodIPs] != ""
// Note: we don't need to check PodIPs and AnnotationPodIPs here, because those cannot be
// non-empty if the corresponding singular field is empty.
}
// getPodIPs extracts the IP addresses from a Kubernetes Pod. We support a single IPv4 address
// and/or a single IPv6. getPodIPs loads the IPs either from the PodIPs and PodIP field, if
// present, or the calico podIP annotation.
func getPodIPs(pod *kapiv1.Pod) ([]*cnet.IPNet, error) {
logc := log.WithFields(log.Fields{"pod": pod.Name, "namespace": pod.Namespace})
var podIPs []string
if ips := pod.Status.PodIPs; len(ips) != 0 {
logc.WithField("ips", ips).Debug("PodIPs field filled in")
for _, ip := range ips {
podIPs = append(podIPs, ip.IP)
}
} else if ip := pod.Status.PodIP; ip != "" {
logc.WithField("ip", ip).Debug("PodIP field filled in")
podIPs = append(podIPs, ip)
} else if ips := pod.Annotations[AnnotationPodIPs]; ips != "" {
logc.WithField("ips", ips).Debug("No PodStatus IPs, use Calico plural annotation")
podIPs = append(podIPs, strings.Split(ips, ",")...)
} else if ip := pod.Annotations[AnnotationPodIP]; ip != "" {
logc.WithField("ip", ip).Debug("No PodStatus IPs, use Calico singular annotation")
podIPs = append(podIPs, ip)
} else if ips := pod.Annotations[AnnotationAWSPodIPs]; ips != "" {
logc.WithField("ips", ips).Debug("No PodStatus IPs, use AWS VPC annotation")
podIPs = append(podIPs, strings.Split(ips, ",")...)
} else {
logc.Debug("Pod has no IP")
return nil, nil
}
var podIPNets []*cnet.IPNet
for _, ip := range podIPs {
_, ipNet, err := cnet.ParseCIDROrIP(ip)
if err != nil {
logc.WithFields(log.Fields{"ip": ip}).WithError(err).Error("Failed to parse pod IP")
return nil, err
}
podIPNets = append(podIPNets, ipNet)
}
return podIPNets, nil
}
// StagedKubernetesNetworkPolicyToStagedName converts a StagedKubernetesNetworkPolicy name into a StagedNetworkPolicy name
func (c converter) StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string {
return fmt.Sprintf(K8sNetworkPolicyNamePrefix + stagedK8sName)
}
// EndpointSliceToKVP converts a k8s EndpointSlice to a model.KVPair.
func (c converter) EndpointSliceToKVP(slice *discovery.EndpointSlice) (*model.KVPair, error) {
return &model.KVPair{
Key: model.ResourceKey{
Name: slice.Name,
Namespace: slice.Namespace,
Kind: model.KindKubernetesEndpointSlice,
},
Value: slice.DeepCopy(),
Revision: slice.ResourceVersion,
}, nil
}
func (c converter) ServiceToKVP(service *kapiv1.Service) (*model.KVPair, error) {
return &model.KVPair{
Key: model.ResourceKey{
Name: service.Name,
Namespace: service.Namespace,
Kind: model.KindKubernetesService,
},
Value: service.DeepCopy(),
Revision: service.ResourceVersion,
}, nil
}
// K8sNetworkPolicyToCalico converts a k8s NetworkPolicy to a model.KVPair.
func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error) {
// Pull out important fields.
policyName := fmt.Sprintf(K8sNetworkPolicyNamePrefix + np.Name)
// We insert all the NetworkPolicy Policies at order 1000.0 after conversion.
// This order might change in future.
order := float64(1000.0)
errorTracker := cerrors.ErrorPolicyConversion{PolicyName: np.Name}
// Generate the ingress rules list.
var ingressRules []apiv3.Rule
for _, r := range np.Spec.Ingress {
rules, err := c.k8sRuleToCalico(r.From, r.Ports, np.Namespace, true)
if err != nil {
log.WithError(err).Warn("dropping k8s rule that couldn't be converted.")
// Add rule to conversion error slice
errorTracker.BadIngressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err))
} else {
ingressRules = append(ingressRules, rules...)
}
}
// Generate the egress rules list.
var egressRules []apiv3.Rule
for _, r := range np.Spec.Egress {
rules, err := c.k8sRuleToCalico(r.To, r.Ports, np.Namespace, false)
if err != nil {
log.WithError(err).Warn("dropping k8s rule that couldn't be converted")
// Add rule to conversion error slice
errorTracker.BadEgressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err))
} else {
egressRules = append(egressRules, rules...)
}
}
// Calculate Types setting.
ingress := false
egress := false
for _, policyType := range np.Spec.PolicyTypes {
switch policyType {
case networkingv1.PolicyTypeIngress:
ingress = true
case networkingv1.PolicyTypeEgress:
egress = true
}
}
types := []apiv3.PolicyType{}
if ingress {
types = append(types, apiv3.PolicyTypeIngress)
}
if egress {
types = append(types, apiv3.PolicyTypeEgress)
} else if len(egressRules) > 0 {
// Egress was introduced at the same time as policyTypes. It shouldn't be possible to
// receive a NetworkPolicy with an egress rule but without "Egress" specified in its types,
// but we'll warn about it anyway.
log.Warn("K8s PolicyTypes don't include 'egress', but NetworkPolicy has egress rules.")
}
// If no types were specified in the policy, then we're running on a cluster that doesn't
// include support for that field in the API. In that case, the correct behavior is for the policy
// to apply to only ingress traffic.
if len(types) == 0 {
types = append(types, apiv3.PolicyTypeIngress)
}
// Create the NetworkPolicy.
policy := apiv3.NewNetworkPolicy()
policy.ObjectMeta = metav1.ObjectMeta{
Name: policyName,
Namespace: np.Namespace,
CreationTimestamp: np.CreationTimestamp,
UID: np.UID,
ResourceVersion: np.ResourceVersion,
}
policy.Spec = apiv3.NetworkPolicySpec{
Order: &order,
Selector: c.k8sSelectorToCalico(&np.Spec.PodSelector, SelectorPod),
Ingress: ingressRules,
Egress: egressRules,
Types: types,
}
// Build the KVPair.
kvp := &model.KVPair{
Key: model.ResourceKey{
Name: policyName,
Namespace: np.Namespace,
Kind: apiv3.KindNetworkPolicy,
},
Value: policy,
Revision: np.ResourceVersion,
}
// Return the KVPair with conversion errors if applicable
return kvp, errorTracker.GetError()
}
// k8sSelectorToCalico takes a namespaced k8s label selector and returns the Calico
// equivalent.
func (c converter) k8sSelectorToCalico(s *metav1.LabelSelector, selectorType selectorType) string {
// Only prefix pod selectors - this won't work for namespace selectors.
selectors := []string{}
if selectorType == SelectorPod {
selectors = append(selectors, fmt.Sprintf("%s == 'k8s'", apiv3.LabelOrchestrator))
}
if s == nil {
return strings.Join(selectors, " && ")
}
// For namespace selectors, if they are present but have no terms, it means "select all
// namespaces". We use empty string to represent the nil namespace selector, so use all() to
// represent all namespaces.
if selectorType == SelectorNamespace && len(s.MatchLabels) == 0 && len(s.MatchExpressions) == 0 {
return "all()"
}
// matchLabels is a map key => value, it means match if (label[key] ==
// value) for all keys.
keys := make([]string, 0, len(s.MatchLabels))
for k := range s.MatchLabels {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := s.MatchLabels[k]
selectors = append(selectors, fmt.Sprintf("%s == '%s'", k, v))
}
// matchExpressions is a list of in/notin/exists/doesnotexist tests.
for _, e := range s.MatchExpressions {
valueList := strings.Join(e.Values, "', '")
// Each selector is formatted differently based on the operator.
switch e.Operator {
case metav1.LabelSelectorOpIn:
selectors = append(selectors, fmt.Sprintf("%s in { '%s' }", e.Key, valueList))
case metav1.LabelSelectorOpNotIn:
selectors = append(selectors, fmt.Sprintf("%s not in { '%s' }", e.Key, valueList))
case metav1.LabelSelectorOpExists:
selectors = append(selectors, fmt.Sprintf("has(%s)", e.Key))
case metav1.LabelSelectorOpDoesNotExist:
selectors = append(selectors, fmt.Sprintf("! has(%s)", e.Key))
}
}
return strings.Join(selectors, " && ")
}
func (c converter) k8sRuleToCalico(rPeers []networkingv1.NetworkPolicyPeer, rPorts []networkingv1.NetworkPolicyPort, ns string, ingress bool) ([]apiv3.Rule, error) {
rules := []apiv3.Rule{}
peers := []*networkingv1.NetworkPolicyPeer{}
ports := []*networkingv1.NetworkPolicyPort{}
// Built up a list of the sources and a list of the destinations.
for _, f := range rPeers {
// We need to add a copy of the peer so all the rules don't
// point to the same location.
peers = append(peers, &networkingv1.NetworkPolicyPeer{
NamespaceSelector: f.NamespaceSelector,
PodSelector: f.PodSelector,
IPBlock: f.IPBlock,
})
}
for _, p := range rPorts {
// We need to add a copy of the port so all the rules don't
// point to the same location.
port := networkingv1.NetworkPolicyPort{}
if p.Port != nil {
portval := intstr.FromString(p.Port.String())
port.Port = &portval
}
if p.Protocol != nil {
protval := kapiv1.Protocol(fmt.Sprintf("%s", *p.Protocol))
port.Protocol = &protval
} else {
// TCP is the implicit default (as per the definition of NetworkPolicyPort).
// Make the default explicit here because our data-model always requires
// the protocol to be specified if we're doing a port match.
port.Protocol = &protoTCP
}
if p.EndPort != nil {
port.EndPort = p.EndPort
}
ports = append(ports, &port)
}
// If there no peers, or no ports, represent that as nil.
if len(peers) == 0 {
peers = []*networkingv1.NetworkPolicyPeer{nil}
}
if len(ports) == 0 {
ports = []*networkingv1.NetworkPolicyPort{nil}
}
protocolPorts := map[string][]numorstring.Port{}
for _, port := range ports {
protocol, calicoPorts, err := c.k8sPortToCalicoFields(port)
if err != nil {
return nil, fmt.Errorf("failed to parse k8s port: %s", err)
}
if protocol == nil && calicoPorts == nil {
// If nil, no ports were specified, or an empty port struct was provided, which we translate to allowing all.
// We want to use a nil protocol and a nil list of ports, which will allow any destination (for ingress).
// Given we're gonna allow all, we may as well break here and keep only this rule
protocolPorts = map[string][]numorstring.Port{"": nil}
break
}
pStr := protocol.String()
// treat nil as 'all ports'
if calicoPorts == nil {
protocolPorts[pStr] = nil
} else if _, ok := protocolPorts[pStr]; !ok || len(protocolPorts[pStr]) > 0 {
// don't overwrite a nil (allow all ports) if present; if no ports yet for this protocol
// or 1+ ports which aren't 'all ports', then add the present ports
protocolPorts[pStr] = append(protocolPorts[pStr], calicoPorts...)
}
}
protocols := make([]string, 0, len(protocolPorts))
for k := range protocolPorts {
protocols = append(protocols, k)
}
// Ensure deterministic output
sort.Strings(protocols)
// Combine destinations with sources to generate rules. We generate one rule per protocol,
// with each rule containing all the allowed ports.
for _, protocolStr := range protocols {
calicoPorts := protocolPorts[protocolStr]
calicoPorts = SimplifyPorts(calicoPorts)
var protocol *numorstring.Protocol
if protocolStr != "" {
p := numorstring.ProtocolFromString(protocolStr)
protocol = &p
}
for _, peer := range peers {
selector, nsSelector, nets, notNets := c.k8sPeerToCalicoFields(peer, ns)
if ingress {
// Build inbound rule and append to list.
rules = append(rules, apiv3.Rule{
Action: "Allow",
Protocol: protocol,
Source: apiv3.EntityRule{
Selector: selector,
NamespaceSelector: nsSelector,
Nets: nets,
NotNets: notNets,
},
Destination: apiv3.EntityRule{
Ports: calicoPorts,
},
})
} else {
// Build outbound rule and append to list.
rules = append(rules, apiv3.Rule{
Action: "Allow",
Protocol: protocol,
Destination: apiv3.EntityRule{
Ports: calicoPorts,
Selector: selector,
NamespaceSelector: nsSelector,
Nets: nets,
NotNets: notNets,
},
})
}
}
}
return rules, nil
}
// SimplifyPorts calculates a minimum set of port ranges that cover the given set of ports.
// For example, if the input was [80, 81, 82, 9090, "foo"] the output would consist of
// [80-82, 9090, "foo"] in some order.
func SimplifyPorts(ports []numorstring.Port) []numorstring.Port {
if len(ports) <= 1 {
return ports
}
var numericPorts []int
var outputPorts []numorstring.Port
for _, p := range ports {
if p.PortName != "" {
// Pass named ports through immediately, there's nothing to be done for them.
outputPorts = append(outputPorts, p)
} else {
// Work with ints to avoid overflow with the uint16 port type.
// In practice, we currently only get single ports here so this
// loop should run exactly once.
for i := int(p.MinPort); i <= int(p.MaxPort); i++ {
numericPorts = append(numericPorts, i)
}
}
}
if len(numericPorts) <= 1 {
// We have nothing to combine, short-circuit.
return ports
}
// Sort the ports so it will be easy to find ranges.
sort.Ints(numericPorts)
// Each pass around this outer loop extracts one port range from the sorted slice
// and it moves the slice along to the start of the next range.
for len(numericPorts) > 0 {
// Initialise the next range to the contain only the first port in the slice.
firstPortInRange := numericPorts[0]
lastPortInRange := firstPortInRange
// Scan ahead, looking for ports that can be combined into this range.
numericPorts = numericPorts[1:]
for len(numericPorts) > 0 {
nextPort := numericPorts[0]
if nextPort > lastPortInRange+1 {
// This port can't be coalesced with the existing range, break out so
// that we record the range; then we'll loop again and pick up this
// port as the start of a new range.
break
}
// The next port is either equal to the last port (due to a duplicate port
// in the input) or it is exactly one greater. Extend the range to include
// it.
lastPortInRange = nextPort
numericPorts = numericPorts[1:]
}
// Record the port.
outputPorts = appendPortRange(outputPorts, firstPortInRange, lastPortInRange)
}
return outputPorts
}
func appendPortRange(ports []numorstring.Port, first, last int) []numorstring.Port {
portRange, err := numorstring.PortFromRange(uint16(first), uint16(last))
if err != nil {
log.WithError(err).Panic("Failed to make port range from ports that should have been pre-validated.")
}
return append(ports, portRange)
}
func (c converter) k8sPortToCalicoFields(port *networkingv1.NetworkPolicyPort) (protocol *numorstring.Protocol, dstPorts []numorstring.Port, err error) {
// If no port info, return zero values for all fields (protocol, dstPorts).
if port == nil {
return
}
// Port information available.
dstPorts, err = c.k8sPortToCalico(*port)
if err != nil {
return
}
protocol = c.k8sProtocolToCalico(port.Protocol)
return
}
func (c converter) k8sProtocolToCalico(protocol *kapiv1.Protocol) *numorstring.Protocol {
if protocol != nil {
p := numorstring.ProtocolFromString(string(*protocol))
return &p
}
return nil
}
func (c converter) k8sPeerToCalicoFields(peer *networkingv1.NetworkPolicyPeer, ns string) (selector, nsSelector string, nets []string, notNets []string) {
// If no peer, return zero values for all fields (selector, nets and !nets).
if peer == nil {
return
}
// Peer information available.
// Determine the source selector for the rule.
if peer.IPBlock != nil {
// Convert the CIDR to include.
_, ipNet, err := cnet.ParseCIDR(peer.IPBlock.CIDR)
if err != nil {
log.WithField("cidr", peer.IPBlock.CIDR).WithError(err).Error("Failed to parse CIDR")
return
}
nets = []string{ipNet.String()}
// Convert the CIDRs to exclude.
for _, exception := range peer.IPBlock.Except {
_, ipNet, err = cnet.ParseCIDR(exception)
if err != nil {
log.WithField("cidr", exception).WithError(err).Error("Failed to parse CIDR")
return
}
notNets = append(notNets, ipNet.String())
}
// If IPBlock is set, then PodSelector and NamespaceSelector cannot be.
return
}
// IPBlock is not set to get here.
// Note that k8sSelectorToCalico() accepts nil values of the selector.
selector = c.k8sSelectorToCalico(peer.PodSelector, SelectorPod)
nsSelector = c.k8sSelectorToCalico(peer.NamespaceSelector, SelectorNamespace)
return
}
func (c converter) k8sPortToCalico(port networkingv1.NetworkPolicyPort) ([]numorstring.Port, error) {
var portList []numorstring.Port
if port.Port != nil {
calicoPort := port.Port.String()
if port.EndPort != nil {
calicoPort = fmt.Sprintf("%s:%d", calicoPort, *port.EndPort)
}
p, err := numorstring.PortFromString(calicoPort)
if err != nil {
return nil, fmt.Errorf("invalid port %+v: %s", calicoPort, err)
}
return append(portList, p), nil
}
// No ports - return empty list.
return portList, nil
}
// ProfileNameToNamespace extracts the Namespace name from the given Profile name.
func (c converter) ProfileNameToNamespace(profileName string) (string, error) {
// Profile objects backed by Namespaces have form "kns.<ns_name>"
if !strings.HasPrefix(profileName, NamespaceProfileNamePrefix) {
// This is not backed by a Kubernetes Namespace.
return "", fmt.Errorf("Profile %s not backed by a Namespace", profileName)
}
return strings.TrimPrefix(profileName, NamespaceProfileNamePrefix), nil
}
// serviceAccountNameToProfileName creates a profile name that is a join
// of 'ksa.' + namespace + "." + serviceaccount name.
func serviceAccountNameToProfileName(sa, namespace string) string {
// Need to incorporate the namespace into the name of the sa based profile
// to make them globally unique
if namespace == "" {
namespace = "default"
}
return ServiceAccountProfileNamePrefix + namespace + "." + sa
}
// ServiceAccountToProfile converts a ServiceAccount to a Calico Profile. The Profile stores
// labels from the ServiceAccount which are inherited by the WorkloadEndpoints within
// the Profile.
func (c converter) ServiceAccountToProfile(sa *kapiv1.ServiceAccount) (*model.KVPair, error) {
// Generate the labels to apply to the profile, using a special prefix
// to indicate that these are the labels from the parent Kubernetes ServiceAccount.
labels := map[string]string{}
for k, v := range sa.ObjectMeta.Labels {
labels[ServiceAccountLabelPrefix+k] = v
}
// Add a label for the serviceaccount's name. This allows exact namespace matching
// based on name within the serviceAccountSelector.
labels[ServiceAccountLabelPrefix+NameLabel] = sa.Name
name := serviceAccountNameToProfileName(sa.Name, sa.Namespace)
profile := apiv3.NewProfile()
profile.ObjectMeta = metav1.ObjectMeta{
Name: name,
CreationTimestamp: sa.CreationTimestamp,
UID: sa.UID,
}
profile.Spec.LabelsToApply = labels
// Embed the profile in a KVPair.
kvp := model.KVPair{
Key: model.ResourceKey{
Name: name,
Kind: apiv3.KindProfile,
},
Value: profile,
Revision: c.JoinProfileRevisions("", sa.ResourceVersion),
}
return &kvp, nil
}
// ProfileNameToServiceAccount extracts the ServiceAccount name from the given Profile name.
func (c converter) ProfileNameToServiceAccount(profileName string) (ns, sa string, err error) {
// Profile objects backed by ServiceAccounts have form "ksa.<namespace>.<sa_name>"
if !strings.HasPrefix(profileName, ServiceAccountProfileNamePrefix) {
// This is not backed by a Kubernetes ServiceAccount.
err = fmt.Errorf("Profile %s not backed by a ServiceAccount", profileName)
return
}
names := strings.SplitN(profileName, ".", 3)
if len(names) != 3 {
err = fmt.Errorf("Profile %s is not formatted correctly", profileName)
return
}
ns = names[1]
sa = names[2]
return
}
// JoinProfileRevisions constructs the revision from the individual namespace and serviceaccount
// revisions.
// This is conditional on the feature flag for serviceaccount set or not.
func (c converter) JoinProfileRevisions(nsRev, saRev string) string {
return nsRev + "/" + saRev
}
// SplitProfileRevision extracts the namespace and serviceaccount revisions from the combined
// revision returned on the KDD service account based profile.
// This is conditional on the feature flag for serviceaccount set or not.
func (c converter) SplitProfileRevision(rev string) (nsRev string, saRev string, err error) {
if rev == "" || rev == "0" {
return
}
revs := strings.Split(rev, "/")
if len(revs) != 2 {
err = fmt.Errorf("ResourceVersion is not valid: %s", rev)
return
}
nsRev = revs[0]
saRev = revs[1]
return
}
func stringsToIPNets(ipStrings []string) ([]*cnet.IPNet, error) {
var podIPNets []*cnet.IPNet
for _, ip := range ipStrings {
_, ipNet, err := cnet.ParseCIDROrIP(ip)
if err != nil {
return nil, err
}
podIPNets = append(podIPNets, ipNet)
}
return podIPNets, nil
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2016-2020 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.
// TODO move the WorkloadEndpoint converters to is own package. Some refactoring of the annotation and label constants
// is necessary to avoid circular imports, which is why this has been deferred.
package conversion
import (
kapiv1 "k8s.io/api/core/v1"
"github.com/projectcalico/calico/libcalico-go/lib/backend/model"
)
type WorkloadEndpointConverter interface {
VethNameForWorkload(namespace, podName string) string
PodToWorkloadEndpoints(pod *kapiv1.Pod) ([]*model.KVPair, error)
}
func NewWorkloadEndpointConverter() WorkloadEndpointConverter {
return &defaultWorkloadEndpointConverter{}
}

View File

@@ -0,0 +1,285 @@
// Copyright (c) 2016-2021 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 conversion
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"os"
"strings"
log "github.com/sirupsen/logrus"
kapiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
"github.com/projectcalico/api/pkg/lib/numorstring"
libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/calico/libcalico-go/lib/backend/model"
"github.com/projectcalico/calico/libcalico-go/lib/json"
"github.com/projectcalico/calico/libcalico-go/lib/names"
cnet "github.com/projectcalico/calico/libcalico-go/lib/net"
)
type defaultWorkloadEndpointConverter struct{}
// VethNameForWorkload returns a deterministic veth name
// for the given Kubernetes workload (WEP) name and namespace.
func (wc defaultWorkloadEndpointConverter) VethNameForWorkload(namespace, podname string) string {
// A SHA1 is always 20 bytes long, and so is sufficient for generating the
// veth name and mac addr.
h := sha1.New()
h.Write([]byte(fmt.Sprintf("%s.%s", namespace, podname)))
prefix := os.Getenv("FELIX_INTERFACEPREFIX")
if prefix == "" {
// Prefix is not set. Default to "cali"
prefix = "cali"
} else {
// Prefix is set - use the first value in the list.
splits := strings.Split(prefix, ",")
prefix = splits[0]
}
log.WithField("prefix", prefix).Debugf("Using prefix to create a WorkloadEndpoint veth name")
return fmt.Sprintf("%s%s", prefix, hex.EncodeToString(h.Sum(nil))[:11])
}
func (wc defaultWorkloadEndpointConverter) PodToWorkloadEndpoints(pod *kapiv1.Pod) ([]*model.KVPair, error) {
wep, err := wc.podToDefaultWorkloadEndpoint(pod)
if err != nil {
return nil, err
}
return []*model.KVPair{wep}, nil
}
// PodToWorkloadEndpoint converts a Pod to a WorkloadEndpoint. It assumes the calling code
// has verified that the provided Pod is valid to convert to a WorkloadEndpoint.
// PodToWorkloadEndpoint requires a Pods Name and Node Name to be populated. It will
// fail to convert from a Pod to WorkloadEndpoint otherwise.
func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kapiv1.Pod) (*model.KVPair, error) {
log.WithField("pod", pod).Debug("Converting pod to WorkloadEndpoint")
// Get all the profiles that apply
var profiles []string
// Pull out the Namespace based profile off the pod name and Namespace.
profiles = append(profiles, NamespaceProfileNamePrefix+pod.Namespace)
// Pull out the Serviceaccount based profile off the pod SA and namespace
if pod.Spec.ServiceAccountName != "" {
profiles = append(profiles, serviceAccountNameToProfileName(pod.Spec.ServiceAccountName, pod.Namespace))
}
wepids := names.WorkloadEndpointIdentifiers{
Node: pod.Spec.NodeName,
Orchestrator: apiv3.OrchestratorKubernetes,
Endpoint: "eth0",
Pod: pod.Name,
}
wepName, err := wepids.CalculateWorkloadEndpointName(false)
if err != nil {
return nil, err
}
podIPNets, err := getPodIPs(pod)
if err != nil {
// IP address was present but malformed in some way, handle as an explicit failure.
return nil, err
}
if IsFinished(pod) {
// Pod is finished but not yet deleted. In this state the IP will have been freed and returned to the pool
// so we need to make sure we don't let the caller believe it still belongs to this endpoint.
// Pods with no IPs will get filtered out before they get to Felix in the watcher syncer cache layer.
// We can't pretend the workload endpoint is deleted _here_ because that would confuse users of the
// native v3 Watch() API.
log.Debug("Pod is in a 'finished' state so no longer owns its IP(s).")
podIPNets = nil
}
ipNets := []string{}
for _, ipNet := range podIPNets {
ipNets = append(ipNets, ipNet.String())
}
// Generate the interface name based on workload. This must match
// the host-side veth configured by the CNI plugin.
interfaceName := wc.VethNameForWorkload(pod.Namespace, pod.Name)
// Build the labels map. Start with the pod labels, and append two additional labels for
// namespace and orchestrator matches.
labels := pod.Labels
if labels == nil {
labels = make(map[string]string, 2)
}
labels[apiv3.LabelNamespace] = pod.Namespace
labels[apiv3.LabelOrchestrator] = apiv3.OrchestratorKubernetes
if pod.Spec.ServiceAccountName != "" && len(pod.Spec.ServiceAccountName) < 63 {
// For backwards compatibility, include the label if less than 63 characters.
labels[apiv3.LabelServiceAccount] = pod.Spec.ServiceAccountName
}
// Pull out floating IP annotation
var floatingIPs []libapiv3.IPNAT
if annotation, ok := pod.Annotations["cni.projectcalico.org/floatingIPs"]; ok && len(podIPNets) > 0 {
// Parse Annotation data
var ips []string
err := json.Unmarshal([]byte(annotation), &ips)
if err != nil {
return nil, fmt.Errorf("failed to parse '%s' as JSON: %s", annotation, err)
}
// Get IPv4 and IPv6 targets for NAT
var podnetV4, podnetV6 *cnet.IPNet
for _, ipNet := range podIPNets {
if ipNet.IP.To4() != nil {
podnetV4 = ipNet
netmask, _ := podnetV4.Mask.Size()
if netmask != 32 {
return nil, fmt.Errorf("PodIP %v is not a valid IPv4: Mask size is %d, not 32", ipNet, netmask)
}
} else {
podnetV6 = ipNet
netmask, _ := podnetV6.Mask.Size()
if netmask != 128 {
return nil, fmt.Errorf("PodIP %v is not a valid IPv6: Mask size is %d, not 128", ipNet, netmask)
}
}
}
for _, ip := range ips {
if strings.Contains(ip, ":") {
if podnetV6 != nil {
floatingIPs = append(floatingIPs, libapiv3.IPNAT{
InternalIP: podnetV6.IP.String(),
ExternalIP: ip,
})
}
} else {
if podnetV4 != nil {
floatingIPs = append(floatingIPs, libapiv3.IPNAT{
InternalIP: podnetV4.IP.String(),
ExternalIP: ip,
})
}
}
}
}
// Handle source IP spoofing annotation
var sourcePrefixes []string
if annotation, ok := pod.Annotations["cni.projectcalico.org/allowedSourcePrefixes"]; ok && annotation != "" {
// Parse Annotation data
var requestedSourcePrefixes []string
err := json.Unmarshal([]byte(annotation), &requestedSourcePrefixes)
if err != nil {
return nil, fmt.Errorf("failed to parse '%s' as JSON: %s", annotation, err)
}
// Filter out any invalid entries and normalize the CIDRs.
for _, prefix := range requestedSourcePrefixes {
if _, n, err := cnet.ParseCIDR(prefix); err != nil {
return nil, fmt.Errorf("failed to parse '%s' as a CIDR: %s", prefix, err)
} else {
sourcePrefixes = append(sourcePrefixes, n.String())
}
}
}
// Map any named ports through.
var endpointPorts []libapiv3.WorkloadEndpointPort
for _, container := range pod.Spec.Containers {
for _, containerPort := range container.Ports {
if containerPort.ContainerPort != 0 && (containerPort.HostPort != 0 || containerPort.Name != "") {
var modelProto numorstring.Protocol
switch containerPort.Protocol {
case kapiv1.ProtocolUDP:
modelProto = numorstring.ProtocolFromString("udp")
case kapiv1.ProtocolSCTP:
modelProto = numorstring.ProtocolFromString("sctp")
case kapiv1.ProtocolTCP, kapiv1.Protocol("") /* K8s default is TCP. */ :
modelProto = numorstring.ProtocolFromString("tcp")
default:
log.WithFields(log.Fields{
"protocol": containerPort.Protocol,
"pod": pod,
"port": containerPort,
}).Debug("Ignoring named port with unknown protocol")
continue
}
endpointPorts = append(endpointPorts, libapiv3.WorkloadEndpointPort{
Name: containerPort.Name,
Protocol: modelProto,
Port: uint16(containerPort.ContainerPort),
HostPort: uint16(containerPort.HostPort),
HostIP: containerPort.HostIP,
})
}
}
}
// Get the container ID if present. This is used in the CNI plugin to distinguish different pods that have
// the same name. For example, restarted stateful set pods.
containerID := pod.Annotations[AnnotationContainerID]
// Create the workload endpoint.
wep := libapiv3.NewWorkloadEndpoint()
wep.ObjectMeta = metav1.ObjectMeta{
Name: wepName,
Namespace: pod.Namespace,
CreationTimestamp: pod.CreationTimestamp,
UID: pod.UID,
Labels: labels,
GenerateName: pod.GenerateName,
}
wep.Spec = libapiv3.WorkloadEndpointSpec{
Orchestrator: "k8s",
Node: pod.Spec.NodeName,
Pod: pod.Name,
ContainerID: containerID,
Endpoint: "eth0",
InterfaceName: interfaceName,
Profiles: profiles,
IPNetworks: ipNets,
Ports: endpointPorts,
IPNATs: floatingIPs,
ServiceAccountName: pod.Spec.ServiceAccountName,
AllowSpoofedSourcePrefixes: sourcePrefixes,
}
if v, ok := pod.Annotations["k8s.v1.cni.cncf.io/network-status"]; ok {
if wep.Annotations == nil {
wep.Annotations = make(map[string]string)
}
wep.Annotations["k8s.v1.cni.cncf.io/network-status"] = v
}
// Embed the workload endpoint into a KVPair.
kvp := model.KVPair{
Key: model.ResourceKey{
Name: wepName,
Namespace: pod.Namespace,
Kind: libapiv3.KindWorkloadEndpoint,
},
Value: wep,
Revision: pod.ResourceVersion,
}
return &kvp, nil
}

View File

@@ -0,0 +1,58 @@
// 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 model
import (
"fmt"
"reflect"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
typeBGPNode = reflect.TypeOf(BGPNode{})
)
type BGPNodeKey struct {
Host string
}
func (key BGPNodeKey) defaultPath() (string, error) {
if key.Host == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "host"}
}
k := "/calico/bgp/v1/host/" + key.Host
return k, nil
}
func (key BGPNodeKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key BGPNodeKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key BGPNodeKey) valueType() (reflect.Type, error) {
return typeBGPNode, nil
}
func (key BGPNodeKey) String() string {
return fmt.Sprintf("BGPNodeKey(host=%s)", key.Host)
}
type BGPNode struct {
}

View File

@@ -0,0 +1,162 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchGlobalBGPConfig = regexp.MustCompile("^/?calico/bgp/v1/global/(.+)$")
matchNodeBGPConfig = regexp.MustCompile("^/?calico/bgp/v1/host/([^/]+)/(.+)$")
typeGlobalBGPConfig = rawStringType
typeNodeBGPConfig = rawStringType
)
type GlobalBGPConfigKey struct {
// The name of the global BGP config key.
Name string `json:"-" validate:"required,name"`
}
func (key GlobalBGPConfigKey) defaultPath() (string, error) {
return key.defaultDeletePath()
}
func (key GlobalBGPConfigKey) defaultDeletePath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/bgp/v1/global/%s", key.Name)
return e, nil
}
func (key GlobalBGPConfigKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key GlobalBGPConfigKey) valueType() (reflect.Type, error) {
return typeGlobalBGPConfig, nil
}
func (key GlobalBGPConfigKey) String() string {
return fmt.Sprintf("GlobalBGPConfig(name=%s)", key.Name)
}
type GlobalBGPConfigListOptions struct {
Name string
}
func (options GlobalBGPConfigListOptions) defaultPathRoot() string {
k := "/calico/bgp/v1/global"
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", options.Name)
return k
}
func (options GlobalBGPConfigListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get GlobalFelixConfig key from %s", path)
r := matchGlobalBGPConfig.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
name := r[0][1]
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return GlobalBGPConfigKey{Name: name}
}
type NodeBGPConfigKey struct {
// The hostname for the host specific BGP config
Nodename string `json:"-" validate:"required,name"`
// The name of the host specific BGP config key.
Name string `json:"-" validate:"required,name"`
}
func (key NodeBGPConfigKey) defaultPath() (string, error) {
return key.defaultDeletePath()
}
func (key NodeBGPConfigKey) defaultDeletePath() (string, error) {
if key.Nodename == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/bgp/v1/host/%s/%s", key.Nodename, key.Name)
return e, nil
}
func (key NodeBGPConfigKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key NodeBGPConfigKey) valueType() (reflect.Type, error) {
return typeNodeBGPConfig, nil
}
func (key NodeBGPConfigKey) String() string {
return fmt.Sprintf("HostBGPConfig(node=%s; name=%s)", key.Nodename, key.Name)
}
type NodeBGPConfigListOptions struct {
Nodename string
Name string
}
func (options NodeBGPConfigListOptions) defaultPathRoot() string {
k := "/calico/bgp/v1/host/%s"
if options.Nodename == "" {
return k
}
k = k + fmt.Sprintf("/%s", options.Nodename)
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", options.Name)
return k
}
func (options NodeBGPConfigListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get HostConfig key from %s", path)
r := matchNodeBGPConfig.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
nodename := r[0][1]
name := r[0][2]
if options.Nodename != "" && nodename != options.Nodename {
log.Debugf("Didn't match nodename %s != %s", options.Nodename, nodename)
return nil
}
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return NodeBGPConfigKey{Nodename: nodename, Name: name}
}

View File

@@ -0,0 +1,230 @@
// Copyright (c) 2020 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/api/pkg/lib/numorstring"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchGlobalBGPPeer = regexp.MustCompile("^/?calico/bgp/v1/global/peer_v./([^/]+)$")
matchHostBGPPeer = regexp.MustCompile("^/?calico/bgp/v1/host/([^/]+)/peer_v./([^/]+)$")
typeBGPPeer = reflect.TypeOf(BGPPeer{})
ipPortSeparator = "-"
defaultPort uint16 = 179
)
type NodeBGPPeerKey struct {
Nodename string `json:"-" validate:"omitempty"`
PeerIP net.IP `json:"-" validate:"required"`
Port uint16 `json:"-" validate:"omitempty"`
}
func (key NodeBGPPeerKey) defaultPath() (string, error) {
if key.PeerIP.IP == nil {
return "", errors.ErrorInsufficientIdentifiers{Name: "peerIP"}
}
if key.Nodename == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
e := fmt.Sprintf("/calico/bgp/v1/host/%s/peer_v%d/%s",
key.Nodename, key.PeerIP.Version(), combineIPAndPort(key.PeerIP, key.Port))
return e, nil
}
func (key NodeBGPPeerKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key NodeBGPPeerKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key NodeBGPPeerKey) valueType() (reflect.Type, error) {
return typeBGPPeer, nil
}
func (key NodeBGPPeerKey) String() string {
return fmt.Sprintf("BGPPeer(node=%s, ip=%s, port=%d)", key.Nodename, key.PeerIP, key.Port)
}
type NodeBGPPeerListOptions struct {
Nodename string
PeerIP net.IP
Port uint16
}
func (options NodeBGPPeerListOptions) defaultPathRoot() string {
if options.Nodename == "" {
return "/calico/bgp/v1/host"
} else if options.PeerIP.IP == nil {
return fmt.Sprintf("/calico/bgp/v1/host/%s",
options.Nodename)
} else {
return fmt.Sprintf("/calico/bgp/v1/host/%s/peer_v%d/%s",
options.Nodename, options.PeerIP.Version(), combineIPAndPort(options.PeerIP, options.Port))
}
}
func (options NodeBGPPeerListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get BGPPeer key from %s", path)
nodename := ""
var port uint16
peerIP := net.IP{}
ekeyb := []byte(path)
if r := matchHostBGPPeer.FindAllSubmatch(ekeyb, -1); len(r) == 1 {
var ipBytes []byte
ipBytes, port = extractIPAndPort(string(r[0][2]))
nodename = string(r[0][1])
if err := peerIP.UnmarshalText(ipBytes); err != nil {
log.WithError(err).WithField("PeerIP", r[0][2]).Error("Error unmarshalling GlobalBGPPeer IP address")
return nil
}
} else {
log.Debugf("%s didn't match regex", path)
return nil
}
if options.PeerIP.IP != nil && !options.PeerIP.Equal(peerIP.IP) {
log.Debugf("Didn't match peerIP %s != %s", options.PeerIP.String(), peerIP.String())
return nil
}
if options.Nodename != "" && nodename != options.Nodename {
log.Debugf("Didn't match hostname %s != %s", options.Nodename, nodename)
return nil
}
if port == 0 {
return NodeBGPPeerKey{PeerIP: peerIP, Nodename: nodename}
}
return NodeBGPPeerKey{PeerIP: peerIP, Nodename: nodename, Port: port}
}
type GlobalBGPPeerKey struct {
PeerIP net.IP `json:"-" validate:"required"`
Port uint16 `json:"-" validate:"omitempty"`
}
func (key GlobalBGPPeerKey) defaultPath() (string, error) {
if key.PeerIP.IP == nil {
return "", errors.ErrorInsufficientIdentifiers{Name: "peerIP"}
}
e := fmt.Sprintf("/calico/bgp/v1/global/peer_v%d/%s",
key.PeerIP.Version(), combineIPAndPort(key.PeerIP, key.Port))
return e, nil
}
func (key GlobalBGPPeerKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key GlobalBGPPeerKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key GlobalBGPPeerKey) valueType() (reflect.Type, error) {
return typeBGPPeer, nil
}
func (key GlobalBGPPeerKey) String() string {
return fmt.Sprintf("BGPPeer(global, ip=%s, port=%d)", key.PeerIP, key.Port)
}
type GlobalBGPPeerListOptions struct {
PeerIP net.IP
Port uint16
}
func (options GlobalBGPPeerListOptions) defaultPathRoot() string {
if options.PeerIP.IP == nil {
return "/calico/bgp/v1/global"
} else {
return fmt.Sprintf("/calico/bgp/v1/global/peer_v%d/%s",
options.PeerIP.Version(), combineIPAndPort(options.PeerIP, options.Port))
}
}
func (options GlobalBGPPeerListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get BGPPeer key from %s", path)
peerIP := net.IP{}
ekeyb := []byte(path)
var port uint16
if r := matchGlobalBGPPeer.FindAllSubmatch(ekeyb, -1); len(r) == 1 {
var ipBytes []byte
ipBytes, port = extractIPAndPort(string(r[0][1]))
if err := peerIP.UnmarshalText(ipBytes); err != nil {
log.WithError(err).WithField("PeerIP", r[0][1]).Error("Error unmarshalling GlobalBGPPeer IP address")
return nil
}
} else {
log.Debugf("%s didn't match regex", path)
return nil
}
if options.PeerIP.IP != nil && !options.PeerIP.Equal(peerIP.IP) {
log.Debugf("Didn't match peerIP %s != %s", options.PeerIP.String(), peerIP.String())
return nil
}
if port == 0 {
return GlobalBGPPeerKey{PeerIP: peerIP, Port: port}
}
return GlobalBGPPeerKey{PeerIP: peerIP, Port: port}
}
type BGPPeer struct {
// PeerIP is the IP address of the BGP peer.
PeerIP net.IP `json:"ip"`
// ASNum is the AS number of the peer. Note that we write out the
// value as a string in to the backend, because confd templating
// converts large uints to float e notation which breaks the BIRD
// configuration.
ASNum numorstring.ASNumber `json:"as_num,string"`
}
func extractIPAndPort(ipPort string) ([]byte, uint16) {
arr := strings.Split(ipPort, ipPortSeparator)
if len(arr) == 2 {
port, err := strconv.ParseUint(arr[1], 0, 16)
if err != nil {
log.Warningf("Error extracting port. %#v", err)
return []byte(ipPort), defaultPort
}
return []byte(arr[0]), uint16(port)
}
return []byte(ipPort), defaultPort
}
func combineIPAndPort(ip net.IP, port uint16) string {
if port == 0 || port == defaultPort {
return ip.String()
} else {
strPort := strconv.Itoa(int(port))
return ip.String() + ipPortSeparator + strPort
}
}

View File

@@ -0,0 +1,235 @@
// Copyright (c) 2016-2021 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"math/big"
"reflect"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
const (
// Common attributes which may be set on allocations by clients.
IPAMBlockAttributePod = "pod"
IPAMBlockAttributeNamespace = "namespace"
IPAMBlockAttributeNode = "node"
IPAMBlockAttributeType = "type"
IPAMBlockAttributeTypeIPIP = "ipipTunnelAddress"
IPAMBlockAttributeTypeVXLAN = "vxlanTunnelAddress"
IPAMBlockAttributeTypeVXLANV6 = "vxlanV6TunnelAddress"
IPAMBlockAttributeTypeWireguard = "wireguardTunnelAddress"
IPAMBlockAttributeTypeWireguardV6 = "wireguardV6TunnelAddress"
IPAMBlockAttributeTimestamp = "timestamp"
)
var (
matchBlock = regexp.MustCompile("^/?calico/ipam/v2/assignment/ipv./block/([^/]+)$")
typeBlock = reflect.TypeOf(AllocationBlock{})
)
type BlockKey struct {
CIDR net.IPNet `json:"-" validate:"required,name"`
}
func (key BlockKey) defaultPath() (string, error) {
if key.CIDR.IP == nil {
return "", errors.ErrorInsufficientIdentifiers{}
}
c := strings.Replace(key.CIDR.String(), "/", "-", 1)
e := fmt.Sprintf("/calico/ipam/v2/assignment/ipv%d/block/%s", key.CIDR.Version(), c)
return e, nil
}
func (key BlockKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key BlockKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key BlockKey) valueType() (reflect.Type, error) {
return typeBlock, nil
}
func (key BlockKey) String() string {
return fmt.Sprintf("BlockKey(cidr=%s)", key.CIDR.String())
}
type BlockListOptions struct {
IPVersion int `json:"-"`
}
func (options BlockListOptions) defaultPathRoot() string {
k := "/calico/ipam/v2/assignment/"
if options.IPVersion != 0 {
k = k + fmt.Sprintf("ipv%d/", options.IPVersion)
}
return k
}
func (options BlockListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Block key from %s", path)
r := matchBlock.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("%s didn't match regex", path)
return nil
}
cidrStr := strings.Replace(r[0][1], "-", "/", 1)
_, cidr, err := net.ParseCIDR(cidrStr)
if err != nil {
log.Debugf("find an invalid cidr %s for path=%v , info=%v ", r[0][1], path, err)
return nil
}
return BlockKey{CIDR: *cidr}
}
type AllocationBlock struct {
// The block's CIDR.
CIDR net.IPNet `json:"cidr"`
// Affinity of the block, if this block has one. If set, it will be of the form
// "host:<hostname>". If not set, this block is not affine to a host.
Affinity *string `json:"affinity"`
// Array of allocations in-use within this block. nil entries mean the allocation is free.
// For non-nil entries at index i, the index is the ordinal of the allocation within this block
// and the value is the index of the associated attributes in the Attributes array.
Allocations []*int `json:"allocations"`
// Unallocated is an ordered list of allocations which are free in the block.
Unallocated []int `json:"unallocated"`
// Attributes is an array of arbitrary metadata associated with allocations in the block. To find
// attributes for a given allocation, use the value of the allocation's entry in the Allocations array
// as the index of the element in this array.
Attributes []AllocationAttribute `json:"attributes"`
// We store a sequence number that is updated each time the block is written.
// Each allocation will also store the sequence number of the block at the time of its creation.
// When releasing an IP, passing the sequence number associated with the allocation allows us
// to protect against a race condition and ensure the IP hasn't been released and re-allocated
// since the release request.
SequenceNumber uint64 `json:"sequenceNumber"`
// Map of allocated ordinal within the block to sequence number of the block at
// the time of allocation. Kubernetes does not allow numerical keys for maps, so
// the key is cast to a string.
SequenceNumberForAllocation map[string]uint64 `json:"sequenceNumberForAllocation"`
// Deleted is an internal boolean used to workaround a limitation in the Kubernetes API whereby
// deletion will not return a conflict error if the block has been updated.
Deleted bool `json:"deleted"`
// HostAffinity is deprecated in favor of Affinity.
// This is only to keep compatibility with existing deployments.
// The data format should be `Affinity: host:hostname` (not `hostAffinity: hostname`).
HostAffinity *string `json:"hostAffinity,omitempty"`
}
func (b *AllocationBlock) SetSequenceNumberForOrdinal(ordinal int) {
if b.SequenceNumberForAllocation == nil {
b.SequenceNumberForAllocation = map[string]uint64{}
}
b.SequenceNumberForAllocation[fmt.Sprintf("%d", ordinal)] = b.SequenceNumber
}
func (b *AllocationBlock) GetSequenceNumberForOrdinal(ordinal int) uint64 {
return b.SequenceNumberForAllocation[fmt.Sprintf("%d", ordinal)]
}
func (b *AllocationBlock) ClearSequenceNumberForOrdinal(ordinal int) {
delete(b.SequenceNumberForAllocation, fmt.Sprintf("%d", ordinal))
}
func (b *AllocationBlock) MarkDeleted() {
b.Deleted = true
}
func (b *AllocationBlock) IsDeleted() bool {
return b.Deleted
}
func (b *AllocationBlock) Host() string {
if b.Affinity != nil && strings.HasPrefix(*b.Affinity, "host:") {
return strings.TrimPrefix(*b.Affinity, "host:")
}
return ""
}
type Allocation struct {
Addr net.IP
Host string
}
func (b *AllocationBlock) NonAffineAllocations() []Allocation {
var allocs []Allocation
myHost := b.Host()
for ordinal, attrIdx := range b.Allocations {
if attrIdx == nil {
continue // Skip unallocated IPs.
}
if *attrIdx >= len(b.Attributes) {
log.WithField("block", b).Warnf("Missing attributes for IP with ordinal %d", ordinal)
continue
}
attrs := b.Attributes[*attrIdx]
host := attrs.AttrSecondary[IPAMBlockAttributeNode]
if myHost != "" && host == myHost {
continue // Skip allocations that are affine to this block.
}
a := Allocation{
Addr: b.OrdinalToIP(ordinal),
Host: host,
}
allocs = append(allocs, a)
}
return allocs
}
// Get number of addresses covered by the block
func (b *AllocationBlock) NumAddresses() int {
ones, size := b.CIDR.Mask.Size()
numAddresses := 1 << uint(size-ones)
return numAddresses
}
// Find the ordinal (i.e. how far into the block) a given IP lies. Returns an error if the IP is outside the block.
func (b *AllocationBlock) IPToOrdinal(ip net.IP) (int, error) {
ipAsInt := net.IPToBigInt(ip)
baseInt := net.IPToBigInt(net.IP{b.CIDR.IP})
ord := big.NewInt(0).Sub(ipAsInt, baseInt).Int64()
if ord < 0 || ord >= int64(b.NumAddresses()) {
return 0, fmt.Errorf("IP %s not in block %s", ip, b.CIDR)
}
return int(ord), nil
}
// Calculates the IP at the given position within the block. ord=0 gives the first IP in the block.
func (b *AllocationBlock) OrdinalToIP(ord int) net.IP {
return b.CIDR.NthIP(ord)
}
type AllocationAttribute struct {
AttrPrimary *string `json:"handle_id"`
AttrSecondary map[string]string `json:"secondary"`
}

View File

@@ -0,0 +1,118 @@
// Copyright (c) 2016-2019 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchBlockAffinity = regexp.MustCompile("^/?calico/ipam/v2/host/([^/]+)/ipv./block/([^/]+)$")
typeBlockAff = reflect.TypeOf(BlockAffinity{})
)
type BlockAffinityState string
const (
StateConfirmed BlockAffinityState = "confirmed"
StatePending BlockAffinityState = "pending"
StatePendingDeletion BlockAffinityState = "pendingDeletion"
)
type BlockAffinityKey struct {
CIDR net.IPNet `json:"-" validate:"required,name"`
Host string `json:"-"`
}
type BlockAffinity struct {
State BlockAffinityState `json:"state"`
Deleted bool `json:"deleted"`
}
func (key BlockAffinityKey) defaultPath() (string, error) {
if key.CIDR.IP == nil || key.Host == "" {
return "", errors.ErrorInsufficientIdentifiers{}
}
c := strings.Replace(key.CIDR.String(), "/", "-", 1)
e := fmt.Sprintf("/calico/ipam/v2/host/%s/ipv%d/block/%s", key.Host, key.CIDR.Version(), c)
return e, nil
}
func (key BlockAffinityKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key BlockAffinityKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key BlockAffinityKey) valueType() (reflect.Type, error) {
return typeBlockAff, nil
}
func (key BlockAffinityKey) String() string {
return fmt.Sprintf("BlockAffinityKey(cidr=%s, host=%s)", key.CIDR, key.Host)
}
type BlockAffinityListOptions struct {
Host string
IPVersion int
}
func (options BlockAffinityListOptions) defaultPathRoot() string {
k := "/calico/ipam/v2/host/"
if options.Host != "" {
k = k + options.Host
if options.IPVersion != 0 {
k = k + fmt.Sprintf("/ipv%d/block", options.IPVersion)
}
}
return k
}
func (options BlockAffinityListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Block affinity key from %s", path)
r := matchBlockAffinity.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("%s didn't match regex", path)
return nil
}
cidrStr := strings.Replace(r[0][2], "-", "/", 1)
_, cidr, _ := net.ParseCIDR(cidrStr)
if cidr == nil {
log.Debugf("Failed to parse CIDR in block affinity path: %q", path)
return nil
}
host := r[0][1]
if options.Host != "" && options.Host != host {
log.Debugf("Didn't match hostname: %s != %s", options.Host, host)
return nil
}
if options.IPVersion != 0 && options.IPVersion != cidr.Version() {
log.Debugf("Didn't match IP version. %d != %d", options.IPVersion, cidr.Version())
return nil
}
return BlockAffinityKey{CIDR: *cidr, Host: host}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2019 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
type DeletionMarker interface {
MarkDeleted()
IsDeleted() bool
}

View File

@@ -0,0 +1,183 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"reflect"
"regexp"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchGlobalConfig = regexp.MustCompile("^/?calico/v1/config/(.+)$")
matchHostConfig = regexp.MustCompile("^/?calico/v1/host/([^/]+)/config/(.+)$")
matchReadyFlag = regexp.MustCompile("^/calico/v1/Ready$")
typeGlobalConfig = rawStringType
typeHostConfig = rawStringType
typeReadyFlag = rawBoolType
)
type ReadyFlagKey struct {
}
func (key ReadyFlagKey) defaultPath() (string, error) {
return "/calico/v1/Ready", nil
}
func (key ReadyFlagKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key ReadyFlagKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key ReadyFlagKey) valueType() (reflect.Type, error) {
return typeReadyFlag, nil
}
func (key ReadyFlagKey) String() string {
return "ReadyFlagKey()"
}
type GlobalConfigKey struct {
Name string `json:"-" validate:"required,name"`
}
func (key GlobalConfigKey) defaultPath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/v1/config/%s", key.Name)
return e, nil
}
func (key GlobalConfigKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key GlobalConfigKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key GlobalConfigKey) valueType() (reflect.Type, error) {
return typeGlobalConfig, nil
}
func (key GlobalConfigKey) String() string {
return fmt.Sprintf("GlobalFelixConfig(name=%s)", key.Name)
}
type GlobalConfigListOptions struct {
Name string
}
func (options GlobalConfigListOptions) defaultPathRoot() string {
k := "/calico/v1/config"
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", options.Name)
return k
}
func (options GlobalConfigListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get GlobalConfig key from %s", path)
r := matchGlobalConfig.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
name := r[0][1]
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return GlobalConfigKey{Name: name}
}
type HostConfigKey struct {
Hostname string `json:"-" validate:"required,name"`
Name string `json:"-" validate:"required,name"`
}
func (key HostConfigKey) defaultPath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
e := fmt.Sprintf("/calico/v1/host/%s/config/%s", key.Hostname, key.Name)
return e, nil
}
func (key HostConfigKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key HostConfigKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key HostConfigKey) valueType() (reflect.Type, error) {
return typeHostConfig, nil
}
func (key HostConfigKey) String() string {
return fmt.Sprintf("HostConfig(node=%s,name=%s)", key.Hostname, key.Name)
}
type HostConfigListOptions struct {
Hostname string
Name string
}
func (options HostConfigListOptions) defaultPathRoot() string {
k := "/calico/v1/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/config", options.Hostname)
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", options.Name)
return k
}
func (options HostConfigListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get HostConfig key from %s", path)
r := matchHostConfig.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
hostname := r[0][1]
name := r[0][2]
if options.Hostname != "" && hostname != options.Hostname {
log.Debugf("Didn't match hostname %s != %s", options.Hostname, hostname)
return nil
}
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return HostConfigKey{Hostname: hostname, Name: name}
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchHostEndpoint = regexp.MustCompile("^/?calico/v1/host/([^/]+)/endpoint/([^/]+)$")
typeHostEndpoint = reflect.TypeOf(HostEndpoint{})
)
type HostEndpointKey struct {
Hostname string `json:"-" validate:"required,hostname"`
EndpointID string `json:"-" validate:"required,namespacedName"`
}
func (key HostEndpointKey) defaultPath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
if key.EndpointID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/v1/host/%s/endpoint/%s",
key.Hostname, escapeName(key.EndpointID))
return e, nil
}
func (key HostEndpointKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key HostEndpointKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key HostEndpointKey) valueType() (reflect.Type, error) {
return typeHostEndpoint, nil
}
func (key HostEndpointKey) String() string {
return fmt.Sprintf("HostEndpoint(node=%s, name=%s)", key.Hostname, key.EndpointID)
}
type HostEndpointListOptions struct {
Hostname string
EndpointID string
}
func (options HostEndpointListOptions) defaultPathRoot() string {
k := "/calico/v1/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/endpoint", options.Hostname)
if options.EndpointID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.EndpointID))
return k
}
func (options HostEndpointListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get HostEndpoint key from %s", path)
r := matchHostEndpoint.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
hostname := r[0][1]
endpointID := unescapeName(r[0][2])
if options.Hostname != "" && hostname != options.Hostname {
log.Debugf("Didn't match hostname %s != %s", options.Hostname, hostname)
return nil
}
if options.EndpointID != "" && endpointID != options.EndpointID {
log.Debugf("Didn't match endpointID %s != %s", options.EndpointID, endpointID)
return nil
}
return HostEndpointKey{Hostname: hostname, EndpointID: endpointID}
}
type HostEndpoint struct {
Name string `json:"name,omitempty" validate:"omitempty,interface"`
ExpectedIPv4Addrs []net.IP `json:"expected_ipv4_addrs,omitempty" validate:"omitempty,dive,ipv4"`
ExpectedIPv6Addrs []net.IP `json:"expected_ipv6_addrs,omitempty" validate:"omitempty,dive,ipv6"`
Labels map[string]string `json:"labels,omitempty" validate:"omitempty,labels"`
ProfileIDs []string `json:"profile_ids,omitempty" validate:"omitempty,dive,name"`
Ports []EndpointPort `json:"ports,omitempty" validate:"dive"`
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchHostEndpointStatus = regexp.MustCompile("^/?calico/felix/v1/host/([^/]+)/endpoint/([^/]+)$")
typeHostEndpointStatus = reflect.TypeOf(HostEndpointStatus{})
)
type HostEndpointStatusKey struct {
Hostname string `json:"-" validate:"required,hostname"`
EndpointID string `json:"-" validate:"required,namespacedName"`
}
func (key HostEndpointStatusKey) defaultPath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
if key.EndpointID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/felix/v1/host/%s/endpoint/%s",
key.Hostname, escapeName(key.EndpointID))
return e, nil
}
func (key HostEndpointStatusKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key HostEndpointStatusKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key HostEndpointStatusKey) valueType() (reflect.Type, error) {
return typeHostEndpointStatus, nil
}
func (key HostEndpointStatusKey) String() string {
return fmt.Sprintf("HostEndpointStatus(hostname=%s, name=%s)", key.Hostname, key.EndpointID)
}
type HostEndpointStatusListOptions struct {
Hostname string
EndpointID string
}
func (options HostEndpointStatusListOptions) defaultPathRoot() string {
k := "/calico/felix/v1/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/endpoint", options.Hostname)
if options.EndpointID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.EndpointID))
return k
}
func (options HostEndpointStatusListOptions) KeyFromDefaultPath(ekey string) Key {
log.Debugf("Get HostEndpointStatus key from %s", ekey)
r := matchHostEndpointStatus.FindAllStringSubmatch(ekey, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
hostname := r[0][1]
endpointID := unescapeName(r[0][2])
if options.Hostname != "" && hostname != options.Hostname {
log.Debugf("Didn't match hostname %s != %s", options.Hostname, hostname)
return nil
}
if options.EndpointID != "" && endpointID != options.EndpointID {
log.Debugf("Didn't match endpointID %s != %s", options.EndpointID, endpointID)
return nil
}
return HostEndpointStatusKey{Hostname: hostname, EndpointID: endpointID}
}
type HostEndpointStatus struct {
Status string `json:"status"`
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"reflect"
)
const (
IPAMConfigGlobalName = "default"
)
var typeIPAMConfig = reflect.TypeOf(IPAMConfig{})
type IPAMConfigKey struct{}
func (key IPAMConfigKey) defaultPath() (string, error) {
return "/calico/ipam/v2/config", nil
}
func (key IPAMConfigKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key IPAMConfigKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key IPAMConfigKey) valueType() (reflect.Type, error) {
return typeIPAMConfig, nil
}
func (key IPAMConfigKey) String() string {
return "IPAMConfigKey()"
}
type IPAMConfig struct {
StrictAffinity bool `json:"strict_affinity,omitempty"`
AutoAllocateBlocks bool `json:"auto_allocate_blocks,omitempty"`
MaxBlocksPerHost int `json:"maxBlocksPerHost,omitempty"`
}

View File

@@ -0,0 +1,84 @@
// Copyright (c) 2016,2020 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchHandle = regexp.MustCompile("^/?calico/ipam/v2/handle/([^/]+)$")
typeHandle = reflect.TypeOf(IPAMHandle{})
)
type IPAMHandleKey struct {
HandleID string `json:"id"`
}
func (key IPAMHandleKey) defaultPath() (string, error) {
if key.HandleID == "" {
return "", errors.ErrorInsufficientIdentifiers{}
}
e := fmt.Sprintf("/calico/ipam/v2/handle/%s", key.HandleID)
return e, nil
}
func (key IPAMHandleKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key IPAMHandleKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key IPAMHandleKey) valueType() (reflect.Type, error) {
return typeHandle, nil
}
func (key IPAMHandleKey) String() string {
return fmt.Sprintf("IPAMHandleKey(id=%s)", key.HandleID)
}
type IPAMHandleListOptions struct {
// TODO: Have some options here?
}
func (options IPAMHandleListOptions) defaultPathRoot() string {
k := "/calico/ipam/v2/handle/"
// TODO: Allow filtering on individual host?
return k
}
func (options IPAMHandleListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get IPAM handle key from %s", path)
r := matchHandle.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("%s didn't match regex", path)
return nil
}
return IPAMHandleKey{HandleID: r[0][1]}
}
type IPAMHandle struct {
HandleID string `json:"-"`
Block map[string]int `json:"block"`
Deleted bool `json:"deleted"`
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
typeIPAMHost = reflect.TypeOf(IPAMHost{})
)
type IPAMHostKey struct {
Host string
}
func (key IPAMHostKey) defaultPath() (string, error) {
if key.Host == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "host"}
}
k := "/calico/ipam/v2/host/" + key.Host
return k, nil
}
func (key IPAMHostKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key IPAMHostKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key IPAMHostKey) valueType() (reflect.Type, error) {
return typeIPAMHost, nil
}
func (key IPAMHostKey) String() string {
return fmt.Sprintf("IPAMHostKey(host=%s)", key.Host)
}
type IPAMHost struct {
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) 2016,2021 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/backend/encap"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchIPPool = regexp.MustCompile("^/?calico/v1/ipam/v./pool/([^/]+)$")
typeIPPool = reflect.TypeOf(IPPool{})
)
type IPPoolKey struct {
CIDR net.IPNet `json:"-" validate:"required,name"`
}
func (key IPPoolKey) defaultPath() (string, error) {
if key.CIDR.IP == nil {
return "", errors.ErrorInsufficientIdentifiers{Name: "cidr"}
}
c := strings.Replace(key.CIDR.String(), "/", "-", 1)
e := fmt.Sprintf("/calico/v1/ipam/v%d/pool/%s", key.CIDR.Version(), c)
return e, nil
}
func (key IPPoolKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key IPPoolKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key IPPoolKey) valueType() (reflect.Type, error) {
return typeIPPool, nil
}
func (key IPPoolKey) String() string {
return fmt.Sprintf("IPPool(cidr=%s)", key.CIDR)
}
type IPPoolListOptions struct {
CIDR net.IPNet
}
func (options IPPoolListOptions) defaultPathRoot() string {
k := "/calico/v1/ipam/"
if options.CIDR.IP == nil {
return k
}
c := strings.Replace(options.CIDR.String(), "/", "-", 1)
k = k + fmt.Sprintf("v%d/pool/", options.CIDR.Version()) + fmt.Sprintf("%s", c)
return k
}
func (options IPPoolListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Pool key from %s", path)
r := matchIPPool.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("%s didn't match regex", path)
return nil
}
cidrStr := strings.Replace(r[0][1], "-", "/", 1)
_, cidr, err := net.ParseCIDR(cidrStr)
if err != nil {
log.WithError(err).Warningf("Failed to parse CIDR %s", cidrStr)
return nil
}
if options.CIDR.IP != nil && !reflect.DeepEqual(*cidr, options.CIDR) {
log.Debugf("Didn't match cidr %s != %s", options.CIDR.String(), cidr.String())
return nil
}
return IPPoolKey{CIDR: *cidr}
}
type IPPool struct {
CIDR net.IPNet `json:"cidr"`
IPIPInterface string `json:"ipip"`
IPIPMode encap.Mode `json:"ipip_mode"`
VXLANMode encap.Mode `json:"vxlan_mode"`
Masquerade bool `json:"masquerade"`
IPAM bool `json:"ipam"`
Disabled bool `json:"disabled"`
DisableBGPExport bool `json:"disableBGPExport"`
}

View File

@@ -0,0 +1,627 @@
// Copyright (c) 2016-2020 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"bytes"
"fmt"
net2 "net"
"reflect"
"strings"
"time"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/types"
"github.com/projectcalico/calico/libcalico-go/lib/json"
"github.com/projectcalico/calico/libcalico-go/lib/namespace"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
// RawString is used a value type to indicate that the value is a bare non-JSON string
type rawString string
type rawBool bool
type rawIP net.IP
var rawStringType = reflect.TypeOf(rawString(""))
var rawBoolType = reflect.TypeOf(rawBool(true))
var rawIPType = reflect.TypeOf(rawIP{})
// Key represents a parsed datastore key.
type Key interface {
// defaultPath() returns a common path representation of the object used by
// etcdv3 and other datastores.
defaultPath() (string, error)
// defaultDeletePath() returns a common path representation used by etcdv3
// and other datastores to delete the object.
defaultDeletePath() (string, error)
// defaultDeleteParentPaths() returns an ordered slice of paths that should
// be removed after deleting the primary path (given by defaultDeletePath),
// provided there are no child entries associated with those paths. This is
// only used by directory based KV stores (such as etcdv3). With a directory
// based KV store, creation of a resource may also create parent directory entries
// that could be shared by multiple resources, and therefore the parent directories
// can only be removed when there are no more resources under them. The list of
// parent paths is ordered, and directories should be removed in the order supplied
// in the slice and only if the directory is empty.
defaultDeleteParentPaths() ([]string, error)
// valueType returns the object type associated with this key.
valueType() (reflect.Type, error)
// String returns a unique string representation of this key. The string
// returned by this method must uniquely identify this Key.
String() string
}
// Interface used to perform datastore lookups.
type ListInterface interface {
// defaultPathRoot() returns a default stringified root path, i.e. path
// to the directory containing all the keys to be listed.
defaultPathRoot() string
// BUG(smc) I think we should remove this and use the package KeyFromDefaultPath function.
// KeyFromDefaultPath parses the default path representation of the
// Key type for this list. It returns nil if passed a different kind
// of path.
KeyFromDefaultPath(key string) Key
}
// KVPair holds a typed key and value object as well as datastore specific
// revision information.
//
// The Value is dependent on the Key, but in general will be on of the following
// types:
// - A pointer to a struct
// - A slice or map
// - A bare string, boolean value or IP address (i.e. without quotes, so not
// JSON format).
type KVPair struct {
Key Key
Value interface{}
Revision string
UID *types.UID
TTL time.Duration // For writes, if non-zero, key has a TTL.
}
// KVPairList hosts a slice of KVPair structs and a Revision, returned from a Ls
type KVPairList struct {
KVPairs []*KVPair
Revision string
}
// KeyToDefaultPath converts one of the Keys from this package into a unique
// '/'-delimited path, which is suitable for use as the key when storing the
// value in a hierarchical (i.e. one with directories and leaves) key/value
// datastore such as etcd v3.
//
// Each unique key returns a unique path.
//
// Keys with a hierarchical relationship share a common prefix. However, in
// order to support datastores that do not support storing data at non-leaf
// nodes in the hierarchy (such as etcd v3), the path returned for a "parent"
// key, is not a direct ancestor of its children.
func KeyToDefaultPath(key Key) (string, error) {
return key.defaultPath()
}
// KeyToDefaultDeletePath converts one of the Keys from this package into a
// unique '/'-delimited path, which is suitable for use as the key when
// (recursively) deleting the value from a hierarchical (i.e. one with
// directories and leaves) key/value datastore such as etcd v3.
//
// KeyToDefaultDeletePath returns a different path to KeyToDefaultPath when
// it is a passed a Key that represents a non-leaf which, for example, has its
// own metadata but also contains other resource types as children.
//
// KeyToDefaultDeletePath returns the common prefix of the non-leaf key and
// its children so that a recursive delete of that key would delete the
// object itself and any children it has.
func KeyToDefaultDeletePath(key Key) (string, error) {
return key.defaultDeletePath()
}
// KeyToDefaultDeleteParentPaths returns a slice of '/'-delimited
// paths which are used to delete parent entries that may be auto-created
// by directory-based KV stores (e.g. etcd v3). These paths should also be
// removed provided they have no more child entries.
//
// The list of parent paths is ordered, and directories should be removed
// in the order supplied in the slice and only if the directory is empty.
//
// For example,
//
// KeyToDefaultDeletePaths(WorkloadEndpointKey{
// Nodename: "h",
// OrchestratorID: "o",
// WorkloadID: "w",
// EndpointID: "e",
// })
//
// returns
//
// ["/calico/v1/host/h/workload/o/w/endpoint",
//
// "/calico/v1/host/h/workload/o/w"]
//
// indicating that these paths should also be deleted when they are empty.
// In this example it is equivalent to deleting the workload when there are
// no more endpoints in the workload.
func KeyToDefaultDeleteParentPaths(key Key) ([]string, error) {
return key.defaultDeleteParentPaths()
}
// ListOptionsToDefaultPathRoot converts list options struct into a
// common-prefix path suitable for querying a datastore that uses the paths
// returned by KeyToDefaultPath.
func ListOptionsToDefaultPathRoot(listOptions ListInterface) string {
return listOptions.defaultPathRoot()
}
// ListOptionsIsFullyQualified returns true if the options actually specify a fully
// qualified resource rather than a partial match.
func ListOptionsIsFullyQualified(listOptions ListInterface) bool {
// Construct the path prefix and then check to see if that actually corresponds to
// the path of a resource instance.
return listOptions.KeyFromDefaultPath(listOptions.defaultPathRoot()) != nil
}
// IsListOptionsLastSegmentPrefix returns true if the final segment of the default path
// root is a name prefix rather than the full name.
func IsListOptionsLastSegmentPrefix(listOptions ListInterface) bool {
// Only supported for ResourceListOptions.
rl, ok := listOptions.(ResourceListOptions)
return ok && rl.IsLastSegmentIsPrefix()
}
// KeyFromDefaultPath parses the default path representation of a key into one
// of our <Type>Key structs. Returns nil if the string doesn't match one of
// our key types.
func KeyFromDefaultPath(path string) Key {
// "v3" resource keys strictly require a leading slash but older "v1" keys were permissive.
// For ease of parsing, strip the slash off now but pass it down to keyFromDefaultPathInner so
// it can check for it later.
normalizedPath := path
if strings.HasPrefix(normalizedPath, "/") {
normalizedPath = normalizedPath[1:]
}
parts := strings.Split(normalizedPath, "/")
if len(parts) < 3 {
// After removing the optional `/` prefix, should have at least 3 segments.
return nil
}
return keyFromDefaultPathInner(path, parts)
}
func keyFromDefaultPathInner(path string, parts []string) Key {
if parts[0] != "calico" {
return nil
}
switch parts[1] {
case "v1":
switch parts[2] {
case "ipam":
return IPPoolListOptions{}.KeyFromDefaultPath(path)
case "config":
return GlobalConfigKey{Name: strings.Join(parts[3:], "/")}
case "host":
if len(parts) < 5 {
return nil
}
hostname := parts[3]
switch parts[4] {
case "workload":
if len(parts) != 9 || parts[7] != "endpoint" {
return nil
}
return WorkloadEndpointKey{
Hostname: unescapeName(hostname),
OrchestratorID: unescapeName(parts[5]),
WorkloadID: unescapeName(parts[6]),
EndpointID: unescapeName(parts[8]),
}
case "endpoint":
if len(parts) != 6 {
return nil
}
return HostEndpointKey{
Hostname: unescapeName(hostname),
EndpointID: unescapeName(parts[5]),
}
case "config":
return HostConfigKey{
Hostname: hostname,
Name: strings.Join(parts[5:], "/"),
}
case "metadata":
if len(parts) != 5 {
return nil
}
return HostMetadataKey{
Hostname: hostname,
}
case "bird_ip":
if len(parts) != 5 {
return nil
}
return HostIPKey{
Hostname: hostname,
}
case "wireguard":
if len(parts) != 5 {
return nil
}
return WireguardKey{
NodeName: hostname,
}
}
case "netset":
if len(parts) != 4 {
return nil
}
return NetworkSetKey{
Name: unescapeName(parts[3]),
}
case "Ready":
if len(parts) > 3 || path[0] != '/' {
return nil
}
return ReadyFlagKey{}
case "policy":
if len(parts) < 6 {
return nil
}
switch parts[3] {
case "tier":
if len(parts) < 6 {
return nil
}
switch parts[5] {
case "policy":
if len(parts) != 7 {
return nil
}
return PolicyKey{
Name: unescapeName(parts[6]),
}
}
case "profile":
pk := unescapeName(parts[4])
switch parts[5] {
case "rules":
return ProfileRulesKey{ProfileKey: ProfileKey{pk}}
case "labels":
return ProfileLabelsKey{ProfileKey: ProfileKey{pk}}
}
}
}
case "bgp":
switch parts[2] {
case "v1":
if len(parts) < 5 {
return nil
}
switch parts[3] {
case "global":
return GlobalBGPConfigListOptions{}.KeyFromDefaultPath(path)
case "host":
if len(parts) < 6 {
return nil
}
return NodeBGPConfigListOptions{}.KeyFromDefaultPath(path)
}
}
case "ipam":
if len(parts) < 5 {
return nil
}
switch parts[2] {
case "v2":
switch parts[3] {
case "assignment":
return BlockListOptions{}.KeyFromDefaultPath(path)
case "handle":
if len(parts) > 5 {
return nil
}
return IPAMHandleKey{
HandleID: parts[4],
}
case "host":
return BlockAffinityListOptions{}.KeyFromDefaultPath(path)
}
}
case "resources":
switch parts[2] {
case "v3":
// v3 resource keys strictly require the leading slash.
if len(parts) < 6 || parts[3] != "projectcalico.org" || path[0] != '/' {
return nil
}
switch len(parts) {
case 6:
ri, ok := resourceInfoByPlural[unescapeName(parts[4])]
if !ok {
log.Warnf("(BUG) unknown resource type: %v", path)
return nil
}
if namespace.IsNamespaced(ri.kind) {
log.Warnf("(BUG) Path is a global resource, but resource is namespaced: %v", path)
return nil
}
log.Debugf("Path is a global resource: %v", path)
return ResourceKey{
Kind: ri.kind,
Name: unescapeName(parts[5]),
}
case 7:
ri, ok := resourceInfoByPlural[unescapeName(parts[4])]
if !ok {
log.Warnf("(BUG) unknown resource type: %v", path)
return nil
}
if !namespace.IsNamespaced(ri.kind) {
log.Warnf("(BUG) Path is a namespaced resource, but resource is global: %v", path)
return nil
}
log.Debugf("Path is a namespaced resource: %v", path)
return ResourceKey{
Kind: ri.kind,
Namespace: unescapeName(parts[5]),
Name: unescapeName(parts[6]),
}
}
}
case "felix":
if len(parts) < 4 {
return nil
}
switch parts[2] {
case "v1":
switch parts[3] {
case "host":
if len(parts) != 7 || parts[5] != "endpoint" {
return nil
}
return HostEndpointStatusKey{
Hostname: parts[4],
EndpointID: unescapeName(parts[6]),
}
}
case "v2":
if len(parts) < 7 {
return nil
}
if parts[4] != "host" {
return nil
}
switch parts[6] {
case "status":
return ActiveStatusReportListOptions{}.KeyFromDefaultPath(path)
case "last_reported_status":
return LastStatusReportListOptions{}.KeyFromDefaultPath(path)
case "workload":
return WorkloadEndpointStatusListOptions{}.KeyFromDefaultPath(path)
}
}
}
log.Debugf("Path is unknown: %v", path)
return nil
}
// OldKeyFromDefaultPath is the old, (slower) implementation of KeyFromDefaultPath. It is kept to allow
// fuzzing the new version against it. Parses the default path representation of a key into one
// of our <Type>Key structs. Returns nil if the string doesn't match one of
// our key types.
func OldKeyFromDefaultPath(path string) Key {
if m := matchWorkloadEndpoint.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a workload endpoint: %v", path)
return WorkloadEndpointKey{
Hostname: unescapeName(m[1]),
OrchestratorID: unescapeName(m[2]),
WorkloadID: unescapeName(m[3]),
EndpointID: unescapeName(m[4]),
}
} else if m := matchHostEndpoint.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host endpoint: %v", path)
return HostEndpointKey{
Hostname: unescapeName(m[1]),
EndpointID: unescapeName(m[2]),
}
} else if m := matchNetworkSet.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a network set: %v", path)
return NetworkSetKey{
Name: unescapeName(m[1]),
}
} else if m := matchGlobalResource.FindStringSubmatch(path); m != nil {
ri, ok := resourceInfoByPlural[unescapeName(m[1])]
if !ok {
log.Warnf("(BUG) unknown resource type: %v", path)
return nil
}
if namespace.IsNamespaced(ri.kind) {
log.Warnf("(BUG) Path is a global resource, but resource is namespaced: %v", path)
return nil
}
log.Debugf("Path is a global resource: %v", path)
return ResourceKey{
Kind: ri.kind,
Name: unescapeName(m[2]),
}
} else if m := matchNamespacedResource.FindStringSubmatch(path); m != nil {
ri, ok := resourceInfoByPlural[unescapeName(m[1])]
if !ok {
log.Warnf("(BUG) unknown resource type: %v", path)
return nil
}
if !namespace.IsNamespaced(ri.kind) {
log.Warnf("(BUG) Path is a namespaced resource, but resource is global: %v", path)
return nil
}
log.Debugf("Path is a namespaced resource: %v", path)
return ResourceKey{
Kind: resourceInfoByPlural[unescapeName(m[1])].kind,
Namespace: unescapeName(m[2]),
Name: unescapeName(m[3]),
}
} else if m := matchPolicy.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a policy: %v", path)
return PolicyKey{
Name: unescapeName(m[2]),
}
} else if m := matchProfile.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a profile: %v (%v)", path, m[2])
pk := ProfileKey{unescapeName(m[1])}
switch m[2] {
case "rules":
log.Debugf("Profile rules")
return ProfileRulesKey{ProfileKey: pk}
case "labels":
log.Debugf("Profile labels")
return ProfileLabelsKey{ProfileKey: pk}
}
return nil
} else if m := matchHostIp.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host ID: %v", path)
return HostIPKey{Hostname: m[1]}
} else if m := matchWireguard.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a node name: %v", path)
return WireguardKey{NodeName: m[1]}
} else if m := matchIPPool.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a pool: %v", path)
mungedCIDR := m[1]
cidr := strings.Replace(mungedCIDR, "-", "/", 1)
_, c, err := net.ParseCIDR(cidr)
if err != nil {
log.WithError(err).Warningf("Failed to parse CIDR %s", cidr)
} else {
return IPPoolKey{CIDR: *c}
}
} else if m := matchGlobalConfig.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a global felix config: %v", path)
return GlobalConfigKey{Name: m[1]}
} else if m := matchHostConfig.FindStringSubmatch(path); m != nil {
log.Debugf("Path is a host config: %v", path)
return HostConfigKey{Hostname: m[1], Name: m[2]}
} else if matchReadyFlag.MatchString(path) {
log.Debugf("Path is a ready flag: %v", path)
return ReadyFlagKey{}
} else if k := (NodeBGPConfigListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (GlobalBGPConfigListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (BlockAffinityListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (BlockListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (HostEndpointStatusListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (WorkloadEndpointStatusListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (ActiveStatusReportListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else if k := (LastStatusReportListOptions{}).KeyFromDefaultPath(path); k != nil {
return k
} else {
log.Debugf("Path is unknown: %v", path)
}
// Not a key we know about.
return nil
}
// ParseValue parses the default JSON representation of our data into one of
// our value structs, according to the type of key. I.e. if passed a
// PolicyKey as the first parameter, it will try to parse rawData into a
// Policy struct.
func ParseValue(key Key, rawData []byte) (interface{}, error) {
valueType, err := key.valueType()
if err != nil {
return nil, err
}
if valueType == rawStringType {
return string(rawData), nil
}
if valueType == rawBoolType {
return string(rawData) == "true", nil
}
if valueType == rawIPType {
ip := net2.ParseIP(string(rawData))
if ip == nil {
return nil, nil
}
return &net.IP{IP: ip}, nil
}
value := reflect.New(valueType)
elem := value.Elem()
if elem.Kind() == reflect.Struct && elem.NumField() > 0 {
if elem.Field(0).Type() == reflect.ValueOf(key).Type() {
elem.Field(0).Set(reflect.ValueOf(key))
}
}
iface := value.Interface()
err = json.Unmarshal(rawData, iface)
if err != nil {
// This is a special case to address backwards compatibility from the time when we had no state information as block affinity value.
// example:
// Key: "/calico/ipam/v2/host/myhost.io/ipv4/block/172.29.82.0-26"
// Value: ""
// In 3.0.7 we added block affinity state as the value, so old "" value is no longer a valid JSON, so for that
// particular case we replace the "" with a "{}" so it can be parsed and we don't leak blocks after upgrade to Calico 3.0.7
// See: https://github.com/projectcalico/calico/issues/1956
if bytes.Equal(rawData, []byte(``)) && valueType == typeBlockAff {
rawData = []byte(`{}`)
if err = json.Unmarshal(rawData, iface); err != nil {
return nil, err
}
} else {
log.Warningf("Failed to unmarshal %#v into value %#v",
string(rawData), value)
return nil, err
}
}
if elem.Kind() != reflect.Struct {
// Pointer to a map or slice, unwrap.
iface = elem.Interface()
}
return iface, nil
}
// SerializeValue serializes a value in the model to a []byte to be stored in the datastore. This
// performs the opposite processing to ParseValue()
func SerializeValue(d *KVPair) ([]byte, error) {
valueType, err := d.Key.valueType()
if err != nil {
return nil, err
}
if d.Value == nil {
return json.Marshal(nil)
}
if valueType == rawStringType {
return []byte(d.Value.(string)), nil
}
if valueType == rawBoolType {
return []byte(fmt.Sprint(d.Value)), nil
}
if valueType == rawIPType {
return []byte(fmt.Sprint(d.Value)), nil
}
return json.Marshal(d.Value)
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2021 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
const (
KindKubernetesEndpointSlice = "KubernetesEndpointSlice"
)

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2021 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
const (
KindKubernetesNetworkPolicy = "KubernetesNetworkPolicy"
)

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2021 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
const (
KindKubernetesService = "KubernetesService"
)

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import "strings"
// escapeName removes any "/" from the name and URL encodes it to %2f,
// and necessarily removes % and encodes to %25.
func escapeName(name string) string {
name = strings.Replace(name, "%", "%25", -1)
return strings.Replace(name, "/", "%2f", -1)
}
// unescapeName replaces %2f and %25 in the name back to be a / and %.
func unescapeName(name string) string {
name = strings.Replace(name, "%2f", "/", -1)
return strings.Replace(name, "%25", "%", -1)
}

View File

@@ -0,0 +1,95 @@
// 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 model
import (
"fmt"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchNetworkSet = regexp.MustCompile("^/?calico/v1/netset/([^/]+)$")
typeNetworkSet = reflect.TypeOf(NetworkSet{})
)
type NetworkSetKey struct {
Name string `json:"-" validate:"required,namespacedName"`
}
func (key NetworkSetKey) defaultPath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/v1/netset/%s", escapeName(key.Name))
return e, nil
}
func (key NetworkSetKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key NetworkSetKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key NetworkSetKey) valueType() (reflect.Type, error) {
return typeNetworkSet, nil
}
func (key NetworkSetKey) String() string {
return fmt.Sprintf("NetworkSet(name=%s)", key.Name)
}
type NetworkSetListOptions struct {
Name string
}
func (options NetworkSetListOptions) defaultPathRoot() string {
k := "/calico/v1/netset"
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.Name))
return k
}
func (options NetworkSetListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get NetworkSet key from %s", path)
r := matchNetworkSet.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
name := unescapeName(r[0][1])
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return NetworkSetKey{Name: name}
}
type NetworkSet struct {
Nets []net.IPNet `json:"nets,omitempty" validate:"omitempty,dive,cidr"`
Labels map[string]string `json:"labels,omitempty" validate:"omitempty,labels"`
ProfileIDs []string `json:"profile_ids,omitempty" validate:"omitempty,dive,name"`
}

View File

@@ -0,0 +1,282 @@
// Copyright (c) 2016,2020 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
goerrors "errors"
"fmt"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/api/pkg/lib/numorstring"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
typeNode = reflect.TypeOf(Node{})
typeHostMetadata = reflect.TypeOf(HostMetadata{})
typeOrchRefs = reflect.TypeOf([]OrchRef{})
typeHostIp = rawIPType
typeWireguard = reflect.TypeOf(Wireguard{})
matchHostMetadata = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/metadata$`)
matchHostIp = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/bird_ip$`)
matchWireguard = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/wireguard$`)
)
type Node struct {
// Felix specific configuration
FelixIPv4 *net.IP
// Node specific labels
Labels map[string]string `json:"labels,omitempty"`
// BGP specific configuration
BGPIPv4Addr *net.IP
BGPIPv6Addr *net.IP
BGPIPv4Net *net.IPNet
BGPIPv6Net *net.IPNet
BGPASNumber *numorstring.ASNumber
OrchRefs []OrchRef `json:"orchRefs,omitempty"`
}
type OrchRef struct {
Orchestrator string `json:"orchestrator,omitempty"`
NodeName string `json:"nodeName,omitempty"`
}
type Wireguard struct {
InterfaceIPv4Addr *net.IP `json:"interfaceIPv4Addr,omitempty"`
PublicKey string `json:"publicKey,omitempty"`
InterfaceIPv6Addr *net.IP `json:"interfaceIPv6Addr,omitempty"`
PublicKeyV6 string `json:"publicKeyV6,omitempty"`
}
type NodeKey struct {
Hostname string
}
func (key NodeKey) defaultPath() (string, error) {
return "", goerrors.New("Node is a composite type, so not handled with a single path")
}
func (key NodeKey) defaultDeletePath() (string, error) {
return "", goerrors.New("Node is a composite type, so not handled with a single path")
}
func (key NodeKey) defaultDeleteParentPaths() ([]string, error) {
return nil, goerrors.New("Node is composite type, so not handled with a single path")
}
func (key NodeKey) valueType() (reflect.Type, error) {
return typeNode, nil
}
func (key NodeKey) String() string {
return fmt.Sprintf("Node(name=%s)", key.Hostname)
}
type NodeListOptions struct {
Hostname string
}
func (options NodeListOptions) defaultPathRoot() string {
return ""
}
func (options NodeListOptions) KeyFromDefaultPath(path string) Key {
return nil
}
// The node is a composite of the following subcomponents:
// - The host metadata. This is the primary subcomponent and is used to enumerate
// hosts. However, for backwards compatibility, the etcd driver needs to handle
// that this may not exist, and instead need to enumerate based on directory.
// - The host IPv4 address used by Calico to lock down IPIP traffic.
// - The BGP IPv4 and IPv6 addresses
// - The BGP ASN.
type HostMetadata struct {
}
type HostMetadataKey struct {
Hostname string
}
func (key HostMetadataKey) defaultPath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
return fmt.Sprintf("/calico/v1/host/%s/metadata", key.Hostname), nil
}
func (key HostMetadataKey) defaultDeletePath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
return fmt.Sprintf("/calico/v1/host/%s", key.Hostname), nil
}
func (key HostMetadataKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key HostMetadataKey) valueType() (reflect.Type, error) {
return typeHostMetadata, nil
}
func (key HostMetadataKey) String() string {
return fmt.Sprintf("Node(name=%s)", key.Hostname)
}
type HostMetadataListOptions struct {
Hostname string
}
func (options HostMetadataListOptions) defaultPathRoot() string {
if options.Hostname == "" {
return "/calico/v1/host"
} else {
return fmt.Sprintf("/calico/v1/host/%s/metadata", options.Hostname)
}
}
func (options HostMetadataListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Node key from %s", path)
if r := matchHostMetadata.FindAllStringSubmatch(path, -1); len(r) == 1 {
return HostMetadataKey{Hostname: r[0][1]}
} else {
log.Debugf("%s didn't match regex", path)
return nil
}
}
// The Felix Host IP Key.
type HostIPKey struct {
Hostname string
}
func (key HostIPKey) defaultPath() (string, error) {
return fmt.Sprintf("/calico/v1/host/%s/bird_ip",
key.Hostname), nil
}
func (key HostIPKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key HostIPKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key HostIPKey) valueType() (reflect.Type, error) {
return typeHostIp, nil
}
func (key HostIPKey) String() string {
return fmt.Sprintf("Node(name=%s)", key.Hostname)
}
type OrchRefKey struct {
Hostname string
}
func (key OrchRefKey) defaultPath() (string, error) {
return fmt.Sprintf("/calico/v1/host/%s/orchestrator_refs",
key.Hostname), nil
}
func (key OrchRefKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key OrchRefKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key OrchRefKey) valueType() (reflect.Type, error) {
return typeOrchRefs, nil
}
func (key OrchRefKey) String() string {
return fmt.Sprintf("OrchRefs(nodename=%s)", key.Hostname)
}
type OrchRefListOptions struct {
Hostname string
}
func (options OrchRefListOptions) defaultPathRoot() string {
return fmt.Sprintf("/calico/v1/host/%s/orchestrator_refs", options.Hostname)
}
func (options OrchRefListOptions) KeyFromDefaultPath(path string) Key {
return OrchRefKey{Hostname: options.Hostname}
}
// The Felix Wireguard Key.
type WireguardKey struct {
NodeName string
}
func (key WireguardKey) defaultPath() (string, error) {
if key.NodeName == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
return fmt.Sprintf("/calico/v1/host/%s/wireguard",
key.NodeName), nil
}
func (key WireguardKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key WireguardKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key WireguardKey) valueType() (reflect.Type, error) {
return typeWireguard, nil
}
func (key WireguardKey) String() string {
return fmt.Sprintf("Node(nodename=%s)", key.NodeName)
}
type WireguardListOptions struct {
NodeName string
}
func (options WireguardListOptions) defaultPathRoot() string {
if options.NodeName == "" {
return "/calico/v1/host"
} else {
return fmt.Sprintf("/calico/v1/host/%s/wireguard", options.NodeName)
}
}
func (options WireguardListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Node key from %s", path)
if r := matchWireguard.FindAllStringSubmatch(path, -1); len(r) == 1 {
return WireguardKey{NodeName: r[0][1]}
} else {
log.Debugf("%s didn't match regex", path)
return nil
}
}

View File

@@ -0,0 +1,126 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"regexp"
"reflect"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchPolicy = regexp.MustCompile("^/?calico/v1/policy/tier/([^/]+)/policy/([^/]+)$")
typePolicy = reflect.TypeOf(Policy{})
)
type PolicyKey struct {
Name string `json:"-" validate:"required,name"`
}
func (key PolicyKey) defaultPath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/v1/policy/tier/default/policy/%s",
escapeName(key.Name))
return e, nil
}
func (key PolicyKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key PolicyKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key PolicyKey) valueType() (reflect.Type, error) {
return typePolicy, nil
}
func (key PolicyKey) String() string {
return fmt.Sprintf("Policy(name=%s)", key.Name)
}
type PolicyListOptions struct {
Name string
}
func (options PolicyListOptions) defaultPathRoot() string {
k := "/calico/v1/policy/tier/default/policy"
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.Name))
return k
}
func (options PolicyListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Policy key from %s", path)
r := matchPolicy.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
name := unescapeName(r[0][2])
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
return PolicyKey{Name: name}
}
type Policy struct {
Namespace string `json:"namespace,omitempty" validate:"omitempty"`
Order *float64 `json:"order,omitempty" validate:"omitempty"`
InboundRules []Rule `json:"inbound_rules,omitempty" validate:"omitempty,dive"`
OutboundRules []Rule `json:"outbound_rules,omitempty" validate:"omitempty,dive"`
Selector string `json:"selector" validate:"selector"`
DoNotTrack bool `json:"untracked,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
PreDNAT bool `json:"pre_dnat,omitempty"`
ApplyOnForward bool `json:"apply_on_forward,omitempty"`
Types []string `json:"types,omitempty"`
}
func (p Policy) String() string {
parts := make([]string, 0)
if p.Order != nil {
parts = append(parts, fmt.Sprintf("order:%v", *p.Order))
}
parts = append(parts, fmt.Sprintf("selector:%#v", p.Selector))
inRules := make([]string, len(p.InboundRules))
for ii, rule := range p.InboundRules {
inRules[ii] = rule.String()
}
parts = append(parts, fmt.Sprintf("inbound:%v", strings.Join(inRules, ";")))
outRules := make([]string, len(p.OutboundRules))
for ii, rule := range p.OutboundRules {
outRules[ii] = rule.String()
}
parts = append(parts, fmt.Sprintf("outbound:%v", strings.Join(outRules, ";")))
parts = append(parts, fmt.Sprintf("untracked:%v", p.DoNotTrack))
parts = append(parts, fmt.Sprintf("pre_dnat:%v", p.PreDNAT))
parts = append(parts, fmt.Sprintf("apply_on_forward:%v", p.ApplyOnForward))
parts = append(parts, fmt.Sprintf("types:%v", strings.Join(p.Types, ";")))
return strings.Join(parts, ",")
}

View File

@@ -0,0 +1,208 @@
// Copyright (c) 2016 Tigera, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"regexp"
"reflect"
"sort"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchProfile = regexp.MustCompile("^/?calico/v1/policy/profile/([^/]+)/(rules|labels)$")
typeProfile = reflect.TypeOf(Profile{})
)
// The profile key actually returns the common parent of the three separate entries.
// It is useful to define this to re-use some of the common machinery, and can be used
// for delete processing since delete needs to remove the common parent.
type ProfileKey struct {
Name string `json:"-" validate:"required,name"`
}
func (key ProfileKey) defaultPath() (string, error) {
if key.Name == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
e := fmt.Sprintf("/calico/v1/policy/profile/%s", escapeName(key.Name))
return e, nil
}
func (key ProfileKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key ProfileKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key ProfileKey) valueType() (reflect.Type, error) {
return typeProfile, nil
}
func (key ProfileKey) String() string {
return fmt.Sprintf("Profile(name=%s)", key.Name)
}
// ProfileRulesKey implements the KeyInterface for the profile rules
type ProfileRulesKey struct {
ProfileKey
}
func (key ProfileRulesKey) defaultPath() (string, error) {
e, err := key.ProfileKey.defaultPath()
return e + "/rules", err
}
func (key ProfileRulesKey) valueType() (reflect.Type, error) {
return reflect.TypeOf(ProfileRules{}), nil
}
func (key ProfileRulesKey) String() string {
return fmt.Sprintf("ProfileRules(name=%s)", key.Name)
}
// ProfileLabelsKey implements the KeyInterface for the profile labels
type ProfileLabelsKey struct {
ProfileKey
}
func (key ProfileLabelsKey) defaultPath() (string, error) {
e, err := key.ProfileKey.defaultPath()
return e + "/labels", err
}
func (key ProfileLabelsKey) valueType() (reflect.Type, error) {
return reflect.TypeOf(map[string]string{}), nil
}
func (key ProfileLabelsKey) String() string {
return fmt.Sprintf("ProfileLabels(name=%s)", key.Name)
}
type ProfileListOptions struct {
Name string
}
func (options ProfileListOptions) defaultPathRoot() string {
k := "/calico/v1/policy/profile"
if options.Name == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.Name))
return k
}
func (options ProfileListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get Profile key from %s", path)
r := matchProfile.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
name := unescapeName(r[0][1])
kind := r[0][2]
if options.Name != "" && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
pk := ProfileKey{Name: name}
switch kind {
case "labels":
return ProfileLabelsKey{ProfileKey: pk}
case "rules":
return ProfileRulesKey{ProfileKey: pk}
}
return pk
}
// The profile structure is defined to allow the client to define a conversion interface
// to map between the API and backend profiles. However, in the actual underlying
// implementation the profile is written as three separate entries - rules, tags and labels.
type Profile struct {
Rules ProfileRules
Tags []string
Labels map[string]string
}
type ProfileRules struct {
InboundRules []Rule `json:"inbound_rules,omitempty" validate:"omitempty,dive"`
OutboundRules []Rule `json:"outbound_rules,omitempty" validate:"omitempty,dive"`
}
func (_ *ProfileListOptions) ListConvert(ds []*KVPair) []*KVPair {
profiles := make(map[string]*KVPair)
var name string
for _, d := range ds {
switch t := d.Key.(type) {
case ProfileLabelsKey:
name = t.Name
case ProfileRulesKey:
name = t.Name
default:
panic(fmt.Errorf("Unexpected key type: %v", t))
}
// Get the KVPair for the profile, initialising if just created.
pd, ok := profiles[name]
if !ok {
log.Debugf("Initialise profile %v", name)
pd = &KVPair{
Value: &Profile{},
Key: ProfileKey{Name: name},
}
profiles[name] = pd
}
p := pd.Value.(*Profile)
switch t := d.Value.(type) {
case map[string]string: // must be labels
log.Debugf("Store labels %v", t)
p.Labels = t
case *ProfileRules: // must be rules
log.Debugf("Store rules %v", t)
p.Rules = *t
default:
panic(fmt.Errorf("Unexpected type: %v", t))
}
pd.Value = p
}
log.Debugf("Map of profiles: %v", profiles)
// To store the keys in slice in sorted order
var keys []string
for k := range profiles {
keys = append(keys, k)
}
sort.Strings(keys)
out := make([]*KVPair, len(keys))
for i, k := range keys {
out[i] = profiles[k]
}
log.Debugf("Sorted groups of profiles: %v", out)
return out
}

View File

@@ -0,0 +1,34 @@
// 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 model
import "fmt"
const (
NoRegion string = "no-region"
RegionPrefix string = "region-"
)
func RegionString(region string) string {
if region != "" {
return RegionPrefix + region
} else {
return NoRegion
}
}
func ErrorSlashInRegionString(regionString string) error {
return fmt.Errorf("RegionString %v is invalid because it includes a slash", regionString)
}

View File

@@ -0,0 +1,321 @@
// Copyright (c) 2016-2021 Tigera, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"reflect"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
kapiv1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3"
"github.com/projectcalico/calico/libcalico-go/lib/namespace"
)
// Name/type information about a single resource.
type resourceInfo struct {
typeOf reflect.Type
plural string
kindLower string
kind string
}
var (
matchGlobalResource = regexp.MustCompile("^/calico/resources/v3/projectcalico[.]org/([^/]+)/([^/]+)$")
matchNamespacedResource = regexp.MustCompile("^/calico/resources/v3/projectcalico[.]org/([^/]+)/([^/]+)/([^/]+)$")
resourceInfoByKindLower = make(map[string]resourceInfo)
resourceInfoByPlural = make(map[string]resourceInfo)
)
func registerResourceInfo(kind string, plural string, typeOf reflect.Type) {
kindLower := strings.ToLower(kind)
plural = strings.ToLower(plural)
ri := resourceInfo{
typeOf: typeOf,
kindLower: kindLower,
kind: kind,
plural: plural,
}
resourceInfoByKindLower[kindLower] = ri
resourceInfoByPlural[plural] = ri
}
func init() {
registerResourceInfo(
apiv3.KindBGPPeer,
"bgppeers",
reflect.TypeOf(apiv3.BGPPeer{}),
)
registerResourceInfo(
apiv3.KindBGPConfiguration,
"bgpconfigurations",
reflect.TypeOf(apiv3.BGPConfiguration{}),
)
registerResourceInfo(
apiv3.KindClusterInformation,
"clusterinformations",
reflect.TypeOf(apiv3.ClusterInformation{}),
)
registerResourceInfo(
apiv3.KindFelixConfiguration,
"felixconfigurations",
reflect.TypeOf(apiv3.FelixConfiguration{}),
)
registerResourceInfo(
apiv3.KindGlobalNetworkPolicy,
"globalnetworkpolicies",
reflect.TypeOf(apiv3.GlobalNetworkPolicy{}),
)
registerResourceInfo(
apiv3.KindHostEndpoint,
"hostendpoints",
reflect.TypeOf(apiv3.HostEndpoint{}),
)
registerResourceInfo(
apiv3.KindGlobalNetworkSet,
"globalnetworksets",
reflect.TypeOf(apiv3.GlobalNetworkSet{}),
)
registerResourceInfo(
apiv3.KindIPPool,
"ippools",
reflect.TypeOf(apiv3.IPPool{}),
)
registerResourceInfo(
apiv3.KindIPReservation,
"ipreservations",
reflect.TypeOf(apiv3.IPReservation{}),
)
registerResourceInfo(
apiv3.KindNetworkPolicy,
"networkpolicies",
reflect.TypeOf(apiv3.NetworkPolicy{}),
)
registerResourceInfo(
KindKubernetesNetworkPolicy,
"kubernetesnetworkpolicies",
reflect.TypeOf(apiv3.NetworkPolicy{}),
)
registerResourceInfo(
KindKubernetesEndpointSlice,
"kubernetesendpointslices",
reflect.TypeOf(discovery.EndpointSlice{}),
)
registerResourceInfo(
apiv3.KindNetworkSet,
"networksets",
reflect.TypeOf(apiv3.NetworkSet{}),
)
registerResourceInfo(
libapiv3.KindNode,
"nodes",
reflect.TypeOf(libapiv3.Node{}),
)
registerResourceInfo(
apiv3.KindCalicoNodeStatus,
"caliconodestatuses",
reflect.TypeOf(apiv3.CalicoNodeStatus{}),
)
registerResourceInfo(
apiv3.KindProfile,
"profiles",
reflect.TypeOf(apiv3.Profile{}),
)
registerResourceInfo(
libapiv3.KindWorkloadEndpoint,
"workloadendpoints",
reflect.TypeOf(libapiv3.WorkloadEndpoint{}),
)
registerResourceInfo(
libapiv3.KindIPAMConfig,
"ipamconfigs",
reflect.TypeOf(libapiv3.IPAMConfig{}),
)
registerResourceInfo(
apiv3.KindKubeControllersConfiguration,
"kubecontrollersconfigurations",
reflect.TypeOf(apiv3.KubeControllersConfiguration{}))
registerResourceInfo(
KindKubernetesService,
"kubernetesservice",
reflect.TypeOf(kapiv1.Service{}),
)
registerResourceInfo(
libapiv3.KindBlockAffinity,
"blockaffinities",
reflect.TypeOf(libapiv3.BlockAffinity{}),
)
registerResourceInfo(
apiv3.KindBGPFilter,
"BGPFilters",
reflect.TypeOf(apiv3.BGPFilter{}),
)
}
type ResourceKey struct {
// The name of the resource.
Name string
// The namespace of the resource. Not required if the resource is not namespaced.
Namespace string
// The resource kind.
Kind string
}
func (key ResourceKey) defaultPath() (string, error) {
return key.defaultDeletePath()
}
func (key ResourceKey) defaultDeletePath() (string, error) {
ri, ok := resourceInfoByKindLower[strings.ToLower(key.Kind)]
if !ok {
return "", fmt.Errorf("couldn't convert key: %+v", key)
}
if namespace.IsNamespaced(key.Kind) {
return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s/%s", ri.plural, key.Namespace, key.Name), nil
}
return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s", ri.plural, key.Name), nil
}
func (key ResourceKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key ResourceKey) valueType() (reflect.Type, error) {
ri, ok := resourceInfoByKindLower[strings.ToLower(key.Kind)]
if !ok {
return nil, fmt.Errorf("Unexpected resource kind: " + key.Kind)
}
return ri.typeOf, nil
}
func (key ResourceKey) String() string {
if namespace.IsNamespaced(key.Kind) {
return fmt.Sprintf("%s(%s/%s)", key.Kind, key.Namespace, key.Name)
}
return fmt.Sprintf("%s(%s)", key.Kind, key.Name)
}
type ResourceListOptions struct {
// The name of the resource.
Name string
// The namespace of the resource. Not required if the resource is not namespaced.
Namespace string
// The resource kind.
Kind string
// Whether the name is prefix rather than the full name.
Prefix bool
}
// If the Kind, Namespace and Name are specified, but the Name is a prefix then the
// last segment of this path is a prefix.
func (options ResourceListOptions) IsLastSegmentIsPrefix() bool {
return len(options.Kind) != 0 &&
(len(options.Namespace) != 0 || !namespace.IsNamespaced(options.Kind)) &&
len(options.Name) != 0 &&
options.Prefix
}
func (options ResourceListOptions) KeyFromDefaultPath(path string) Key {
ri, ok := resourceInfoByKindLower[strings.ToLower(options.Kind)]
if !ok {
log.Panic("Unexpected resource kind: " + options.Kind)
}
if namespace.IsNamespaced(options.Kind) {
log.Debugf("Get Namespaced Resource key from %s", path)
r := matchNamespacedResource.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
kindPlural := r[0][1]
namespace := r[0][2]
name := r[0][3]
if len(options.Kind) == 0 {
panic("Kind must be specified in List option but is not")
}
if kindPlural != ri.plural {
log.Debugf("Didn't match kind %s != %s", kindPlural, kindPlural)
return nil
}
if len(options.Namespace) != 0 && namespace != options.Namespace {
log.Debugf("Didn't match namespace %s != %s", options.Namespace, namespace)
return nil
}
if len(options.Name) != 0 {
if options.Prefix && !strings.HasPrefix(name, options.Name) {
log.Debugf("Didn't match name prefix %s != prefix(%s)", options.Name, name)
return nil
} else if !options.Prefix && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
}
return ResourceKey{Kind: options.Kind, Namespace: namespace, Name: name}
}
log.Debugf("Get Global Resource key from %s", path)
r := matchGlobalResource.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
kindPlural := r[0][1]
name := r[0][2]
if kindPlural != ri.plural {
log.Debugf("Didn't match kind %s != %s", kindPlural, ri.plural)
return nil
}
if len(options.Name) != 0 {
if options.Prefix && !strings.HasPrefix(name, options.Name) {
log.Debugf("Didn't match name prefix %s != prefix(%s)", options.Name, name)
return nil
} else if !options.Prefix && name != options.Name {
log.Debugf("Didn't match name %s != %s", options.Name, name)
return nil
}
}
return ResourceKey{Kind: options.Kind, Name: name}
}
func (options ResourceListOptions) defaultPathRoot() string {
ri, ok := resourceInfoByKindLower[strings.ToLower(options.Kind)]
if !ok {
log.Panic("Unexpected resource kind: " + options.Kind)
}
k := "/calico/resources/v3/projectcalico.org/" + ri.plural
if namespace.IsNamespaced(options.Kind) {
if options.Namespace == "" {
return k
}
k = k + "/" + options.Namespace
}
if options.Name == "" {
return k
}
return k + "/" + options.Name
}
func (options ResourceListOptions) String() string {
return options.Kind
}

View File

@@ -0,0 +1,268 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"strconv"
"strings"
apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
"github.com/projectcalico/api/pkg/lib/numorstring"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
type Rule struct {
Action string `json:"action,omitempty"`
IPVersion *int `json:"ip_version,omitempty" validate:"omitempty,ipVersion"`
Protocol *numorstring.Protocol `json:"protocol,omitempty" validate:"omitempty"`
NotProtocol *numorstring.Protocol `json:"!protocol,omitempty" validate:"omitempty"`
// ICMP validation notes: 0 is a valid (common) ICMP type and code. Type = 255 is not assigned
// to any protocol and the Linux kernel doesn't support matching on it so we validate against
// it.
ICMPType *int `json:"icmp_type,omitempty" validate:"omitempty,gte=0,lt=255"`
ICMPCode *int `json:"icmp_code,omitempty" validate:"omitempty,gte=0,lte=255"`
NotICMPType *int `json:"!icmp_type,omitempty" validate:"omitempty,gte=0,lt=255"`
NotICMPCode *int `json:"!icmp_code,omitempty" validate:"omitempty,gte=0,lte=255"`
SrcTag string `json:"src_tag,omitempty" validate:"omitempty,tag"`
SrcNet *net.IPNet `json:"src_net,omitempty" validate:"omitempty"`
SrcNets []*net.IPNet `json:"src_nets,omitempty" validate:"omitempty"`
SrcSelector string `json:"src_selector,omitempty" validate:"omitempty,selector"`
SrcPorts []numorstring.Port `json:"src_ports,omitempty" validate:"omitempty,dive"`
SrcService string `json:"src_service,omitempty" validate:"omitempty"`
SrcServiceNamespace string `json:"src_service_ns,omitempty" validate:"omitempty"`
DstTag string `json:"dst_tag,omitempty" validate:"omitempty,tag"`
DstSelector string `json:"dst_selector,omitempty" validate:"omitempty,selector"`
DstNet *net.IPNet `json:"dst_net,omitempty" validate:"omitempty"`
DstNets []*net.IPNet `json:"dst_nets,omitempty" validate:"omitempty"`
DstPorts []numorstring.Port `json:"dst_ports,omitempty" validate:"omitempty,dive"`
DstService string `json:"dst_service,omitempty" validate:"omitempty"`
DstServiceNamespace string `json:"dst_service_ns,omitempty" validate:"omitempty"`
NotSrcTag string `json:"!src_tag,omitempty" validate:"omitempty,tag"`
NotSrcNet *net.IPNet `json:"!src_net,omitempty" validate:"omitempty"`
NotSrcNets []*net.IPNet `json:"!src_nets,omitempty" validate:"omitempty"`
NotSrcSelector string `json:"!src_selector,omitempty" validate:"omitempty,selector"`
NotSrcPorts []numorstring.Port `json:"!src_ports,omitempty" validate:"omitempty,dive"`
NotDstTag string `json:"!dst_tag,omitempty" validate:"omitempty"`
NotDstSelector string `json:"!dst_selector,omitempty" validate:"omitempty,selector"`
NotDstNet *net.IPNet `json:"!dst_net,omitempty" validate:"omitempty"`
NotDstNets []*net.IPNet `json:"!dst_nets,omitempty" validate:"omitempty"`
NotDstPorts []numorstring.Port `json:"!dst_ports,omitempty" validate:"omitempty,dive"`
// These fields allow us to pass through the raw match criteria from the V3 datamodel unmodified.
// The selectors above are formed in the update processor layer by combining the original
// selectors, namespace selectors and service account selectors into one.
OriginalSrcSelector string `json:"orig_src_selector,omitempty" validate:"omitempty,selector"`
OriginalSrcNamespaceSelector string `json:"orig_src_namespace_selector,omitempty" validate:"omitempty,selector"`
OriginalDstSelector string `json:"orig_dst_selector,omitempty" validate:"omitempty,selector"`
OriginalDstNamespaceSelector string `json:"orig_dst_namespace_selector,omitempty" validate:"omitempty,selector"`
OriginalNotSrcSelector string `json:"!orig_src_selector,omitempty" validate:"omitempty,selector"`
OriginalNotDstSelector string `json:"!orig_dst_selector,omitempty" validate:"omitempty,selector"`
OriginalSrcServiceAccountNames []string `json:"orig_src_service_acct_names,omitempty" validate:"omitempty"`
OriginalSrcServiceAccountSelector string `json:"orig_src_service_acct_selector,omitempty" validate:"omitempty,selector"`
OriginalDstServiceAccountNames []string `json:"orig_dst_service_acct_names,omitempty" validate:"omitempty"`
OriginalDstServiceAccountSelector string `json:"orig_dst_service_acct_selector,omitempty" validate:"omitempty,selector"`
// These fields allow us to pass through application layer selectors from the V3 datamodel.
HTTPMatch *HTTPMatch `json:"http,omitempty" validate:"omitempty"`
LogPrefix string `json:"log_prefix,omitempty" validate:"omitempty"`
Metadata *RuleMetadata `json:"metadata,omitempty" validate:"omitempty"`
}
type HTTPMatch struct {
Methods []string `json:"methods,omitempty" validate:"omitempty"`
Paths []apiv3.HTTPPath `json:"paths,omitempty" validate:"omitempty"`
}
type RuleMetadata struct {
Annotations map[string]string `json:"annotations,omitempty"`
}
func combineNets(n *net.IPNet, nets []*net.IPNet) []*net.IPNet {
if n == nil {
return nets
}
if len(nets) == 0 {
return []*net.IPNet{n}
}
var combination = make([]*net.IPNet, len(nets)+1)
copy(combination, nets)
combination[len(nets)] = n
return combination
}
func (r Rule) AllSrcNets() []*net.IPNet {
return combineNets(r.SrcNet, r.SrcNets)
}
func (r Rule) AllDstNets() []*net.IPNet {
return combineNets(r.DstNet, r.DstNets)
}
func (r Rule) AllNotSrcNets() []*net.IPNet {
return combineNets(r.NotSrcNet, r.NotSrcNets)
}
func (r Rule) AllNotDstNets() []*net.IPNet {
return combineNets(r.NotDstNet, r.NotDstNets)
}
func joinNets(nets []*net.IPNet) string {
parts := make([]string, len(nets))
for i, n := range nets {
parts[i] = n.String()
}
return strings.Join(parts, ",")
}
func (r Rule) String() string {
parts := make([]string, 0)
// Action.
if r.Action != "" {
parts = append(parts, r.Action)
} else {
parts = append(parts, "Allow")
}
// Global packet attributes that don't depend on direction.
if r.Protocol != nil {
parts = append(parts, r.Protocol.String())
}
if r.NotProtocol != nil {
parts = append(parts, "!"+r.NotProtocol.String())
}
if r.ICMPType != nil {
parts = append(parts, "type", strconv.Itoa(*r.ICMPType))
}
if r.ICMPCode != nil {
parts = append(parts, "code", strconv.Itoa(*r.ICMPCode))
}
if r.NotICMPType != nil {
parts = append(parts, "!type", strconv.Itoa(*r.NotICMPType))
}
if r.NotICMPCode != nil {
parts = append(parts, "!code", strconv.Itoa(*r.NotICMPCode))
}
{
// Source attributes. New block ensures that fromParts goes out-of-scope before
// we calculate toParts. This prevents copy/paste errors.
fromParts := make([]string, 0)
if len(r.SrcPorts) > 0 {
srcPorts := make([]string, len(r.SrcPorts))
for ii, port := range r.SrcPorts {
srcPorts[ii] = port.String()
}
fromParts = append(fromParts, "ports", strings.Join(srcPorts, ","))
}
if r.SrcTag != "" {
fromParts = append(fromParts, "tag", r.SrcTag)
}
if r.SrcSelector != "" {
fromParts = append(fromParts, "selector", fmt.Sprintf("%#v", r.SrcSelector))
}
srcNets := r.AllSrcNets()
if len(srcNets) != 0 {
fromParts = append(fromParts, "cidr", joinNets(srcNets))
}
if len(r.NotSrcPorts) > 0 {
notSrcPorts := make([]string, len(r.NotSrcPorts))
for ii, port := range r.NotSrcPorts {
notSrcPorts[ii] = port.String()
}
fromParts = append(fromParts, "!ports", strings.Join(notSrcPorts, ","))
}
if r.NotSrcTag != "" {
fromParts = append(fromParts, "!tag", r.NotSrcTag)
}
if r.NotSrcSelector != "" {
fromParts = append(fromParts, "!selector", fmt.Sprintf("%#v", r.NotSrcSelector))
}
notSrcNets := r.AllNotSrcNets()
if len(notSrcNets) != 0 {
fromParts = append(fromParts, "!cidr", joinNets(notSrcNets))
}
if len(fromParts) > 0 {
parts = append(parts, "from")
parts = append(parts, fromParts...)
}
}
{
// Destination attributes.
toParts := make([]string, 0)
if len(r.DstPorts) > 0 {
DstPorts := make([]string, len(r.DstPorts))
for ii, port := range r.DstPorts {
DstPorts[ii] = port.String()
}
toParts = append(toParts, "ports", strings.Join(DstPorts, ","))
}
if r.DstTag != "" {
toParts = append(toParts, "tag", r.DstTag)
}
if r.DstSelector != "" {
toParts = append(toParts, "selector", fmt.Sprintf("%#v", r.DstSelector))
}
dstNets := r.AllDstNets()
if len(dstNets) != 0 {
toParts = append(toParts, "cidr", joinNets(dstNets))
}
if len(r.NotDstPorts) > 0 {
notDstPorts := make([]string, len(r.NotDstPorts))
for ii, port := range r.NotDstPorts {
notDstPorts[ii] = port.String()
}
toParts = append(toParts, "!ports", strings.Join(notDstPorts, ","))
}
if r.NotDstTag != "" {
toParts = append(toParts, "!tag", r.NotDstTag)
}
if r.NotDstSelector != "" {
toParts = append(toParts, "!selector", fmt.Sprintf("%#v", r.NotDstSelector))
}
notDstNets := r.AllNotDstNets()
if len(notDstNets) != 0 {
toParts = append(toParts, "!cidr", joinNets(notDstNets))
}
// HTTPMatch are destination rules.
if r.HTTPMatch != nil {
if len(r.HTTPMatch.Methods) > 0 {
toParts = append(toParts, "httpMethods", fmt.Sprintf("%+v", r.HTTPMatch.Methods))
}
if len(r.HTTPMatch.Paths) > 0 {
toParts = append(toParts, "httpPaths", fmt.Sprintf("%+v", r.HTTPMatch.Paths))
}
}
if len(toParts) > 0 {
parts = append(parts, "to")
parts = append(parts, toParts...)
}
}
return strings.Join(parts, " ")
}

View File

@@ -0,0 +1,184 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"reflect"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchActiveStatusReport = regexp.MustCompile("^/?calico/felix/v2/([^/]+)/host/([^/]+)/status$")
matchLastStatusReport = regexp.MustCompile("^/?calico/felix/v2/([^/]+)/host/([^/]+)/last_reported_status")
typeStatusReport = reflect.TypeOf(StatusReport{})
)
type ActiveStatusReportKey struct {
Hostname string `json:"-" validate:"required,hostname"`
RegionString string
}
func (key ActiveStatusReportKey) defaultPath() (string, error) {
return key.defaultDeletePath()
}
func (key ActiveStatusReportKey) defaultDeletePath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "hostname"}
}
if key.RegionString == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "regionString"}
}
if strings.Contains(key.RegionString, "/") {
return "", ErrorSlashInRegionString(key.RegionString)
}
e := fmt.Sprintf("/calico/felix/v2/%s/host/%s/status", key.RegionString, key.Hostname)
return e, nil
}
func (key ActiveStatusReportKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key ActiveStatusReportKey) valueType() (reflect.Type, error) {
return typeStatusReport, nil
}
func (key ActiveStatusReportKey) String() string {
return fmt.Sprintf("StatusReport(hostname=%s)", key.Hostname)
}
type ActiveStatusReportListOptions struct {
Hostname string
RegionString string
}
func (options ActiveStatusReportListOptions) defaultPathRoot() string {
k := "/calico/felix/v2/"
if options.RegionString == "" {
return k
}
k = k + options.RegionString + "/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/status", options.Hostname)
return k
}
func (options ActiveStatusReportListOptions) KeyFromDefaultPath(ekey string) Key {
log.Debugf("Get StatusReport key from %s", ekey)
r := matchActiveStatusReport.FindAllStringSubmatch(ekey, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
regionString := r[0][1]
name := r[0][2]
if options.RegionString != "" && regionString != options.RegionString {
log.Debugf("Didn't match region %s != %s", options.RegionString, regionString)
return nil
}
if options.Hostname != "" && name != options.Hostname {
log.Debugf("Didn't match name %s != %s", options.Hostname, name)
return nil
}
return ActiveStatusReportKey{Hostname: name, RegionString: regionString}
}
type LastStatusReportKey struct {
Hostname string `json:"-" validate:"required,hostname"`
RegionString string
}
func (key LastStatusReportKey) defaultPath() (string, error) {
return key.defaultDeletePath()
}
func (key LastStatusReportKey) defaultDeletePath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "hostname"}
}
if key.RegionString == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "regionString"}
}
if strings.Contains(key.RegionString, "/") {
return "", ErrorSlashInRegionString(key.RegionString)
}
e := fmt.Sprintf("/calico/felix/v2/%s/host/%s/last_reported_status", key.RegionString, key.Hostname)
return e, nil
}
func (key LastStatusReportKey) defaultDeleteParentPaths() ([]string, error) {
return nil, nil
}
func (key LastStatusReportKey) valueType() (reflect.Type, error) {
return typeStatusReport, nil
}
func (key LastStatusReportKey) String() string {
return fmt.Sprintf("StatusReport(hostname=%s)", key.Hostname)
}
type LastStatusReportListOptions struct {
Hostname string
RegionString string
}
func (options LastStatusReportListOptions) defaultPathRoot() string {
k := "/calico/felix/v2/"
if options.RegionString == "" {
return k
}
k = k + options.RegionString + "/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/last_reported_status", options.Hostname)
return k
}
func (options LastStatusReportListOptions) KeyFromDefaultPath(ekey string) Key {
log.Debugf("Get StatusReport key from %s", ekey)
r := matchLastStatusReport.FindAllStringSubmatch(ekey, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
regionString := r[0][1]
name := r[0][2]
if options.RegionString != "" && regionString != options.RegionString {
log.Debugf("Didn't match region %s != %s", options.RegionString, regionString)
return nil
}
if options.Hostname != "" && name != options.Hostname {
log.Debugf("Didn't match name %s != %s", options.Hostname, name)
return nil
}
return LastStatusReportKey{Hostname: name, RegionString: regionString}
}
type StatusReport struct {
Timestamp string `json:"time"`
UptimeSeconds float64 `json:"uptime"`
FirstUpdate bool `json:"first_update"`
}

View File

@@ -0,0 +1,185 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/api/pkg/lib/numorstring"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
"github.com/projectcalico/calico/libcalico-go/lib/net"
)
var (
matchWorkloadEndpoint = regexp.MustCompile("^/?calico/v1/host/([^/]+)/workload/([^/]+)/([^/]+)/endpoint/([^/]+)$")
)
type WorkloadEndpointKey struct {
Hostname string `json:"-"`
OrchestratorID string `json:"-"`
WorkloadID string `json:"-"`
EndpointID string `json:"-"`
}
func (key WorkloadEndpointKey) defaultPath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "node"}
}
if key.OrchestratorID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "orchestrator"}
}
if key.WorkloadID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "workload"}
}
if key.EndpointID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "name"}
}
return fmt.Sprintf("/calico/v1/host/%s/workload/%s/%s/endpoint/%s",
key.Hostname, escapeName(key.OrchestratorID), escapeName(key.WorkloadID), escapeName(key.EndpointID)), nil
}
func (key WorkloadEndpointKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key WorkloadEndpointKey) defaultDeleteParentPaths() ([]string, error) {
if key.Hostname == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "node"}
}
if key.OrchestratorID == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "orchestrator"}
}
if key.WorkloadID == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "workload"}
}
workload := fmt.Sprintf("/calico/v1/host/%s/workload/%s/%s",
key.Hostname, escapeName(key.OrchestratorID), escapeName(key.WorkloadID))
endpoints := workload + "/endpoint"
return []string{endpoints, workload}, nil
}
func (key WorkloadEndpointKey) valueType() (reflect.Type, error) {
return reflect.TypeOf(WorkloadEndpoint{}), nil
}
func (key WorkloadEndpointKey) String() string {
return fmt.Sprintf("WorkloadEndpoint(node=%s, orchestrator=%s, workload=%s, name=%s)",
key.Hostname, key.OrchestratorID, key.WorkloadID, key.EndpointID)
}
type WorkloadEndpointListOptions struct {
Hostname string
OrchestratorID string
WorkloadID string
EndpointID string
}
func (options WorkloadEndpointListOptions) defaultPathRoot() string {
k := "/calico/v1/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/workload", options.Hostname)
if options.OrchestratorID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.OrchestratorID))
if options.WorkloadID == "" {
return k
}
k = k + fmt.Sprintf("/%s/endpoint", escapeName(options.WorkloadID))
if options.EndpointID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.EndpointID))
return k
}
func (options WorkloadEndpointListOptions) KeyFromDefaultPath(path string) Key {
log.Debugf("Get WorkloadEndpoint key from %s", path)
r := matchWorkloadEndpoint.FindAllStringSubmatch(path, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
hostname := r[0][1]
orch := unescapeName(r[0][2])
workload := unescapeName(r[0][3])
endpointID := unescapeName(r[0][4])
if options.Hostname != "" && hostname != options.Hostname {
log.Debugf("Didn't match hostname %s != %s", options.Hostname, hostname)
return nil
}
if options.OrchestratorID != "" && orch != options.OrchestratorID {
log.Debugf("Didn't match orchestrator %s != %s", options.OrchestratorID, orch)
return nil
}
if options.WorkloadID != "" && workload != options.WorkloadID {
log.Debugf("Didn't match workload %s != %s", options.WorkloadID, workload)
return nil
}
if options.EndpointID != "" && endpointID != options.EndpointID {
log.Debugf("Didn't match endpoint ID %s != %s", options.EndpointID, endpointID)
return nil
}
return WorkloadEndpointKey{
Hostname: hostname,
OrchestratorID: orch,
WorkloadID: workload,
EndpointID: endpointID,
}
}
type WorkloadEndpoint struct {
State string `json:"state"`
Name string `json:"name"`
ActiveInstanceID string `json:"active_instance_id"`
Mac *net.MAC `json:"mac"`
ProfileIDs []string `json:"profile_ids"`
IPv4Nets []net.IPNet `json:"ipv4_nets"`
IPv6Nets []net.IPNet `json:"ipv6_nets"`
IPv4NAT []IPNAT `json:"ipv4_nat,omitempty"`
IPv6NAT []IPNAT `json:"ipv6_nat,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
IPv4Gateway *net.IP `json:"ipv4_gateway,omitempty" validate:"omitempty,ipv4"`
IPv6Gateway *net.IP `json:"ipv6_gateway,omitempty" validate:"omitempty,ipv6"`
Ports []EndpointPort `json:"ports,omitempty" validate:"dive"`
GenerateName string `json:"generate_name,omitempty"`
AllowSpoofedSourcePrefixes []net.IPNet `json:"allow_spoofed_source_ips,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}
type EndpointPort struct {
Name string `json:"name" validate:"name"`
Protocol numorstring.Protocol `json:"protocol"`
Port uint16 `json:"port" validate:"gt=0"`
}
// IPNat contains a single NAT mapping for a WorkloadEndpoint resource.
type IPNAT struct {
// The internal IP address which must be associated with the owning endpoint via the
// configured IPNetworks for the endpoint.
IntIP net.IP `json:"int_ip" validate:"ip"`
// The external IP address.
ExtIP net.IP `json:"ext_ip" validate:"ip"`
}

View File

@@ -0,0 +1,178 @@
// Copyright (c) 2016-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 model
import (
"fmt"
"strings"
"regexp"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/projectcalico/calico/libcalico-go/lib/errors"
)
var (
matchWorkloadEndpointStatus = regexp.MustCompile("^/?calico/felix/v2/([^/]+)/host/([^/]+)/workload/([^/]+)/([^/]+)/endpoint/([^/]+)$")
)
type WorkloadEndpointStatusKey struct {
Hostname string `json:"-"`
OrchestratorID string `json:"-"`
WorkloadID string `json:"-"`
EndpointID string `json:"-"`
RegionString string
}
func (key WorkloadEndpointStatusKey) defaultPath() (string, error) {
if key.Hostname == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "hostname"}
}
if key.OrchestratorID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "orchestrator"}
}
if key.WorkloadID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "workload"}
}
if key.EndpointID == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "endpoint"}
}
if key.RegionString == "" {
return "", errors.ErrorInsufficientIdentifiers{Name: "regionString"}
}
if strings.Contains(key.RegionString, "/") {
return "", ErrorSlashInRegionString(key.RegionString)
}
return fmt.Sprintf("/calico/felix/v2/%s/host/%s/workload/%s/%s/endpoint/%s",
key.RegionString,
key.Hostname, escapeName(key.OrchestratorID), escapeName(key.WorkloadID), escapeName(key.EndpointID)), nil
}
func (key WorkloadEndpointStatusKey) defaultDeletePath() (string, error) {
return key.defaultPath()
}
func (key WorkloadEndpointStatusKey) defaultDeleteParentPaths() ([]string, error) {
if key.Hostname == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "hostname"}
}
if key.OrchestratorID == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "orchestrator"}
}
if key.WorkloadID == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "workload"}
}
if key.RegionString == "" {
return nil, errors.ErrorInsufficientIdentifiers{Name: "regionString"}
}
if strings.Contains(key.RegionString, "/") {
return nil, ErrorSlashInRegionString(key.RegionString)
}
workload := fmt.Sprintf("/calico/felix/v2/%s/host/%s/workload/%s/%s",
key.RegionString,
key.Hostname, escapeName(key.OrchestratorID), escapeName(key.WorkloadID))
endpoints := workload + "/endpoint"
return []string{endpoints, workload}, nil
}
func (key WorkloadEndpointStatusKey) valueType() (reflect.Type, error) {
return reflect.TypeOf(WorkloadEndpointStatus{}), nil
}
func (key WorkloadEndpointStatusKey) String() string {
return fmt.Sprintf("WorkloadEndpointStatus(hostname=%s, orchestrator=%s, workload=%s, name=%s)",
key.Hostname, key.OrchestratorID, key.WorkloadID, key.EndpointID)
}
type WorkloadEndpointStatusListOptions struct {
Hostname string
OrchestratorID string
WorkloadID string
EndpointID string
RegionString string
}
func (options WorkloadEndpointStatusListOptions) defaultPathRoot() string {
k := "/calico/felix/v2/"
if options.RegionString == "" {
return k
}
k = k + options.RegionString + "/host"
if options.Hostname == "" {
return k
}
k = k + fmt.Sprintf("/%s/workload", options.Hostname)
if options.OrchestratorID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.OrchestratorID))
if options.WorkloadID == "" {
return k
}
k = k + fmt.Sprintf("/%s/endpoint", escapeName(options.WorkloadID))
if options.EndpointID == "" {
return k
}
k = k + fmt.Sprintf("/%s", escapeName(options.EndpointID))
return k
}
func (options WorkloadEndpointStatusListOptions) KeyFromDefaultPath(ekey string) Key {
log.Debugf("Get WorkloadEndpoint key from %s", ekey)
r := matchWorkloadEndpointStatus.FindAllStringSubmatch(ekey, -1)
if len(r) != 1 {
log.Debugf("Didn't match regex")
return nil
}
regionString := r[0][1]
hostname := r[0][2]
orchID := unescapeName(r[0][3])
workloadID := unescapeName(r[0][4])
endpointID := unescapeName(r[0][5])
if options.RegionString != "" && regionString != options.RegionString {
log.Debugf("Didn't match region %s != %s", options.RegionString, regionString)
return nil
}
if options.Hostname != "" && hostname != options.Hostname {
log.Debugf("Didn't match hostname %s != %s", options.Hostname, hostname)
return nil
}
if options.OrchestratorID != "" && orchID != options.OrchestratorID {
log.Debugf("Didn't match orchestrator %s != %s", options.OrchestratorID, orchID)
return nil
}
if options.WorkloadID != "" && workloadID != options.WorkloadID {
log.Debugf("Didn't match workload %s != %s", options.WorkloadID, workloadID)
return nil
}
if options.EndpointID != "" && endpointID != options.EndpointID {
log.Debugf("Didn't match endpoint ID %s != %s", options.EndpointID, endpointID)
return nil
}
return WorkloadEndpointStatusKey{
Hostname: hostname,
OrchestratorID: orchID,
WorkloadID: workloadID,
EndpointID: endpointID,
RegionString: regionString,
}
}
type WorkloadEndpointStatus struct {
Status string `json:"status"`
}