Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ping default gw from withing the cluster to verify connectivity
Browse files Browse the repository at this point in the history
In case it fails it will rollback nmstate changes.

Signed-off-by: Quique Llorente <[email protected]>
qinqon committed Oct 8, 2019
1 parent 06dded4 commit bcfcd4e
Showing 5 changed files with 110 additions and 25 deletions.
2 changes: 1 addition & 1 deletion build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM fedora:30

RUN sudo dnf install -y nmstate iproute && \
RUN sudo dnf install -y nmstate iproute iputils && \
sudo dnf clean all

# TODO: Delete this line after we update nmstate to include the change
37 changes: 37 additions & 0 deletions pkg/helper/client.go
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
yaml "sigs.k8s.io/yaml"

"github.com/tidwall/gjson"

"github.com/gobwas/glob"
nmstatev1alpha1 "github.com/nmstate/kubernetes-nmstate/pkg/apis/nmstate/v1alpha1"
)
@@ -159,6 +161,30 @@ func UpdateCurrentState(client client.Client, nodeNetworkState *nmstatev1alpha1.
return nil
}

func ping(target string) error {
return exec.Command("ping", "-c", "3", target).Run()
}

func defaultGw() (string, error) {
observedStateRaw, err := show()
if err != nil {
return "", fmt.Errorf("error running nmstatectl show: %v", err)
}

currentState, err := yaml.YAMLToJSON([]byte(observedStateRaw))
if err != nil {
return "", fmt.Errorf("Impossible to convert current state to JSON")
}

defaultGw := gjson.ParseBytes([]byte(currentState)).
Get("routes.running.#(destination==\"0.0.0.0/0\").next-hop-address").String()
if defaultGw == "" {
return "", fmt.Errorf("Impossible to retrieve default gw")
}

return defaultGw, nil
}

func ApplyDesiredState(nodeNetworkState *nmstatev1alpha1.NodeNetworkState) (string, error) {
desiredState := string(nodeNetworkState.Spec.DesiredState)
if len(desiredState) == 0 {
@@ -188,10 +214,21 @@ func ApplyDesiredState(nodeNetworkState *nmstatev1alpha1.NodeNetworkState) (stri
}
}

defaultGw, err := defaultGw()
if err != nil {
return commandOutput, rollback(err)
}

err = ping(defaultGw)
if err != nil {
return commandOutput, rollback(fmt.Errorf("error pinging external address after network reconfiguration"))
}

_, err = commit()
if err != nil {
return commandOutput, rollback(err)
}

commandOutput += fmt.Sprintf("setOutput: %s \n", setOutput)
return commandOutput, nil
}
21 changes: 0 additions & 21 deletions test/e2e/default_bridged_network_test.go
Original file line number Diff line number Diff line change
@@ -2,16 +2,13 @@ package e2e

import (
"context"
"fmt"
"time"

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

"github.com/tidwall/gjson"

yaml "sigs.k8s.io/yaml"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"

@@ -118,31 +115,13 @@ var _ = PDescribe("NodeNetworkConfigurationPolicy default bridged network", func
})
})

func currentStateJSON(node string) []byte {
key := types.NamespacedName{Name: node}
currentState := nodeNetworkState(key).Status.CurrentState
currentStateJson, err := yaml.YAMLToJSON([]byte(currentState))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return currentStateJson
}

func ipv4Address(node string, name string) string {
path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.address.0.ip", name)
return gjson.ParseBytes(currentStateJSON(node)).Get(path).String()
}

func defaultRouteNextHopInterface(node string) AsyncAssertion {
return Eventually(func() string {
path := "routes.running.#(destination==\"0.0.0.0/0\").next-hop-interface"
return gjson.ParseBytes(currentStateJSON(node)).Get(path).String()
}, 15*time.Second, 1*time.Second)
}

func dhcpFlag(node string, name string) bool {
path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.dhcp", name)
return gjson.ParseBytes(currentStateJSON(node)).Get(path).Bool()
}

