diff --git a/functional/fixtures/units/hello@.service b/functional/fixtures/units/hello@.service new file mode 100644 index 000000000..910a8a5b5 --- /dev/null +++ b/functional/fixtures/units/hello@.service @@ -0,0 +1,2 @@ +[Service] +ExecStart=/bin/bash -c "while true; do echo Hello, World %i!; sleep 1; done" diff --git a/functional/platform/nspawn.go b/functional/platform/nspawn.go index 3690c7877..142c097d8 100644 --- a/functional/platform/nspawn.go +++ b/functional/platform/nspawn.go @@ -39,9 +39,11 @@ const ( ) var fleetdBinPath string +var fleetctlBinPath string func init() { fleetdBinPath = os.Getenv("FLEETD_BIN") + fleetctlBinPath = os.Getenv("FLEETCTL_BIN") if fleetdBinPath == "" { fmt.Println("FLEETD_BIN environment variable must be set") os.Exit(1) @@ -49,6 +51,13 @@ func init() { fmt.Printf("%v\n", err) os.Exit(1) } + if fleetctlBinPath == "" { + fmt.Println("FLEETCTL_BIN environment variable must be set") + os.Exit(1) + } else if _, err := os.Stat(fleetctlBinPath); err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } // sanity check etcd availability cmd := exec.Command("etcdctl", "ls") out, err := cmd.CombinedOutput() @@ -94,13 +103,28 @@ func (nc *nspawnCluster) keyspace() string { return fmt.Sprintf("/fleet_functional/%s", nc.name) } +// This function adds --endpoint flag if --tunnel flag is not used +// Usefull for "fleetctl fd-forward" tests +func handleEndpointFlag(m Member, args *[]string) { + result := true + for _, arg := range *args { + if strings.Contains(arg, "-- ") || strings.Contains(arg, "--tunnel") { + result = false + break + } + } + if result { + *args = append([]string{"--endpoint=" + m.Endpoint()}, *args...) + } +} + func (nc *nspawnCluster) Fleetctl(m Member, args ...string) (string, string, error) { - args = append([]string{"--endpoint=" + m.Endpoint()}, args...) + handleEndpointFlag(m, &args) return util.RunFleetctl(args...) } func (nc *nspawnCluster) FleetctlWithInput(m Member, input string, args ...string) (string, string, error) { - args = append([]string{"--endpoint=" + m.Endpoint()}, args...) + handleEndpointFlag(m, &args) return util.RunFleetctlWithInput(input, args...) } @@ -228,14 +252,14 @@ func (nc *nspawnCluster) prepCluster() (err error) { return nil } -func (nc *nspawnCluster) insertFleetd(dir string) error { - cmd := fmt.Sprintf("mkdir -p %s/opt/fleet", dir) +func (nc *nspawnCluster) insertBin(src string, dst string) error { + cmd := fmt.Sprintf("mkdir -p %s/opt/fleet", dst) if _, _, err := run(cmd); err != nil { return err } - fleetdBinDst := path.Join(dir, "opt", "fleet", "fleetd") - return copyFile(fleetdBinPath, fleetdBinDst, 0755) + binDst := path.Join(dst, "opt", "fleet", path.Base(src)) + return copyFile(src, binDst, 0755) } func (nc *nspawnCluster) buildConfigDrive(dir, ip string) error { @@ -303,8 +327,6 @@ func (nc *nspawnCluster) createMember(id string) (m Member, err error) { // minimum requirements for running systemd/coreos in a container fmt.Sprintf("mkdir -p %s/usr", fsdir), fmt.Sprintf("cp /etc/os-release %s/etc", fsdir), - fmt.Sprintf("echo 'core:x:500:500:CoreOS Admin:/home/core:/bin/bash' > %s/etc/passwd", fsdir), - fmt.Sprintf("echo 'core:x:500:' > %s/etc/group", fsdir), fmt.Sprintf("ln -s /proc/self/mounts %s/etc/mtab", fsdir), fmt.Sprintf("ln -s usr/lib64 %s/lib64", fsdir), fmt.Sprintf("ln -s lib64 %s/lib", fsdir), @@ -354,11 +376,45 @@ UseDNS no return } - if err = nc.insertFleetd(fsdir); err != nil { + filesContents := []struct { + path string + contents string + mode os.FileMode + }{ + { + "/etc/passwd", + "core:x:500:500:CoreOS Admin:/home/core:/bin/bash", + 0644, + }, + { + "/etc/group", + "core:x:500:", + 0644, + }, + { + "/home/core/.bash_profile", + "export PATH=/opt/fleet:$PATH", + 0644, + }, + } + + for _, file := range filesContents { + if err = ioutil.WriteFile(path.Join(fsdir, file.path), []byte(file.contents), file.mode); err != nil { + log.Printf("Failed writing %s: %v", path.Join(fsdir, file.path), err) + return + } + } + + if err = nc.insertBin(fleetdBinPath, fsdir); err != nil { log.Printf("Failed preparing fleetd in filesystem: %v", err) return } + if err = nc.insertBin(fleetctlBinPath, fsdir); err != nil { + log.Printf("Failed preparing fleetctl in filesystem: %v", err) + return + } + if err = nc.buildConfigDrive(fsdir, nm.IP()); err != nil { log.Printf("Failed building config drive: %v", err) return diff --git a/functional/tunnel_test.go b/functional/tunnel_test.go new file mode 100644 index 000000000..1939f5541 --- /dev/null +++ b/functional/tunnel_test.go @@ -0,0 +1,89 @@ +// Copyright 2016 CoreOS, Inc. +// +// 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 functional + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + "syscall" + "testing" + + "github.com/coreos/fleet/functional/platform" +) + +// Start three units using ssh tunnel +func TestTunnelScheduleBatchUnits(t *testing.T) { + cluster, err := platform.NewNspawnCluster("smoke") + if err != nil { + t.Fatal(err) + } + defer cluster.Destroy() + + members, err := platform.CreateNClusterMembers(cluster, 3) + if err != nil { + t.Fatal(err) + } + m0 := members[0] + _, err = cluster.WaitForNMachines(m0, 3) + if err != nil { + t.Fatal(err) + } + + tmp, err := ioutil.TempFile(os.TempDir(), "known-hosts") + if err != nil { + t.Fatal(err) + } + tmp.Close() + defer syscall.Unlink(tmp.Name()) + + khFile := tmp.Name() + + // Launch one unit + if stdout, stderr, err := cluster.FleetctlWithInput(m0, "yes", + fmt.Sprintf("--tunnel=%s", m0.IP()), + "--strict-host-key-checking=true", + fmt.Sprintf("--known-hosts-file=%s", khFile), + "start", + "fixtures/units/hello.service"); err != nil { + t.Fatalf("Unable to submit one unit using ssh tunnel: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } else if strings.Contains(stderr, "Error") { + t.Fatalf("Failed to correctly submit unit using ssh tunnel: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } + + // Combine all parameters and units in one args slice + args := []string{ + fmt.Sprintf("--tunnel=%s", m0.IP()), + "--strict-host-key-checking=true", + fmt.Sprintf("--known-hosts-file=%s", khFile), + "start", + } + for i := 1; i <= 10; i++ { + args = append(args, fmt.Sprintf("fixtures/units/hello@%d.service", i)) + } + + // Launch a batch of units + if stdout, stderr, err := cluster.Fleetctl(m0, args...); err != nil { + t.Fatalf("Unable to submit batch of units using ssh tunnel: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } else if strings.Contains(stderr, "Error") { + t.Fatalf("Failed to correctly submit batch of units using ssh tunnel: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err) + } + + _, err = cluster.WaitForNActiveUnits(m0, 11) + if err != nil { + t.Fatal(err) + } +} diff --git a/functional/util/config.go b/functional/util/config.go index 1c29ab2dc..7063da5cf 100644 --- a/functional/util/config.go +++ b/functional/util/config.go @@ -50,9 +50,12 @@ coreos: Address={{.IP}}/16 - name: fleet.socket command: start + - name: fleet-tcp.socket + command: start content: | [Socket] ListenStream={{printf "%d" .FleetAPIPort}} + Service=fleet.service - name: fleet.service command: start content: | diff --git a/ssh/ssh.go b/ssh/ssh.go index ca02dd949..b66844912 100644 --- a/ssh/ssh.go +++ b/ssh/ssh.go @@ -30,10 +30,14 @@ import ( type SSHForwardingClient struct { agentForwarding bool *gossh.Client + authAgentReqSent bool } func (s *SSHForwardingClient) ForwardAgentAuthentication(session *gossh.Session) error { - if s.agentForwarding { + if s.agentForwarding && !s.authAgentReqSent { + // We are allowed to send "auth-agent-req@openssh.com" request only once per channel + // otherwise ssh daemon replies with the "SSH2_MSG_CHANNEL_FAILURE 100" + s.authAgentReqSent = true return gosshagent.RequestAgentForwarding(session) } return nil @@ -50,7 +54,7 @@ func newSSHForwardingClient(client *gossh.Client, agentForwarding bool) (*SSHFor return nil, err } - return &SSHForwardingClient{agentForwarding, client}, nil + return &SSHForwardingClient{agentForwarding, client, false}, nil } // makeSession initializes a gossh.Session connected to the invoking process's stdout/stderr/stdout.