blob: 793cc287adf36f67d982db9b920945fec4451fca [file] [log] [blame]
Lorenz Brun52f7f292020-06-24 16:42:02 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package main
18
19import (
Serge Bazanski075465c2021-11-16 15:38:49 +010020 "context"
Jan Schära9b060b2024-08-07 10:42:29 +020021 "errors"
22 "flag"
23 "fmt"
Lorenz Brun52f7f292020-06-24 16:42:02 +020024 "log"
Jan Schära9b060b2024-08-07 10:42:29 +020025 "net"
Tim Windelschmidtb765f242024-05-08 01:40:02 +020026 "os"
27 "os/signal"
Jan Schära9b060b2024-08-07 10:42:29 +020028 "strconv"
29 "strings"
30 "time"
Lorenz Brun52f7f292020-06-24 16:42:02 +020031
Jan Schär5abcc7a2024-08-26 13:59:11 +020032 "github.com/bazelbuild/rules_go/go/runfiles"
33
Jan Schära9b060b2024-08-07 10:42:29 +020034 "source.monogon.dev/metropolis/cli/flagdefs"
Serge Bazanski1f8cad72023-03-20 16:58:10 +010035 metroctl "source.monogon.dev/metropolis/cli/metroctl/core"
Jan Schära9b060b2024-08-07 10:42:29 +020036 "source.monogon.dev/metropolis/node"
37 cpb "source.monogon.dev/metropolis/proto/common"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020038 mlaunch "source.monogon.dev/metropolis/test/launch"
Lorenz Brun52f7f292020-06-24 16:42:02 +020039)
40
Jan Schär5abcc7a2024-08-26 13:59:11 +020041var (
42 // These are filled by bazel at linking time with the canonical path of
43 // their corresponding file. Inside the init function we resolve it
44 // with the rules_go runfiles package to the real path.
45 xMetroctlPath string
46)
47
48func init() {
49 var err error
50 for _, path := range []*string{
51 &xMetroctlPath,
52 } {
53 *path, err = runfiles.Rlocation(*path)
54 if err != nil {
55 panic(err)
56 }
57 }
58}
59
Jan Schära9b060b2024-08-07 10:42:29 +020060const maxNodes = 256
61
62func nodeSetFlag(p *[]int, name string, usage string) {
63 flag.Func(name, usage, func(val string) error {
64 for _, part := range strings.Split(val, ",") {
65 part = strings.TrimSpace(part)
66 if part == "" {
67 continue
68 }
69 startStr, endStr, ok := strings.Cut(part, "-")
70 if !ok {
71 endStr = startStr
72 }
73 start, err := strconv.Atoi(startStr)
74 if err != nil {
75 return err
76 }
77 end, err := strconv.Atoi(endStr)
78 if err != nil {
79 return err
80 }
81 if end >= maxNodes {
82 return fmt.Errorf("node index %v out of range, there can be at most %v nodes", end, maxNodes)
83 }
84 if end < start {
85 return fmt.Errorf("invalid range %q, end is smaller than start", part)
86 }
87 for i := start; i <= end; i++ {
88 *p = append(*p, i)
89 }
90 }
91 return nil
Serge Bazanski075465c2021-11-16 15:38:49 +010092 })
Jan Schära9b060b2024-08-07 10:42:29 +020093}
94
Jan Schär07003572024-08-26 10:42:16 +020095func memoryMiBFlag(p *int, name string, usage string) {
Jan Schära9b060b2024-08-07 10:42:29 +020096 flag.Func(name, usage, func(val string) error {
97 multiplier := 1
98 switch {
99 case strings.HasSuffix(val, "M"):
100 case strings.HasSuffix(val, "G"):
101 multiplier = 1024
102 default:
103 return errors.New("must have suffix M for MiB or G for GiB")
104 }
105 intVal, err := strconv.Atoi(val[:len(val)-1])
106 if err != nil {
107 return err
108 }
109 *p = multiplier * intVal
110 return nil
111 })
112}
113
Jan Schär07003572024-08-26 10:42:16 +0200114func diskBytesFlag(p *uint64, name string, usage string) {
115 flag.Func(name, usage, func(val string) error {
116 var multiplier uint64
117 switch {
118 case strings.HasSuffix(val, "M"):
119 multiplier = 1024 * 1024
120 case strings.HasSuffix(val, "G"):
121 multiplier = 1024 * 1024 * 1024
122 case strings.HasSuffix(val, "T"):
123 multiplier = 1024 * 1024 * 1024 * 1024
124 default:
125 return errors.New("must have suffix M for MiB, G for GiB or T for TiB")
126 }
127 intVal, err := strconv.ParseUint(val[:len(val)-1], 10, 64)
128 if err != nil {
129 return err
130 }
131 *p = multiplier * intVal
132 return nil
133 })
134}
135
Jan Schära9b060b2024-08-07 10:42:29 +0200136func main() {
Serge Bazanski20498dd2024-09-30 17:07:08 +0000137 clusterConfig := cpb.ClusterConfiguration{
138 KubernetesConfig: &cpb.ClusterConfiguration_KubernetesConfig{
139 NodeLabelsToSynchronize: []*cpb.ClusterConfiguration_KubernetesConfig_NodeLabelsToSynchronize{
140 {Regexp: "^" + mlaunch.NodeNumberKey + "$"},
141 },
142 },
143 }
Jan Schära9b060b2024-08-07 10:42:29 +0200144 opts := mlaunch.ClusterOptions{
145 NodeLogsToFiles: true,
146 InitialClusterConfiguration: &clusterConfig,
147 }
148 var consensusMemberList, kubernetesControllerList, kubernetesWorkerList []int
149
150 flag.IntVar(&opts.NumNodes, "num-nodes", 3, "Number of cluster nodes")
Jan Schär39f4f5c2024-10-29 09:41:50 +0100151 flag.StringVar(&clusterConfig.ClusterDomain, "cluster-domain", "cluster.internal", "Cluster domain")
Jan Schära9b060b2024-08-07 10:42:29 +0200152 flagdefs.TPMModeVar(flag.CommandLine, &clusterConfig.TpmMode, "tpm-mode", cpb.ClusterConfiguration_TPM_MODE_REQUIRED, "TPM mode to set on cluster")
153 flagdefs.StorageSecurityPolicyVar(flag.CommandLine, &clusterConfig.StorageSecurityPolicy, "storage-security", cpb.ClusterConfiguration_STORAGE_SECURITY_POLICY_NEEDS_INSECURE, "Storage security policy to set on cluster")
154 flag.IntVar(&opts.Node.CPUs, "cpu", 1, "Number of virtual CPUs of each node")
155 flag.IntVar(&opts.Node.ThreadsPerCPU, "threads-per-cpu", 1, "Number of threads per CPU")
Jan Schär07003572024-08-26 10:42:16 +0200156 memoryMiBFlag(&opts.Node.MemoryMiB, "ram", "RAM size of each node, with suffix M for MiB or G for GiB")
157 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 +0200158 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 +0200159 nodeSetFlag(&consensusMemberList, "consensus-member", "List of nodes which get the Consensus Member role. Example: 0,3-5")
160 nodeSetFlag(&kubernetesControllerList, "kubernetes-controller", "List of nodes which get the Kubernetes Controller role. Example: 0,3-5")
161 nodeSetFlag(&kubernetesWorkerList, "kubernetes-worker", "List of nodes which get the Kubernetes Worker role. Example: 0,3-5")
162 flag.Parse()
163
164 if opts.NumNodes >= maxNodes {
165 log.Fatalf("num-nodes (%v) is too large, there can be at most %v nodes", opts.NumNodes, maxNodes)
166 }
167 for _, list := range [][]int{consensusMemberList, kubernetesControllerList, kubernetesWorkerList} {
168 for i := len(list) - 1; i >= 0; i-- {
169 if list[i] >= opts.NumNodes {
170 log.Fatalf("Node index %v out of range, can be at most %v", list[i], opts.NumNodes-1)
171 }
172 }
173 }
174
175 ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
176 cl, err := mlaunch.LaunchCluster(ctx, opts)
Serge Bazanski075465c2021-11-16 15:38:49 +0100177 if err != nil {
178 log.Fatalf("LaunchCluster: %v", err)
179 }
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100180
Jan Schära9b060b2024-08-07 10:42:29 +0200181 for _, node := range consensusMemberList {
182 cl.MakeConsensusMember(ctx, cl.NodeIDs[node])
183 }
184 for _, node := range kubernetesControllerList {
185 cl.MakeKubernetesController(ctx, cl.NodeIDs[node])
186 }
187 for _, node := range kubernetesWorkerList {
188 cl.MakeKubernetesWorker(ctx, cl.NodeIDs[node])
189 }
190
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100191 wpath, err := cl.MakeMetroctlWrapper()
192 if err != nil {
193 log.Fatalf("MakeWrapper: %v", err)
194 }
195
Jan Schära9b060b2024-08-07 10:42:29 +0200196 apiserver := cl.Nodes[cl.NodeIDs[0]].ManagementAddress
197 // Wait for the API server to start listening.
198 for {
199 conn, err := cl.DialNode(ctx, net.JoinHostPort(apiserver, node.KubernetesAPIWrappedPort.PortString()))
200 if err == nil {
201 conn.Close()
202 break
203 }
204 time.Sleep(100 * time.Millisecond)
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100205 }
206
Serge Bazanski6a328e62024-02-12 17:37:50 +0100207 configName := "launch-cluster"
Jan Schär5abcc7a2024-08-26 13:59:11 +0200208 if err := metroctl.InstallKubeletConfig(ctx, xMetroctlPath, cl.ConnectOptions(), configName, apiserver); err != nil {
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100209 log.Fatalf("InstallKubeletConfig: %v", err)
210 }
211
Serge Bazanski075465c2021-11-16 15:38:49 +0100212 log.Printf("Launch: Cluster running!")
Serge Bazanski1f8cad72023-03-20 16:58:10 +0100213 log.Printf(" To access cluster use: metroctl %s", cl.MetroctlFlags())
214 log.Printf(" Or use this handy wrapper: %s", wpath)
215 log.Printf(" To access Kubernetes, use kubectl --context=%s", configName)
Serge Bazanski075465c2021-11-16 15:38:49 +0100216
217 <-ctx.Done()
218 cl.Close()
Lorenz Brun52f7f292020-06-24 16:42:02 +0200219}