internal/resource/statefulset_utils.go (240 lines of code) (raw):
package resource
import (
"fmt"
. "git.jetbrains.team/tch/teamcity-operator/api/v1beta1"
v1 "k8s.io/api/apps/v1"
v12 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"strings"
)
const (
TEAMCITY_CONTAINER_NAME = "teamcity-server"
TEAMCITY_DB_USER_SECRET_KEY = "connectionProperties.user"
TEAMCITY_DB_PASSWORD_SECRET_KEY = "connectionProperties.password"
TEAMCITY_DB_URL_SECRET_KEY = "connectionUrl"
)
func CreateEmptyStatefulSet(name string, namespace string, labels map[string]string) v1.StatefulSet {
return v1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: labels,
},
Spec: v1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: v12.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: v12.PodSpec{
InitContainers: []v12.Container{},
Containers: []v12.Container{},
},
},
},
}
}
func DefaultEnvironmentVariableBuilder(nodeName string, xmxValue string, dataDirPath string, extraServerOpts string) []v12.EnvVar {
return []v12.EnvVar{
PodNameEnvVariableBuilder(),
PodNamespaceEnvVariableBuilder(),
DataDirPathEnvVar(dataDirPath),
LogDirPathEnvVar(dataDirPath),
ServerMemOptsEnvVar(xmxValue),
ServerOptsEnvVar(dataDirPath, nodeName, extraServerOpts),
}
}
func DataDirPathEnvVar(dataDirPath string) v12.EnvVar {
return v12.EnvVar{
Name: "TEAMCITY_DATA_PATH",
Value: fmt.Sprintf("%s", dataDirPath),
}
}
func LogDirPathEnvVar(dataDirPath string) v12.EnvVar {
return v12.EnvVar{
Name: "TEAMCITY_LOGS_PATH",
Value: fmt.Sprintf("%s%s", dataDirPath, "/logs"),
}
}
func ServerMemOptsEnvVar(xmxValue string) v12.EnvVar {
return v12.EnvVar{
Name: "TEAMCITY_SERVER_MEM_OPTS",
Value: fmt.Sprintf("%s%s", "-Xmx", xmxValue),
}
}
func ServerOptsEnvVar(dataDirPath string, nodeName string, extraServerOpts string) v12.EnvVar {
return v12.EnvVar{
Name: "TEAMCITY_SERVER_OPTS",
Value: "-XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC" +
fmt.Sprintf(" -XX:HeapDumpPath=%s%s%s", dataDirPath, "/memoryDumps/", nodeName) +
fmt.Sprintf(" -Dteamcity.server.nodeId=%s", nodeName) +
fmt.Sprintf(" -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE)") +
extraServerOpts,
}
}
func XmxValueCalculator(percentage int64, requestedMemoryValue int64) (xmxValue string) {
ratio := float64(percentage) / 100
xmxValue = resource.NewQuantity(int64(ratio*float64(requestedMemoryValue)), resource.DecimalSI).String()
return
}
func LifecycleOptionsBuilder() (lifecycle *v12.Lifecycle) {
lifecycle = &v12.Lifecycle{
PostStart: nil,
PreStop: &v12.LifecycleHandler{
Exec: &v12.ExecAction{
Command: []string{"/bin/sh", "-c", "/opt/teamcity/bin/shutdown.sh"},
},
HTTPGet: nil,
},
}
return
}
func ConvertStartUpPropertiesToServerOptions(startupProperties map[string]string) (res string) {
sortedKeys := SortKeysAlphabeticallyInMap(startupProperties)
for _, k := range sortedKeys {
res += fmt.Sprintf(" -D%s=%s", k, startupProperties[k])
}
return
}
func PodNameEnvVariableBuilder() v12.EnvVar {
return v12.EnvVar{
Name: "POD_NAME",
ValueFrom: &v12.EnvVarSource{
FieldRef: &v12.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
}
}
func PodNamespaceEnvVariableBuilder() v12.EnvVar {
return v12.EnvVar{
Name: "POD_NAMESPACE",
ValueFrom: &v12.EnvVarSource{
FieldRef: &v12.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
}
}
func BuildVolumesFromPersistentVolumeClaims(persistentVolumeClaims []CustomPersistentVolumeClaim) (volumes []v12.Volume) {
for _, claim := range persistentVolumeClaims {
volumes = append(volumes, createVolumeFromCustomPersistentVolumeClaim(claim))
}
return
}
func createVolumeFromCustomPersistentVolumeClaim(persistentVolumeClaim CustomPersistentVolumeClaim) v12.Volume {
return v12.Volume{Name: persistentVolumeClaim.Name,
VolumeSource: v12.VolumeSource{
PersistentVolumeClaim: &v12.PersistentVolumeClaimVolumeSource{
ClaimName: persistentVolumeClaim.Name},
},
}
}
func BuildVolumeMountsFromPersistentVolumeClaims(persistentVolumeClaims []CustomPersistentVolumeClaim) (volumeMounts []v12.VolumeMount) {
for _, claim := range persistentVolumeClaims {
volumeMounts = append(volumeMounts, createVolumeMountFromCustomPersistentVolumeClaim(claim))
}
return
}
func createVolumeMountFromCustomPersistentVolumeClaim(persistentVolumeClaim CustomPersistentVolumeClaim) v12.VolumeMount {
return v12.VolumeMount{Name: persistentVolumeClaim.VolumeMount.Name, MountPath: persistentVolumeClaim.VolumeMount.MountPath}
}
func ConfigureContainer(instance *TeamCity, node Node, container *v12.Container) {
container.Name = TEAMCITY_CONTAINER_NAME
container.Image = instance.Spec.Image
container.ImagePullPolicy = v12.PullIfNotPresent
container.Lifecycle = LifecycleOptionsBuilder()
container.LivenessProbe = &node.Spec.LivenessProbeSettings
container.ReadinessProbe = &node.Spec.ReadinessProbeSettings
container.StartupProbe = &node.Spec.StartupProbeSettings
container.Resources.Limits = node.Spec.Limits
container.Resources.Requests = node.Spec.Requests
container.Ports = []v12.ContainerPort{instance.Spec.TeamCityServerPort}
container.LivenessProbe.ProbeHandler.HTTPGet = &instance.Spec.ReadinessEndpoint
container.ReadinessProbe.ProbeHandler.HTTPGet = &instance.Spec.ReadinessEndpoint
container.StartupProbe.ProbeHandler.HTTPGet = &instance.Spec.HealthEndpoint
allPersistentVolumeClaims := instance.GetAllCustomPersistentVolumeClaim()
volumeMounts := BuildVolumeMountsFromPersistentVolumeClaims(allPersistentVolumeClaims)
container.VolumeMounts = volumeMounts
envVars := BuildEnvVariablesFromGlobalAndNodeSpecificSettings(instance, node)
container.Env = envVars
}
func ConfigureStatefulSet(instance *TeamCity, node Node, current *v1.StatefulSet) {
allPersistentVolumeClaims := instance.GetAllCustomPersistentVolumeClaim()
volumes := BuildVolumesFromPersistentVolumeClaims(allPersistentVolumeClaims)
current.Spec.Replicas = pointer.Int32(1)
current.Spec.Template.Annotations = node.Annotations
current.Spec.Template.Spec.Volumes = volumes
current.Spec.Template.Spec.InitContainers = node.Spec.InitContainers
current.Spec.Template.Spec.NodeSelector = node.Spec.NodeSelector
current.Spec.Template.Spec.Affinity = &node.Spec.Affinity
current.Spec.Template.Spec.SecurityContext = &node.Spec.PodSecurityContext
current.Spec.Template.Spec.ServiceAccountName = instance.Spec.ServiceAccount.Name
current.Spec.Template.Spec.DeprecatedServiceAccount = ""
}
func ConvertNodeEnvVars(env map[string]string) (envVars []v12.EnvVar) {
for k, v := range env {
var envVar = v12.EnvVar{
Name: k,
Value: v,
ValueFrom: nil,
}
envVars = append(envVars, envVar)
}
return envVars
}
func DatabaseEnvVarBuilder(databaseSecretName string) []v12.EnvVar {
return []v12.EnvVar{
{
Name: "TEAMCITY_DB_USER",
ValueFrom: &v12.EnvVarSource{
SecretKeyRef: &v12.SecretKeySelector{
LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName},
Key: TEAMCITY_DB_USER_SECRET_KEY,
},
},
},
{
Name: "TEAMCITY_DB_PASSWORD",
ValueFrom: &v12.EnvVarSource{
SecretKeyRef: &v12.SecretKeySelector{
LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName},
Key: TEAMCITY_DB_PASSWORD_SECRET_KEY,
},
},
},
{
Name: "TEAMCITY_DB_URL",
ValueFrom: &v12.EnvVarSource{
SecretKeyRef: &v12.SecretKeySelector{
LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName},
Key: TEAMCITY_DB_URL_SECRET_KEY,
},
},
},
}
}
func BuildEnvVariablesFromGlobalAndNodeSpecificSettings(instance *TeamCity, node Node) []v12.EnvVar {
dataDirPath := instance.DataDirPath()
extraServerOpts := ConvertStartUpPropertiesToServerOptions(instance.Spec.StartupPropertiesConfig)
var responsibilities string
if len(node.Spec.Responsibilities) > 0 {
responsibilities = ConvertResponsibilitiesToServerOptions(node.Spec.Responsibilities)
}
extraServerOpts = responsibilities + extraServerOpts
xmxValue := XmxValueCalculator(instance.Spec.XmxPercentage, node.Spec.Requests.Memory().Value())
envVars := DefaultEnvironmentVariableBuilder(node.Name, xmxValue, dataDirPath, extraServerOpts)
nodeSpecificEnvVars := ConvertNodeEnvVars(node.Spec.Env)
envVars = append(envVars, nodeSpecificEnvVars...)
if instance.DatabaseSecretProvided() {
databaseEnvVars := DatabaseEnvVarBuilder(instance.Spec.DatabaseSecret.Secret)
envVars = append(envVars, databaseEnvVars...)
}
return envVars
}
func ConvertResponsibilitiesToServerOptions(responsibilities []string) string {
stringResponsibilities := strings.Join(responsibilities, ",")
return fmt.Sprintf(" -Dteamcity.server.responsibilities=%s", stringResponsibilities)
}