Skip to content

Commit

Permalink
Add distributed status management for EgressFirewall.
Browse files Browse the repository at this point in the history
Add Status.Messages field to record statuses from zones,
make egressfirewall status a subresource.

Signed-off-by: Nadia Pinaeva <[email protected]>
  • Loading branch information
npinaeva committed Nov 2, 2023
1 parent 9008646 commit 299ca33
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 40 deletions.
7 changes: 7 additions & 0 deletions dist/images/ovnkube.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,12 @@ ovn-cluster-manager() {
fi
echo "egressservice_enabled_flag=${egressservice_enabled_flag}"

egressfirewall_enabled_flag=
if [[ ${ovn_egressfirewall_enable} == "true" ]]; then
egressfirewall_enabled_flag="--enable-egress-firewall"
fi
echo "egressfirewall_enabled_flag=${egressfirewall_enabled_flag}"

hybrid_overlay_flags=
if [[ ${ovn_hybrid_overlay_enable} == "true" ]]; then
hybrid_overlay_flags="--enable-hybrid-overlay"
Expand Down Expand Up @@ -2029,6 +2035,7 @@ ovn-cluster-manager() {

echo "=============== ovn-cluster-manager ========== MASTER ONLY"
/usr/bin/ovnkube --init-cluster-manager ${K8S_NODE} \
${egressfirewall_enabled_flag} \
${egressip_enabled_flag} \
${egressip_healthcheck_port_flag} \
${egressservice_enabled_flag} \
Expand Down
13 changes: 10 additions & 3 deletions dist/templates/k8s.ovn.org_egressfirewalls.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.11.3
creationTimestamp: null
controller-gen.kubebuilder.io/version: v0.13.0
name: egressfirewalls.k8s.ovn.org
spec:
group: k8s.ovn.org
Expand Down Expand Up @@ -157,12 +156,20 @@ spec:
status:
description: Observed status of EgressFirewall
properties:
messages:
items:
type: string
type: array
x-kubernetes-list-type: set
status:
type: string
required:
- messages
type: object
required:
- spec
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
2 changes: 2 additions & 0 deletions dist/templates/rbac-ovnkube-cluster-manager.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ rules:
- egressips
- egressservices
- adminpolicybasedexternalroutes
- egressfirewalls
verbs: [ "get", "list", "watch" ]
- apiGroups: ["k8s.ovn.org"]
resources:
Expand All @@ -80,4 +81,5 @@ rules:
- apiGroups: ["k8s.ovn.org"]
resources:
- adminpolicybasedexternalroutes/status
- egressfirewalls/status
verbs: [ "patch", "update" ]
2 changes: 1 addition & 1 deletion dist/templates/rbac-ovnkube-master.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ rules:
verbs: [ "patch", "update" ]
- apiGroups: ["k8s.ovn.org"]
resources:
- egressfirewalls
- egressfirewalls/status
- egressips
- egressqoses
- egressservices/status
Expand Down
2 changes: 1 addition & 1 deletion dist/templates/rbac-ovnkube-node.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ rules:
verbs: ["list", "get", "watch"]
- apiGroups: ["k8s.ovn.org"]
resources:
- egressfirewalls
- egressfirewalls/status
- adminpolicybasedexternalroutes/status
verbs: [ "patch", "update" ]
- apiGroups: ["policy.networking.k8s.io"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1"
adminpolicybasedrouteclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/clientset/versioned"
adminpolicybasedroutelisters "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1/apis/listers/adminpolicybasedroute/v1"
egressfirewallapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1"
egressfirewallclientset "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/clientset/versioned"
egressfirewalllisters "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/listers/egressfirewall/v1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -66,3 +69,43 @@ func (m *apbRouteManager) updateStatus(route *adminpolicybasedrouteapi.AdminPoli
return []byte(fmt.Sprintf(`{"apiVersion":"k8s.ovn.org/adminpolicybasedroutelisters","kind":"AdminPolicyBasedExternalRoute","status":{"status":"%s"}}`, newStatus)),
route.Status.Status != newStatus
}

type egressFirewallManager struct {
lister egressfirewalllisters.EgressFirewallLister
client egressfirewallclientset.Interface
}

func newEgressFirewallManager(lister egressfirewalllisters.EgressFirewallLister, client egressfirewallclientset.Interface) *egressFirewallManager {
return &egressFirewallManager{
lister: lister,
client: client,
}
}

func (m *egressFirewallManager) get(namespace, name string) (*egressfirewallapi.EgressFirewall, error) {

Check failure on line 85 in go-controller/pkg/clustermanager/status_manager/resource_manager.go

View workflow job for this annotation

GitHub Actions / Lint

func `(*egressFirewallManager).get` is unused (unused)
return m.lister.EgressFirewalls(namespace).Get(name)
}

func (m *egressFirewallManager) patch(ctx context.Context, namespace, name string, pt ktypes.PatchType, data []byte, opts metav1.PatchOptions,

Check failure on line 89 in go-controller/pkg/clustermanager/status_manager/resource_manager.go

View workflow job for this annotation

GitHub Actions / Lint

func `(*egressFirewallManager).patch` is unused (unused)
subresources ...string) (*egressfirewallapi.EgressFirewall, error) {
return m.client.K8sV1().EgressFirewalls(namespace).Patch(ctx, name, pt, data, opts, subresources...)
}

func (m *egressFirewallManager) statusChanged(oldObj, newObj *egressfirewallapi.EgressFirewall) bool {

Check failure on line 94 in go-controller/pkg/clustermanager/status_manager/resource_manager.go

View workflow job for this annotation

GitHub Actions / Lint

func `(*egressFirewallManager).statusChanged` is unused (unused)
return !reflect.DeepEqual(oldObj.Status.Messages, newObj.Status.Messages)
}

func (m *egressFirewallManager) updateStatus(egressFirewall *egressfirewallapi.EgressFirewall) ([]byte, bool) {

Check failure on line 98 in go-controller/pkg/clustermanager/status_manager/resource_manager.go

View workflow job for this annotation

GitHub Actions / Lint

func `(*egressFirewallManager).updateStatus` is unused (unused)
if egressFirewall == nil || len(egressFirewall.Status.Messages) == 0 {
return nil, false
}
newStatus := "EgressFirewall Rules applied"
for _, message := range egressFirewall.Status.Messages {
if strings.Contains(message, types.EgressFirewallApplyError) {
newStatus = types.EgressFirewallApplyError
break
}
}
return []byte(fmt.Sprintf(`{"apiVersion":"k8s.ovn.org/v1","kind":"EgressFirewall","status":{"status":"%s"}}`, newStatus)),
egressFirewall.Status.Status != newStatus
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
adminpolicybasedrouteapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/adminpolicybasedroute/v1"
egressfirewallapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"

Expand Down Expand Up @@ -170,6 +171,14 @@ func NewStatusManager(wf *factory.WatchFactory, ovnClient *util.OVNClusterManage
)
sm.typedManagers["adminpolicybasedexternalroutes"] = apbRouteManager
}
if config.OVNKubernetesFeature.EnableEgressFirewall {
egressFirewallManager := newStatusManager[*egressfirewallapi.EgressFirewall](
"egressfirewalls",
wf.EgressFirewallInformer().Informer(),
newEgressFirewallManager(wf.EgressFirewallInformer().Lister(), ovnClient.EgressFirewallClient),
)
sm.typedManagers["egressfirewalls"] = egressFirewallManager
}
return sm
}

Expand Down
4 changes: 4 additions & 0 deletions go-controller/pkg/crd/egressfirewall/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
// +resource:path=egressfirewall
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:printcolumn:name="EgressFirewall Status",type=string,JSONPath=".status.status"
// +kubebuilder:subresource:status
// EgressFirewall describes the current egress firewall for a Namespace.
// Traffic from a pod to an IP address outside the cluster will be checked against
// each EgressFirewallRule in the pod's namespace's EgressFirewall, in
Expand All @@ -35,6 +36,9 @@ type EgressFirewall struct {

type EgressFirewallStatus struct {
Status string `json:"status,omitempty"`
// +patchStrategy=merge
// +listType=set
Messages []string `json:"messages"`
}

// EgressFirewallSpec is a desired state description of EgressFirewall.
Expand Down
11 changes: 11 additions & 0 deletions go-controller/pkg/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
egressfirewallapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1"
egressfirewallscheme "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/clientset/versioned/scheme"
egressfirewallinformerfactory "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/informers/externalversions"
egressfirewallinformer "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/informers/externalversions/egressfirewall/v1"
egressfirewalllister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/listers/egressfirewall/v1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
Expand Down Expand Up @@ -546,6 +547,7 @@ func NewNodeWatchFactory(ovnClientset *util.OVNNodeClientset, nodeName string) (
func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset) (*WatchFactory, error) {
wf := &WatchFactory{
iFactory: informerfactory.NewSharedInformerFactory(ovnClientset.KubeClient, resyncInterval),
efFactory: egressfirewallinformerfactory.NewSharedInformerFactory(ovnClientset.EgressFirewallClient, resyncInterval),
eipFactory: egressipinformerfactory.NewSharedInformerFactory(ovnClientset.EgressIPClient, resyncInterval),
cpipcFactory: ocpcloudnetworkinformerfactory.NewSharedInformerFactory(ovnClientset.CloudNetworkClient, resyncInterval),
egressServiceFactory: egressserviceinformerfactory.NewSharedInformerFactoryWithOptions(ovnClientset.EgressServiceClient, resyncInterval),
Expand Down Expand Up @@ -631,6 +633,11 @@ func NewClusterManagerWatchFactory(ovnClientset *util.OVNClusterManagerClientset
wf.apbRouteFactory.K8s().V1().AdminPolicyBasedExternalRoutes().Informer()
}

if config.OVNKubernetesFeature.EnableEgressFirewall {
// make sure shared informer is created for a factory, so on wf.efFactory.Start() it is initialized and caches are synced.
wf.efFactory.K8s().V1().EgressFirewalls().Informer()
}

return wf, nil
}

Expand Down Expand Up @@ -1243,6 +1250,10 @@ func (wf *WatchFactory) EgressIPInformer() egressipinformer.EgressIPInformer {
return wf.eipFactory.K8s().V1().EgressIPs()
}

func (wf *WatchFactory) EgressFirewallInformer() egressfirewallinformer.EgressFirewallInformer {
return wf.efFactory.K8s().V1().EgressFirewalls()
}

// withServiceNameAndNoHeadlessServiceSelector returns a LabelSelector (added to the
// watcher for EndpointSlices) that will only choose EndpointSlices with a non-empty
// "kubernetes.io/service-name" label and without "service.kubernetes.io/headless"
Expand Down
9 changes: 5 additions & 4 deletions go-controller/pkg/factory/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,11 @@ var _ = Describe("Watch Factory Operations", func() {
EgressServiceClient: egressServiceFakeClient,
}
ovnCMClientset = &util.OVNClusterManagerClientset{
KubeClient: fakeClient,
EgressIPClient: egressIPFakeClient,
CloudNetworkClient: cloudNetworkFakeClient,
EgressServiceClient: egressServiceFakeClient,
KubeClient: fakeClient,
EgressIPClient: egressIPFakeClient,
CloudNetworkClient: cloudNetworkFakeClient,
EgressServiceClient: egressServiceFakeClient,
EgressFirewallClient: egressFirewallFakeClient,
}

pods = make([]*v1.Pod, 0)
Expand Down
11 changes: 2 additions & 9 deletions go-controller/pkg/ovn/default_network_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,16 +768,9 @@ func (h *defaultNetworkControllerEventHandler) AddResource(obj interface{}, from
}

case factory.EgressFirewallType:
var err error
egressFirewall := obj.(*egressfirewall.EgressFirewall).DeepCopy()
if err = h.oc.addEgressFirewall(egressFirewall); err != nil {
egressFirewall.Status.Status = egressFirewallAddError
} else {
egressFirewall.Status.Status = egressFirewallAppliedCorrectly
metrics.UpdateEgressFirewallRuleCount(float64(len(egressFirewall.Spec.Egress)))
metrics.IncrementEgressFirewallCount()
}
if statusErr := h.oc.updateEgressFirewallStatusWithRetry(egressFirewall); statusErr != nil {
err := h.oc.addEgressFirewall(egressFirewall)
if statusErr := h.oc.setEgressFirewallStatus(egressFirewall, err); statusErr != nil {
klog.Errorf("Failed to update egress firewall status %s, error: %v",
getEgressFirewallNamespacedName(egressFirewall), statusErr)
}
Expand Down
61 changes: 40 additions & 21 deletions go-controller/pkg/ovn/egressfirewall.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ovn

import (
"context"
"fmt"
"net"
"strconv"
Expand All @@ -12,6 +13,7 @@ import (
egressfirewallapi "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1"
libovsdbops "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/ops"
libovsdbutil "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/libovsdb/util"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/metrics"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/nbdb"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
Expand All @@ -20,16 +22,16 @@ import (
kapi "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ktypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
utilpointer "k8s.io/utils/pointer"
)

const (
egressFirewallAppliedCorrectly = "EgressFirewall Rules applied"
egressFirewallAddError = "EgressFirewall Rules not correctly added"
aclDeleteBatchSize = 1000
)

Expand Down Expand Up @@ -205,6 +207,7 @@ func (oc *DefaultNetworkController) addEgressFirewall(egressFirewall *egressfire
defer ef.Unlock()
// egressFirewall may already exist, if previous add failed, cleanup
if _, loaded := oc.egressFirewalls.Load(egressFirewall.Namespace); loaded {
klog.Infof("Egress firewall in namespace 5s already exists, cleanup", egressFirewall.Namespace)
err := oc.deleteEgressFirewall(egressFirewall)
if err != nil {
return fmt.Errorf("failed to cleanup existing egress firewall %s on add: %v", egressFirewall.Namespace, err)
Expand All @@ -215,8 +218,8 @@ func (oc *DefaultNetworkController) addEgressFirewall(egressFirewall *egressfire
for i, egressFirewallRule := range egressFirewall.Spec.Egress {
// process Rules into egressFirewallRules for egressFirewall struct
if i > types.EgressFirewallStartPriority-types.MinimumReservedEgressFirewallPriority {
klog.Warningf("egressFirewall for namespace %s has too many rules, the rest will be ignored",
egressFirewall.Namespace)
errorList = append(errorList, fmt.Errorf("egressFirewall for namespace %s has too many rules, max allowed number is %v",
egressFirewall.Namespace, types.EgressFirewallStartPriority-types.MinimumReservedEgressFirewallPriority))
break
}
efr, err := oc.newEgressFirewallRule(egressFirewallRule, i)
Expand Down Expand Up @@ -287,23 +290,6 @@ func (oc *DefaultNetworkController) deleteEgressFirewall(egressFirewallObj *egre
return nil
}

func (oc *DefaultNetworkController) updateEgressFirewallStatusWithRetry(egressFirewall *egressfirewallapi.EgressFirewall) error {
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
ef, err := oc.watchFactory.GetEgressFirewall(egressFirewall.Namespace, egressFirewall.Name)
if err != nil {
return err
}
c := ef.DeepCopy()
c.Status.Status = egressFirewall.Status.Status
return oc.kube.UpdateEgressFirewall(c)
})
if retryErr != nil {
return fmt.Errorf("error in updating status on EgressFirewall %s/%s: %v",
egressFirewall.Namespace, egressFirewall.Name, retryErr)
}
return nil
}

func (oc *DefaultNetworkController) addEgressFirewallRules(ef *egressFirewall, hashedAddressSetNameIPv4,
hashedAddressSetNameIPv6 string, aclLogging *libovsdbutil.ACLLoggingLevels, ruleIDs ...int) error {
var ops []libovsdb.Operation
Expand Down Expand Up @@ -766,3 +752,36 @@ func (oc *DefaultNetworkController) updateEgressFirewallForNode(oldNode, newNode

return efErr
}

func (oc *DefaultNetworkController) setEgressFirewallStatus(egressFirewall *egressfirewallapi.EgressFirewall, handlerErr error) error {
newMsg := oc.zone + ": "
if handlerErr != nil {
newMsg += types.EgressFirewallApplyError + ": " + handlerErr.Error()
} else {
newMsg += egressFirewallAppliedCorrectly
metrics.UpdateEgressFirewallRuleCount(float64(len(egressFirewall.Spec.Egress)))
metrics.IncrementEgressFirewallCount()
}
needsUpdate := true
for _, message := range egressFirewall.Status.Messages {
if message == newMsg {
// found previous status
needsUpdate = false
break
}
}
if !needsUpdate {
return nil
}

patchOptions := metav1.PatchOptions{
Force: utilpointer.Bool(true),
FieldManager: oc.zone,
}

patch := []byte(fmt.Sprintf(`{"apiVersion":"k8s.ovn.org/v1","kind":"EgressFirewall","status":{"messages":["%s"]}}`, newMsg))
_, err := oc.kube.EgressFirewallClient.K8sV1().EgressFirewalls(egressFirewall.Namespace).Patch(context.TODO(), egressFirewall.Name,
ktypes.ApplyPatchType, patch, patchOptions, "status")

return err
}
3 changes: 2 additions & 1 deletion go-controller/pkg/types/resource_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package types

// this file defines error messages that are used to figure out if a resource reconciliation failed
const (
APBRouteErrorMsg = "failed to apply policy"
APBRouteErrorMsg = "failed to apply policy"
EgressFirewallApplyError = "EgressFirewall Rules not correctly applied"
)
2 changes: 2 additions & 0 deletions go-controller/pkg/util/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type OVNClusterManagerClientset struct {
NetworkAttchDefClient networkattchmentdefclientset.Interface
EgressServiceClient egressserviceclientset.Interface
AdminPolicyRouteClient adminpolicybasedrouteclientset.Interface
EgressFirewallClient egressfirewallclientset.Interface
}

const (
Expand Down Expand Up @@ -163,6 +164,7 @@ func (cs *OVNClientset) GetClusterManagerClientset() *OVNClusterManagerClientset
NetworkAttchDefClient: cs.NetworkAttchDefClient,
EgressServiceClient: cs.EgressServiceClient,
AdminPolicyRouteClient: cs.AdminPolicyRouteClient,
EgressFirewallClient: cs.EgressFirewallClient,
}
}

Expand Down

0 comments on commit 299ca33

Please sign in to comment.