diff --git a/Makefile b/Makefile index 7fbcd9080..dfc0dc763 100644 --- a/Makefile +++ b/Makefile @@ -115,9 +115,7 @@ functests: hack/run-functests.sh unittests: - # functests are marked with "// +build !unittests" and will be skipped - GOFLAGS=-mod=vendor go test -v --tags unittests ./... - #TODO - copy in unit tests + GOFLAGS=-mod=vendor go test -v ./pkg/... gofmt: @echo "Running gofmt" diff --git a/build/Dockerfile b/build/Dockerfile index f4180efd7..9ef6a5338 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -2,5 +2,5 @@ FROM registry.access.redhat.com/ubi8/ubi-minimal USER nobody +COPY assets /assets ADD _output/bin/performance-addon-operators /usr/local/bin/performance-operator - diff --git a/build/assets/scripts/pre-boot-tuning.sh b/build/assets/scripts/pre-boot-tuning.sh new file mode 100755 index 000000000..f3ece624e --- /dev/null +++ b/build/assets/scripts/pre-boot-tuning.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +set -euo pipefail + +non_iso_cpumask="" +cpu_affinity="" + +get_reserved_cores() { + cores=() + while read part; do + if [[ $part =~ - ]]; then + cores+=($(seq ${part/-/ })) + elif [[ $part =~ , ]]; then + continue + else + cores+=($part) + fi + done < <( echo ${NON_ISOLATED_CPUS} | tr ',' '\n' ) +} + +# $1 - 0 for irq balance banned cpus masking , 1 for non isolated cpus masking +get_cpu_mask() { + if [ "$1" = "1" ]; then + mask=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ) + else + mask=( 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ) + fi + get_reserved_cores + for core in ${cores[*]}; do + mask[$core]=$1 + done + cpumaskBinary=`echo ${mask[@]}| rev` + cpumaskBinary=${cpumaskBinary//[[:space:]]/} + non_iso_cpumask=`printf '%08x\n' "$((2#$cpumaskBinary))"` +} + +get_cpu_affinity() { + cpu_affinity="" + get_reserved_cores + for core in ${cores[*]}; do + cpu_affinity+=" $core" + done + echo "CPU Affinity set to $cpu_affinity" +} + +# TODO - find a more robust approach than keeping the last timestamp +RHCOS_OSTREE_PATH=$(ls -td /boot/ostree/*/ | head -1) +RHCOS_OSTREE_BOOTLOADER_PATH=${RHCOS_OSTREE_PATH#"/boot"} +INITRD_GENERATION_DIR="/root/initrd" +INITRD_NEW_IMAGE="${RHCOS_OSTREE_PATH}/iso_initrd.img" + +# TODO: improve check for applied configuration +if [ -f ${INITRD_NEW_IMAGE} ] && grep -qsR "iso_initrd.img" "/boot/loader/entries/"; then + echo "Pre boot tuning configuration already applied" + echo "Setting kernel rcuo* threads to the housekeeping cpus" + get_cpu_mask 1 + pgrep rcuo* | while read line; do taskset -p $non_iso_cpumask $line || true; done +else + # Clean up + rm -rf ${INITRD_GENERATION_DIR} + + # Create initrd image + mkdir ${INITRD_GENERATION_DIR} + mkdir -p ${INITRD_GENERATION_DIR}/usr/lib/dracut/hooks/pre-udev/ + mkdir -p ${INITRD_GENERATION_DIR}/etc/systemd/ + mkdir -p ${INITRD_GENERATION_DIR}/etc/sysconfig/ + touch ${INITRD_GENERATION_DIR}/etc/systemd/system.conf + touch ${INITRD_GENERATION_DIR}/etc/sysconfig/irqbalance + touch ${INITRD_GENERATION_DIR}/usr/lib/dracut/hooks/pre-udev/00-tuned-pre-udev.sh + chmod +x ${INITRD_GENERATION_DIR}/usr/lib/dracut/hooks/pre-udev/00-tuned-pre-udev.sh + + get_cpu_mask 1 + echo '#!/bin/sh + + type getargs >/dev/null 2>&1 || . /lib/dracut-lib.sh + + #cpumask="$(getargs non_iso_cpumask)" + cpumask='$non_iso_cpumask' + + log() + { + echo "tuned: $@" >> /dev/kmsg + } + + if [ -n "$cpumask" ]; then + for file in /sys/devices/virtual/workqueue/cpumask /sys/bus/workqueue/devices/writeback/cpumask; do + log "setting $file CPU mask to $cpumask" + if ! echo $cpumask > $file 2>/dev/null; then + log "ERROR: could not write CPU mask for $file" + fi + done + fi' > ${INITRD_GENERATION_DIR}/usr/lib/dracut/hooks/pre-udev/00-tuned-pre-udev.sh + + # Set CPU affinity according to NON_ISOLATED_CPUS + get_cpu_affinity + echo "[Manager]" >> ${INITRD_GENERATION_DIR}/etc/systemd/system.conf + echo "CPUAffinity=$cpu_affinity" >> ${INITRD_GENERATION_DIR}/etc/systemd/system.conf + + # Set IRQ banned cpu according to NON_ISOLATED_CPUS + get_cpu_mask 0 + echo "IRQBALANCE_BANNED_CPUS=$non_iso_cpumask" >> ${INITRD_GENERATION_DIR}/etc/sysconfig/irqbalance + + find ${INITRD_GENERATION_DIR} | cpio -co >${INITRD_NEW_IMAGE} + + # Get current ostree config file according to the latest version + current_ver=1 + entry_file=$(ls -td /boot/loader/entries/* | head -1) + while read -r line ; do + ver=`awk '/version/ {print $2}' $line` + if [ "$ver" -gt "$current_ver" ]; then + current_ver=$ver + entry_file=$line + fi + done <<<$(egrep $(uname -r) -lr /boot/loader/entries/) + + sed -i "s^initrd .*\$^& ${RHCOS_OSTREE_BOOTLOADER_PATH}iso_initrd.img^" $entry_file + + #TODO - once RHCOS image contains the initrd content we can set parameters with rpm-ostree: + #rpm-ostree initramfs --enable --arg=-I --arg=/etc/systemd/system.conf + #rpm-ostree initramfs --enable --arg=-I --arg=/etc/sysconfig/irqbalance + + touch /var/reboot +fi diff --git a/build/assets/scripts/reboot.sh b/build/assets/scripts/reboot.sh new file mode 100644 index 000000000..7c66820bd --- /dev/null +++ b/build/assets/scripts/reboot.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ -f /var/reboot ]]; then + rm -f /var/reboot + echo "File /var/reboot exists, initiate reboot" + systemctl reboot +fi diff --git a/build/assets/scripts/rt-kernel.sh b/build/assets/scripts/rt-kernel.sh new file mode 100644 index 000000000..02aff8725 --- /dev/null +++ b/build/assets/scripts/rt-kernel.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_DIR="/etc/yum.repos.d" +RT_REPO="${REPO_DIR}/rt-kernel.repo" + +# Enable yum repo +if [[ -f $RT_REPO ]] +then + # The env var might have been changed, so always create new rt repo + rm $RT_REPO +fi + +mkdir -p $REPO_DIR +cat > $RT_REPO < github.com/go-log/log v0.1.0 github.com/openshift/api => github.com/openshift/api v0.0.0-20191220175332-378bec237e34 // release-4.4 github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20191205152420-9faca5198b4f // release-4.4 + github.com/openshift/cluster-node-tuning-operator => github.com/openshift/cluster-node-tuning-operator v0.0.0-20191217222311-500135cb8754 // release-4.4 github.com/openshift/machine-config-operator => github.com/openshift/machine-config-operator v0.0.0-20191220033234-347a7a09e869 // release-4.4 golang.org/x/tools => golang.org/x/tools v0.0.0-20191206213732-070c9d21b343 ) diff --git a/go.sum b/go.sum index e00c897a2..df7de9221 100644 --- a/go.sum +++ b/go.sum @@ -530,6 +530,7 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kevinburke/go-bindata v3.16.0+incompatible/go.mod h1:/pEEZ72flUW2p0yi30bslSp9YqD9pysLxunQDdb2CPM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= @@ -684,9 +685,13 @@ github.com/openshift/client-go v0.0.0-20191205152420-9faca5198b4f h1:1ak9jgsR7+v github.com/openshift/client-go v0.0.0-20191205152420-9faca5198b4f/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk= github.com/openshift/cluster-api v0.0.0-20190923092624-4024de4fa64d/go.mod h1:mNsD1dsD4T57kV4/C6zTHke/Ro166xgnyyRZqkamiEU= github.com/openshift/cluster-etcd-operator v0.0.0-alpha.0.0.20191025163650-5854b5c48ce4/go.mod h1:vcBAUefK8pQmTPQ3jlCemORXhnRnq3aTsfOSVeaWliY= +github.com/openshift/cluster-node-tuning-operator v0.0.0-20191217222311-500135cb8754 h1:3rqk/tZZahKAjuAhRyfDWZbjH9udCtfXnWc9sSOYuNs= +github.com/openshift/cluster-node-tuning-operator v0.0.0-20191217222311-500135cb8754/go.mod h1:vfr0XwZQai3/NJgHoHFWFXLo+qKboxg5AinYlyx0lZ4= +github.com/openshift/crd-schema-gen v1.0.0/go.mod h1:jTmSmtfJzK2emb3ucPkHqvoOe//PuNhR3aBiUBbg/rc= github.com/openshift/imagebuilder v1.1.1/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/openshift/library-go v0.0.0-20190619114638-6b58b672ee58/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= github.com/openshift/library-go v0.0.0-20191003152030-97c62d8a2901/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= +github.com/openshift/library-go v0.0.0-20191024144423-664354b88b39/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= github.com/openshift/machine-config-operator v0.0.0-20191220033234-347a7a09e869 h1:CAPIebw+Q76kRPY0k4LXzJNTYgr6eYcH6MCynx7vB/c= github.com/openshift/machine-config-operator v0.0.0-20191220033234-347a7a09e869/go.mod h1:0IIYHSoQ7nLsMoPB29Z/N9KMh/PpywcJMCdoWtRIY9E= github.com/openshift/origin v0.0.0-20160503220234-8f127d736703/go.mod h1:0Rox5r9C8aQn6j1oAOQ0c1uC86mYbUFObzjBRvUKHII= diff --git a/pkg/apis/performance/v1alpha1/performanceprofile_types.go b/pkg/apis/performance/v1alpha1/performanceprofile_types.go index 05bbe7d9a..319025097 100644 --- a/pkg/apis/performance/v1alpha1/performanceprofile_types.go +++ b/pkg/apis/performance/v1alpha1/performanceprofile_types.go @@ -44,15 +44,15 @@ type HugePages struct { // HugePage defines the number of allocated huge pages of the specific size. type HugePage struct { // Size defines huge page size, maps to the 'hugepagesz' kernel boot parameter. - Size *HugePageSize `json:"size,omitempty"` + Size HugePageSize `json:"size,omitempty"` // Count defines amount of huge pages, maps to the 'hugepages' kernel boot parameter. - Count *int32 `json:"count,omitempty"` + Count int32 `json:"count,omitempty"` } // RealTimeKernel defines the set of parameters relevant for the real time kernel. type RealTimeKernel struct { - // Enabled enables real time kernel on relevant nodes. - Enabled *bool `json:"enabled,omitempty"` + // RepoURL defines the URL to the repository with real time kernel packages + RepoURL *string `json:"repoURL,omitempty"` } // PerformanceProfileStatus defines the observed state of PerformanceProfile. diff --git a/pkg/apis/performance/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/performance/v1alpha1/zz_generated.deepcopy.go index 5cf967b0a..33638f2cf 100644 --- a/pkg/apis/performance/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/performance/v1alpha1/zz_generated.deepcopy.go @@ -42,16 +42,6 @@ func (in *CPU) DeepCopy() *CPU { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HugePage) DeepCopyInto(out *HugePage) { *out = *in - if in.Size != nil { - in, out := &in.Size, &out.Size - *out = new(HugePageSize) - **out = **in - } - if in.Count != nil { - in, out := &in.Count, &out.Count - *out = new(int32) - **out = **in - } return } @@ -76,9 +66,7 @@ func (in *HugePages) DeepCopyInto(out *HugePages) { if in.Pages != nil { in, out := &in.Pages, &out.Pages *out = make([]HugePage, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + copy(*out, *in) } return } @@ -211,9 +199,9 @@ func (in *PerformanceProfileStatus) DeepCopy() *PerformanceProfileStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RealTimeKernel) DeepCopyInto(out *RealTimeKernel) { *out = *in - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) + if in.RepoURL != nil { + in, out := &in.RepoURL, &out.RepoURL + *out = new(string) **out = **in } return diff --git a/pkg/controller/performanceprofile/components/consts.go b/pkg/controller/performanceprofile/components/consts.go new file mode 100644 index 000000000..455c57d83 --- /dev/null +++ b/pkg/controller/performanceprofile/components/consts.go @@ -0,0 +1,30 @@ +package components + +const ( + // AssetsDir defines the directory with assets under the operator image + AssetsDir = "/assets" +) +const ( + // LabelMachineConfigurationRole defines the label for machine configuration role + LabelMachineConfigurationRole = "machineconfiguration.openshift.io/role" + // LableMachineConfigPoolRole defines the label for machine config pool role + LableMachineConfigPoolRole = "machineconfigpool.openshift.io/role" + // RoleWorker defines the worker role + RoleWorker = "worker" + // RoleWorkerPerformance defines the worker role for performance sensitive workflows + RoleWorkerPerformance = "worker-performance" +) + +const ( + // NamespaceNodeTuningOperator defines the tuned profiles namespace + NamespaceNodeTuningOperator = "openshift-cluster-node-tuning-operator" + // ProfileNameNetworkLatency defines the network latency tuned profile name + ProfileNameNetworkLatency = "openshift-node-network-latency" + // ProfileNameWorkerRT defines the real time kernel performance tuned profile name + ProfileNameWorkerRT = "openshift-node-real-time-kernel" +) + +const ( + // FeatureGateLatencySensetiveName defines the latency sensetive feature gate name + FeatureGateLatencySensetiveName = "latency-sensetive" +) \ No newline at end of file diff --git a/pkg/controller/performanceprofile/components/featuregate/featuregate.go b/pkg/controller/performanceprofile/components/featuregate/featuregate.go new file mode 100644 index 000000000..792740966 --- /dev/null +++ b/pkg/controller/performanceprofile/components/featuregate/featuregate.go @@ -0,0 +1,26 @@ +package featuregate + +import ( + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + configv1 "github.com/openshift/api/config/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NewLatencySensitive returns new latency sensetive feature gate object +func NewLatencySensitive() *configv1.FeatureGate { + return &configv1.FeatureGate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: configv1.GroupVersion.String(), + Kind: "FeatureGate", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: components.FeatureGateLatencySensetiveName, + }, + Spec: configv1.FeatureGateSpec{ + FeatureGateSelection: configv1.FeatureGateSelection{ + FeatureSet: configv1.LatencySensitive, + }, + }, + } +} diff --git a/pkg/controller/performanceprofile/components/featuregate/featuregate_suite_test.go b/pkg/controller/performanceprofile/components/featuregate/featuregate_suite_test.go new file mode 100644 index 000000000..a3d87a09a --- /dev/null +++ b/pkg/controller/performanceprofile/components/featuregate/featuregate_suite_test.go @@ -0,0 +1,13 @@ +package featuregate + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFeatureGate(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Feature Gate Suite") +} diff --git a/pkg/controller/performanceprofile/components/featuregate/featuregate_test.go b/pkg/controller/performanceprofile/components/featuregate/featuregate_test.go new file mode 100644 index 000000000..7f533cd8c --- /dev/null +++ b/pkg/controller/performanceprofile/components/featuregate/featuregate_test.go @@ -0,0 +1,16 @@ +package featuregate + +import ( + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Feature Gate", func() { + It("should generate yaml with 'LatencySensitive' feature set", func() { + fg := NewLatencySensitive() + y, err := yaml.Marshal(fg) + Expect(err).ToNot(HaveOccurred()) + Expect(string(y)).To(ContainSubstring("featureSet: LatencySensitive")) + }) +}) diff --git a/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig.go b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig.go new file mode 100644 index 000000000..eb257bdec --- /dev/null +++ b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig.go @@ -0,0 +1,64 @@ +package kubeletconfig + +import ( + "time" + + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + machineconfigv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" +) + +const ( + cpuManagerPolicyStatic = "static" + defaultKubeReservedCPU = "1000m" + defaultKubeReservedMemory = "500Mi" + defaultSystemReservedCPU = "1000m" + defaultSystemReservedMemory = "500Mi" + topologyManagerPolicyBestEffort = "best-effort" +) + +// NewPerformance returns new KubeletConfig object for performance sensetive workflows +func NewPerformance(profile *performancev1alpha1.PerformanceProfile) *machineconfigv1.KubeletConfig { + name := components.GetComponentName(profile.Name, components.RoleWorkerPerformance) + kubeletConfig := &kubeletconfigv1beta1.KubeletConfiguration{ + CPUManagerPolicy: cpuManagerPolicyStatic, + CPUManagerReconcilePeriod: metav1.Duration{Duration: 5 * time.Second}, + TopologyManagerPolicy: topologyManagerPolicyBestEffort, + KubeReserved: map[string]string{ + "cpu": defaultKubeReservedCPU, + "memory": defaultKubeReservedMemory, + }, + SystemReserved: map[string]string{ + "cpu": defaultSystemReservedCPU, + "memory": defaultSystemReservedMemory, + }, + } + + if profile.Spec.CPU != nil && profile.Spec.CPU.Reserved != nil { + kubeletConfig.ReservedSystemCPUs = string(*profile.Spec.CPU.Reserved) + } + + return &machineconfigv1.KubeletConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: machineconfigv1.GroupVersion.String(), + Kind: "KubeletConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: machineconfigv1.KubeletConfigSpec{ + MachineConfigPoolSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + components.LableMachineConfigPoolRole: name, + }, + }, + KubeletConfig: &runtime.RawExtension{ + Object: kubeletConfig, + }, + }, + } +} diff --git a/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_suite_test.go b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_suite_test.go new file mode 100644 index 000000000..9eb3d5245 --- /dev/null +++ b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_suite_test.go @@ -0,0 +1,13 @@ +package kubeletconfig + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFeatureGate(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Kubelet Config Suite") +} diff --git a/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_test.go b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_test.go new file mode 100644 index 000000000..7ee103de4 --- /dev/null +++ b/pkg/controller/performanceprofile/components/kubeletconfig/kubeletconfig_test.go @@ -0,0 +1,27 @@ +package kubeletconfig + +import ( + "fmt" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + testutils "github.com/openshift-kni/performance-addon-operators/pkg/utils/testing" +) + +var _ = Describe("Kubelet Config", func() { + It("should generate yaml with expected parameters", func() { + profile := testutils.NewPerformanceProfile("test") + kc := NewPerformance(profile) + + y, err := yaml.Marshal(kc) + Expect(err).ToNot(HaveOccurred()) + + manifest := string(y) + Expect(manifest).To(ContainSubstring(fmt.Sprintf("%s: %s", components.LableMachineConfigPoolRole, components.RoleWorkerPerformance))) + Expect(manifest).To(ContainSubstring("reservedSystemCPUs: 0-3")) + Expect(manifest).To(ContainSubstring("topologyManagerPolicy: best-effort")) + Expect(manifest).To(ContainSubstring("cpuManagerPolicy: static")) + }) +}) diff --git a/pkg/controller/performanceprofile/components/machineconfig/machineconfig.go b/pkg/controller/performanceprofile/components/machineconfig/machineconfig.go new file mode 100644 index 000000000..e5c0b13dd --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfig/machineconfig.go @@ -0,0 +1,288 @@ +package machineconfig + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + + "github.com/coreos/go-systemd/unit" + igntypes "github.com/coreos/ignition/config/v2_2/types" + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + machineconfigv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" +) + +const ( + defaultIgnitionVersion = "2.2.0" + defaultFileSystem = "root" + defaultIgnitionContentSource = "data:text/plain;charset=utf-8;base64" +) + +const ( + rtKernel = "rt-kernel" + preBootTuning = "pre-boot-tuning" + reboot = "reboot" + bashScriptsDir = "/usr/local/bin" +) + +const ( + systemdSectionUnit = "Unit" + systemdSectionService = "Service" + systemdSectionInstall = "Install" + systemdDescription = "Description" + systemdWants = "Wants" + systemdAfter = "After" + systemdBefore = "Before" + systemdEnvironment = "Environment" + systemdType = "Type" + systemdRemainAfterExit = "RemainAfterExit" + systemdExecStart = "ExecStart" + systemdWantedBy = "WantedBy" +) + +const ( + systemdServiceKubelet = "kubelet.service" + systemdServiceTypeOneshot = "oneshot" + systemdTargetMultiUser = "multi-user.target" + systemdTargetNetworkOnline = "network-online.target" + systemdTrue = "true" +) + +const ( + environmentRTRepoURL = "RT_REPO_URL" + environmentNonIsolatedCpus = "NON_ISOLATED_CPUS" +) + +// NewPerformance returns new machine configuration object for performance sensetive workflows +func NewPerformance(assetsDir string, profile *performancev1alpha1.PerformanceProfile) (*machineconfigv1.MachineConfig, error) { + name := components.GetComponentName(profile.Name, components.RoleWorkerPerformance) + mc := &machineconfigv1.MachineConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: machineconfigv1.GroupVersion.String(), + Kind: "MachineConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + components.LabelMachineConfigurationRole: name, + }, + }, + Spec: machineconfigv1.MachineConfigSpec{}, + } + + ignitionConfig, err := getIgnitionConfig(assetsDir, *profile.Spec.RealTimeKernel.RepoURL, profile.Spec.CPU.NonIsolated) + if err != nil { + return nil, err + } + + mc.Spec.Config = *ignitionConfig + mc.Spec.KernelArguments = getKernelArgs(profile.Spec.HugePages, profile.Spec.CPU.Isolated) + + return mc, nil +} + +func getKernelArgs(hugePages *performancev1alpha1.HugePages, isolatedCPUs *performancev1alpha1.CPUSet) []string { + kargs := []string{ + "nohz=on", + "nosoftlockup", + "nmi_watchdog=0", + "audit=0", + "mce=off", + "irqaffinity=0", + "skew_tick=1", + "processor.max_cstate=1", + "idle=poll", + "intel_pstate=disable", + "intel_idle.max_cstate=0", + "intel_iommu=on", + "iommu=pt", + } + + if isolatedCPUs != nil { + kargs = append(kargs, fmt.Sprintf("isolcpus=%s", string(*isolatedCPUs))) + } + + if hugePages != nil { + if hugePages.DefaultHugePagesSize != nil { + kargs = append(kargs, fmt.Sprintf("default_hugepagesz=%s", string(*hugePages.DefaultHugePagesSize))) + } + + for _, page := range hugePages.Pages { + kargs = append(kargs, fmt.Sprintf("hugepagesz=%s", string(page.Size))) + kargs = append(kargs, fmt.Sprintf("hugepages=%d", page.Count)) + } + } + return kargs +} + +func getIgnitionConfig(assetsDir string, rtRepoURL string, nonIsolatedCpus *performancev1alpha1.CPUSet) (*igntypes.Config, error) { + mode := 0700 + ignitionConfig := &igntypes.Config{ + Ignition: igntypes.Ignition{ + Version: defaultIgnitionVersion, + }, + Storage: igntypes.Storage{ + Files: []igntypes.File{}, + }, + } + + for _, script := range []string{preBootTuning, reboot, rtKernel} { + content, err := ioutil.ReadFile(fmt.Sprintf("%s/scripts/%s.sh", assetsDir, script)) + if err != nil { + return nil, err + } + contentBase64 := base64.StdEncoding.EncodeToString(content) + ignitionConfig.Storage.Files = append(ignitionConfig.Storage.Files, igntypes.File{ + Node: igntypes.Node{ + Filesystem: defaultFileSystem, + Path: getBashScriptPath(script), + }, + FileEmbedded1: igntypes.FileEmbedded1{ + Contents: igntypes.FileContents{ + Source: fmt.Sprintf("%s,%s", defaultIgnitionContentSource, contentBase64), + }, + Mode: &mode, + }, + }) + } + + rtKernelService, err := getSystemdContent(getRTKernelUnitOptions(rtRepoURL)) + if err != nil { + return nil, err + } + + preBootTuningService, err := getSystemdContent( + getPreBootTuningUnitOptions(string(*nonIsolatedCpus)), + ) + if err != nil { + return nil, err + } + + rebootService, err := getSystemdContent(getRebootUnitOptions()) + if err != nil { + return nil, err + } + + ignitionConfig.Systemd = igntypes.Systemd{ + Units: []igntypes.Unit{ + { + Contents: rtKernelService, + Enabled: pointer.BoolPtr(true), + Name: getSystemdService(rtKernel), + }, + { + Contents: preBootTuningService, + Enabled: pointer.BoolPtr(true), + Name: getSystemdService(preBootTuning), + }, + { + Contents: rebootService, + Enabled: pointer.BoolPtr(true), + Name: getSystemdService(reboot), + }, + }, + } + return ignitionConfig, nil +} + +func getBashScriptPath(scriptName string) string { + return fmt.Sprintf("%s/%s.sh", bashScriptsDir, scriptName) +} + +func getSystemdEnvironment(key string, value string) string { + return fmt.Sprintf("%s=%s", key, value) +} + +func getSystemdService(serviceName string) string { + return fmt.Sprintf("%s.service", serviceName) +} + +func getSystemdContent(options []*unit.UnitOption) (string, error) { + outReader := unit.Serialize(options) + outBytes, err := ioutil.ReadAll(outReader) + if err != nil { + return "", err + } + return string(outBytes), nil +} + +func getRTKernelUnitOptions(rtRepoURL string) []*unit.UnitOption { + return []*unit.UnitOption{ + // [Unit] + // Description + unit.NewUnitOption(systemdSectionUnit, systemdDescription, "RT kernel patch"), + // Wants + unit.NewUnitOption(systemdSectionUnit, systemdWants, systemdTargetNetworkOnline), + // After + unit.NewUnitOption(systemdSectionUnit, systemdAfter, systemdTargetNetworkOnline), + // Before + unit.NewUnitOption(systemdSectionUnit, systemdBefore, systemdServiceKubelet), + unit.NewUnitOption(systemdSectionUnit, systemdBefore, getSystemdService(preBootTuning)), + // [Service] + // Environment + unit.NewUnitOption(systemdSectionService, systemdEnvironment, getSystemdEnvironment(environmentRTRepoURL, rtRepoURL)), + // Type + unit.NewUnitOption(systemdSectionService, systemdType, systemdServiceTypeOneshot), + // RemainAfterExit + unit.NewUnitOption(systemdSectionService, systemdRemainAfterExit, systemdTrue), + // ExecStart + unit.NewUnitOption(systemdSectionService, systemdExecStart, getBashScriptPath(rtKernel)), + // [Install] + // WantedBy + unit.NewUnitOption(systemdSectionInstall, systemdWantedBy, systemdTargetMultiUser), + } +} + +func getRebootUnitOptions() []*unit.UnitOption { + return []*unit.UnitOption{ + // [Unit] + // Description + unit.NewUnitOption(systemdSectionUnit, systemdDescription, "Reboot initiated by rt-kernel and pre-boot-tuning"), + // Wants + unit.NewUnitOption(systemdSectionUnit, systemdWants, systemdTargetNetworkOnline), + // After + unit.NewUnitOption(systemdSectionUnit, systemdAfter, systemdTargetNetworkOnline), + // Before + unit.NewUnitOption(systemdSectionUnit, systemdBefore, systemdServiceKubelet), + // [Service] + // Type + unit.NewUnitOption(systemdSectionService, systemdType, systemdServiceTypeOneshot), + // RemainAfterExit + unit.NewUnitOption(systemdSectionService, systemdRemainAfterExit, systemdTrue), + // ExecStart + unit.NewUnitOption(systemdSectionService, systemdExecStart, getBashScriptPath(reboot)), + // [Install] + // WantedBy + unit.NewUnitOption(systemdSectionInstall, systemdWantedBy, systemdTargetMultiUser), + } +} + +func getPreBootTuningUnitOptions(nonIsolatedCpus string) []*unit.UnitOption { + return []*unit.UnitOption{ + // [Unit] + // Description + unit.NewUnitOption(systemdSectionUnit, systemdDescription, "Reboot initiated by rt-kernel and pre-boot-tuning"), + // Wants + unit.NewUnitOption(systemdSectionUnit, systemdWants, getSystemdService(rtKernel)), + // After + unit.NewUnitOption(systemdSectionUnit, systemdAfter, getSystemdService(rtKernel)), + // Before + unit.NewUnitOption(systemdSectionUnit, systemdBefore, systemdServiceKubelet), + unit.NewUnitOption(systemdSectionUnit, systemdBefore, getSystemdService(reboot)), + // [Service] + // Environment + unit.NewUnitOption(systemdSectionService, systemdEnvironment, getSystemdEnvironment(environmentNonIsolatedCpus, nonIsolatedCpus)), + // Type + unit.NewUnitOption(systemdSectionService, systemdType, systemdServiceTypeOneshot), + // RemainAfterExit + unit.NewUnitOption(systemdSectionService, systemdRemainAfterExit, systemdTrue), + // ExecStart + unit.NewUnitOption(systemdSectionService, systemdExecStart, getBashScriptPath(preBootTuning)), + // [Install] + // WantedBy + unit.NewUnitOption(systemdSectionInstall, systemdWantedBy, systemdTargetMultiUser), + } +} diff --git a/pkg/controller/performanceprofile/components/machineconfig/machineconfig_suite_test.go b/pkg/controller/performanceprofile/components/machineconfig/machineconfig_suite_test.go new file mode 100644 index 000000000..da6d74f76 --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfig/machineconfig_suite_test.go @@ -0,0 +1,13 @@ +package machineconfig + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFeatureGate(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Machine Config Suite") +} diff --git a/pkg/controller/performanceprofile/components/machineconfig/machineconfig_test.go b/pkg/controller/performanceprofile/components/machineconfig/machineconfig_test.go new file mode 100644 index 000000000..c3e31ec1c --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfig/machineconfig_test.go @@ -0,0 +1,110 @@ +package machineconfig + +import ( + "fmt" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + testutils "github.com/openshift-kni/performance-addon-operators/pkg/utils/testing" +) + +const testAssetsDir = "../../../../../build/assets" +const expectedSystemdUnits = ` + - contents: | + [Unit] + Description=RT kernel patch + Wants=network-online.target + After=network-online.target + Before=kubelet.service + Before=pre-boot-tuning.service + + [Service] + Environment=RT_REPO_URL=https://test.test + Type=oneshot + RemainAfterExit=true + ExecStart=/usr/local/bin/rt-kernel.sh + + [Install] + WantedBy=multi-user.target + enabled: true + name: rt-kernel.service + - contents: | + [Unit] + Description=Reboot initiated by rt-kernel and pre-boot-tuning + Wants=rt-kernel.service + After=rt-kernel.service + Before=kubelet.service + Before=reboot.service + + [Service] + Environment=NON_ISOLATED_CPUS=2-3 + Type=oneshot + RemainAfterExit=true + ExecStart=/usr/local/bin/pre-boot-tuning.sh + + [Install] + WantedBy=multi-user.target + enabled: true + name: pre-boot-tuning.service + - contents: | + [Unit] + Description=Reboot initiated by rt-kernel and pre-boot-tuning + Wants=network-online.target + After=network-online.target + Before=kubelet.service + + [Service] + Type=oneshot + RemainAfterExit=true + ExecStart=/usr/local/bin/reboot.sh + + [Install] + WantedBy=multi-user.target + enabled: true + name: reboot.service +` +const expectedBootArguments = ` + kernelArguments: + - nohz=on + - nosoftlockup + - nmi_watchdog=0 + - audit=0 + - mce=off + - irqaffinity=0 + - skew_tick=1 + - processor.max_cstate=1 + - idle=poll + - intel_pstate=disable + - intel_idle.max_cstate=0 + - intel_iommu=on + - iommu=pt + - isolcpus=4-7 + - default_hugepagesz=1G + - hugepagesz=1G + - hugepages=4 + - hugepagesz=2M + - hugepages=1024 +` + +var _ = Describe("Machine Config", func() { + It("should generate yaml with expected parameters", func() { + profile := testutils.NewPerformanceProfile("test") + profile.Spec.HugePages.Pages = append(profile.Spec.HugePages.Pages, performancev1alpha1.HugePage{ + Count: 1024, + Size: "2M", + }) + mc, err := NewPerformance(testAssetsDir, profile) + Expect(err).ToNot(HaveOccurred()) + + y, err := yaml.Marshal(mc) + Expect(err).ToNot(HaveOccurred()) + + manifest := string(y) + Expect(manifest).To(ContainSubstring(fmt.Sprintf("%s: %s", components.LabelMachineConfigurationRole, components.RoleWorkerPerformance))) + Expect(manifest).To(ContainSubstring(expectedSystemdUnits)) + Expect(manifest).To(ContainSubstring(expectedBootArguments)) + }) +}) diff --git a/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool.go b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool.go new file mode 100644 index 000000000..f40c42225 --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool.go @@ -0,0 +1,40 @@ +package machineconfigpool + +import ( + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + machineconfigv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NewPerformance returns new machine config pool for performance sensitive workflows +func NewPerformance(profile *performancev1alpha1.PerformanceProfile) *machineconfigv1.MachineConfigPool { + name := components.GetComponentName(profile.Name, components.RoleWorkerPerformance) + return &machineconfigv1.MachineConfigPool{ + TypeMeta: metav1.TypeMeta{ + APIVersion: machineconfigv1.GroupVersion.String(), + Kind: "MachineConfigPool", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + components.LableMachineConfigPoolRole: name, + }, + }, + Spec: machineconfigv1.MachineConfigPoolSpec{ + MachineConfigSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: components.LabelMachineConfigurationRole, + Operator: metav1.LabelSelectorOpIn, + Values: []string{components.RoleWorker, name}, + }, + }, + }, + NodeSelector: &metav1.LabelSelector{ + MatchLabels: profile.Spec.NodeSelector, + }, + }, + } +} diff --git a/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_suite_test.go b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_suite_test.go new file mode 100644 index 000000000..9a864bf3b --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_suite_test.go @@ -0,0 +1,13 @@ +package machineconfigpool + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFeatureGate(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Machine Config Pool Suite") +} diff --git a/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_test.go b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_test.go new file mode 100644 index 000000000..b3e675457 --- /dev/null +++ b/pkg/controller/performanceprofile/components/machineconfigpool/machineconfigpool_test.go @@ -0,0 +1,33 @@ +package machineconfigpool + +import ( + "fmt" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + testutils "github.com/openshift-kni/performance-addon-operators/pkg/utils/testing" +) + +const expectedMachineConfigSelectorValues = ` + values: + - worker + - worker-performance-test +` + +var _ = Describe("Machine Config Pool", func() { + It("should generate yaml with expected parameters", func() { + profile := testutils.NewPerformanceProfile("test") + profile.Spec.NodeSelector = map[string]string{"test": "test"} + mcp := NewPerformance(profile) + + y, err := yaml.Marshal(mcp) + Expect(err).ToNot(HaveOccurred()) + + manifest := string(y) + Expect(manifest).To(ContainSubstring(fmt.Sprintf("%s: %s", "test", "test"))) + Expect(manifest).To(ContainSubstring(fmt.Sprintf("key: %s", components.LabelMachineConfigurationRole))) + Expect(manifest).To(ContainSubstring(expectedMachineConfigSelectorValues)) + }) +}) diff --git a/pkg/controller/performanceprofile/components/tuned/tuned.go b/pkg/controller/performanceprofile/components/tuned/tuned.go new file mode 100644 index 000000000..2759a7df4 --- /dev/null +++ b/pkg/controller/performanceprofile/components/tuned/tuned.go @@ -0,0 +1,142 @@ +package tuned + +import ( + "bytes" + "fmt" + "io/ioutil" + "sort" + "text/template" + + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + "github.com/openshift-kni/performance-addon-operators/pkg/controller/performanceprofile/components" + tunedv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" +) + +const ( + labelKeyNetworkLatency = "tuned.openshift.io/network-latency" +) + +const ( + templateIsolatedCpus = "IsolatedCpus" +) + +func new(name string, profiles []tunedv1.TunedProfile, recommends []tunedv1.TunedRecommend) *tunedv1.Tuned { + return &tunedv1.Tuned{ + TypeMeta: metav1.TypeMeta{ + APIVersion: tunedv1.SchemeGroupVersion.String(), + Kind: "Tuned", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: components.NamespaceNodeTuningOperator, + }, + Spec: tunedv1.TunedSpec{ + Profile: profiles, + Recommend: recommends, + }, + } +} + +// NewNetworkLatency returns Tuned profile for network latency sensitive workflows +func NewNetworkLatency(assetsDir string) (*tunedv1.Tuned, error) { + name := components.ProfileNameNetworkLatency + profileData, err := getProfileData(getProfilePath(name, assetsDir), nil) + if err != nil { + return nil, err + } + + profiles := []tunedv1.TunedProfile{ + { + Name: &name, + Data: &profileData, + }, + } + + priority := uint64(30) + recommends := []tunedv1.TunedRecommend{ + { + Profile: &name, + Priority: &priority, + Match: []tunedv1.TunedMatch{ + { + Label: pointer.StringPtr(labelKeyNetworkLatency), + }, + }, + }, + } + return new(name, profiles, recommends), nil +} + +// NewWorkerRealTimeKernel returns tuned profile for performance sensitive workflows on top of real time kernel +func NewWorkerRealTimeKernel(assetsDir string, profile *performancev1alpha1.PerformanceProfile) (*tunedv1.Tuned, error) { + profileData, err := getProfileData(getProfilePath(components.ProfileNameWorkerRT, assetsDir), map[string]string{ + templateIsolatedCpus: string(*profile.Spec.CPU.Isolated), + }) + if err != nil { + return nil, err + } + + name := components.GetComponentName(profile.Name, components.ProfileNameWorkerRT) + profiles := []tunedv1.TunedProfile{ + { + Name: &name, + Data: &profileData, + }, + } + + // we should sort our matches, otherwise we can not predict the order of nested matches + sortedKeys := []string{} + for k := range profile.Spec.NodeSelector { + sortedKeys = append(sortedKeys, k) + } + sort.Strings(sortedKeys) + + priority := uint64(30) + recommends := []tunedv1.TunedRecommend{ + { + Profile: &name, + Priority: &priority, + Match: getProfileMatches(sortedKeys, profile.Spec.NodeSelector), + }, + } + return new(name, profiles, recommends), nil +} + +func getProfilePath(name string, assetsDir string) string { + return fmt.Sprintf("%s/tuned/%s", assetsDir, name) +} + +func getProfileData(profileOperatorlPath string, data interface{}) (string, error) { + profileContent, err := ioutil.ReadFile(profileOperatorlPath) + if err != nil { + return "", err + } + + profile := &bytes.Buffer{} + profileTemplate := template.Must(template.New("profile").Parse(string(profileContent))) + if err := profileTemplate.Execute(profile, data); err != nil { + return "", err + } + return profile.String(), nil +} + +func getProfileMatches(sortedKeys []string, matchNodeLabels map[string]string) []tunedv1.TunedMatch { + matches := []tunedv1.TunedMatch{} + for _, label := range sortedKeys { + value, ok := matchNodeLabels[label] + if !ok { + continue + } + + delete(matchNodeLabels, label) + matches = append(matches, tunedv1.TunedMatch{ + Label: pointer.StringPtr(label), + Value: pointer.StringPtr(value), + Match: getProfileMatches(sortedKeys, matchNodeLabels), + }) + } + return matches +} diff --git a/pkg/controller/performanceprofile/components/tuned/tuned_suite_test.go b/pkg/controller/performanceprofile/components/tuned/tuned_suite_test.go new file mode 100644 index 000000000..52dfcaaf1 --- /dev/null +++ b/pkg/controller/performanceprofile/components/tuned/tuned_suite_test.go @@ -0,0 +1,13 @@ +package tuned + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFeatureGate(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Tuned Suite") +} diff --git a/pkg/controller/performanceprofile/components/tuned/tuned_test.go b/pkg/controller/performanceprofile/components/tuned/tuned_test.go new file mode 100644 index 000000000..9c183f557 --- /dev/null +++ b/pkg/controller/performanceprofile/components/tuned/tuned_test.go @@ -0,0 +1,40 @@ +package tuned + +import ( + "fmt" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + testutils "github.com/openshift-kni/performance-addon-operators/pkg/utils/testing" +) + +const testAssetsDir = "../../../../../build/assets" +const expectedMatchSelector = ` + - match: + - label: label1 + match: + - label: label2 + value: label2 + value: label1 +` + +var _ = Describe("Tuned", func() { + Context("with worker real time kerbnel profile", func() { + It("should generate yaml with expected parameters", func() { + profile := testutils.NewPerformanceProfile("test") + profile.Spec.NodeSelector = map[string]string{ + "label1": "label1", + "label2": "label2", + } + tuned, err := NewWorkerRealTimeKernel(testAssetsDir, profile) + Expect(err).ToNot(HaveOccurred()) + y, err := yaml.Marshal(tuned) + Expect(err).ToNot(HaveOccurred()) + + manifest := string(y) + Expect(manifest).To(ContainSubstring(fmt.Sprintf("isolated_cores=4-7"))) + Expect(manifest).To(ContainSubstring(expectedMatchSelector)) + }) + }) +}) diff --git a/pkg/controller/performanceprofile/components/utils.go b/pkg/controller/performanceprofile/components/utils.go new file mode 100644 index 000000000..1d4708f8a --- /dev/null +++ b/pkg/controller/performanceprofile/components/utils.go @@ -0,0 +1,10 @@ +package components + +import ( + "fmt" +) + +// GetComponentName returns the component name for the specific performance profile +func GetComponentName(profileName string, prefix string) string { + return fmt.Sprintf("%s-%s", prefix, profileName) +} diff --git a/pkg/utils/testing/testing.go b/pkg/utils/testing/testing.go new file mode 100644 index 000000000..93969bbbb --- /dev/null +++ b/pkg/utils/testing/testing.go @@ -0,0 +1,55 @@ +package testing + +import ( + performancev1alpha1 "github.com/openshift-kni/performance-addon-operators/pkg/apis/performance/v1alpha1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" +) + +const ( + // HugePageSize defines the huge page size used for tests + HugePageSize = performancev1alpha1.HugePageSize("1G") + // IsolatedCPUs defines the isolated CPU set used for tests + IsolatedCPUs = performancev1alpha1.CPUSet("4-7") + // NonIsolateCPUs defines the non-isolated CPU set used for tests + NonIsolateCPUs = performancev1alpha1.CPUSet("2-3") + // ReservedCPUs defines the reserved CPU set used for tests + ReservedCPUs = performancev1alpha1.CPUSet("0-3") + // RepoURL defines the real-time kernel repository URL used for tests + RepoURL = "https://test.test" +) + +// NewPerformanceProfile returns new performance profile object that used for tests +func NewPerformanceProfile(name string) *performancev1alpha1.PerformanceProfile { + size := HugePageSize + isolatedCPUs := IsolatedCPUs + nonIsolateCPUs := NonIsolateCPUs + reservedCPUs := ReservedCPUs + + return &performancev1alpha1.PerformanceProfile{ + TypeMeta: metav1.TypeMeta{Kind: "PerformanceProfile"}, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: performancev1alpha1.PerformanceProfileSpec{ + CPU: &performancev1alpha1.CPU{ + Isolated: &isolatedCPUs, + NonIsolated: &nonIsolateCPUs, + Reserved: &reservedCPUs, + }, + HugePages: &performancev1alpha1.HugePages{ + DefaultHugePagesSize: &size, + Pages: []performancev1alpha1.HugePage{ + { + Count: 4, + Size: size, + }, + }, + }, + RealTimeKernel: &performancev1alpha1.RealTimeKernel{ + RepoURL: pointer.StringPtr(RepoURL), + }, + }, + } +} diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/LICENSE b/vendor/github.com/openshift/cluster-node-tuning-operator/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/register.go b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/register.go new file mode 100644 index 000000000..47e03b7d7 --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/register.go @@ -0,0 +1,6 @@ +package tuned + +// GroupName is the group name used in this package +const ( + GroupName = "tuned.openshift.io" +) diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/doc.go b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/doc.go new file mode 100644 index 000000000..b778cdf45 --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/doc.go @@ -0,0 +1,5 @@ +// +k8s:deepcopy-gen=package +// +groupName=tuned.openshift.io + +// Package v1 is the v1 version of the API. +package v1 // import "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/register.go b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/register.go new file mode 100644 index 000000000..97cce295c --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/register.go @@ -0,0 +1,41 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + tuned "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: tuned.GroupName, Version: "v1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder initializes a scheme builder + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme is a global function that registers this API group & version to a scheme + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Tuned{}, + &TunedList{}, + &Profile{}, + &ProfileList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/tuned_types.go b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/tuned_types.go new file mode 100644 index 000000000..cac19d4ee --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/tuned_types.go @@ -0,0 +1,127 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // TunedDefaultResourceName is the name of the Node Tuning Operator's default custom tuned resource + TunedDefaultResourceName = "default" + + // TunedRenderedResourceName is the name of the Node Tuning Operator's tuned resource combined out of + // all the other custom tuned resources + TunedRenderedResourceName = "rendered" + + // TunedClusterOperatorResourceName is the name of the clusteroperator resource + // that reflects the node tuning operator status. + TunedClusterOperatorResourceName = "node-tuning" +) + +///////////////////////////////////////////////////////////////////////////////// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Tuned is a collection of rules that allows cluster-wide deployment +// of node-level sysctls and more flexibility to add custom tuning +// specified by user needs. These rules are translated and passed to all +// containerized tuned daemons running in the cluster in the format that +// the daemons understand. The responsibility for applying the node-level +// tuning then lies with the containerized tuned daemons. More info: +// https://github.com/openshift/cluster-node-tuning-operator +type Tuned struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is the specification of the desired behavior of Tuned. More info: + // https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + Spec TunedSpec `json:"spec,omitempty"` + Status TunedStatus `json:"status,omitempty"` +} + +type TunedSpec struct { + // Tuned profiles. + Profile []TunedProfile `json:"profile"` + // Selection logic for all tuned profiles. + Recommend []TunedRecommend `json:"recommend"` +} + +// A tuned profile. +type TunedProfile struct { + // Name of the tuned profile to be used in the recommend section. + Name *string `json:"name"` + // Specification of the tuned profile to be consumed by the tuned daemon. + Data *string `json:"data"` +} + +// Selection logic for a single tuned profile. +type TunedRecommend struct { + // Name of the tuned profile to recommend. + Profile *string `json:"profile"` + + // Tuned profile priority. Highest priority is 0. + // +kubebuilder:validation:Minimum=0 + Priority *uint64 `json:"priority"` + // Rules governing application of a tuned profile connected by logical OR operator. + Match []TunedMatch `json:"match,omitempty"` +} + +// Rules governing application of a tuned profile. +type TunedMatch struct { + // Node or Pod label name. + Label *string `json:"label"` + // Node or Pod label value. If omitted, the presence of label name is enough to match. + Value *string `json:"value,omitempty"` + // Match type: [node/pod]. If omitted, "node" is assumed. + // +kubebuilder:validation:Enum={"node","pod"} + Type *string `json:"type,omitempty"` + + // Additional rules governing application of the tuned profile connected by logical AND operator. + // +kubebuilder:pruning:PreserveUnknownFields + Match []TunedMatch `json:"match,omitempty"` +} + +// TunedStatus is the status for a Tuned resource +type TunedStatus struct { +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TunedList is a list of Tuned resources +type TunedList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Tuned `json:"items"` +} + +///////////////////////////////////////////////////////////////////////////////// +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Profile is a specification for a Profile resource +type Profile struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ProfileSpec `json:"spec,omitempty"` + Status ProfileStatus `json:"status,omitempty"` +} + +type ProfileSpec struct { + Config ProfileConfig `json:"config"` +} + +type ProfileConfig struct { + TunedProfile string `json:"tunedProfile"` +} + +// ProfileStatus is the status for a Profile resource +type ProfileStatus struct { +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// ProfileList is a list of Profile resources +type ProfileList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Profile `json:"items"` +} diff --git a/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/zz_generated.deepcopy.go b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/zz_generated.deepcopy.go new file mode 100644 index 000000000..17b8f1aff --- /dev/null +++ b/vendor/github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1/zz_generated.deepcopy.go @@ -0,0 +1,323 @@ +// +build !ignore_autogenerated + +// Code generated by main. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Profile) DeepCopyInto(out *Profile) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Profile. +func (in *Profile) DeepCopy() *Profile { + if in == nil { + return nil + } + out := new(Profile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Profile) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProfileConfig) DeepCopyInto(out *ProfileConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileConfig. +func (in *ProfileConfig) DeepCopy() *ProfileConfig { + if in == nil { + return nil + } + out := new(ProfileConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProfileList) DeepCopyInto(out *ProfileList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Profile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileList. +func (in *ProfileList) DeepCopy() *ProfileList { + if in == nil { + return nil + } + out := new(ProfileList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ProfileList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProfileSpec) DeepCopyInto(out *ProfileSpec) { + *out = *in + out.Config = in.Config + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileSpec. +func (in *ProfileSpec) DeepCopy() *ProfileSpec { + if in == nil { + return nil + } + out := new(ProfileSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProfileStatus) DeepCopyInto(out *ProfileStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileStatus. +func (in *ProfileStatus) DeepCopy() *ProfileStatus { + if in == nil { + return nil + } + out := new(ProfileStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tuned) DeepCopyInto(out *Tuned) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tuned. +func (in *Tuned) DeepCopy() *Tuned { + if in == nil { + return nil + } + out := new(Tuned) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tuned) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedList) DeepCopyInto(out *TunedList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tuned, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedList. +func (in *TunedList) DeepCopy() *TunedList { + if in == nil { + return nil + } + out := new(TunedList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TunedList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedMatch) DeepCopyInto(out *TunedMatch) { + *out = *in + if in.Label != nil { + in, out := &in.Label, &out.Label + *out = new(string) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(string) + **out = **in + } + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make([]TunedMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedMatch. +func (in *TunedMatch) DeepCopy() *TunedMatch { + if in == nil { + return nil + } + out := new(TunedMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedProfile) DeepCopyInto(out *TunedProfile) { + *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedProfile. +func (in *TunedProfile) DeepCopy() *TunedProfile { + if in == nil { + return nil + } + out := new(TunedProfile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedRecommend) DeepCopyInto(out *TunedRecommend) { + *out = *in + if in.Profile != nil { + in, out := &in.Profile, &out.Profile + *out = new(string) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(uint64) + **out = **in + } + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make([]TunedMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedRecommend. +func (in *TunedRecommend) DeepCopy() *TunedRecommend { + if in == nil { + return nil + } + out := new(TunedRecommend) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedSpec) DeepCopyInto(out *TunedSpec) { + *out = *in + if in.Profile != nil { + in, out := &in.Profile, &out.Profile + *out = make([]TunedProfile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Recommend != nil { + in, out := &in.Recommend, &out.Recommend + *out = make([]TunedRecommend, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedSpec. +func (in *TunedSpec) DeepCopy() *TunedSpec { + if in == nil { + return nil + } + out := new(TunedSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TunedStatus) DeepCopyInto(out *TunedStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TunedStatus. +func (in *TunedStatus) DeepCopy() *TunedStatus { + if in == nil { + return nil + } + out := new(TunedStatus) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/github.com/operator-framework/operator-sdk/pkg/leader/doc.go b/vendor/github.com/operator-framework/operator-sdk/pkg/leader/doc.go deleted file mode 100644 index b88c30a2c..000000000 --- a/vendor/github.com/operator-framework/operator-sdk/pkg/leader/doc.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Package leader implements Leader For Life, a simple alternative to lease-based -leader election. - -Both the Leader For Life and lease-based approaches to leader election are -built on the concept that each candidate will attempt to create a resource with -the same GVK, namespace, and name. Whichever candidate succeeds becomes the -leader. The rest receive "already exists" errors and wait for a new -opportunity. - -Leases provide a way to indirectly observe whether the leader still exists. The -leader must periodically renew its lease, usually by updating a timestamp in -its lock record. If it fails to do so, it is presumed dead, and a new election -takes place. If the leader is in fact still alive but unreachable, it is -expected to gracefully step down. A variety of factors can cause a leader to -fail at updating its lease, but continue acting as the leader before succeeding -at stepping down. - -In the "leader for life" approach, a specific Pod is the leader. Once -established (by creating a lock record), the Pod is the leader until it is -destroyed. There is no possibility for multiple pods to think they are the -leader at the same time. The leader does not need to renew a lease, consider -stepping down, or do anything related to election activity once it becomes the -leader. - -The lock record in this case is a ConfigMap whose OwnerReference is set to the -Pod that is the leader. When the leader is destroyed, the ConfigMap gets -garbage-collected, enabling a different candidate Pod to become the leader. - -Leader for Life requires that all candidate Pods be in the same Namespace. It -uses the downwards API to determine the pod name, as hostname is not reliable. -You should run it configured with: - -env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name -*/ -package leader diff --git a/vendor/github.com/operator-framework/operator-sdk/pkg/leader/leader.go b/vendor/github.com/operator-framework/operator-sdk/pkg/leader/leader.go deleted file mode 100644 index 3c9c2472f..000000000 --- a/vendor/github.com/operator-framework/operator-sdk/pkg/leader/leader.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package leader - -import ( - "context" - "time" - - "github.com/operator-framework/operator-sdk/pkg/k8sutil" - - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - crclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -var log = logf.Log.WithName("leader") - -// maxBackoffInterval defines the maximum amount of time to wait between -// attempts to become the leader. -const maxBackoffInterval = time.Second * 16 - -// Become ensures that the current pod is the leader within its namespace. If -// run outside a cluster, it will skip leader election and return nil. It -// continuously tries to create a ConfigMap with the provided name and the -// current pod set as the owner reference. Only one can exist at a time with -// the same name, so the pod that successfully creates the ConfigMap is the -// leader. Upon termination of that pod, the garbage collector will delete the -// ConfigMap, enabling a different pod to become the leader. -func Become(ctx context.Context, lockName string) error { - log.Info("Trying to become the leader.") - - ns, err := k8sutil.GetOperatorNamespace() - if err != nil { - if err == k8sutil.ErrNoNamespace || err == k8sutil.ErrRunLocal { - log.Info("Skipping leader election; not running in a cluster.") - return nil - } - return err - } - - config, err := config.GetConfig() - if err != nil { - return err - } - - client, err := crclient.New(config, crclient.Options{}) - if err != nil { - return err - } - - owner, err := myOwnerRef(ctx, client, ns) - if err != nil { - return err - } - - // check for existing lock from this pod, in case we got restarted - existing := &corev1.ConfigMap{} - key := crclient.ObjectKey{Namespace: ns, Name: lockName} - err = client.Get(ctx, key, existing) - - switch { - case err == nil: - for _, existingOwner := range existing.GetOwnerReferences() { - if existingOwner.Name == owner.Name { - log.Info("Found existing lock with my name. I was likely restarted.") - log.Info("Continuing as the leader.") - return nil - } - log.Info("Found existing lock", "LockOwner", existingOwner.Name) - } - case apierrors.IsNotFound(err): - log.Info("No pre-existing lock was found.") - default: - log.Error(err, "Unknown error trying to get ConfigMap") - return err - } - - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: lockName, - Namespace: ns, - OwnerReferences: []metav1.OwnerReference{*owner}, - }, - } - - // try to create a lock - backoff := time.Second - for { - err := client.Create(ctx, cm) - switch { - case err == nil: - log.Info("Became the leader.") - return nil - case apierrors.IsAlreadyExists(err): - existingOwners := existing.GetOwnerReferences() - switch { - case len(existingOwners) != 1: - log.Info("Leader lock configmap must have exactly one owner reference.", "ConfigMap", existing) - case existingOwners[0].Kind != "Pod": - log.Info("Leader lock configmap owner reference must be a pod.", "OwnerReference", existingOwners[0]) - default: - leaderPod := &corev1.Pod{} - key = crclient.ObjectKey{Namespace: ns, Name: existingOwners[0].Name} - err = client.Get(ctx, key, leaderPod) - switch { - case apierrors.IsNotFound(err): - log.Info("Leader pod has been deleted, waiting for garbage collection do remove the lock.") - case err != nil: - return err - case isPodEvicted(*leaderPod) && leaderPod.GetDeletionTimestamp() == nil: - log.Info("Operator pod with leader lock has been evicted.", "leader", leaderPod.Name) - log.Info("Deleting evicted leader.") - // Pod may not delete immediately, continue with backoff - err := client.Delete(ctx, leaderPod) - if err != nil { - log.Error(err, "Leader pod could not be deleted.") - } - - default: - log.Info("Not the leader. Waiting.") - } - } - - select { - case <-time.After(wait.Jitter(backoff, .2)): - if backoff < maxBackoffInterval { - backoff *= 2 - } - continue - case <-ctx.Done(): - return ctx.Err() - } - default: - log.Error(err, "Unknown error creating ConfigMap") - return err - } - } -} - -// myOwnerRef returns an OwnerReference that corresponds to the pod in which -// this code is currently running. -// It expects the environment variable POD_NAME to be set by the downwards API -func myOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) { - myPod, err := k8sutil.GetPod(ctx, client, ns) - if err != nil { - return nil, err - } - - owner := &metav1.OwnerReference{ - APIVersion: "v1", - Kind: "Pod", - Name: myPod.ObjectMeta.Name, - UID: myPod.ObjectMeta.UID, - } - return owner, nil -} - -func isPodEvicted(pod corev1.Pod) bool { - podFailed := pod.Status.Phase == corev1.PodFailed - podEvicted := pod.Status.Reason == "Evicted" - return podFailed && podEvicted -} diff --git a/vendor/k8s.io/utils/pointer/OWNERS b/vendor/k8s.io/utils/pointer/OWNERS new file mode 100644 index 000000000..0d6392752 --- /dev/null +++ b/vendor/k8s.io/utils/pointer/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: +- apelisse +- stewart-yu +- thockin +reviewers: +- apelisse +- stewart-yu +- thockin diff --git a/vendor/k8s.io/utils/pointer/pointer.go b/vendor/k8s.io/utils/pointer/pointer.go new file mode 100644 index 000000000..5365a1136 --- /dev/null +++ b/vendor/k8s.io/utils/pointer/pointer.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pointer + +import ( + "fmt" + "reflect" +) + +// AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when, +// for example, an API struct is handled by plugins which need to distinguish +// "no plugin accepted this spec" from "this spec is empty". +// +// This function is only valid for structs and pointers to structs. Any other +// type will cause a panic. Passing a typed nil pointer will return true. +func AllPtrFieldsNil(obj interface{}) bool { + v := reflect.ValueOf(obj) + if !v.IsValid() { + panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) + } + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { + return false + } + } + return true +} + +// Int32Ptr returns a pointer to an int32 +func Int32Ptr(i int32) *int32 { + return &i +} + +// Int64Ptr returns a pointer to an int64 +func Int64Ptr(i int64) *int64 { + return &i +} + +// Int32PtrDerefOr dereference the int32 ptr and returns it if not nil, +// else returns def. +func Int32PtrDerefOr(ptr *int32, def int32) int32 { + if ptr != nil { + return *ptr + } + return def +} + +// BoolPtr returns a pointer to a bool +func BoolPtr(b bool) *bool { + return &b +} + +// StringPtr returns a pointer to the passed string. +func StringPtr(s string) *string { + return &s +} + +// Float32Ptr returns a pointer to the passed float32. +func Float32Ptr(i float32) *float32 { + return &i +} + +// Float64Ptr returns a pointer to the passed float64. +func Float64Ptr(i float64) *float64 { + return &i +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6a0b056e4..e323c29db 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,15 +24,15 @@ github.com/coreos/go-semver/semver # github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/go-systemd/unit # github.com/coreos/ignition v0.34.0 -github.com/coreos/ignition/config/v2_2 github.com/coreos/ignition/config/v2_2/types +github.com/coreos/ignition/config/v2_2 github.com/coreos/ignition/config/shared/errors +github.com/coreos/ignition/config/shared/validations +github.com/coreos/ignition/config/validate/report github.com/coreos/ignition/config/util github.com/coreos/ignition/config/v2_1 github.com/coreos/ignition/config/v2_1/types github.com/coreos/ignition/config/validate -github.com/coreos/ignition/config/validate/report -github.com/coreos/ignition/config/shared/validations github.com/coreos/ignition/config/v2_4_experimental/types github.com/coreos/ignition/config/v2_0 github.com/coreos/ignition/config/v2_0/types @@ -160,9 +160,12 @@ github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util # github.com/openshift/api v3.9.1-0.20191111211345-a27ff30ebf09+incompatible => github.com/openshift/api v0.0.0-20191220175332-378bec237e34 github.com/openshift/api/config/v1 -# github.com/openshift/client-go v0.0.0-20191001081553-3b0e988f8cb0 => github.com/openshift/client-go v0.0.0-20191205152420-9faca5198b4f +# github.com/openshift/client-go v0.0.0-20191022152013-2823239d2298 => github.com/openshift/client-go v0.0.0-20191205152420-9faca5198b4f github.com/openshift/client-go/config/clientset/versioned/typed/config/v1 github.com/openshift/client-go/config/clientset/versioned/scheme +# github.com/openshift/cluster-node-tuning-operator v0.0.0-00010101000000-000000000000 => github.com/openshift/cluster-node-tuning-operator v0.0.0-20191217222311-500135cb8754 +github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1 +github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned # github.com/openshift/machine-config-operator v4.2.0-alpha.0.0.20190917115525-033375cbe820+incompatible => github.com/openshift/machine-config-operator v0.0.0-20191220033234-347a7a09e869 github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/typed/machineconfiguration.openshift.io/v1 github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1 @@ -170,7 +173,6 @@ github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/s # github.com/operator-framework/operator-sdk v0.13.0 github.com/operator-framework/operator-sdk/pkg/k8sutil github.com/operator-framework/operator-sdk/pkg/kube-metrics -github.com/operator-framework/operator-sdk/pkg/leader github.com/operator-framework/operator-sdk/pkg/log/zap github.com/operator-framework/operator-sdk/pkg/metrics github.com/operator-framework/operator-sdk/pkg/restmapper @@ -320,31 +322,31 @@ k8s.io/api/storage/v1beta1 k8s.io/api/admission/v1beta1 # k8s.io/apimachinery v0.17.0 => k8s.io/apimachinery v0.17.0 k8s.io/apimachinery/pkg/util/intstr +k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/apis/meta/v1 -k8s.io/apimachinery/pkg/labels +k8s.io/apimachinery/pkg/api/errors +k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/runtime k8s.io/apimachinery/pkg/runtime/schema -k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/types k8s.io/apimachinery/pkg/apis/meta/v1/unstructured k8s.io/apimachinery/pkg/runtime/serializer k8s.io/apimachinery/pkg/watch -k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/api/meta k8s.io/apimachinery/pkg/util/runtime -k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/util/net k8s.io/apimachinery/pkg/util/sets k8s.io/apimachinery/pkg/conversion k8s.io/apimachinery/pkg/fields +k8s.io/apimachinery/pkg/labels k8s.io/apimachinery/pkg/selection -k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/util/errors +k8s.io/apimachinery/pkg/util/validation +k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/pkg/conversion/queryparams k8s.io/apimachinery/pkg/util/json k8s.io/apimachinery/pkg/util/naming -k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/runtime/serializer/json k8s.io/apimachinery/pkg/runtime/serializer/protobuf @@ -454,6 +456,7 @@ k8s.io/kube-state-metrics/pkg/metrics_store # k8s.io/kubelet v0.0.0 => k8s.io/kubelet v0.17.0 k8s.io/kubelet/config/v1beta1 # k8s.io/utils v0.0.0-20191114184206-e782cd3c129f +k8s.io/utils/pointer k8s.io/utils/buffer k8s.io/utils/trace k8s.io/utils/integer