blob: 1aec556cd05a38f925b97e7d8b584b93d1121caf [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun52f7f292020-06-24 16:42:02 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun52f7f292020-06-24 16:42:02 +02003
4package main
5
6import (
Serge Bazanski075465c2021-11-16 15:38:49 +01007 "context"
Jan Schära9b060b2024-08-07 10:42:29 +02008 "errors"
9 "flag"
10 "fmt"
Lorenz Brun52f7f292020-06-24 16:42:02 +020011 "log"
Jan Schära9b060b2024-08-07 10:42:29 +020012 "net"
Tim Windelschmidtb765f242024-05-08 01:40:02 +020013 "os"
14 "os/signal"
Jan Schära9b060b2024-08-07 10:42:29 +020015 "strconv"
16 "strings"
17 "time"
Lorenz Brun52f7f292020-06-24 16:42:02 +020018
Jan Schär5abcc7a2024-08-26 13:59:11 +020019 "github.com/bazelbuild/rules_go/go/runfiles"
20
Jan Schära9b060b2024-08-07 10:42:29 +020021 "source.monogon.dev/metropolis/cli/flagdefs"
Serge Bazanski1f8cad72023-03-20 16:58:10 +010022 metroctl "source.monogon.dev/metropolis/cli/metroctl/core"
Jan Schära9b060b2024-08-07 10:42:29 +020023 "source.monogon.dev/metropolis/node"
24 cpb "source.monogon.dev/metropolis/proto/common"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020025 mlaunch "source.monogon.dev/metropolis/test/launch"
Lorenz Brun52f7f292020-06-24 16:42:02 +020026)
27
Jan Schär5abcc7a2024-08-26 13:59:11 +020028var (
29 // These are filled by bazel at linking time with the canonical path of
30 // their corresponding file. Inside the init function we resolve it
31 // with the rules_go runfiles package to the real path.
32 xMetroctlPath string
33)
34
35func init() {
36 var err error
37 for _, path := range []*string{
38 &xMetroctlPath,
39 } {
40 *path, err = runfiles.Rlocation(*path)
41 if err != nil {
42 panic(err)
43 }
44 }
45}
46
Jan Schära9b060b2024-08-07 10:42:29 +020047const maxNodes = 256
48
49func nodeSetFlag(p *[]int, name string, usage string) {
50 flag.Func(name, usage, func(val string) error {
51 for _, part := range strings.Split(val, ",") {
52 part = strings.TrimSpace(part)
53 if part == "" {
54 continue
55 }
56 startStr, endStr, ok := strings.Cut(part, "-")
57 if !ok {
58 endStr = startStr
59 }
60 start, err := strconv.Atoi(startStr)
61 if err != nil {
62 return err
63 }
64 end, err := strconv.Atoi(endStr)
65 if err != nil {
66 return err
67 }
68 if end >= maxNodes {
69 return fmt.Errorf("node index %v out of range, there can be at most %v nodes", end, maxNodes)
70 }
71 if end < start {
72 return fmt.Errorf("invalid range %q, end is smaller than start", part)
73 }
74 for i := start; i <= end; i++ {
75 *p = append(*p, i)
76 }
77 }
78 return nil
Serge Bazanski075465c2021-11-16 15:38:49 +010079 })
Jan Schära9b060b2024-08-07 10:42:29 +020080}
81
Jan Schär07003572024-08-26 10:42:16 +020082func memoryMiBFlag(p *int, name string, usage string) {
Jan Schära9b060b2024-08-07 10:42:29 +020083 flag.Func(name, usage, func(val string) error {
84 multiplier := 1
85 switch {
86 case strings.HasSuffix(val, "M"):
87 case strings.HasSuffix(val, "G"):
88 multiplier = 1024
89 default:
90 return errors.New("must have suffix M for MiB or G for GiB")
91 }
92 intVal, err := strconv.Atoi(val[:len(val)-1])
93 if err != nil {
94 return err
95 }
96 *p = multiplier * intVal
97 return nil
98 })
99}
100
Jan Schär07003572024-08-26 10:42:16 +0200101func diskBytesFlag(p *uint64, name string, usage string) {
102 flag.Func(name, usage, func(val string) error {
103 var multiplier uint64
104 switch {
105 case strings.HasSuffix(val, "M"):
106 multiplier = 1024 * 1024
107 case strings.HasSuffix(val, "G"):
108 multiplier = 1024 * 1024 * 1024
109 case strings.HasSuffix(val, "T"):
110 multiplier = 1024 * 1024 * 1024 * 1024
111 default:
112 return errors.New("must have suffix M for MiB, G for GiB or T for TiB")
113 }
114 intVal, err := strconv.ParseUint(val[:len(val)-1], 10, 64)
115 if err != nil {
116 return err
117 }
118 *p = multiplier * intVal
119 return nil
120 })
121}
122
Jan Schära9b060b2024-08-07 10:42:29 +0200123func main() {
Serge Bazanski20498dd2024-09-30 17:07:08 +0000124 clusterConfig := cpb.ClusterConfiguration{
Serge Bazanski78567602024-10-31 13:42:04 +0000125 Kubernetes: &cpb.ClusterConfiguration_Kubernetes{
126 NodeLabelsToSynchronize: []*cpb.ClusterConfiguration_Kubernetes_NodeLabelsToSynchronize{
Serge Bazanski20498dd2024-09-30 17:07:08 +0000127 {Regexp: "^" + mlaunch.NodeNumberKey + "$"},
128 },
129 },
130 }
Jan Schära9b060b2024-08-07 10:42:29 +0200131 opts := mlaunch.ClusterOptions{
132 NodeLogsToFiles: true,
133 InitialClusterConfiguration: &clusterConfig,
134 }
135 var consensusMemberList, kubernetesControllerList, kubernetesWorkerList []int
136
137 flag.IntVar(&opts.NumNodes, "num-nodes", 3, "Number of cluster nodes")
Jan Schär39f4f5c2024-10-29 09:41:50 +0100138 flag.StringVar(&clusterConfig.ClusterDomain, "cluster-domain", "cluster.internal", "Cluster domain")
Jan Schära9b060b2024-08-07 10:42:29 +0200139 flagdefs.TPMModeVar(flag.CommandLine, &clusterConfig.TpmMode, "tpm-mode", cpb.ClusterConfiguration_TPM_MODE_REQUIRED, "TPM mode to set on cluster")
140 flagdefs.StorageSecurityPolicyVar(flag.CommandLine, &clusterConfig.StorageSecurityPolicy, "storage-security", cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE, "Storage security policy to set on cluster")
141 flag.IntVar(&opts.Node.CPUs, "cpu", 1, "Number of virtual CPUs of each node")
142 flag.IntVar(&opts.Node.ThreadsPerCPU, "threads-per-cpu", 1, "Number of threads per CPU")
Jan Schär07003572024-08-26 10:42:16 +0200143 memoryMiBFlag(&opts.Node.MemoryMiB, "ram", "RAM size of each node, with suffix M for MiB or G for GiB")
144 diskBytesFlag(&opts.Node.DiskBytes, "disk", "Disk size of each node, with suffix M for MiB, G for GiB or T for TiB")
Jan Schär1dcede92024-09-25 13:07:11 +0200145 flag.BoolVar(&opts.Node.AllowReboot, "allow-reboot", false, "Allow nodes to reboot. This is off by default, to make it more noticeable when a node has crashed.")
Jan Schära9b060b2024-08-07 10:42:29 +0200146 nodeSetFlag(&consensusMemberList, "consensus-member", "List of nodes which get the Consensus Member role. Example: 0,3-5")
147 nodeSetFlag(&kubernetesControllerList, "kubernetes-controller", "List of nodes which get the Kubernetes Controller role. Example: 0,3-5")
148 nodeSetFlag(&kubernetesWorkerList, "kubernetes-worker", "List of nodes which get the Kubernetes Worker role. Example: 0,3-5")
149 flag.Parse()
150
151 if opts.NumNodes >= maxNodes {
152 log.Fatalf("num-nodes (%v) is too large, there can be at most %v nodes", opts.NumNodes, maxNodes)
153 }
154 for _, list := range [][]int{consensusMemberList, kubernetesControllerList, kubernetesWorkerList} {
155 for i := len(list) - 1; i >= 0; i-- {
156 if list[i] >= opts.NumNodes {
157 log.Fatalf("Node index %v out of range, can be at most %v", list[i], opts.NumNodes-1)
158 }
159 }
160 }
161
162 ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
163 cl, err := mlaunch.LaunchCluster(ctx, opts)
Serge Bazanski075465c2021-11-16 15:38:49 +0100164 if err != nil {
165 log.Fatalf("LaunchCluster: %v", err)
166 }
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100167
Jan Schära9b060b2024-08-07 10:42:29 +0200168 for _, node := range consensusMemberList {
169 cl.MakeConsensusMember(ctx, cl.NodeIDs[node])
170 }
171 for _, node := range kubernetesControllerList {
172 cl.MakeKubernetesController(ctx, cl.NodeIDs[node])
173 }
174 for _, node := range kubernetesWorkerList {
175 cl.MakeKubernetesWorker(ctx, cl.NodeIDs[node])
176 }
177
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100178 wpath, err := cl.MakeMetroctlWrapper()
179 if err != nil {
180 log.Fatalf("MakeWrapper: %v", err)
181 }
182
Jan Schära9b060b2024-08-07 10:42:29 +0200183 apiserver := cl.Nodes[cl.NodeIDs[0]].ManagementAddress
184 // Wait for the API server to start listening.
185 for {
186 conn, err := cl.DialNode(ctx, net.JoinHostPort(apiserver, node.KubernetesAPIWrappedPort.PortString()))
187 if err == nil {
188 conn.Close()
189 break
190 }
191 time.Sleep(100 * time.Millisecond)
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100192 }
193
Serge Bazanski6a328e62024-02-12 17:37:50 +0100194 configName := "launch-cluster"
Jan Schär5abcc7a2024-08-26 13:59:11 +0200195 if err := metroctl.InstallKubeletConfig(ctx, xMetroctlPath, cl.ConnectOptions(), configName, apiserver); err != nil {
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100196 log.Fatalf("InstallKubeletConfig: %v", err)
197 }
198
Serge Bazanski075465c2021-11-16 15:38:49 +0100199 log.Printf("Launch: Cluster running!")
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100200 log.Printf(" To access cluster use: metroctl %s", cl.MetroctlFlags())
201 log.Printf(" Or use this handy wrapper: %s", wpath)
202 log.Printf(" To access Kubernetes, use kubectl --context=%s", configName)
Serge Bazanski075465c2021-11-16 15:38:49 +0100203
204 <-ctx.Done()
205 cl.Close()
Lorenz Brun52f7f292020-06-24 16:42:02 +0200206}