m/n/core/cluster: factor out bootstrap into separate file
Test Plan: Refactor.
X-Origin-Diff: phab/D753
GitOrigin-RevId: 2e39db6673b2a0a6c1d7593f230fa691643e4c06
diff --git a/metropolis/node/core/cluster/BUILD.bazel b/metropolis/node/core/cluster/BUILD.bazel
index b240d16..c6e01e5 100644
--- a/metropolis/node/core/cluster/BUILD.bazel
+++ b/metropolis/node/core/cluster/BUILD.bazel
@@ -6,6 +6,7 @@
"cluster.go",
"configuration.go",
"manager.go",
+ "manager_bootstrap.go",
"node.go",
],
importpath = "source.monogon.dev/metropolis/node/core/cluster",
diff --git a/metropolis/node/core/cluster/manager.go b/metropolis/node/core/cluster/manager.go
index 10d699c..65af212 100644
--- a/metropolis/node/core/cluster/manager.go
+++ b/metropolis/node/core/cluster/manager.go
@@ -18,9 +18,6 @@
import (
"context"
- "crypto/ed25519"
- "crypto/rand"
- "encoding/hex"
"errors"
"fmt"
"io/ioutil"
@@ -31,7 +28,6 @@
"source.monogon.dev/metropolis/node/core/consensus"
"source.monogon.dev/metropolis/node/core/localstorage"
"source.monogon.dev/metropolis/node/core/network"
- "source.monogon.dev/metropolis/pkg/pki"
"source.monogon.dev/metropolis/pkg/supervisor"
apb "source.monogon.dev/metropolis/proto/api"
ppb "source.monogon.dev/metropolis/proto/private"
@@ -158,109 +154,6 @@
}
}
-func (m *Manager) bootstrap(ctx context.Context, bootstrap *apb.NodeParameters_ClusterBootstrap) error {
- supervisor.Logger(ctx).Infof("Bootstrapping new cluster, owner public key: %s", hex.EncodeToString(bootstrap.OwnerPublicKey))
- state, unlock := m.lock()
- defer unlock()
-
- state.configuration = &ppb.SealedConfiguration{}
-
- // Mount new storage with generated CUK, and save LUK into sealed config proto.
- supervisor.Logger(ctx).Infof("Bootstrapping: mounting new storage...")
- cuk, err := m.storageRoot.Data.MountNew(state.configuration)
- if err != nil {
- return fmt.Errorf("could not make and mount data partition: %w", err)
- }
-
- pub, priv, err := ed25519.GenerateKey(rand.Reader)
- if err != nil {
- return fmt.Errorf("could not generate node keypair: %w", err)
- }
- supervisor.Logger(ctx).Infof("Bootstrapping: node public key: %s", hex.EncodeToString([]byte(pub)))
-
- node := Node{
- clusterUnlockKey: cuk,
- pubkey: pub,
- state: ppb.Node_FSM_STATE_UP,
- // TODO(q3k): make this configurable.
- consensusMember: &NodeRoleConsensusMember{},
- kubernetesWorker: &NodeRoleKubernetesWorker{},
- }
-
- // Run worker to keep updating /ephemeral/hosts (and thus, /etc/hosts) with
- // our own IP address. This ensures that the node's ID always resolves to
- // its current external IP address.
- supervisor.Run(ctx, "hostsfile", func(ctx context.Context) error {
- supervisor.Signal(ctx, supervisor.SignalHealthy)
- watcher := m.networkService.Watch()
- for {
- status, err := watcher.Get(ctx)
- if err != nil {
- return err
- }
- err = node.ConfigureLocalHostname(ctx, &m.storageRoot.Ephemeral, status.ExternalAddress)
- if err != nil {
- return fmt.Errorf("could not configure hostname: %w", err)
- }
- }
- })
-
- // Bring up consensus with this node as the only member.
- m.consensus = consensus.New(consensus.Config{
- Data: &m.storageRoot.Data.Etcd,
- Ephemeral: &m.storageRoot.Ephemeral.Consensus,
- NewCluster: true,
- Name: node.ID(),
- })
-
- supervisor.Logger(ctx).Infof("Bootstrapping: starting consensus...")
- if err := supervisor.Run(ctx, "consensus", m.consensus.Run); err != nil {
- return fmt.Errorf("when starting consensus: %w", err)
- }
-
- supervisor.Logger(ctx).Info("Bootstrapping: waiting for consensus...")
- if err := m.consensus.WaitReady(ctx); err != nil {
- return fmt.Errorf("consensus service failed to become ready: %w", err)
- }
- supervisor.Logger(ctx).Info("Bootstrapping: consensus ready.")
-
- kv := m.consensus.KVRoot()
- node.KV = kv
-
- // Create Metropolis CA and this node's certificate.
- caCertBytes, _, err := PKICA.Ensure(ctx, kv)
- if err != nil {
- return fmt.Errorf("failed to create cluster CA: %w", err)
- }
- nodeCert := PKINamespace.New(PKICA, "", pki.Server([]string{node.ID()}, nil))
- nodeCert.UseExistingKey(priv)
- nodeCertBytes, _, err := nodeCert.Ensure(ctx, kv)
- if err != nil {
- return fmt.Errorf("failed to create node certificate: %w", err)
- }
-
- if err := m.storageRoot.Data.Node.Credentials.CACertificate.Write(caCertBytes, 0400); err != nil {
- return fmt.Errorf("failed to write CA certificate: %w", err)
- }
- if err := m.storageRoot.Data.Node.Credentials.Certificate.Write(nodeCertBytes, 0400); err != nil {
- return fmt.Errorf("failed to write node certificate: %w", err)
- }
- if err := m.storageRoot.Data.Node.Credentials.Key.Write(priv, 0400); err != nil {
- return fmt.Errorf("failed to write node private key: %w", err)
- }
-
- // Update our Node obejct in etcd.
- if err := node.Store(ctx, kv); err != nil {
- return fmt.Errorf("failed to store new node in etcd: %w", err)
- }
-
- state.setResult(&node, nil)
-
- supervisor.Signal(ctx, supervisor.SignalHealthy)
- supervisor.Signal(ctx, supervisor.SignalDone)
- return nil
-}
-
func (m *Manager) register(ctx context.Context, bootstrap *apb.NodeParameters_ClusterRegister) error {
return fmt.Errorf("unimplemented")
}
diff --git a/metropolis/node/core/cluster/manager_bootstrap.go b/metropolis/node/core/cluster/manager_bootstrap.go
new file mode 100644
index 0000000..e6f94a2
--- /dev/null
+++ b/metropolis/node/core/cluster/manager_bootstrap.go
@@ -0,0 +1,135 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 cluster
+
+import (
+ "context"
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+
+ "source.monogon.dev/metropolis/node/core/consensus"
+ "source.monogon.dev/metropolis/pkg/pki"
+ "source.monogon.dev/metropolis/pkg/supervisor"
+ apb "source.monogon.dev/metropolis/proto/api"
+ ppb "source.monogon.dev/metropolis/proto/private"
+)
+
+func (m *Manager) bootstrap(ctx context.Context, bootstrap *apb.NodeParameters_ClusterBootstrap) error {
+ supervisor.Logger(ctx).Infof("Bootstrapping new cluster, owner public key: %s", hex.EncodeToString(bootstrap.OwnerPublicKey))
+ state, unlock := m.lock()
+ defer unlock()
+
+ state.configuration = &ppb.SealedConfiguration{}
+
+ // Mount new storage with generated CUK, and save LUK into sealed config proto.
+ supervisor.Logger(ctx).Infof("Bootstrapping: mounting new storage...")
+ cuk, err := m.storageRoot.Data.MountNew(state.configuration)
+ if err != nil {
+ return fmt.Errorf("could not make and mount data partition: %w", err)
+ }
+
+ pub, priv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return fmt.Errorf("could not generate node keypair: %w", err)
+ }
+ supervisor.Logger(ctx).Infof("Bootstrapping: node public key: %s", hex.EncodeToString([]byte(pub)))
+
+ node := Node{
+ clusterUnlockKey: cuk,
+ pubkey: pub,
+ state: ppb.Node_FSM_STATE_UP,
+ // TODO(q3k): make this configurable.
+ consensusMember: &NodeRoleConsensusMember{},
+ kubernetesWorker: &NodeRoleKubernetesWorker{},
+ }
+
+ // Run worker to keep updating /ephemeral/hosts (and thus, /etc/hosts) with
+ // our own IP address. This ensures that the node's ID always resolves to
+ // its current external IP address.
+ supervisor.Run(ctx, "hostsfile", func(ctx context.Context) error {
+ supervisor.Signal(ctx, supervisor.SignalHealthy)
+ watcher := m.networkService.Watch()
+ for {
+ status, err := watcher.Get(ctx)
+ if err != nil {
+ return err
+ }
+ err = node.ConfigureLocalHostname(ctx, &m.storageRoot.Ephemeral, status.ExternalAddress)
+ if err != nil {
+ return fmt.Errorf("could not configure hostname: %w", err)
+ }
+ }
+ })
+
+ // Bring up consensus with this node as the only member.
+ m.consensus = consensus.New(consensus.Config{
+ Data: &m.storageRoot.Data.Etcd,
+ Ephemeral: &m.storageRoot.Ephemeral.Consensus,
+ NewCluster: true,
+ Name: node.ID(),
+ })
+
+ supervisor.Logger(ctx).Infof("Bootstrapping: starting consensus...")
+ if err := supervisor.Run(ctx, "consensus", m.consensus.Run); err != nil {
+ return fmt.Errorf("when starting consensus: %w", err)
+ }
+
+ supervisor.Logger(ctx).Info("Bootstrapping: waiting for consensus...")
+ if err := m.consensus.WaitReady(ctx); err != nil {
+ return fmt.Errorf("consensus service failed to become ready: %w", err)
+ }
+ supervisor.Logger(ctx).Info("Bootstrapping: consensus ready.")
+
+ kv := m.consensus.KVRoot()
+ node.KV = kv
+
+ // Create Metropolis CA and this node's certificate.
+ caCertBytes, _, err := PKICA.Ensure(ctx, kv)
+ if err != nil {
+ return fmt.Errorf("failed to create cluster CA: %w", err)
+ }
+ nodeCert := PKINamespace.New(PKICA, "", pki.Server([]string{node.ID()}, nil))
+ nodeCert.UseExistingKey(priv)
+ nodeCertBytes, _, err := nodeCert.Ensure(ctx, kv)
+ if err != nil {
+ return fmt.Errorf("failed to create node certificate: %w", err)
+ }
+
+ if err := m.storageRoot.Data.Node.Credentials.CACertificate.Write(caCertBytes, 0400); err != nil {
+ return fmt.Errorf("failed to write CA certificate: %w", err)
+ }
+ if err := m.storageRoot.Data.Node.Credentials.Certificate.Write(nodeCertBytes, 0400); err != nil {
+ return fmt.Errorf("failed to write node certificate: %w", err)
+ }
+ if err := m.storageRoot.Data.Node.Credentials.Key.Write(priv, 0400); err != nil {
+ return fmt.Errorf("failed to write node private key: %w", err)
+ }
+
+ // Update our Node obejct in etcd.
+ if err := node.Store(ctx, kv); err != nil {
+ return fmt.Errorf("failed to store new node in etcd: %w", err)
+ }
+
+ state.setResult(&node, nil)
+
+ supervisor.Signal(ctx, supervisor.SignalHealthy)
+ supervisor.Signal(ctx, supervisor.SignalDone)
+ return nil
+}
+