diff --git a/Makefile b/Makefile index 22983700a..fd89ccfe3 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,8 @@ TRAEFIK_ENVS := \ -e VERSION \ -e CODENAME \ -e TESTDIRS \ - -e CI + -e CI \ + -e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container. SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/' | grep -v '^integration/vendor/') diff --git a/integration/fixtures/marathon/simple.toml b/integration/fixtures/marathon/simple.toml index fedfdfb3d..c388ec5b9 100644 --- a/integration/fixtures/marathon/simple.toml +++ b/integration/fixtures/marathon/simple.toml @@ -1,9 +1,15 @@ defaultEntryPoints = ["http"] +logLevel = "DEBUG" + [entryPoints] [entryPoints.http] address = ":8000" -logLevel = "DEBUG" +[web] +address = ":9090" [marathon] + endpoint = "{{.MarathonURL}}" + watch = true + exposedByDefault = true diff --git a/integration/marathon_test.go b/integration/marathon_test.go index 4c202d98f..c62e99e13 100644 --- a/integration/marathon_test.go +++ b/integration/marathon_test.go @@ -1,53 +1,129 @@ package integration import ( + "fmt" "net/http" + "os" "time" "github.com/containous/traefik/integration/try" + "github.com/containous/traefik/types" + marathon "github.com/gambol99/go-marathon" "github.com/go-check/check" checker "github.com/vdemeester/shakers" ) +const ( + containerNameMesosSlave = "mesos-slave" + containerNameMarathon = "marathon" +) + // Marathon test suites (using libcompose) -type MarathonSuite struct{ BaseSuite } +type MarathonSuite struct { + BaseSuite + marathonURL string +} func (s *MarathonSuite) SetUpSuite(c *check.C) { s.createComposeProject(c, "marathon") s.composeProject.Start(c) - // FIXME Doesn't work... - //// "github.com/gambol99/go-marathon" - //config := marathon.NewDefaultConfig() - // - //marathonClient, err := marathon.NewClient(config) - //if err != nil { - // c.Fatalf("Error creating Marathon client. %v", err) - //} - // - //// Wait for Marathon to elect itself leader - //err = try.Do(30*time.Second, func() error { - // leader, err := marathonClient.Leader() - // - // if err != nil || len(leader) == 0 { - // return fmt.Errorf("Leader not found. %v", err) - // } - // - // return nil - //}) - // - //c.Assert(err, checker.IsNil) + marathonIPAddr := s.composeProject.Container(c, containerNameMarathon).NetworkSettings.IPAddress + c.Assert(marathonIPAddr, checker.Not(checker.HasLen), 0) + s.marathonURL = "http://" + marathonIPAddr + ":8080" + + // Wait for Marathon readiness prior to creating the client so that we + // don't run into the "all cluster members down" state right from the + // start. + err := try.GetRequest(s.marathonURL+"/v2/leader", 1*time.Minute, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + // Add entry for Mesos slave container IP address in the hosts file so + // that Traefik can properly forward traffic. + // This is necessary as long as we are still using the docker-compose v1 + // spec. Once we switch to v2 or higher, we can have both the test/builder + // container and the Mesos slave container join the same custom network and + // enjoy DNS-discoverable container host names. + mesosSlaveIPAddr := s.composeProject.Container(c, containerNameMesosSlave).NetworkSettings.IPAddress + c.Assert(mesosSlaveIPAddr, checker.Not(checker.HasLen), 0) + err = s.extendDockerHostsFile(containerNameMesosSlave, mesosSlaveIPAddr) + c.Assert(err, checker.IsNil) } -func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) { - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/marathon/simple.toml")) +// extendDockerHostsFile extends the hosts file (/etc/hosts) by the given +// host/IP address mapping if we are running inside a container. +func (s *MarathonSuite) extendDockerHostsFile(host, ipAddr string) error { + const hostsFile = "/etc/hosts" + + // Determine if the run inside a container. The most reliable way to + // do this is to inject an indicator, which we do in terms of an + // environment variable. + // (See also https://groups.google.com/d/topic/docker-user/JOGE7AnJ3Gw/discussion.) + if os.Getenv("CONTAINER") == "DOCKER" { + // We are running inside a container -- extend the hosts file. + file, err := os.OpenFile(hostsFile, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return err + } + defer file.Close() + + if _, err = file.WriteString(fmt.Sprintf("%s\t%s\n", ipAddr, host)); err != nil { + return err + } + } + + return nil +} + +func (s *MarathonSuite) TestConfigurationUpdate(c *check.C) { + // Start Traefik. + file := s.adaptFile(c, "fixtures/marathon/simple.toml", struct { + MarathonURL string + }{s.marathonURL}) + defer os.Remove(file) + cmd, output := s.cmdTraefik(withConfigFile(file)) err := cmd.Start() c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - // TODO validate : run on 80 - // Expected a 404 as we did not configure anything - err = try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) - + // Wait for Traefik to turn ready. + err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound)) c.Assert(err, checker.IsNil) + + // Prepare Marathon client. + config := marathon.NewDefaultConfig() + config.URL = s.marathonURL + client, err := marathon.NewClient(config) + c.Assert(err, checker.IsNil) + + // Show the Traefik log if any assertion fails. If the entire test runs + // to a successful completion, we flip the flag at the very end and don't + // display anything. + showTraefikLog := true + defer func() { + if showTraefikLog { + s.displayTraefikLog(c, output) + } + }() + + // Create test application to be deployed. + app := marathon.NewDockerApplication(). + Name("/whoami"). + CPU(0.1). + Memory(32). + AddLabel(types.LabelFrontendRule, "PathPrefix:/service") + app.Container.Docker.Bridged(). + Expose(80). + Container("emilevauge/whoami") + + // Deploy the test application. + deploy, err := client.UpdateApplication(app, false) + c.Assert(err, checker.IsNil) + // Wait for deployment to complete. + c.Assert(client.WaitOnDeployment(deploy.DeploymentID, 1*time.Minute), checker.IsNil) + + // Query application via Traefik. + err = try.GetRequest("http://127.0.0.1:8000/service", 30*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + showTraefikLog = false } diff --git a/integration/resources/compose/marathon.yml b/integration/resources/compose/marathon.yml index 13113e51e..f29ca1382 100644 --- a/integration/resources/compose/marathon.yml +++ b/integration/resources/compose/marathon.yml @@ -1,43 +1,54 @@ -zk: - image: bobrik/zookeeper - net: host - environment: - ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181 - ZK_ID: " 1" +zookeeper: + image: zookeeper:3.4.10 -master: - image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404 - net: host +mesos-master: + links: + - zookeeper + image: mesosphere/mesos-master:1.0.1-2.0.93.ubuntu1404 + # Uncomment published ports for interactive debugging. + # ports: + # - "5050:5050" environment: - MESOS_ZK: zk://127.0.0.1:2181/mesos - MESOS_HOSTNAME: 127.0.0.1 - MESOS_IP: 127.0.0.1 - MESOS_QUORUM: " 1" - MESOS_CLUSTER: docker-compose - MESOS_WORK_DIR: /var/lib/mesos + - MESOS_HOSTNAME=mesos-master + - MESOS_CLUSTER=local + - MESOS_REGISTRY=in_memory + - MESOS_LOG_DIR=/var/log + - MESOS_WORK_DIR=/var/lib/mesos + - MESOS_ZK=zk://zookeeper:2181/mesos -slave: - image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404 - net: host - pid: host +mesos-slave: + links: + - zookeeper + - mesos-master + image: mesosphere/mesos-slave-dind:0.3.0_mesos-1.0.1_docker-1.10.3_ubuntu-14.04.5 privileged: true + # Uncomment published ports for interactive debugging. + # ports: + # - "5051:5051" environment: - MESOS_MASTER: zk://127.0.0.1:2181/mesos - MESOS_HOSTNAME: 127.0.0.1 - MESOS_IP: 127.0.0.1 - MESOS_CONTAINERIZERS: docker,mesos - volumes: - - /sys/fs/cgroup:/sys/fs/cgroup - - /usr/bin/docker:/usr/bin/docker:ro - - /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro - - /var/run/docker.sock:/var/run/docker.sock - - /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0 + - MESOS_HOSTNAME=mesos-slave + - MESOS_CONTAINERIZERS=docker,mesos + - MESOS_ISOLATOR=cgroups/cpu,cgroups/mem + - MESOS_LOG_DIR=/var/log + - MESOS_MASTER=zk://zookeeper:2181/mesos + - MESOS_PORT=5051 + - MESOS_WORK_DIR=/var/lib/mesos + - MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins + - MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD=90secs + - MESOS_DOCKER_STOP_TIMEOUT=60secs + - MESOS_RESOURCES=cpus:2;mem:2048;disk:20480;ports(*):[12000-12999] marathon: - image: mesosphere/marathon:v1.1.1 - net: host + links: + - zookeeper + - mesos-master + - mesos-slave + image: mesosphere/marathon:v1.3.12 + # Uncomment published ports for interactive debugging. + # ports: + # - "8080:8080" + extra_hosts: + - "mesos-slave:172.17.0.1" environment: - MARATHON_MASTER: zk://127.0.0.1:2181/mesos - MARATHON_ZK: zk://127.0.0.1:2181/marathon - MARATHON_HOSTNAME: 127.0.0.1 - command: --event_subscriber http_callback \ No newline at end of file + - MARATHON_ZK=zk://zookeeper:2181/marathon + - MARATHON_MASTER=zk://zookeeper:2181/mesos