diff --git a/Makefile b/Makefile index 6657b29985..e47a3ae411 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ help: .SUFFIXES: all: deb rpm calico/felix -test: ut +test: ut fv GO_BUILD_CONTAINER?=calico/go-build:v0.6 @@ -292,6 +292,12 @@ bin/calico-felix: $(FELIX_GO_FILES) vendor/.up-to-date ( ldd bin/calico-felix 2>&1 | grep -q "Not a valid dynamic program" || \ ( echo "Error: bin/calico-felix was not statically linked"; false ) )' +bin/iptables-locker: $(FELIX_GO_FILES) vendor/.up-to-date + @echo Building iptables-locker... + mkdir -p bin + $(DOCKER_GO_BUILD) \ + sh -c 'go build -v -i -o $@ -v $(LDFLAGS) "github.com/projectcalico/felix/fv/iptables-locker"' + bin/k8sfv.test: $(K8SFV_GO_FILES) vendor/.up-to-date @echo Building $@... $(DOCKER_GO_BUILD) \ @@ -324,6 +330,16 @@ ut combined.coverprofile: vendor/.up-to-date $(FELIX_GO_FILES) @echo Running Go UTs. $(DOCKER_GO_BUILD) ./utils/run-coverage +bin/fv.test: vendor/.up-to-date $(FELIX_GO_FILES) + $(DOCKER_GO_BUILD) go test ./fv -c --tags fvtests -o bin/fv.test + +.PHONY: fv +fv: calico/felix bin/iptables-locker bin/fv.test + @echo Running Go FVs. + # For now, we pre-build the binary so that we can run it outside a container and allow it + # to interact with docker. + bin/fv.test + bin/check-licenses: $(FELIX_GO_FILES) $(DOCKER_GO_BUILD) go build -v -i -o $@ "github.com/projectcalico/felix/check-licenses" diff --git a/fv/fv.go b/fv/fv.go new file mode 100644 index 0000000000..b6a6d43dde --- /dev/null +++ b/fv/fv.go @@ -0,0 +1,16 @@ +// 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. + +// The fv packge contains FV tests that execute Felix for-real. +package fv diff --git a/fv/fv_suite_test.go b/fv/fv_suite_test.go new file mode 100644 index 0000000000..f605c8719c --- /dev/null +++ b/fv/fv_suite_test.go @@ -0,0 +1,35 @@ +// 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 fv_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/gomega" + + "github.com/projectcalico/libcalico-go/lib/testutils" +) + +func init() { + testutils.HookLogrusForGinkgo() +} + +func TestFv(t *testing.T) { + RegisterFailHandler(Fail) + junitReporter := reporters.NewJUnitReporter("junit.xml") + RunSpecsWithDefaultAndCustomReporters(t, "FV Suite", []Reporter{junitReporter}) +} diff --git a/fv/iptables-locker/iptables-locker.go b/fv/iptables-locker/iptables-locker.go new file mode 100644 index 0000000000..83879237ed --- /dev/null +++ b/fv/iptables-locker/iptables-locker.go @@ -0,0 +1,55 @@ +// 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 main + +import ( + "time" + + log "github.com/Sirupsen/logrus" + "github.com/docopt/docopt-go" + + "github.com/projectcalico/felix/iptables" +) + +const usage = `iptables-locker, test tool for grabbing the iptables lock. + +Usage: + iptables-locker + +` + +func main() { + arguments, err := docopt.Parse(usage, nil, true, "v0.1", false) + if err != nil { + println(usage) + log.WithError(err).Fatal("Failed to parse usage") + } + durationStr := arguments[""].(string) + duration, err := time.ParseDuration(durationStr) + if err != nil { + println(usage) + log.WithError(err).Fatal("Failed to parse usage") + } + + iptablesLock := iptables.NewSharedLock( + "/run/xtables.lock", + 1*time.Second, + 50*time.Millisecond, + ) + iptablesLock.Lock() + println("LOCKED") + time.Sleep(duration) + iptablesLock.Unlock() +} diff --git a/fv/iptables_lock_test.go b/fv/iptables_lock_test.go new file mode 100644 index 0000000000..4f6215b052 --- /dev/null +++ b/fv/iptables_lock_test.go @@ -0,0 +1,112 @@ +// +build fvtests + +// 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 fv_test + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("with running container", func() { + var containerIdx int + var containerName string + var felixCmd *exec.Cmd + + cmdInContainer := func(cmd ...string) *exec.Cmd { + arg := []string{"exec", containerName} + arg = append(arg, cmd...) + return exec.Command("docker", arg...) + } + + BeforeEach(func() { + containerName = fmt.Sprintf("felix-fv-%d-%d", os.Getpid(), containerIdx) + containerIdx++ + felixDir, err := os.Getwd() + Expect(err).NotTo(HaveOccurred()) + // Run a felix container. The tests in this file don't actually rely on Felix + // but the calico/felix container has all the iptables dependencies we need to + // check the lock behaviour. + felixCmd = exec.Command("docker", "run", + "--rm", + "--name", containerName, + "-v", fmt.Sprintf("%s:/codebase", felixDir), + "--privileged", + "calico/felix") + err = felixCmd.Start() + Expect(err).NotTo(HaveOccurred()) + + for { + cmd := exec.Command("docker", "ps") + out, err := cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + if strings.Contains(string(out), containerName) { + break + } + } + }) + AfterEach(func() { + // Send an interrupt to ensure that docker gracefully shuts down the container. + // If we kill the docker process then it detaches the container. + felixCmd.Process.Signal(os.Interrupt) + }) + + Describe("with the lock being held for 2s", func() { + var lockCmd *exec.Cmd + BeforeEach(func() { + // Start the iptables-locker, which is a simple test app that locks + // the iptables lock and then releases it after a timeout. + lockCmd = cmdInContainer("/codebase/bin/iptables-locker", "2s") + stdErr, err := lockCmd.StderrPipe() + Expect(err).NotTo(HaveOccurred()) + lockCmd.Start() + + // Wait for the iptables-locker to tell us that it actually acquired the + // lock. + scanner := bufio.NewScanner(stdErr) + Expect(scanner.Scan()).To(BeTrue()) + Expect(scanner.Text()).To(Equal("LOCKED")) + Expect(scanner.Err()).NotTo(HaveOccurred()) + }) + + It("iptables should fail to get the lock in 1s", func() { + iptCmd := cmdInContainer("iptables", "-w", "1", "-A", "FORWARD") + out, err := iptCmd.CombinedOutput() + Expect(string(out)).To(ContainSubstring("Stopped waiting")) + Expect(err).To(HaveOccurred()) + }) + + It("iptables should succeed in getting the lock after 3s", func() { + iptCmd := cmdInContainer("iptables", "-w", "3", "-A", "FORWARD") + out, err := iptCmd.CombinedOutput() + Expect(string(out)).To(ContainSubstring("Another app is currently holding the xtables lock")) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + if lockCmd != nil { + err := lockCmd.Wait() + Expect(err).NotTo(HaveOccurred()) + } + }) + }) +})