cloudfoundry-incubator/eirini

View on GitHub
k8s/stset/lrp_to_statefulset.go

Summary

Maintainability
B
5 hrs
Test Coverage
A
100%
package stset

import (
    "strconv"

    "code.cloudfoundry.org/eirini"
    "code.cloudfoundry.org/eirini/api"
    "code.cloudfoundry.org/eirini/k8s/shared"
    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/api/resource"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

//counterfeiter:generate . ProbeCreator

const PodAffinityTermWeight = 100

type ProbeCreator func(lrp *api.LRP) *corev1.Probe

type LRPToStatefulSet struct {
    applicationServiceAccount         string
    registrySecretName                string
    allowAutomountServiceAccountToken bool
    allowRunImageAsRoot               bool
    latestMigration                   int
    livenessProbeCreator              ProbeCreator
    readinessProbeCreator             ProbeCreator
}

func NewLRPToStatefulSetConverter(
    applicationServiceAccount string,
    registrySecretName string,
    allowAutomountServiceAccountToken bool,
    allowRunImageAsRoot bool,
    latestMigration int,
    livenessProbeCreator ProbeCreator,
    readinessProbeCreator ProbeCreator,
) *LRPToStatefulSet {
    return &LRPToStatefulSet{
        applicationServiceAccount:         applicationServiceAccount,
        registrySecretName:                registrySecretName,
        allowAutomountServiceAccountToken: allowAutomountServiceAccountToken,
        allowRunImageAsRoot:               allowRunImageAsRoot,
        latestMigration:                   latestMigration,
        livenessProbeCreator:              livenessProbeCreator,
        readinessProbeCreator:             readinessProbeCreator,
    }
}

func (c *LRPToStatefulSet) Convert(statefulSetName string, lrp *api.LRP, privateRegistrySecret *corev1.Secret) (*appsv1.StatefulSet, error) {
    envs := shared.MapToEnvVar(lrp.Env)
    fieldEnvs := []corev1.EnvVar{
        {
            Name: eirini.EnvPodName,
            ValueFrom: &corev1.EnvVarSource{
                FieldRef: &corev1.ObjectFieldSelector{
                    FieldPath: "metadata.name",
                },
            },
        },
        {
            Name: eirini.EnvCFInstanceGUID,
            ValueFrom: &corev1.EnvVarSource{
                FieldRef: &corev1.ObjectFieldSelector{
                    FieldPath: "metadata.uid",
                },
            },
        },
        {
            Name: eirini.EnvCFInstanceIP,
            ValueFrom: &corev1.EnvVarSource{
                FieldRef: &corev1.ObjectFieldSelector{
                    FieldPath: "status.hostIP",
                },
            },
        },
        {
            Name: eirini.EnvCFInstanceInternalIP,
            ValueFrom: &corev1.EnvVarSource{
                FieldRef: &corev1.ObjectFieldSelector{
                    FieldPath: "status.podIP",
                },
            },
        },
    }

    envs = append(envs, fieldEnvs...)
    ports := []corev1.ContainerPort{}

    for _, port := range lrp.Ports {
        ports = append(ports, corev1.ContainerPort{ContainerPort: port})
    }

    livenessProbe := c.livenessProbeCreator(lrp)
    readinessProbe := c.readinessProbeCreator(lrp)

    volumes, volumeMounts := getVolumeSpecs(lrp.VolumeMounts)
    allowPrivilegeEscalation := false
    imagePullSecrets := c.calculateImagePullSecrets(privateRegistrySecret)

    containers := []corev1.Container{
        {
            Name:            ApplicationContainerName,
            Image:           lrp.Image,
            ImagePullPolicy: corev1.PullAlways,
            Command:         lrp.Command,
            Env:             envs,
            Ports:           ports,
            SecurityContext: &corev1.SecurityContext{
                AllowPrivilegeEscalation: &allowPrivilegeEscalation,
            },
            Resources:      getContainerResources(lrp.CPUWeight, lrp.MemoryMB, lrp.DiskMB),
            LivenessProbe:  livenessProbe,
            ReadinessProbe: readinessProbe,
            VolumeMounts:   volumeMounts,
        },
    }

    sidecarContainers := getSidecarContainers(lrp)
    containers = append(containers, sidecarContainers...)
    statefulSet := &appsv1.StatefulSet{
        ObjectMeta: metav1.ObjectMeta{
            Name: statefulSetName,
        },
        Spec: appsv1.StatefulSetSpec{
            PodManagementPolicy: "Parallel",
            Replicas:            int32ptr(lrp.TargetInstances),
            Template: corev1.PodTemplateSpec{
                Spec: corev1.PodSpec{
                    Containers:         containers,
                    ImagePullSecrets:   imagePullSecrets,
                    SecurityContext:    c.getGetSecurityContext(lrp),
                    ServiceAccountName: c.applicationServiceAccount,
                    Volumes:            volumes,
                },
            },
        },
    }

    if !c.allowAutomountServiceAccountToken {
        automountServiceAccountToken := false
        statefulSet.Spec.Template.Spec.AutomountServiceAccountToken = &automountServiceAccountToken
    }

    statefulSet.Spec.Selector = StatefulSetLabelSelector(lrp)

    statefulSet.Spec.Template.Spec.Affinity = &corev1.Affinity{
        PodAntiAffinity: &corev1.PodAntiAffinity{
            PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
                {
                    Weight: PodAffinityTermWeight,
                    PodAffinityTerm: corev1.PodAffinityTerm{
                        TopologyKey: corev1.LabelHostname,
                        LabelSelector: &metav1.LabelSelector{
                            MatchExpressions: toLabelSelectorRequirements(statefulSet.Spec.Selector),
                        },
                    },
                },
            },
        },
    }

    labels := map[string]string{
        LabelOrgGUID:     lrp.OrgGUID,
        LabelOrgName:     lrp.OrgName,
        LabelSpaceGUID:   lrp.SpaceGUID,
        LabelSpaceName:   lrp.SpaceName,
        LabelGUID:        lrp.GUID,
        LabelProcessType: lrp.ProcessType,
        LabelVersion:     lrp.Version,
        LabelAppGUID:     lrp.AppGUID,
        LabelSourceType:  AppSourceType,
    }

    statefulSet.Spec.Template.Labels = labels
    statefulSet.Labels = labels

    annotations := map[string]string{
        AnnotationSpaceName:              lrp.SpaceName,
        AnnotationSpaceGUID:              lrp.SpaceGUID,
        AnnotationOriginalRequest:        lrp.LRP,
        AnnotationAppID:                  lrp.AppGUID,
        AnnotationVersion:                lrp.Version,
        AnnotationLastUpdated:            lrp.LastUpdated,
        AnnotationProcessGUID:            lrp.ProcessGUID(),
        AnnotationAppName:                lrp.AppName,
        AnnotationOrgName:                lrp.OrgName,
        AnnotationOrgGUID:                lrp.OrgGUID,
        shared.AnnotationLatestMigration: strconv.Itoa(c.latestMigration),
    }

    for k, v := range lrp.UserDefinedAnnotations {
        annotations[k] = v
    }

    statefulSet.Annotations = annotations
    statefulSet.Spec.Template.Annotations = annotations
    statefulSet.Spec.Template.Annotations[corev1.SeccompPodAnnotationKey] = corev1.SeccompProfileRuntimeDefault

    return statefulSet, nil
}

