Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions api/v1alpha1/clickhousecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,15 @@ func (v *ClickHouseClusterStatus) GetConditions() *[]metav1.Condition {
return &v.Conditions
}

// SpecificName returns cluster name with resource suffix. Used to generate resource names that may be used in DNS.
// SpecificName returns cluster name with role suffix.
func (v *ClickHouseCluster) SpecificName() string {
return normalizeName(v.Name) + "-clickhouse"
return v.Name + "-clickhouse"
}

// SpecificResourceName returns owned resource name with suffix.
// Base name is truncated to fit in length limits.
func (v *ClickHouseCluster) SpecificResourceName(suffix string) string {
return resourceName(v.SpecificName(), suffix)
}

// Shards returns requested number of shards in the ClickHouseCluster.
Expand Down Expand Up @@ -399,12 +405,12 @@ func (v *ClickHouseCluster) ReplicaIDs() iter.Seq[ClickHouseReplicaID] {

// HeadlessServiceName returns name of the headless service for the ClickHouseCluster.
func (v *ClickHouseCluster) HeadlessServiceName() string {
return v.SpecificName() + "-headless"
return v.SpecificResourceName("headless")
}

// PodDisruptionBudgetNameByShard returns name of the PodDisruptionBudget for the specific shard.
func (v *ClickHouseCluster) PodDisruptionBudgetNameByShard(shard int32) string {
return fmt.Sprintf("%s-%d", v.SpecificName(), shard)
return v.SpecificResourceName(strconv.FormatInt(int64(shard), 10))
}

// SecretName returns name of the Secret with cluster secret values.
Expand All @@ -414,17 +420,17 @@ func (v *ClickHouseCluster) SecretName() string {
return v.Spec.ExternalSecret.Name
}

return v.SpecificName()
return v.SpecificResourceName("")
}

// ConfigMapNameByReplicaID returns name of the ConfigMap for the specific replica.
func (v *ClickHouseCluster) ConfigMapNameByReplicaID(id ClickHouseReplicaID) string {
return fmt.Sprintf("%s-%d-%d", v.SpecificName(), id.ShardID, id.Index)
return v.SpecificResourceName(fmt.Sprintf("%d-%d", id.ShardID, id.Index))
}

// StatefulSetNameByReplicaID returns name of the StatefulSet for the specific replica.
func (v *ClickHouseCluster) StatefulSetNameByReplicaID(id ClickHouseReplicaID) string {
return fmt.Sprintf("%s-%d-%d", v.SpecificName(), id.ShardID, id.Index)
return v.SpecificResourceName(fmt.Sprintf("%d-%d", id.ShardID, id.Index))
}

// KeeperClusterNamespacedName returns the fully-qualified KeeperCluster reference.
Expand Down
25 changes: 22 additions & 3 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
"k8s.io/apimachinery/pkg/util/intstr"

util "github.com/ClickHouse/clickhouse-operator/internal/controllerutil"
)

// ContainerImage defines a container image with repository, tag or hash.
Expand Down Expand Up @@ -443,9 +445,26 @@ func (s *DefaultPasswordSelector) Validate() error {
return nil
}

// normalizeName removes dots from name to make it valid for use as a hostname or label value, where dots are not allowed.
func normalizeName(name string) string {
return strings.ReplaceAll(name, ".", "-")
const (
maxResourceNameLen = 63
hashLen = 4
)

// resourceName normalizes name and appends suffix, truncating the prefix to a max limit.
func resourceName(name, suffix string) string {
var fullName string
switch {
case len(suffix) == 0:
fullName = name
case len(name)+len(suffix)+1 <= maxResourceNameLen:
fullName = name + "-" + suffix
default:
hash := util.Sha256Hash([]byte(name))[:hashLen]
name = name[:maxResourceNameLen-len(suffix)-hashLen-2]
fullName = fmt.Sprintf("%s-%s-%s", name, hash, suffix)
}

return strings.ReplaceAll(fullName, ".", "-")
}

// formatPodHostname returns hostname for the first pod in the StatefulSet.
Expand Down
20 changes: 13 additions & 7 deletions api/v1alpha1/keepercluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,15 @@ func (v *KeeperClusterStatus) GetConditions() *[]metav1.Condition {
return &v.Conditions
}

// SpecificName returns cluster name with resource suffix. Used to generate resource names.
// SpecificName returns cluster name with role suffix.
func (v *KeeperCluster) SpecificName() string {
return normalizeName(v.Name) + "-keeper"
return v.Name + "-keeper"
}

// SpecificResourceName returns owned resource name with suffix.
// Base name is truncated to fit in length limits.
func (v *KeeperCluster) SpecificResourceName(suffix string) string {
return resourceName(v.SpecificName(), suffix)
}

// Replicas returns requested number of replicas in the cluster.
Expand All @@ -260,27 +266,27 @@ const (

// HeadlessServiceName returns headless service name for the Keeper cluster.
func (v *KeeperCluster) HeadlessServiceName() string {
return v.SpecificName() + "-headless"
return v.SpecificResourceName("headless")
}

// PodDisruptionBudgetName returns PodDisruptionBudget name for the Keeper cluster.
func (v *KeeperCluster) PodDisruptionBudgetName() string {
return v.SpecificName()
return v.SpecificResourceName("")
}

// QuorumConfigMapName returns ConfigMap name mounted in every replica.
func (v *KeeperCluster) QuorumConfigMapName() string {
return fmt.Sprintf("%s-quorum-%s-%d", v.SpecificName(), KeeperConfigMapNameSuffix, latestKeeperQuorumConfigMapVersion)
return v.SpecificResourceName(fmt.Sprintf("quorum-%s-%d", KeeperConfigMapNameSuffix, latestKeeperQuorumConfigMapVersion))
}

// ConfigMapNameByReplicaID returns ConfigMap name for given replica ID.
func (v *KeeperCluster) ConfigMapNameByReplicaID(replicaID KeeperReplicaID) string {
return fmt.Sprintf("%s-%d-%s-v%d", v.SpecificName(), replicaID, KeeperConfigMapNameSuffix, latestKeeperConfigMapVersion)
return v.SpecificResourceName(fmt.Sprintf("%d-%s-v%d", replicaID, KeeperConfigMapNameSuffix, latestKeeperConfigMapVersion))
}

// StatefulSetNameByReplicaID returns StatefulSet name for given replica ID.
func (v *KeeperCluster) StatefulSetNameByReplicaID(replicaID KeeperReplicaID) string {
return fmt.Sprintf("%s-%d", v.SpecificName(), replicaID)
return v.SpecificResourceName(fmt.Sprintf("%d", replicaID))
}

// HostnameByID returns domain name for the specific replica to access within Kubernetes cluster.
Expand Down
21 changes: 21 additions & 0 deletions api/v1alpha1/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,25 @@ var _ = Describe("ClickHouseCluster", func() {
Expect(hostname).To(Equal("test-clickhouse-0-1-0.test-clickhouse-headless.test-ns.svc.internal.corp.example.com"))
})
})

