blob: 481c81869d5977aefa7ebe8fcc1e138b07f1e77c [file] [log] [blame]
package cluster
import (
"errors"
"fmt"
"source.monogon.dev/metropolis/node/core/consensus/client"
"source.monogon.dev/metropolis/node/core/identity"
cpb "source.monogon.dev/metropolis/proto/common"
)
var (
ErrNoLocalConsensus = errors.New("this node does not have direct access to etcd")
)
// Status is returned to Cluster clients (ie., node code) on Manager.Watch/.Get.
type Status struct {
// State is the current state of the cluster, as seen by the node.
State cpb.ClusterState
// hasLocalConsensus is true if the local node is running a local consensus
// (etcd) server.
HasLocalConsensus bool
// consensusClient is an etcd client to the local consensus server if the node
// has such a server and the cluster state is HOME or SPLIT.
consensusClient client.Namespaced
// Credentials used for the node to authenticate to the Curator and other
// cluster services.
Credentials *identity.NodeCredentials
}
// ConsensusUser is the to-level user of an etcd client in Metropolis node
// code. These need to be defined ahead of time in an Go 'enum', and different
// ConsensusUsers should not be shared by different codepaths.
type ConsensusUser string
const (
ConsensusUserKubernetesPKI ConsensusUser = "kubernetes-pki"
ConsensusUserCurator ConsensusUser = "curator"
)
// ConsensusClient returns an etcd/consensus client for a given ConsensusUser.
// The node must be running a local consensus/etcd server.
func (s *Status) ConsensusClient(user ConsensusUser) (client.Namespaced, error) {
if !s.HasLocalConsensus {
return nil, ErrNoLocalConsensus
}
// Ensure that we already are connected to etcd and are in a state in which we
// should be handing out cluster connectivity.
if s.consensusClient == nil {
return nil, fmt.Errorf("not connected")
}
switch s.State {
case cpb.ClusterState_CLUSTER_STATE_HOME:
case cpb.ClusterState_CLUSTER_STATE_SPLIT:
// The consensus client is resistant to being split off, and will serve
// as soon as the split is resolved.
default:
return nil, fmt.Errorf("refusing connection with cluster state %v", s.State)
}
// Ensure only defined 'applications' are used to prevent programmer error and
// casting to ConsensusUser from an arbitrary string.
switch user {
case ConsensusUserKubernetesPKI:
case ConsensusUserCurator:
default:
return nil, fmt.Errorf("unknown ConsensusUser %q", user)
}
client, err := s.consensusClient.Sub(string(user))
if err != nil {
return nil, fmt.Errorf("retrieving subclient failed: %w", err)
}
return client, nil
}