blob: 2863021617fa434e49120da5ca831ee280feb080 [file] [log] [blame]
Mateusz Zalegafed8fe52022-07-14 16:19:35 +02001package test
2
3import (
4 "context"
5 "encoding/pem"
6 "fmt"
7 "log"
8 "os"
9 "strings"
10 "testing"
11 "time"
12
13 "source.monogon.dev/metropolis/cli/pkg/datafile"
14 "source.monogon.dev/metropolis/pkg/cmd"
15 "source.monogon.dev/metropolis/test/launch/cluster"
16 "source.monogon.dev/metropolis/test/util"
17)
18
Mateusz Zalegadb75e212022-08-04 17:31:34 +020019// mctlExpectOutput returns true in the event the expected string is found in
20// metroctl output, and false otherwise.
21func mctlExpectOutput(t *testing.T, ctx context.Context, args []string, expect string) (bool, error) {
Mateusz Zalegafed8fe52022-07-14 16:19:35 +020022 t.Helper()
23
24 path, err := datafile.ResolveRunfile("metropolis/cli/metroctl/metroctl_/metroctl")
25 if err != nil {
Mateusz Zalegadb75e212022-08-04 17:31:34 +020026 return false, fmt.Errorf("couldn't resolve metroctl binary: %v", err)
Mateusz Zalegafed8fe52022-07-14 16:19:35 +020027 }
28
29 log.Printf("$ metroctl %s", strings.Join(args, " "))
Mateusz Zalega6cdc9762022-08-03 17:15:01 +020030 // Terminate metroctl as soon as the expected output is found.
31 found, err := cmd.RunCommand(ctx, path, args, cmd.TerminateIfFound(expect))
Mateusz Zalegafed8fe52022-07-14 16:19:35 +020032 if err != nil {
Mateusz Zalegadb75e212022-08-04 17:31:34 +020033 return false, fmt.Errorf("while running metroctl: %v", err)
34 }
35 return found, nil
36}
37
38// mctlFailIfMissing will return a non-nil error value either if the expected
39// output string s is missing in metroctl output, or in case metroctl can't be
40// launched.
41func mctlFailIfMissing(t *testing.T, ctx context.Context, args []string, s string) error {
42 found, err := mctlExpectOutput(t, ctx, args, s)
43 if err != nil {
44 return err
Mateusz Zalegafed8fe52022-07-14 16:19:35 +020045 }
46 if !found {
Mateusz Zalegadb75e212022-08-04 17:31:34 +020047 return fmt.Errorf("expected output is missing: \"%s\"", s)
48 }
49 return nil
50}
51
52// mctlFailIfFound will return a non-nil error value either if the expected
53// output string s is found in metroctl output, or in case metroctl can't be
54// launched.
55func mctlFailIfFound(t *testing.T, ctx context.Context, args []string, s string) error {
56 found, err := mctlExpectOutput(t, ctx, args, s)
57 if err != nil {
58 return err
59 }
60 if found {
61 return fmt.Errorf("unexpected output was found: \"%s\"", s)
Mateusz Zalegafed8fe52022-07-14 16:19:35 +020062 }
63 return nil
64}
65
66func TestMetroctl(t *testing.T) {
67 ctx, ctxC := context.WithCancel(context.Background())
68 defer ctxC()
69
70 co := cluster.ClusterOptions{
71 NumNodes: 2,
72 }
73 cl, err := cluster.LaunchCluster(context.Background(), co)
74 if err != nil {
75 t.Fatalf("LaunchCluster failed: %v", err)
76 }
77 defer func() {
78 err := cl.Close()
79 if err != nil {
80 t.Fatalf("cluster Close failed: %v", err)
81 }
82 }()
83
84 socksRemote := fmt.Sprintf("localhost:%d", cl.Ports[cluster.SOCKSPort])
85 var clusterEndpoints []string
86 for _, ep := range cl.Nodes {
87 clusterEndpoints = append(clusterEndpoints, ep.ManagementAddress)
88 }
89
90 ownerPem := pem.EncodeToMemory(&pem.Block{
91 Type: "METROPOLIS INITIAL OWNER PRIVATE KEY",
92 Bytes: cluster.InsecurePrivateKey,
93 })
94 if err := os.WriteFile("owner-key.pem", ownerPem, 0644); err != nil {
95 log.Fatal("Couldn't write owner-key.pem")
96 }
97
98 commonOpts := []string{
99 "--proxy=" + socksRemote,
100 "--config=.",
101 }
102
103 var endpointOpts []string
104 for _, ep := range clusterEndpoints {
105 endpointOpts = append(endpointOpts, "--endpoints="+ep)
106 }
107
108 log.Printf("metroctl: Cluster's running, starting tests...")
109 st := t.Run("Init", func(t *testing.T) {
110 util.TestEventual(t, "metroctl takeownership", ctx, 30*time.Second, func(ctx context.Context) error {
111 // takeownership needs just a single endpoint pointing at the initial node.
112 var args []string
113 args = append(args, commonOpts...)
114 args = append(args, endpointOpts[0])
115 args = append(args, "takeownership")
Mateusz Zalegadb75e212022-08-04 17:31:34 +0200116 return mctlFailIfMissing(t, ctx, args, "Successfully retrieved owner credentials")
Mateusz Zalegafed8fe52022-07-14 16:19:35 +0200117 })
118 })
119 if !st {
120 t.Fatalf("metroctl: Couldn't get cluster ownership.")
121 }
Mateusz Zalegadb75e212022-08-04 17:31:34 +0200122 t.Run("list", func(t *testing.T) {
123 util.TestEventual(t, "metroctl list", ctx, 10*time.Second, func(ctx context.Context) error {
124 var args []string
125 args = append(args, commonOpts...)
126 args = append(args, endpointOpts...)
127 args = append(args, "node", "list")
128 // Expect both node IDs to show up in the results.
129 if err := mctlFailIfMissing(t, ctx, args, cl.NodeIDs[0]); err != nil {
130 return err
131 }
132 return mctlFailIfMissing(t, ctx, args, cl.NodeIDs[1])
133 })
134 })
135 t.Run("list [nodeID]", func(t *testing.T) {
136 util.TestEventual(t, "metroctl list [nodeID]", ctx, 10*time.Second, func(ctx context.Context) error {
137 var args []string
138 args = append(args, commonOpts...)
139 args = append(args, endpointOpts...)
140 args = append(args, "node", "list", cl.NodeIDs[1])
141 // Expect just the supplied node IDs to show up in the results.
142 if err := mctlFailIfFound(t, ctx, args, cl.NodeIDs[0]); err != nil {
143 return err
144 }
145 return mctlFailIfMissing(t, ctx, args, cl.NodeIDs[1])
146 })
147 })
148 t.Run("list --output", func(t *testing.T) {
149 util.TestEventual(t, "metroctl list --output", ctx, 10*time.Second, func(ctx context.Context) error {
150 var args []string
151 args = append(args, commonOpts...)
152 args = append(args, endpointOpts...)
153 args = append(args, "node", "list", "--output", "list.txt")
154 // In this case metroctl should write its output to a file rather than stdout.
155 if err := mctlFailIfFound(t, ctx, args, cl.NodeIDs[0]); err != nil {
156 return err
157 }
158 od, err := os.ReadFile("list.txt")
159 if err != nil {
160 return fmt.Errorf("while reading metroctl output file: %v", err)
161 }
162 if !strings.Contains(string(od), cl.NodeIDs[0]) {
163 return fmt.Errorf("expected node ID hasn't been found in metroctl output")
164 }
165 return nil
166 })
167 })
168 t.Run("list --filter", func(t *testing.T) {
169 util.TestEventual(t, "metroctl list --filter", ctx, 10*time.Second, func(ctx context.Context) error {
170 nid := cl.NodeIDs[1]
171 naddr := cl.Nodes[nid].ManagementAddress
172
173 var args []string
174 args = append(args, commonOpts...)
175 args = append(args, endpointOpts...)
176 // Filter list results based on nodes' external addresses.
177 args = append(args, "node", "list", "--filter", fmt.Sprintf("node.status.external_address==\"%s\"", naddr))
178 // Expect the second node's ID to show up in the results.
179 if err := mctlFailIfMissing(t, ctx, args, cl.NodeIDs[1]); err != nil {
180 return err
181 }
182 // The first node should've been filtered away.
183 return mctlFailIfFound(t, ctx, args, cl.NodeIDs[0])
184 })
185 })
Mateusz Zalegafed8fe52022-07-14 16:19:35 +0200186}