Describe("SpecificResourceName", func() {
It("should add suffix", func() {
cluster := ClickHouseCluster{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
Expect(cluster.SpecificResourceName("suffix")).To(HaveSuffix("-suffix"))
})

It("should replace forbidden chars", func() {
cluster := ClickHouseCluster{ObjectMeta: metav1.ObjectMeta{Name: "test.name"}}
Expect(cluster.SpecificResourceName("suffix")).To(Not(ContainSubstring(".")))
})

It("should truncate length", func() {
name := "very-very.long.resource-name.that-longer-than-limit"
suffix := "long-suffix-in-result"
cluster := ClickHouseCluster{ObjectMeta: metav1.ObjectMeta{Name: name}}
res := cluster.SpecificResourceName(suffix)
Expect(res).To(HaveLen(maxResourceNameLen), res)
Expect(res).To(HaveSuffix(suffix), res)
})
})
})
26 changes: 12 additions & 14 deletions internal/controller/resourcemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,23 @@ type Controller interface {
GetRecorder() events.EventRecorder
}

type clusterOwner interface {
client.Object
SpecificName() string
SpecificResourceName(suffix string) string
}

// ResourceManager provides Kubernetes resource modification helpers.
type ResourceManager struct {
ctrl Controller
owner client.Object
specificName string
ctrl Controller
owner clusterOwner
}

// NewResourceManager creates a new ResourceManager instance.
func NewResourceManager(
ctrl Controller,
owner interface {
client.Object
SpecificName() string
},
) ResourceManager {
func NewResourceManager(ctrl Controller, owner clusterOwner) ResourceManager {
return ResourceManager{
ctrl: ctrl,
owner: owner,
specificName: owner.SpecificName(),
ctrl: ctrl,
owner: owner,
}
}

Expand Down Expand Up @@ -435,7 +433,7 @@ func ListReplicaResources[
return nil, fmt.Errorf("ListReplicaResources: unsupported RList type %T", list)
}

if err := rm.ctrl.GetClient().List(ctx, list, util.AppRequirements(rm.owner.GetNamespace(), rm.specificName)); err != nil {
if err := rm.ctrl.GetClient().List(ctx, list, util.AppRequirements(rm.owner.GetNamespace(), rm.owner.SpecificName())); err != nil {
return nil, fmt.Errorf("list resources: %w", err)
}

Expand Down
6 changes: 3 additions & 3 deletions internal/controller/versionprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (rm *ResourceManager) cleanupVersionProbeJobs(ctx context.Context, log cont
var jobs batchv1.JobList

if err := cli.List(ctx, &jobs, client.InNamespace(rm.owner.GetNamespace()), client.MatchingLabels(map[string]string{
controllerutil.LabelAppKey: rm.specificName,
controllerutil.LabelAppKey: rm.owner.SpecificName(),
controllerutil.LabelRoleKey: controllerutil.LabelVersionProbe,
})); err != nil {
log.Warn("failed to list obsolete version probe jobs", "error", err)
Expand Down Expand Up @@ -310,11 +310,11 @@ func (rm *ResourceManager) buildVersionProbeJob(cfg VersionProbeConfig, revision
return batchv1.Job{}, fmt.Errorf("set version probe job controller reference: %w", err)
}

job.Name = fmt.Sprintf("%s-version-probe-%s", rm.specificName, revision[:8])
job.Name = rm.owner.SpecificResourceName("version-probe-" + revision[:8])

// Set reserved labels after overrides to ensure they are not modified by user overrides.
job.Labels = controllerutil.MergeMaps(job.Labels, map[string]string{
controllerutil.LabelAppKey: rm.specificName,
controllerutil.LabelAppKey: rm.owner.SpecificName(),
controllerutil.LabelRoleKey: controllerutil.LabelVersionProbe,
})

Expand Down
Loading