func nodeReadyConditionStatus(nodeName string) (corev1.ConditionStatus, error) {
key := types.NamespacedName{Name: nodeName}
node := corev1.Node{}
54 changes: 53 additions & 1 deletion test/e2e/rollback_test.go
Original file line number Diff line number Diff line change
@@ -5,8 +5,30 @@ import (

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

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

func badDefaultGw(address string, nic string) nmstatev1alpha1.State {
return nmstatev1alpha1.State(fmt.Sprintf(`interfaces:
- name: %s
type: ethernet
state: up
ipv4:
dhcp: false
enabled: true
address:
- ip: %s
prefix-length: 24
routes:
config:
- destination: 0.0.0.0/0
metric: 150
next-hop-address: 192.0.2.1
next-hop-interface: %s
`, nic, address, nic))
}

var _ = Describe("rollback", func() {
Context("when an error happens during state configuration", func() {
BeforeEach(func() {
@@ -26,11 +48,41 @@ var _ = Describe("rollback", func() {
for _, node := range nodes {
By(fmt.Sprintf("Check that %s has being rolled back", bridge1))
interfacesNameForNodeEventually(node).ShouldNot(ContainElement(bridge1))
By("Check reconcile re-apply desiredState")
By("Check that desiredState is applied")
interfacesNameForNodeEventually(node).Should(ContainElement(bridge1))
By(fmt.Sprintf("Check that %s is rolled back again", bridge1))
interfacesNameForNodeEventually(node).ShouldNot(ContainElement(bridge1))
}
})
})
Context("when connectivity to default gw is lost after state configuration", func() {
BeforeEach(func() {
By("Configure a invalid default gw")
for _, node := range nodes {
var address string
Eventually(func() string {
address = ipv4Address(node, "eth0")
return address
}, ReadTimeout, ReadInterval).ShouldNot(BeEmpty())
updateDesiredStateAtNode(node, badDefaultGw(address, "eth0"))
}
})
AfterEach(func() {
By("Clean up desired state")
resetDesiredStateForNodes()
})
It("should rollback to a good gw configuration", func() {
for _, node := range nodes {
By("Check that desiredState is applied")
Eventually(func() bool {
return dhcpFlag(node, "eth0")
}, ReadTimeout, ReadInterval).Should(BeFalse())

By("Check that eth0 is rolled back")
Eventually(func() bool {
return dhcpFlag(node, "eth0")
}, ReadTimeout, ReadInterval).Should(BeTrue())
}
})
})
})
21 changes: 19 additions & 2 deletions test/e2e/utils.go
Original file line number Diff line number Diff line change
@@ -180,7 +180,7 @@ func updateDesiredStateAtNode(node string, desiredState nmstatev1alpha1.State) {
}
state.Spec.DesiredState = desiredState
return framework.Global.Client.Update(context.TODO(), &state)
}, ReadTimeout, ReadInterval).ShouldNot(HaveOccurred())
}, ReadTimeout, ReadInterval).ShouldNot(HaveOccurred(), string(desiredState))
}

func updateDesiredState(desiredState nmstatev1alpha1.State) {
@@ -315,7 +315,6 @@ func deleteConnectionAtNodes(name string) []error {
}

func interfaces(state nmstatev1alpha1.State) []interface{} {
By("unmarshal state yaml into unstructured golang")
var stateUnstructured map[string]interface{}
err := yaml.Unmarshal(state, &stateUnstructured)
Expect(err).ToNot(HaveOccurred(), "Should parse correctly yaml: %s", state)
@@ -475,3 +474,21 @@ func nextBond() string {
bridgeCounter++
return fmt.Sprintf("bond%d", bondConunter)
}

func currentStateJSON(node string) []byte {
key := types.NamespacedName{Name: node}
currentState := nodeNetworkState(key).Status.CurrentState
currentStateJson, err := yaml.YAMLToJSON([]byte(currentState))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return currentStateJson
}

func dhcpFlag(node string, name string) bool {
path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.dhcp", name)
return gjson.ParseBytes(currentStateJSON(node)).Get(path).Bool()
}

func ipv4Address(node string, name string) string {
path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.address.0.ip", name)
return gjson.ParseBytes(currentStateJSON(node)).Get(path).String()
}

0 comments on commit bcfcd4e

Please sign in to comment.