Skip to content

Commit

Permalink
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]>
  • Loading branch information
qinqon committed Oct 8, 2019
1 parent 06dded4 commit 281c436
Show file tree
Hide file tree
Showing 5 changed files with 116 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
Expand Down
43 changes: 43 additions & 0 deletions pkg/helper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -159,6 +161,35 @@ func UpdateCurrentState(client client.Client, nodeNetworkState *nmstatev1alpha1.
return nil
}

func ping(target string) (string, error) {

cmd := exec.Command("ping", "-c", "3", target)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
return stdout.String() + stderr.String(), cmd.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 {
Expand Down Expand Up @@ -188,10 +219,22 @@ func ApplyDesiredState(nodeNetworkState *nmstatev1alpha1.NodeNetworkState) (stri
}
}

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

pingOutput, err := ping(defaultGw)
if err != nil {
return pingOutput, rollback(fmt.Errorf("error pinging external address after network reconfiguration: %v"))
}
commandOutput += fmt.Sprintf("pingOutput: %s \n", pingOutput)

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

commandOutput += fmt.Sprintf("setOutput: %s \n", setOutput)
return commandOutput, nil
}
Expand Down
21 changes: 0 additions & 21 deletions test/e2e/default_bridged_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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{}
Expand Down
54 changes: 53 additions & 1 deletion test/e2e/rollback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 281c436

Please sign in to comment.