func (c *LRPToStatefulSet) calculateImagePullSecrets(privateRegistrySecret *corev1.Secret) []corev1.LocalObjectReference {
    imagePullSecrets := []corev1.LocalObjectReference{
        {Name: c.registrySecretName},
    }

    if privateRegistrySecret != nil {
        imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{
            Name: privateRegistrySecret.Name,
        })
    }

    return imagePullSecrets
}

func (c *LRPToStatefulSet) getGetSecurityContext(lrp *api.LRP) *corev1.PodSecurityContext {
    if c.allowRunImageAsRoot {
        return nil
    }

    runAsNonRoot := true

    return &corev1.PodSecurityContext{
        RunAsNonRoot: &runAsNonRoot,
    }
}

func getVolumeSpecs(lrpVolumeMounts []api.VolumeMount) ([]corev1.Volume, []corev1.VolumeMount) {
    volumes := []corev1.Volume{}
    volumeMounts := []corev1.VolumeMount{}

    for _, vm := range lrpVolumeMounts {
        volumes = append(volumes, corev1.Volume{
            Name: vm.ClaimName,
            VolumeSource: corev1.VolumeSource{
                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
                    ClaimName: vm.ClaimName,
                },
            },
        })

        volumeMounts = append(volumeMounts, corev1.VolumeMount{
            Name:      vm.ClaimName,
            MountPath: vm.MountPath,
        })
    }

    return volumes, volumeMounts
}

func getContainerResources(cpuWeight uint8, memoryMB, diskMB int64) corev1.ResourceRequirements {
    memory := *resource.NewScaledQuantity(memoryMB, resource.Mega)
    cpu := toCPUMillicores(cpuWeight)
    ephemeralStorage := *resource.NewScaledQuantity(diskMB, resource.Mega)

    return corev1.ResourceRequirements{
        Limits: corev1.ResourceList{
            corev1.ResourceMemory:           memory,
            corev1.ResourceEphemeralStorage: ephemeralStorage,
        },
        Requests: corev1.ResourceList{
            corev1.ResourceMemory: memory,
            corev1.ResourceCPU:    cpu,
        },
    }
}

func toCPUMillicores(cpuPercentage uint8) resource.Quantity {
    return *resource.NewScaledQuantity(int64(cpuPercentage), resource.Milli)
}

func getSidecarContainers(lrp *api.LRP) []corev1.Container {
    containers := []corev1.Container{}

    for _, s := range lrp.Sidecars {
        c := corev1.Container{
            Name:      s.Name,
            Command:   s.Command,
            Image:     lrp.Image,
            Env:       shared.MapToEnvVar(s.Env),
            Resources: getContainerResources(lrp.CPUWeight, s.MemoryMB, lrp.DiskMB),
        }
        containers = append(containers, c)
    }

    return containers
}

func int32ptr(i int) *int32 {
    u := int32(i)

    return &u
}

func toLabelSelectorRequirements(selector *metav1.LabelSelector) []metav1.LabelSelectorRequirement {
    labels := selector.MatchLabels
    reqs := make([]metav1.LabelSelectorRequirement, 0, len(labels))

    for label, value := range labels {
        reqs = append(reqs, metav1.LabelSelectorRequirement{
            Key:      label,
            Operator: metav1.LabelSelectorOpIn,
            Values:   []string{value},
        })
    }

    return reqs
}

func StatefulSetLabelSelector(lrp *api.LRP) *metav1.LabelSelector {
    return &metav1.LabelSelector{
        MatchLabels: map[string]string{
            LabelGUID:       lrp.GUID,
            LabelVersion:    lrp.Version,
            LabelSourceType: AppSourceType,
        },
    }
}