Skip to content

Commit

Permalink
Add conditions reset mutating webhook (#324)
Browse files Browse the repository at this point in the history
* Add conditions reset mutating webhook

To ensure that policy conditions are reset at updated
policies, this wait a user can safely call kubectl apply
and then kubectl wait, since previous conditions are
cleaned up.

Signed-off-by: Quique Llorente <[email protected]>

* Use AddToManager at webhook

Refactor webhook so they use similar mechanism like controllers
to inject them to the manager.

Signed-off-by: Quique Llorente <[email protected]>

* Update CA bundle at webhook start

Signed-off-by: Quique Llorente <[email protected]>

* Use cert manager

Signed-off-by: Quique Llorente <[email protected]>

* Generalize webhooksever

Signed-off-by: Quique Llorente <[email protected]>

* Show client-go cert manager logs

Signed-off-by: Quique Llorente <[email protected]>

* Support validating webhooks

Signed-off-by: Quique Llorente <[email protected]>

* Review fixes

Signed-off-by: Quique Llorente <[email protected]>

* Call mod tidy to add gomega gstruct

Signed-off-by: Quique Llorente <[email protected]>

* Add unit test for reset condition

Signed-off-by: Quique Llorente <[email protected]>

* Split in two webhooks, one for nncp and other for nncp/status

Signed-off-by: Quique Llorente <[email protected]>
  • Loading branch information
qinqon authored and kubevirt-bot committed Jan 22, 2020
1 parent 5d140c7 commit 125bab7
Show file tree
Hide file tree
Showing 497 changed files with 2,953 additions and 57,688 deletions.
14 changes: 13 additions & 1 deletion cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/nmstate/kubernetes-nmstate/pkg/apis"
"github.com/nmstate/kubernetes-nmstate/pkg/controller"
"github.com/nmstate/kubernetes-nmstate/pkg/webhook"
"github.com/nmstate/kubernetes-nmstate/version"

"github.com/operator-framework/operator-sdk/pkg/k8sutil"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
Expand All @@ -48,6 +50,10 @@ func printVersion() {
func main() {
var logType string

// Print V(2) logs from packages using klog
klog.InitFlags(nil)
flag.Set("v", "2")

// Add the zap logger flag set to the CLI. The flag set must
// be added before calling pflag.Parse().
pflag.CommandLine.AddFlagSet(zap.FlagSet())
Expand Down Expand Up @@ -110,7 +116,13 @@ func main() {

// Setup all Controllers
if err := controller.AddToManager(mgr); err != nil {
log.Error(err, "")
log.Error(err, "Cannot initialize controller")
os.Exit(1)
}

// Setup webhook
if err := webhook.AddToManager(mgr); err != nil {
log.Error(err, "Cannot initialize webhook")
os.Exit(1)
}

Expand Down
56 changes: 56 additions & 0 deletions deploy/operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,59 @@ metadata:
data:
node_network_state_refresh_interval: "5"
interfaces_filter: "veth*"
---
apiVersion: v1
kind: Service
metadata:
name: nmstate-webhook
namespace: nmstate
labels:
app: kubernetes-nmstate
spec:
publishNotReadyAddresses: true
ports:
- port: 443
targetPort: 8443
selector:
app: kubernetes-nmstate
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: nmstate
labels:
app: kubernetes-nmstate
webhooks:
- name: nodenetworkconfigurationpolicies-mutate.nmstate.io
clientConfig:
service:
name: nmstate-webhook
namespace: nmstate
path: "/nodenetworkconfigurationpolicies-mutate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["*"]
apiVersions: ["v1alpha1"]
resources: ["nodenetworkconfigurationpolicies"]
- name: nodenetworkconfigurationpolicies-status-mutate.nmstate.io
clientConfig:
service:
name: nmstate-webhook
namespace: nmstate
path: "/nodenetworkconfigurationpolicies-status-mutate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["*"]
apiVersions: ["v1alpha1"]
resources: ["nodenetworkconfigurationpolicies/status"]
- name: nodenetworkconfigurationpolicies-timestamp-mutate.nmstate.io
clientConfig:
service:
name: nmstate-webhook
namespace: nmstate
path: "/nodenetworkconfigurationpolicies-timestamp-mutate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["*"]
apiVersions: ["v1alpha1"]
resources: ["nodenetworkconfigurationpolicies", "nodenetworkconfigurationpolicies/status"]
19 changes: 19 additions & 0 deletions deploy/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ metadata:
name: nmstate-handler
namespace: nmstate
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
- certificatesigningrequests/approval
verbs:
- "*"
- apiGroups:
- admissionregistration.k8s.io
resources:
- mutatingwebhookconfigurations
verbs:
- '*'
- apiGroups:
- nmstate.io
resources:
Expand All @@ -65,3 +78,9 @@ rules:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- "*"
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ go 1.12

require (
github.com/aktau/github-release v0.7.2
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
github.com/docker/docker v0.7.3-0.20190409004836-2e1cfbca03da
github.com/docker/go-connections v0.3.0
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-logr/logr v0.1.0
github.com/go-openapi/spec v0.19.0
Expand All @@ -17,18 +15,17 @@ require (
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/operator-framework/operator-sdk v0.12.0
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/tidwall/gjson v1.3.4
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc
gomodules.xyz/jsonpatch/v2 v2.0.1
google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/client-go v11.0.0+incompatible
k8s.io/klog v0.3.3
k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d
sigs.k8s.io/controller-runtime v0.3.0
sigs.k8s.io/yaml v1.1.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2
github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containernetworking/cni v0.6.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down
1 change: 1 addition & 0 deletions hack/cluster-sync-handler.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ${KUBECTL} apply -f deploy/crds/nmstate.io_nodenetworkstates_crd.yaml
${KUBECTL} apply -f deploy/crds/nmstate.io_nodenetworkconfigurationpolicies_crd.yaml
${KUBECTL} apply -f deploy/crds/nmstate.io_nodenetworkconfigurationenactments_crd.yaml
${KUBECTL} delete --ignore-not-found -f ${local_handler_manifest}

# Set debug verbosity level for logs when using cluster-sync
sed "s#--v=production#--v=debug#" ${local_handler_manifest} | ${KUBECTL} create -f -

Expand Down
10 changes: 10 additions & 0 deletions pkg/webhook/add_nodenetworkconfigurationpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package webhook

import (
"github.com/nmstate/kubernetes-nmstate/pkg/webhook/nodenetworkconfigurationpolicy"
)

func init() {
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
AddToManagerFuncs = append(AddToManagerFuncs, nodenetworkconfigurationpolicy.Add)
}
53 changes: 53 additions & 0 deletions pkg/webhook/nodenetworkconfigurationpolicy/conditions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package nodenetworkconfigurationpolicy

import (
corev1 "k8s.io/api/core/v1"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

nmstatev1alpha1 "github.com/nmstate/kubernetes-nmstate/pkg/apis/nmstate/v1alpha1"
)

var log = logf.Log.WithName("webhook/nodenetworkconfigurationpolicy/conditions")

func deleteConditions(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) nmstatev1alpha1.NodeNetworkConfigurationPolicy {
policy.Status.Conditions = nmstatev1alpha1.ConditionList{}
return policy
}

func setConditionsUnknown(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) nmstatev1alpha1.NodeNetworkConfigurationPolicy {
unknownConditions := nmstatev1alpha1.ConditionList{}
for _, conditionType := range nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionTypes {
unknownConditions.Set(
conditionType,
corev1.ConditionUnknown,
"", "")
}
policy.Status.Conditions = unknownConditions
return policy
}

func atEmptyConditions(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) bool {
return policy.Status.Conditions == nil || len(policy.Status.Conditions) == 0
}

func deleteConditionsHook() *webhook.Admission {
return &webhook.Admission{
Handler: admission.HandlerFunc(
mutatePolicyHandler(
always,
deleteConditions,
)),
}
}

func setConditionsUnknownHook() *webhook.Admission {
return &webhook.Admission{
Handler: admission.HandlerFunc(
mutatePolicyHandler(
atEmptyConditions,
setConditionsUnknown,
)),
}
}
145 changes: 145 additions & 0 deletions pkg/webhook/nodenetworkconfigurationpolicy/conditions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package nodenetworkconfigurationpolicy

import (
"context"
"fmt"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook"

nmstatev1alpha1 "github.com/nmstate/kubernetes-nmstate/pkg/apis/nmstate/v1alpha1"
)

func expectConditionsUnknown(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) {
numberOfConditionTypes := len(nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionTypes)
ExpectWithOffset(1, policy.Status.Conditions).To(HaveLen(numberOfConditionTypes))
for _, conditionType := range nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionTypes {
condition := policy.Status.Conditions.Find(conditionType)
ExpectWithOffset(1, condition).ToNot(BeNil())
ExpectWithOffset(1, condition.Status).To(Equal(corev1.ConditionUnknown))
ExpectWithOffset(1, condition.Reason).To(Equal(nmstatev1alpha1.ConditionReason("")))
ExpectWithOffset(1, condition.Message).To(Equal(""))
ExpectWithOffset(1, condition.LastTransitionTime.Time).To(BeTemporally(">", time.Unix(0, 0)))
ExpectWithOffset(1, condition.LastHeartbeatTime.Time).To(BeTemporally(">", time.Unix(0, 0)))
}
}

func callHook(hook *webhook.Admission, request webhook.AdmissionRequest) webhook.AdmissionResponse {

response := hook.Handle(context.TODO(), request)
for _, patch := range response.Patches {
_, err := patch.MarshalJSON()
ExpectWithOffset(2, err).ToNot(HaveOccurred(), "The patches should contain valid JSON")
}
ExpectWithOffset(2, response.Allowed).To(BeTrue(), "Mutation of the request should be allowed")
return response
}

func callDeleteConditions(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) webhook.AdmissionResponse {
request := requestForPolicy(policy)
return callHook(deleteConditionsHook(), request)
}

func callSetConditionsUnknown(policy nmstatev1alpha1.NodeNetworkConfigurationPolicy) webhook.AdmissionResponse {
request := requestForPolicy(policy)
return callHook(setConditionsUnknownHook(), request)
}

var _ = Describe("NNCP Conditions Mutating Admission Webhook", func() {
var (
obtainedResponse webhook.AdmissionResponse
policy = nmstatev1alpha1.NodeNetworkConfigurationPolicy{}
)
Context("when setConditionsUnknown is called with nil conditions", func() {
BeforeEach(func() {
policy.Status.Conditions = nil
obtainedResponse = callSetConditionsUnknown(policy)
})
It("should have all policy conditions with Unknown state", func() {
patchedPolicy := patchPolicy(policy, obtainedResponse)
expectConditionsUnknown(patchedPolicy)
})

})
Context("when setConditionsUnknown is called with empty conditions", func() {
BeforeEach(func() {
policy.Status.Conditions = nmstatev1alpha1.ConditionList{}
obtainedResponse = callSetConditionsUnknown(policy)
})
It("should have all policy conditions with Unknown state", func() {
patchedPolicy := patchPolicy(policy, obtainedResponse)
expectConditionsUnknown(patchedPolicy)
})
})
Context("when setConditionsUnknown is called with empty conditions", func() {
BeforeEach(func() {
policy.Status.Conditions = nmstatev1alpha1.ConditionList{}
obtainedResponse = callSetConditionsUnknown(policy)
})
It("should have all policy conditions with Unknown state", func() {
patchedPolicy := patchPolicy(policy, obtainedResponse)
expectConditionsUnknown(patchedPolicy)
})
})
Context("when setConditionsUnknown is called with Some conditions", func() {
BeforeEach(func() {
conditions := nmstatev1alpha1.ConditionList{}
conditions.Set(
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionDegraded,
corev1.ConditionFalse,
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionSuccessfullyConfigured,
"",
)
conditions.Set(
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionAvailable,
corev1.ConditionTrue,
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionSuccessfullyConfigured,
"Foo message",
)
policy.Status.Conditions = conditions
obtainedResponse = callSetConditionsUnknown(policy)
})
It("should not change the conditions", func() {
Expect(obtainedResponse.Patches).To(BeEmpty())
})

})
Context("when deleteConditions is called with empty conditions", func() {
BeforeEach(func() {
policy.Status.Conditions = nmstatev1alpha1.ConditionList{}
obtainedResponse = callDeleteConditions(policy)
})
It("should do nothing", func() {
Expect(obtainedResponse.Patches).To(BeEmpty())
})
})
Context("when deleteConditions is called with some conditions", func() {
BeforeEach(func() {
conditions := nmstatev1alpha1.ConditionList{}
conditions.Set(
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionDegraded,
corev1.ConditionFalse,
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionSuccessfullyConfigured,
"",
)
conditions.Set(
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionAvailable,
corev1.ConditionTrue,
nmstatev1alpha1.NodeNetworkConfigurationPolicyConditionSuccessfullyConfigured,
"Foo message",
)
policy.Status.Conditions = conditions
obtainedResponse = callDeleteConditions(policy)
})
It("should remove all the conditions", func() {
By(fmt.Sprintf("obtainedResponse: %+v", obtainedResponse))
patchedPolicy := patchPolicy(policy, obtainedResponse)
Expect(patchedPolicy.Status.Conditions).To(BeEmpty())
})
})

})
Loading

0 comments on commit 125bab7

Please sign in to comment.