blob: ec0c8af2a843c1d008c85b5f38a7d405b467b21c [file] [log] [blame] [edit]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package platform
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/hashicorp/mdns"
"chromiumos/tast/common/testexec"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/platform/p2p"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: P2PClient,
Desc: "Tests that Chromium OS can download files from local network peers with p2p-client",
Contacts: []string{"ahassani@google.com"},
Attr: []string{"group:mainline"},
})
}
type fakeP2PServer struct {
server *mdns.Server
service *mdns.MDNSService
}
func newFakeP2PServer(port, numConn int, files map[string]int) (*fakeP2PServer, error) {
instance := fmt.Sprintf("instance%d", port)
txt := []string{fmt.Sprintf("num_connections=%d", numConn)}
for name, size := range files {
txt = append(txt, fmt.Sprintf("id_%s=%d", name, size))
}
service, err := mdns.NewMDNSService(instance, p2p.ServiceType, "", "", port, []net.IP{net.ParseIP(p2p.DefaultNSIP)}, txt)
if err != nil {
return nil, errors.Wrap(err, "failed to create mDNS service")
}
server, err := mdns.NewServer(&mdns.Config{Zone: service})
if err != nil {
return nil, errors.Wrap(err, "failed to start mDNS server")
}
s := &fakeP2PServer{server, service}
return s, nil
}
func (s *fakeP2PServer) close() error {
return s.server.Shutdown()
}
func P2PClient(fullCtx context.Context, s *testing.State) {
// Shorten the timeout to allow some time for cleanup.
ctx, cancel := ctxutil.Shorten(fullCtx, 10*time.Second)
defer cancel()
if err := p2p.SetUp(ctx); err != nil {
s.Fatal("Failed to set up: ", err)
}
defer func() {
if err := p2p.CleanUp(fullCtx); err != nil {
s.Error("Failed to clean up: ", err)
}
}()
const (
port1 = 1111
port2 = 2222
port3 = 3333
port4 = 4444
)
s.Log("Starting fake servers")
srv1, err := newFakeP2PServer(port1, 1, map[string]int{"everyone": 1000, "only-a": 5000})
if err != nil {
s.Fatalf("Failed to start a fake server with port %d: %v", port1, err)
}
defer srv1.close()
srv2, err := newFakeP2PServer(port2, 0, map[string]int{"everyone": 10000, "only-b": 8000})
if err != nil {
s.Fatalf("Failed to start a fake server with port %d: %v", port2, err)
}
defer srv2.close()
srv3, err := newFakeP2PServer(port3, 1, map[string]int{"everyone": 20000})
if err != nil {
s.Fatalf("Failed to start a fake server with port %d: %v", port3, err)
}
defer srv3.close()
// Wait for all fake servers to be found by avahi.
if err := testing.Poll(ctx, func(ctx context.Context) error {
cmd := testexec.CommandContext(ctx, "p2p-client", "--list-all")
out, err := cmd.Output()
if err != nil {
return errors.Wrap(err, "failed to enumerate servers")
}
s := string(out)
for _, port := range []int{port1, port2, port3} {
if !strings.Contains(s, fmt.Sprintf("port %d", port)) {
return errors.Errorf("fake server at port %d is not found", port)
}
}
return nil
}, &testing.PollOptions{Interval: time.Second}); err != nil {
s.Fatal("Failed to wait for fake servers to be found by avahi: ", err)
}
// Request a file shared from only one peer.
s.Log("Querying only-a")
cmd := testexec.CommandContext(ctx, "p2p-client", "--get-url=only-a")
if out, err := cmd.Output(); err != nil {
cmd.DumpLog(ctx)
s.Error("p2p-client failed to query only-a: ", err)
} else if got, want := strings.TrimSpace(string(out)), fmt.Sprintf("http://%s:%d/only-a", p2p.DefaultNSIP, port1); got != want {
s.Errorf("p2p-client failed to query only-a; got %s, want %s", got, want)
}
// Check that the num_connections is reported properly.
s.Log("Counting connections")
cmd = testexec.CommandContext(ctx, "p2p-client", "--num-connections")
if out, err := cmd.Output(); err != nil {
cmd.DumpLog(ctx)
s.Error("p2p-client --num-connections failed: ", err)
} else if got := strings.TrimSpace(string(out)); got != "2" {
s.Errorf("p2p-client --num-connections failed; got %s, want 2", got)
}
// Request a file shared from a peer with enough of the file.
s.Log("Querying everyone with --minimum-size=15000")
cmd = testexec.CommandContext(ctx, "p2p-client", "--get-url=everyone", "--minimum-size=15000")
if out, err := cmd.Output(); err != nil {
cmd.DumpLog(ctx)
s.Error("p2p-client failed to query everyone: ", err)
} else if got, want := strings.TrimSpace(string(out)), fmt.Sprintf("http://%s:%d/everyone", p2p.DefaultNSIP, port3); got != want {
s.Errorf("p2p-client failed to query everyone; got %s, want %s", got, want)
}
// Request too many bytes of an existing file.
s.Log("Querying only-b with --minimum-size=10000")
cmd = testexec.CommandContext(ctx, "p2p-client", "--get-url=only-b", "--minimum-size=10000")
if err := cmd.Run(); err == nil {
cmd.DumpLog(ctx)
s.Error("p2p-client succeeded querying only-b; expected to fail")
}
// Check that p2p-client hangs while waiting for a peer when there are too many connections.
s.Log("Adding a new fake server with large connection count")
srv4, err := newFakeP2PServer(port4, 98, map[string]int{"everyone": 10000})
if err != nil {
s.Fatalf("Failed to start a fake server with port %d: %v", port4, err)
}
defer srv4.close()
if err := testing.Poll(ctx, func(ctx context.Context) error {
cmd = testexec.CommandContext(ctx, "p2p-client", "--num-connections")
if out, err := cmd.Output(); err != nil {
return errors.Wrap(err, "failed to count connections")
} else if got := strings.TrimSpace(string(out)); got != "100" {
return errors.Errorf("got %s, want 100", got)
}
return nil
}, &testing.PollOptions{Interval: time.Second}); err != nil {
s.Fatal("Failed to wait for connection count update: ", err)
}
s.Log("Querying only-b")
shortCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
if err := testexec.CommandContext(shortCtx, "p2p-client", "--get-url=only-b").Run(); err == nil {
s.Fatal("p2p-client finished, but should have waited for num_connections to drop")
}
}