mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-29 03:28:41 +00:00

Previously, the operator checked the ProxyGroup status fields for information on how many of the proxies had successfully authed. Use their state Secrets instead as a more reliable source of truth. containerboot has written device_fqdn and device_ips keys to the state Secret since inception, and pod_uid since 1.78.0, so there's no need to use the API for that data. Read it from the state Secret for consistency. However, to ensure we don't read data from a previous run of containerboot, make sure we reset containerboot's state keys on startup. One other knock-on effect of that is ProxyGroups can briefly be marked not Ready while a Pod is restarting. Introduce a new ProxyGroupAvailable condition to more accurately reflect when downstream controllers can implement flows that rely on a ProxyGroup having at least 1 proxy Pod running. Fixes #16327 Change-Id: I026c18e9d23e87109a471a87b8e4fb6271716a66 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
179 lines
7.0 KiB
Go
179 lines
7.0 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !plan9
|
|
|
|
package kube
|
|
|
|
import (
|
|
"slices"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
xslices "golang.org/x/exp/slices"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
|
"tailscale.com/tstime"
|
|
)
|
|
|
|
// SetConnectorCondition ensures that Connector status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes.
|
|
func SetConnectorCondition(cn *tsapi.Connector, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(cn.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
|
|
cn.Status.Conditions = conds
|
|
}
|
|
|
|
// RemoveConnectorCondition will remove condition of the given type if it exists.
|
|
func RemoveConnectorCondition(conn *tsapi.Connector, conditionType tsapi.ConditionType) {
|
|
conn.Status.Conditions = slices.DeleteFunc(conn.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(conditionType)
|
|
})
|
|
}
|
|
|
|
// SetProxyClassCondition ensures that ProxyClass status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes.
|
|
func SetProxyClassCondition(pc *tsapi.ProxyClass, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(pc.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
|
|
pc.Status.Conditions = conds
|
|
}
|
|
|
|
// SetDNSConfigCondition ensures that DNSConfig status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes
|
|
func SetDNSConfigCondition(dnsCfg *tsapi.DNSConfig, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(dnsCfg.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
|
|
dnsCfg.Status.Conditions = conds
|
|
}
|
|
|
|
// SetServiceCondition ensures that Service status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes.
|
|
func SetServiceCondition(svc *corev1.Service, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(svc.Status.Conditions, conditionType, status, reason, message, 0, clock, logger)
|
|
svc.Status.Conditions = conds
|
|
}
|
|
|
|
// GetServiceCondition returns Service condition with the specified type, if it exists on the Service.
|
|
func GetServiceCondition(svc *corev1.Service, conditionType tsapi.ConditionType) *metav1.Condition {
|
|
idx := xslices.IndexFunc(svc.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(conditionType)
|
|
})
|
|
|
|
if idx == -1 {
|
|
return nil
|
|
}
|
|
return &svc.Status.Conditions[idx]
|
|
}
|
|
|
|
// RemoveServiceCondition will remove condition of the given type if it exists.
|
|
func RemoveServiceCondition(svc *corev1.Service, conditionType tsapi.ConditionType) {
|
|
svc.Status.Conditions = slices.DeleteFunc(svc.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(conditionType)
|
|
})
|
|
}
|
|
|
|
// SetRecorderCondition ensures that Recorder status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes.
|
|
func SetRecorderCondition(tsr *tsapi.Recorder, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(tsr.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
|
|
tsr.Status.Conditions = conds
|
|
}
|
|
|
|
// SetProxyGroupCondition ensures that ProxyGroup status has a condition with the
|
|
// given attributes. LastTransitionTime gets set every time condition's status
|
|
// changes.
|
|
func SetProxyGroupCondition(pg *tsapi.ProxyGroup, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) {
|
|
conds := updateCondition(pg.Status.Conditions, conditionType, status, reason, message, gen, clock, logger)
|
|
pg.Status.Conditions = conds
|
|
}
|
|
|
|
func updateCondition(conds []metav1.Condition, conditionType tsapi.ConditionType, status metav1.ConditionStatus, reason, message string, gen int64, clock tstime.Clock, logger *zap.SugaredLogger) []metav1.Condition {
|
|
newCondition := metav1.Condition{
|
|
Type: string(conditionType),
|
|
Status: status,
|
|
Reason: reason,
|
|
Message: message,
|
|
ObservedGeneration: gen,
|
|
}
|
|
|
|
nowTime := metav1.NewTime(clock.Now().Truncate(time.Second))
|
|
newCondition.LastTransitionTime = nowTime
|
|
|
|
idx := xslices.IndexFunc(conds, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(conditionType)
|
|
})
|
|
|
|
if idx == -1 {
|
|
conds = append(conds, newCondition)
|
|
return conds
|
|
}
|
|
|
|
cond := conds[idx] // update the existing condition
|
|
|
|
// If this update doesn't contain a state transition, don't update last
|
|
// transition time.
|
|
if cond.Status == status {
|
|
newCondition.LastTransitionTime = cond.LastTransitionTime
|
|
} else {
|
|
logger.Infof("Status change for condition %s from %s to %s", conditionType, cond.Status, status)
|
|
}
|
|
conds[idx] = newCondition
|
|
return conds
|
|
}
|
|
|
|
func ProxyClassIsReady(pc *tsapi.ProxyClass) bool {
|
|
idx := xslices.IndexFunc(pc.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(tsapi.ProxyClassReady)
|
|
})
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
cond := pc.Status.Conditions[idx]
|
|
return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == pc.Generation
|
|
}
|
|
|
|
func ProxyGroupIsReady(pg *tsapi.ProxyGroup) bool {
|
|
return proxyGroupCondition(pg, tsapi.ProxyGroupReady)
|
|
}
|
|
|
|
func ProxyGroupAvailable(pg *tsapi.ProxyGroup) bool {
|
|
return proxyGroupCondition(pg, tsapi.ProxyGroupAvailable)
|
|
}
|
|
|
|
func proxyGroupCondition(pg *tsapi.ProxyGroup, condType tsapi.ConditionType) bool {
|
|
idx := xslices.IndexFunc(pg.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(condType)
|
|
})
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
cond := pg.Status.Conditions[idx]
|
|
return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == pg.Generation
|
|
}
|
|
|
|
func DNSCfgIsReady(cfg *tsapi.DNSConfig) bool {
|
|
idx := xslices.IndexFunc(cfg.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(tsapi.NameserverReady)
|
|
})
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
cond := cfg.Status.Conditions[idx]
|
|
return cond.Status == metav1.ConditionTrue && cond.ObservedGeneration == cfg.Generation
|
|
}
|
|
|
|
func SvcIsReady(svc *corev1.Service) bool {
|
|
idx := xslices.IndexFunc(svc.Status.Conditions, func(cond metav1.Condition) bool {
|
|
return cond.Type == string(tsapi.ProxyReady)
|
|
})
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
cond := svc.Status.Conditions[idx]
|
|
return cond.Status == metav1.ConditionTrue
|
|
}
|