Set container env var using services. (#573)
* Introduce service env vars.
This commit is contained in:
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@@ -1761,15 +1761,18 @@
|
|||||||
revision = "0317810137be915b9cf888946c6e115c1bfac693"
|
revision = "0317810137be915b9cf888946c6e115c1bfac693"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:02e74ccd0c045a31940dca52ba2545108fd299d4f66632b1bf643fb92b662de2"
|
digest = "1:4ff70ea1888545c3a245a76657ba38a308d2c068bd26fa4310f63560ebaf265c"
|
||||||
name = "k8s.io/kubernetes"
|
name = "k8s.io/kubernetes"
|
||||||
packages = [
|
packages = [
|
||||||
"pkg/api/v1/pod",
|
"pkg/api/v1/pod",
|
||||||
"pkg/apis/core",
|
"pkg/apis/core",
|
||||||
|
"pkg/apis/core/helper",
|
||||||
"pkg/apis/core/pods",
|
"pkg/apis/core/pods",
|
||||||
|
"pkg/apis/core/v1/helper",
|
||||||
"pkg/fieldpath",
|
"pkg/fieldpath",
|
||||||
"pkg/kubelet/apis/cri/runtime/v1alpha2",
|
"pkg/kubelet/apis/cri/runtime/v1alpha2",
|
||||||
"pkg/kubelet/apis/stats/v1alpha1",
|
"pkg/kubelet/apis/stats/v1alpha1",
|
||||||
|
"pkg/kubelet/envvars",
|
||||||
"pkg/kubelet/server/remotecommand",
|
"pkg/kubelet/server/remotecommand",
|
||||||
]
|
]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
@@ -1883,6 +1886,7 @@
|
|||||||
"k8s.io/apimachinery/pkg/types",
|
"k8s.io/apimachinery/pkg/types",
|
||||||
"k8s.io/apimachinery/pkg/util/intstr",
|
"k8s.io/apimachinery/pkg/util/intstr",
|
||||||
"k8s.io/apimachinery/pkg/util/net",
|
"k8s.io/apimachinery/pkg/util/net",
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets",
|
||||||
"k8s.io/apimachinery/pkg/util/strategicpatch",
|
"k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
"k8s.io/apimachinery/pkg/util/uuid",
|
"k8s.io/apimachinery/pkg/util/uuid",
|
||||||
"k8s.io/apimachinery/pkg/util/validation",
|
"k8s.io/apimachinery/pkg/util/validation",
|
||||||
@@ -1906,9 +1910,11 @@
|
|||||||
"k8s.io/client-go/util/workqueue",
|
"k8s.io/client-go/util/workqueue",
|
||||||
"k8s.io/kubernetes/pkg/api/v1/pod",
|
"k8s.io/kubernetes/pkg/api/v1/pod",
|
||||||
"k8s.io/kubernetes/pkg/apis/core/pods",
|
"k8s.io/kubernetes/pkg/apis/core/pods",
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core/v1/helper",
|
||||||
"k8s.io/kubernetes/pkg/fieldpath",
|
"k8s.io/kubernetes/pkg/fieldpath",
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2",
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2",
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1",
|
"k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1",
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/envvars",
|
||||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand",
|
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand",
|
||||||
]
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ func TestGetConfigMap(t *testing.T) {
|
|||||||
func TestListServices(t *testing.T) {
|
func TestListServices(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
lsServices = []*v1.Service{
|
lsServices = []*v1.Service{
|
||||||
testutil.FakeService("namespace-0", "service-0"),
|
testutil.FakeService("namespace-0", "service-0", "1.2.3.1", "TCP", 8081),
|
||||||
testutil.FakeService("namespace-1", "service-1"),
|
testutil.FakeService("namespace-1", "service-1", "1.2.3.2", "TCP", 8082),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const (
|
|||||||
// For each one of these strings, a container that uses the string as its image will be appended to the pod.
|
// For each one of these strings, a container that uses the string as its image will be appended to the pod.
|
||||||
// This method DOES NOT create the pod in the Kubernetes API.
|
// This method DOES NOT create the pod in the Kubernetes API.
|
||||||
func (f *Framework) CreateDummyPodObjectWithPrefix(prefix string, images ...string) *corev1.Pod {
|
func (f *Framework) CreateDummyPodObjectWithPrefix(prefix string, images ...string) *corev1.Pod {
|
||||||
|
enableServiceLink := false
|
||||||
|
|
||||||
pod := &corev1.Pod{
|
pod := &corev1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: prefix,
|
GenerateName: prefix,
|
||||||
@@ -41,6 +43,7 @@ func (f *Framework) CreateDummyPodObjectWithPrefix(prefix string, images ...stri
|
|||||||
Effect: corev1.TaintEffect(f.TaintEffect),
|
Effect: corev1.TaintEffect(f.TaintEffect),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &enableServiceLink,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for idx, img := range images {
|
for idx, img := range images {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ func FakeEventRecorder(bufferSize int) *record.FakeRecorder {
|
|||||||
|
|
||||||
// FakePodWithSingleContainer returns a pod with the specified namespace and name, and having a single container with the specified image.
|
// FakePodWithSingleContainer returns a pod with the specified namespace and name, and having a single container with the specified image.
|
||||||
func FakePodWithSingleContainer(namespace, name, image string) *corev1.Pod {
|
func FakePodWithSingleContainer(namespace, name, image string) *corev1.Pod {
|
||||||
|
enableServiceLink := corev1.DefaultEnableServiceLinks
|
||||||
|
|
||||||
return &corev1.Pod{
|
return &corev1.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
@@ -36,6 +38,7 @@ func FakePodWithSingleContainer(namespace, name, image string) *corev1.Pod {
|
|||||||
Image: image,
|
Image: image,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &enableServiceLink,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,12 +58,19 @@ func FakeSecret(namespace, name string, data map[string]string) *corev1.Secret {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// FakeService returns a service with the specified namespace and name.
|
// FakeService returns a service with the specified namespace and name and service info.
|
||||||
func FakeService(namespace, name string) *corev1.Service {
|
func FakeService(namespace, name, clusterIP, protocol string, port int32) *corev1.Service {
|
||||||
return &corev1.Service{
|
return &corev1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Name: name,
|
Name: name,
|
||||||
},
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: []corev1.ServicePort{{
|
||||||
|
Protocol: corev1.Protocol(protocol),
|
||||||
|
Port: port,
|
||||||
|
}},
|
||||||
|
ClusterIP: clusterIP,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
539
vendor/k8s.io/kubernetes/pkg/apis/core/helper/helpers.go
generated
vendored
Normal file
539
vendor/k8s.io/kubernetes/pkg/apis/core/helper/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"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/sets"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsHugePageResourceName returns true if the resource name has the huge page
|
||||||
|
// resource prefix.
|
||||||
|
func IsHugePageResourceName(name core.ResourceName) bool {
|
||||||
|
return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQuotaHugePageResourceName returns true if the resource name has the quota
|
||||||
|
// related huge page resource prefix.
|
||||||
|
func IsQuotaHugePageResourceName(name core.ResourceName) bool {
|
||||||
|
return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), core.ResourceRequestsHugePagesPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HugePageResourceName returns a ResourceName with the canonical hugepage
|
||||||
|
// prefix prepended for the specified page size. The page size is converted
|
||||||
|
// to its canonical representation.
|
||||||
|
func HugePageResourceName(pageSize resource.Quantity) core.ResourceName {
|
||||||
|
return core.ResourceName(fmt.Sprintf("%s%s", core.ResourceHugePagesPrefix, pageSize.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HugePageSizeFromResourceName returns the page size for the specified huge page
|
||||||
|
// resource name. If the specified input is not a valid huge page resource name
|
||||||
|
// an error is returned.
|
||||||
|
func HugePageSizeFromResourceName(name core.ResourceName) (resource.Quantity, error) {
|
||||||
|
if !IsHugePageResourceName(name) {
|
||||||
|
return resource.Quantity{}, fmt.Errorf("resource name: %s is an invalid hugepage name", name)
|
||||||
|
}
|
||||||
|
pageSize := strings.TrimPrefix(string(name), core.ResourceHugePagesPrefix)
|
||||||
|
return resource.ParseQuantity(pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonConvertibleFields iterates over the provided map and filters out all but
|
||||||
|
// any keys with the "non-convertible.kubernetes.io" prefix.
|
||||||
|
func NonConvertibleFields(annotations map[string]string) map[string]string {
|
||||||
|
nonConvertibleKeys := map[string]string{}
|
||||||
|
for key, value := range annotations {
|
||||||
|
if strings.HasPrefix(key, core.NonConvertibleAnnotationPrefix) {
|
||||||
|
nonConvertibleKeys[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonConvertibleKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic can do semantic deep equality checks for core objects.
|
||||||
|
// Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true
|
||||||
|
var Semantic = conversion.EqualitiesOrDie(
|
||||||
|
func(a, b resource.Quantity) bool {
|
||||||
|
// Ignore formatting, only care that numeric value stayed the same.
|
||||||
|
// TODO: if we decide it's important, it should be safe to start comparing the format.
|
||||||
|
//
|
||||||
|
// Uninitialized quantities are equivalent to 0 quantities.
|
||||||
|
return a.Cmp(b) == 0
|
||||||
|
},
|
||||||
|
func(a, b metav1.MicroTime) bool {
|
||||||
|
return a.UTC() == b.UTC()
|
||||||
|
},
|
||||||
|
func(a, b metav1.Time) bool {
|
||||||
|
return a.UTC() == b.UTC()
|
||||||
|
},
|
||||||
|
func(a, b labels.Selector) bool {
|
||||||
|
return a.String() == b.String()
|
||||||
|
},
|
||||||
|
func(a, b fields.Selector) bool {
|
||||||
|
return a.String() == b.String()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
var standardResourceQuotaScopes = sets.NewString(
|
||||||
|
string(core.ResourceQuotaScopeTerminating),
|
||||||
|
string(core.ResourceQuotaScopeNotTerminating),
|
||||||
|
string(core.ResourceQuotaScopeBestEffort),
|
||||||
|
string(core.ResourceQuotaScopeNotBestEffort),
|
||||||
|
string(core.ResourceQuotaScopePriorityClass),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStandardResourceQuotaScope returns true if the scope is a standard value
|
||||||
|
func IsStandardResourceQuotaScope(str string) bool {
|
||||||
|
return standardResourceQuotaScopes.Has(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var podObjectCountQuotaResources = sets.NewString(
|
||||||
|
string(core.ResourcePods),
|
||||||
|
)
|
||||||
|
|
||||||
|
var podComputeQuotaResources = sets.NewString(
|
||||||
|
string(core.ResourceCPU),
|
||||||
|
string(core.ResourceMemory),
|
||||||
|
string(core.ResourceLimitsCPU),
|
||||||
|
string(core.ResourceLimitsMemory),
|
||||||
|
string(core.ResourceRequestsCPU),
|
||||||
|
string(core.ResourceRequestsMemory),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope
|
||||||
|
func IsResourceQuotaScopeValidForResource(scope core.ResourceQuotaScope, resource string) bool {
|
||||||
|
switch scope {
|
||||||
|
case core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeNotBestEffort, core.ResourceQuotaScopePriorityClass:
|
||||||
|
return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource)
|
||||||
|
case core.ResourceQuotaScopeBestEffort:
|
||||||
|
return podObjectCountQuotaResources.Has(resource)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardContainerResources = sets.NewString(
|
||||||
|
string(core.ResourceCPU),
|
||||||
|
string(core.ResourceMemory),
|
||||||
|
string(core.ResourceEphemeralStorage),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStandardContainerResourceName returns true if the container can make a resource request
|
||||||
|
// for the specified resource
|
||||||
|
func IsStandardContainerResourceName(str string) bool {
|
||||||
|
return standardContainerResources.Has(str) || IsHugePageResourceName(core.ResourceName(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExtendedResourceName returns true if:
|
||||||
|
// 1. the resource name is not in the default namespace;
|
||||||
|
// 2. resource name does not have "requests." prefix,
|
||||||
|
// to avoid confusion with the convention in quota
|
||||||
|
// 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||||
|
func IsExtendedResourceName(name core.ResourceName) bool {
|
||||||
|
if IsNativeResource(name) || strings.HasPrefix(string(name), core.DefaultResourceRequestsPrefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||||
|
nameForQuota := fmt.Sprintf("%s%s", core.DefaultResourceRequestsPrefix, string(name))
|
||||||
|
if errs := validation.IsQualifiedName(string(nameForQuota)); len(errs) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNativeResource returns true if the resource name is in the
|
||||||
|
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||||
|
// implicitly in the kubernetes.io/ namespace.
|
||||||
|
func IsNativeResource(name core.ResourceName) bool {
|
||||||
|
return !strings.Contains(string(name), "/") ||
|
||||||
|
strings.Contains(string(name), core.ResourceDefaultNamespacePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOvercommitAllowed returns true if the resource is in the default
|
||||||
|
// namespace and is not hugepages.
|
||||||
|
func IsOvercommitAllowed(name core.ResourceName) bool {
|
||||||
|
return IsNativeResource(name) &&
|
||||||
|
!IsHugePageResourceName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardLimitRangeTypes = sets.NewString(
|
||||||
|
string(core.LimitTypePod),
|
||||||
|
string(core.LimitTypeContainer),
|
||||||
|
string(core.LimitTypePersistentVolumeClaim),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStandardLimitRangeType returns true if the type is Pod or Container
|
||||||
|
func IsStandardLimitRangeType(str string) bool {
|
||||||
|
return standardLimitRangeTypes.Has(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardQuotaResources = sets.NewString(
|
||||||
|
string(core.ResourceCPU),
|
||||||
|
string(core.ResourceMemory),
|
||||||
|
string(core.ResourceEphemeralStorage),
|
||||||
|
string(core.ResourceRequestsCPU),
|
||||||
|
string(core.ResourceRequestsMemory),
|
||||||
|
string(core.ResourceRequestsStorage),
|
||||||
|
string(core.ResourceRequestsEphemeralStorage),
|
||||||
|
string(core.ResourceLimitsCPU),
|
||||||
|
string(core.ResourceLimitsMemory),
|
||||||
|
string(core.ResourceLimitsEphemeralStorage),
|
||||||
|
string(core.ResourcePods),
|
||||||
|
string(core.ResourceQuotas),
|
||||||
|
string(core.ResourceServices),
|
||||||
|
string(core.ResourceReplicationControllers),
|
||||||
|
string(core.ResourceSecrets),
|
||||||
|
string(core.ResourcePersistentVolumeClaims),
|
||||||
|
string(core.ResourceConfigMaps),
|
||||||
|
string(core.ResourceServicesNodePorts),
|
||||||
|
string(core.ResourceServicesLoadBalancers),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStandardQuotaResourceName returns true if the resource is known to
|
||||||
|
// the quota tracking system
|
||||||
|
func IsStandardQuotaResourceName(str string) bool {
|
||||||
|
return standardQuotaResources.Has(str) || IsQuotaHugePageResourceName(core.ResourceName(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardResources = sets.NewString(
|
||||||
|
string(core.ResourceCPU),
|
||||||
|
string(core.ResourceMemory),
|
||||||
|
string(core.ResourceEphemeralStorage),
|
||||||
|
string(core.ResourceRequestsCPU),
|
||||||
|
string(core.ResourceRequestsMemory),
|
||||||
|
string(core.ResourceRequestsEphemeralStorage),
|
||||||
|
string(core.ResourceLimitsCPU),
|
||||||
|
string(core.ResourceLimitsMemory),
|
||||||
|
string(core.ResourceLimitsEphemeralStorage),
|
||||||
|
string(core.ResourcePods),
|
||||||
|
string(core.ResourceQuotas),
|
||||||
|
string(core.ResourceServices),
|
||||||
|
string(core.ResourceReplicationControllers),
|
||||||
|
string(core.ResourceSecrets),
|
||||||
|
string(core.ResourceConfigMaps),
|
||||||
|
string(core.ResourcePersistentVolumeClaims),
|
||||||
|
string(core.ResourceStorage),
|
||||||
|
string(core.ResourceRequestsStorage),
|
||||||
|
string(core.ResourceServicesNodePorts),
|
||||||
|
string(core.ResourceServicesLoadBalancers),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsStandardResourceName returns true if the resource is known to the system
|
||||||
|
func IsStandardResourceName(str string) bool {
|
||||||
|
return standardResources.Has(str) || IsQuotaHugePageResourceName(core.ResourceName(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
var integerResources = sets.NewString(
|
||||||
|
string(core.ResourcePods),
|
||||||
|
string(core.ResourceQuotas),
|
||||||
|
string(core.ResourceServices),
|
||||||
|
string(core.ResourceReplicationControllers),
|
||||||
|
string(core.ResourceSecrets),
|
||||||
|
string(core.ResourceConfigMaps),
|
||||||
|
string(core.ResourcePersistentVolumeClaims),
|
||||||
|
string(core.ResourceServicesNodePorts),
|
||||||
|
string(core.ResourceServicesLoadBalancers),
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsIntegerResourceName returns true if the resource is measured in integer values
|
||||||
|
func IsIntegerResourceName(str string) bool {
|
||||||
|
return integerResources.Has(str) || IsExtendedResourceName(core.ResourceName(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function aims to check if the service's ClusterIP is set or not
|
||||||
|
// the objective is not to perform validation here
|
||||||
|
func IsServiceIPSet(service *core.Service) bool {
|
||||||
|
return service.Spec.ClusterIP != core.ClusterIPNone && service.Spec.ClusterIP != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardFinalizers = sets.NewString(
|
||||||
|
string(core.FinalizerKubernetes),
|
||||||
|
metav1.FinalizerOrphanDependents,
|
||||||
|
metav1.FinalizerDeleteDependents,
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsStandardFinalizerName(str string) bool {
|
||||||
|
return standardFinalizers.Has(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice,
|
||||||
|
// only if they do not already exist
|
||||||
|
func AddToNodeAddresses(addresses *[]core.NodeAddress, addAddresses ...core.NodeAddress) {
|
||||||
|
for _, add := range addAddresses {
|
||||||
|
exists := false
|
||||||
|
for _, existing := range *addresses {
|
||||||
|
if existing.Address == add.Address && existing.Type == add.Type {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
*addresses = append(*addresses, add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make method on LoadBalancerStatus?
|
||||||
|
func LoadBalancerStatusEqual(l, r *core.LoadBalancerStatus) bool {
|
||||||
|
return ingressSliceEqual(l.Ingress, r.Ingress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressSliceEqual(lhs, rhs []core.LoadBalancerIngress) bool {
|
||||||
|
if len(lhs) != len(rhs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range lhs {
|
||||||
|
if !ingressEqual(&lhs[i], &rhs[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressEqual(lhs, rhs *core.LoadBalancerIngress) bool {
|
||||||
|
if lhs.IP != rhs.IP {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.Hostname != rhs.Hostname {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessModesAsString returns a string representation of an array of access modes.
|
||||||
|
// modes, when present, are always in the same order: RWO,ROX,RWX.
|
||||||
|
func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string {
|
||||||
|
modes = removeDuplicateAccessModes(modes)
|
||||||
|
modesStr := []string{}
|
||||||
|
if containsAccessMode(modes, core.ReadWriteOnce) {
|
||||||
|
modesStr = append(modesStr, "RWO")
|
||||||
|
}
|
||||||
|
if containsAccessMode(modes, core.ReadOnlyMany) {
|
||||||
|
modesStr = append(modesStr, "ROX")
|
||||||
|
}
|
||||||
|
if containsAccessMode(modes, core.ReadWriteMany) {
|
||||||
|
modesStr = append(modesStr, "RWX")
|
||||||
|
}
|
||||||
|
return strings.Join(modesStr, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString
|
||||||
|
func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode {
|
||||||
|
strmodes := strings.Split(modes, ",")
|
||||||
|
accessModes := []core.PersistentVolumeAccessMode{}
|
||||||
|
for _, s := range strmodes {
|
||||||
|
s = strings.Trim(s, " ")
|
||||||
|
switch {
|
||||||
|
case s == "RWO":
|
||||||
|
accessModes = append(accessModes, core.ReadWriteOnce)
|
||||||
|
case s == "ROX":
|
||||||
|
accessModes = append(accessModes, core.ReadOnlyMany)
|
||||||
|
case s == "RWX":
|
||||||
|
accessModes = append(accessModes, core.ReadWriteMany)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessModes
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeDuplicateAccessModes returns an array of access modes without any duplicates
|
||||||
|
func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode {
|
||||||
|
accessModes := []core.PersistentVolumeAccessMode{}
|
||||||
|
for _, m := range modes {
|
||||||
|
if !containsAccessMode(accessModes, m) {
|
||||||
|
accessModes = append(accessModes, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessModes
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool {
|
||||||
|
for _, m := range modes {
|
||||||
|
if m == mode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement core type into a struct that implements
|
||||||
|
// labels.Selector.
|
||||||
|
func NodeSelectorRequirementsAsSelector(nsm []core.NodeSelectorRequirement) (labels.Selector, error) {
|
||||||
|
if len(nsm) == 0 {
|
||||||
|
return labels.Nothing(), nil
|
||||||
|
}
|
||||||
|
selector := labels.NewSelector()
|
||||||
|
for _, expr := range nsm {
|
||||||
|
var op selection.Operator
|
||||||
|
switch expr.Operator {
|
||||||
|
case core.NodeSelectorOpIn:
|
||||||
|
op = selection.In
|
||||||
|
case core.NodeSelectorOpNotIn:
|
||||||
|
op = selection.NotIn
|
||||||
|
case core.NodeSelectorOpExists:
|
||||||
|
op = selection.Exists
|
||||||
|
case core.NodeSelectorOpDoesNotExist:
|
||||||
|
op = selection.DoesNotExist
|
||||||
|
case core.NodeSelectorOpGt:
|
||||||
|
op = selection.GreaterThan
|
||||||
|
case core.NodeSelectorOpLt:
|
||||||
|
op = selection.LessThan
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
|
||||||
|
}
|
||||||
|
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector = selector.Add(*r)
|
||||||
|
}
|
||||||
|
return selector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSelectorRequirementsAsFieldSelector converts the []NodeSelectorRequirement core type into a struct that implements
|
||||||
|
// fields.Selector.
|
||||||
|
func NodeSelectorRequirementsAsFieldSelector(nsm []core.NodeSelectorRequirement) (fields.Selector, error) {
|
||||||
|
if len(nsm) == 0 {
|
||||||
|
return fields.Nothing(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selectors := []fields.Selector{}
|
||||||
|
for _, expr := range nsm {
|
||||||
|
switch expr.Operator {
|
||||||
|
case core.NodeSelectorOpIn:
|
||||||
|
if len(expr.Values) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||||
|
len(expr.Values), expr.Operator)
|
||||||
|
}
|
||||||
|
selectors = append(selectors, fields.OneTermEqualSelector(expr.Key, expr.Values[0]))
|
||||||
|
|
||||||
|
case core.NodeSelectorOpNotIn:
|
||||||
|
if len(expr.Values) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||||
|
len(expr.Values), expr.Operator)
|
||||||
|
}
|
||||||
|
selectors = append(selectors, fields.OneTermNotEqualSelector(expr.Key, expr.Values[0]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%q is not a valid node field selector operator", expr.Operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields.AndSelectors(selectors...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations
|
||||||
|
// and converts it to the []Toleration type in core.
|
||||||
|
func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]core.Toleration, error) {
|
||||||
|
var tolerations []core.Toleration
|
||||||
|
if len(annotations) > 0 && annotations[core.TolerationsAnnotationKey] != "" {
|
||||||
|
err := json.Unmarshal([]byte(annotations[core.TolerationsAnnotationKey]), &tolerations)
|
||||||
|
if err != nil {
|
||||||
|
return tolerations, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tolerations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list.
|
||||||
|
// Returns true if something was updated, false otherwise.
|
||||||
|
func AddOrUpdateTolerationInPod(pod *core.Pod, toleration *core.Toleration) bool {
|
||||||
|
podTolerations := pod.Spec.Tolerations
|
||||||
|
|
||||||
|
var newTolerations []core.Toleration
|
||||||
|
updated := false
|
||||||
|
for i := range podTolerations {
|
||||||
|
if toleration.MatchToleration(&podTolerations[i]) {
|
||||||
|
if Semantic.DeepEqual(toleration, podTolerations[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
newTolerations = append(newTolerations, *toleration)
|
||||||
|
updated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newTolerations = append(newTolerations, podTolerations[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
newTolerations = append(newTolerations, *toleration)
|
||||||
|
}
|
||||||
|
|
||||||
|
pod.Spec.Tolerations = newTolerations
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations
|
||||||
|
// and converts it to the []Taint type in core.
|
||||||
|
func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]core.Taint, error) {
|
||||||
|
var taints []core.Taint
|
||||||
|
if len(annotations) > 0 && annotations[core.TaintsAnnotationKey] != "" {
|
||||||
|
err := json.Unmarshal([]byte(annotations[core.TaintsAnnotationKey]), &taints)
|
||||||
|
if err != nil {
|
||||||
|
return []core.Taint{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return taints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersistentVolumeClass returns StorageClassName.
|
||||||
|
func GetPersistentVolumeClass(volume *core.PersistentVolume) string {
|
||||||
|
// Use beta annotation first
|
||||||
|
if class, found := volume.Annotations[core.BetaStorageClassAnnotation]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume.Spec.StorageClassName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was
|
||||||
|
// requested, it returns "".
|
||||||
|
func GetPersistentVolumeClaimClass(claim *core.PersistentVolumeClaim) string {
|
||||||
|
// Use beta annotation first
|
||||||
|
if class, found := claim.Annotations[core.BetaStorageClassAnnotation]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
|
||||||
|
if claim.Spec.StorageClassName != nil {
|
||||||
|
return *claim.Spec.StorageClassName
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field.
|
||||||
|
func PersistentVolumeClaimHasClass(claim *core.PersistentVolumeClaim) bool {
|
||||||
|
// Use beta annotation first
|
||||||
|
if _, found := claim.Annotations[core.BetaStorageClassAnnotation]; found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if claim.Spec.StorageClassName != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
527
vendor/k8s.io/kubernetes/pkg/apis/core/v1/helper/helpers.go
generated
vendored
Normal file
527
vendor/k8s.io/kubernetes/pkg/apis/core/v1/helper/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsExtendedResourceName returns true if:
|
||||||
|
// 1. the resource name is not in the default namespace;
|
||||||
|
// 2. resource name does not have "requests." prefix,
|
||||||
|
// to avoid confusion with the convention in quota
|
||||||
|
// 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||||
|
func IsExtendedResourceName(name v1.ResourceName) bool {
|
||||||
|
if IsNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name
|
||||||
|
nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name))
|
||||||
|
if errs := validation.IsQualifiedName(string(nameForQuota)); len(errs) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPrefixedNativeResource returns true if the resource name is in the
|
||||||
|
// *kubernetes.io/ namespace.
|
||||||
|
func IsPrefixedNativeResource(name v1.ResourceName) bool {
|
||||||
|
return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNativeResource returns true if the resource name is in the
|
||||||
|
// *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are
|
||||||
|
// implicitly in the kubernetes.io/ namespace.
|
||||||
|
func IsNativeResource(name v1.ResourceName) bool {
|
||||||
|
return !strings.Contains(string(name), "/") ||
|
||||||
|
IsPrefixedNativeResource(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHugePageResourceName returns true if the resource name has the huge page
|
||||||
|
// resource prefix.
|
||||||
|
func IsHugePageResourceName(name v1.ResourceName) bool {
|
||||||
|
return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HugePageResourceName returns a ResourceName with the canonical hugepage
|
||||||
|
// prefix prepended for the specified page size. The page size is converted
|
||||||
|
// to its canonical representation.
|
||||||
|
func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName {
|
||||||
|
return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HugePageSizeFromResourceName returns the page size for the specified huge page
|
||||||
|
// resource name. If the specified input is not a valid huge page resource name
|
||||||
|
// an error is returned.
|
||||||
|
func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) {
|
||||||
|
if !IsHugePageResourceName(name) {
|
||||||
|
return resource.Quantity{}, fmt.Errorf("resource name: %s is an invalid hugepage name", name)
|
||||||
|
}
|
||||||
|
pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix)
|
||||||
|
return resource.ParseQuantity(pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOvercommitAllowed returns true if the resource is in the default
|
||||||
|
// namespace and is not hugepages.
|
||||||
|
func IsOvercommitAllowed(name v1.ResourceName) bool {
|
||||||
|
return IsNativeResource(name) &&
|
||||||
|
!IsHugePageResourceName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAttachableVolumeResourceName(name v1.ResourceName) bool {
|
||||||
|
return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended and Hugepages resources
|
||||||
|
func IsScalarResourceName(name v1.ResourceName) bool {
|
||||||
|
return IsExtendedResourceName(name) || IsHugePageResourceName(name) ||
|
||||||
|
IsPrefixedNativeResource(name) || IsAttachableVolumeResourceName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function aims to check if the service's ClusterIP is set or not
|
||||||
|
// the objective is not to perform validation here
|
||||||
|
func IsServiceIPSet(service *v1.Service) bool {
|
||||||
|
return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToNodeAddresses appends the NodeAddresses to the passed-by-pointer slice,
|
||||||
|
// only if they do not already exist
|
||||||
|
func AddToNodeAddresses(addresses *[]v1.NodeAddress, addAddresses ...v1.NodeAddress) {
|
||||||
|
for _, add := range addAddresses {
|
||||||
|
exists := false
|
||||||
|
for _, existing := range *addresses {
|
||||||
|
if existing.Address == add.Address && existing.Type == add.Type {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
*addresses = append(*addresses, add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make method on LoadBalancerStatus?
|
||||||
|
func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool {
|
||||||
|
return ingressSliceEqual(l.Ingress, r.Ingress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool {
|
||||||
|
if len(lhs) != len(rhs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range lhs {
|
||||||
|
if !ingressEqual(&lhs[i], &rhs[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool {
|
||||||
|
if lhs.IP != rhs.IP {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.Hostname != rhs.Hostname {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make method on LoadBalancerStatus?
|
||||||
|
func LoadBalancerStatusDeepCopy(lb *v1.LoadBalancerStatus) *v1.LoadBalancerStatus {
|
||||||
|
c := &v1.LoadBalancerStatus{}
|
||||||
|
c.Ingress = make([]v1.LoadBalancerIngress, len(lb.Ingress))
|
||||||
|
for i := range lb.Ingress {
|
||||||
|
c.Ingress[i] = lb.Ingress[i]
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessModesAsString returns a string representation of an array of access modes.
|
||||||
|
// modes, when present, are always in the same order: RWO,ROX,RWX.
|
||||||
|
func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string {
|
||||||
|
modes = removeDuplicateAccessModes(modes)
|
||||||
|
modesStr := []string{}
|
||||||
|
if containsAccessMode(modes, v1.ReadWriteOnce) {
|
||||||
|
modesStr = append(modesStr, "RWO")
|
||||||
|
}
|
||||||
|
if containsAccessMode(modes, v1.ReadOnlyMany) {
|
||||||
|
modesStr = append(modesStr, "ROX")
|
||||||
|
}
|
||||||
|
if containsAccessMode(modes, v1.ReadWriteMany) {
|
||||||
|
modesStr = append(modesStr, "RWX")
|
||||||
|
}
|
||||||
|
return strings.Join(modesStr, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessModesAsString returns an array of AccessModes from a string created by GetAccessModesAsString
|
||||||
|
func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode {
|
||||||
|
strmodes := strings.Split(modes, ",")
|
||||||
|
accessModes := []v1.PersistentVolumeAccessMode{}
|
||||||
|
for _, s := range strmodes {
|
||||||
|
s = strings.Trim(s, " ")
|
||||||
|
switch {
|
||||||
|
case s == "RWO":
|
||||||
|
accessModes = append(accessModes, v1.ReadWriteOnce)
|
||||||
|
case s == "ROX":
|
||||||
|
accessModes = append(accessModes, v1.ReadOnlyMany)
|
||||||
|
case s == "RWX":
|
||||||
|
accessModes = append(accessModes, v1.ReadWriteMany)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessModes
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeDuplicateAccessModes returns an array of access modes without any duplicates
|
||||||
|
func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
||||||
|
accessModes := []v1.PersistentVolumeAccessMode{}
|
||||||
|
for _, m := range modes {
|
||||||
|
if !containsAccessMode(accessModes, m) {
|
||||||
|
accessModes = append(accessModes, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accessModes
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||||
|
for _, m := range modes {
|
||||||
|
if m == mode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements
|
||||||
|
// labels.Selector.
|
||||||
|
func NodeSelectorRequirementsAsSelector(nsm []v1.NodeSelectorRequirement) (labels.Selector, error) {
|
||||||
|
if len(nsm) == 0 {
|
||||||
|
return labels.Nothing(), nil
|
||||||
|
}
|
||||||
|
selector := labels.NewSelector()
|
||||||
|
for _, expr := range nsm {
|
||||||
|
var op selection.Operator
|
||||||
|
switch expr.Operator {
|
||||||
|
case v1.NodeSelectorOpIn:
|
||||||
|
op = selection.In
|
||||||
|
case v1.NodeSelectorOpNotIn:
|
||||||
|
op = selection.NotIn
|
||||||
|
case v1.NodeSelectorOpExists:
|
||||||
|
op = selection.Exists
|
||||||
|
case v1.NodeSelectorOpDoesNotExist:
|
||||||
|
op = selection.DoesNotExist
|
||||||
|
case v1.NodeSelectorOpGt:
|
||||||
|
op = selection.GreaterThan
|
||||||
|
case v1.NodeSelectorOpLt:
|
||||||
|
op = selection.LessThan
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%q is not a valid node selector operator", expr.Operator)
|
||||||
|
}
|
||||||
|
r, err := labels.NewRequirement(expr.Key, op, expr.Values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector = selector.Add(*r)
|
||||||
|
}
|
||||||
|
return selector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSelectorRequirementsAsFieldSelector converts the []NodeSelectorRequirement core type into a struct that implements
|
||||||
|
// fields.Selector.
|
||||||
|
func NodeSelectorRequirementsAsFieldSelector(nsm []v1.NodeSelectorRequirement) (fields.Selector, error) {
|
||||||
|
if len(nsm) == 0 {
|
||||||
|
return fields.Nothing(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selectors := []fields.Selector{}
|
||||||
|
for _, expr := range nsm {
|
||||||
|
switch expr.Operator {
|
||||||
|
case v1.NodeSelectorOpIn:
|
||||||
|
if len(expr.Values) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||||
|
len(expr.Values), expr.Operator)
|
||||||
|
}
|
||||||
|
selectors = append(selectors, fields.OneTermEqualSelector(expr.Key, expr.Values[0]))
|
||||||
|
|
||||||
|
case v1.NodeSelectorOpNotIn:
|
||||||
|
if len(expr.Values) != 1 {
|
||||||
|
return nil, fmt.Errorf("unexpected number of value (%d) for node field selector operator %q",
|
||||||
|
len(expr.Values), expr.Operator)
|
||||||
|
}
|
||||||
|
selectors = append(selectors, fields.OneTermNotEqualSelector(expr.Key, expr.Values[0]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%q is not a valid node field selector operator", expr.Operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields.AndSelectors(selectors...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeSelectorRequirementKeysExistInNodeSelectorTerms checks if a NodeSelectorTerm with key is already specified in terms
|
||||||
|
func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorRequirement, terms []v1.NodeSelectorTerm) bool {
|
||||||
|
for _, req := range reqs {
|
||||||
|
for _, term := range terms {
|
||||||
|
for _, r := range term.MatchExpressions {
|
||||||
|
if r.Key == req.Key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchNodeSelectorTerms checks whether the node labels and fields match node selector terms in ORed;
|
||||||
|
// nil or empty term matches no objects.
|
||||||
|
func MatchNodeSelectorTerms(
|
||||||
|
nodeSelectorTerms []v1.NodeSelectorTerm,
|
||||||
|
nodeLabels labels.Set,
|
||||||
|
nodeFields fields.Set,
|
||||||
|
) bool {
|
||||||
|
for _, req := range nodeSelectorTerms {
|
||||||
|
// nil or empty term selects no objects
|
||||||
|
if len(req.MatchExpressions) == 0 && len(req.MatchFields) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.MatchExpressions) != 0 {
|
||||||
|
labelSelector, err := NodeSelectorRequirementsAsSelector(req.MatchExpressions)
|
||||||
|
if err != nil || !labelSelector.Matches(nodeLabels) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.MatchFields) != 0 {
|
||||||
|
fieldSelector, err := NodeSelectorRequirementsAsFieldSelector(req.MatchFields)
|
||||||
|
if err != nil || !fieldSelector.Matches(nodeFields) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopologySelectorRequirementsAsSelector converts the []TopologySelectorLabelRequirement api type into a struct
|
||||||
|
// that implements labels.Selector.
|
||||||
|
func TopologySelectorRequirementsAsSelector(tsm []v1.TopologySelectorLabelRequirement) (labels.Selector, error) {
|
||||||
|
if len(tsm) == 0 {
|
||||||
|
return labels.Nothing(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selector := labels.NewSelector()
|
||||||
|
for _, expr := range tsm {
|
||||||
|
r, err := labels.NewRequirement(expr.Key, selection.In, expr.Values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector = selector.Add(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchTopologySelectorTerms checks whether given labels match topology selector terms in ORed;
|
||||||
|
// nil or empty term matches no objects; while empty term list matches all objects.
|
||||||
|
func MatchTopologySelectorTerms(topologySelectorTerms []v1.TopologySelectorTerm, lbls labels.Set) bool {
|
||||||
|
if len(topologySelectorTerms) == 0 {
|
||||||
|
// empty term list matches all objects
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, req := range topologySelectorTerms {
|
||||||
|
// nil or empty term selects no objects
|
||||||
|
if len(req.MatchLabelExpressions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
labelSelector, err := TopologySelectorRequirementsAsSelector(req.MatchLabelExpressions)
|
||||||
|
if err != nil || !labelSelector.Matches(lbls) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrUpdateTolerationInPodSpec tries to add a toleration to the toleration list in PodSpec.
|
||||||
|
// Returns true if something was updated, false otherwise.
|
||||||
|
func AddOrUpdateTolerationInPodSpec(spec *v1.PodSpec, toleration *v1.Toleration) bool {
|
||||||
|
podTolerations := spec.Tolerations
|
||||||
|
|
||||||
|
var newTolerations []v1.Toleration
|
||||||
|
updated := false
|
||||||
|
for i := range podTolerations {
|
||||||
|
if toleration.MatchToleration(&podTolerations[i]) {
|
||||||
|
if helper.Semantic.DeepEqual(toleration, podTolerations[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
newTolerations = append(newTolerations, *toleration)
|
||||||
|
updated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newTolerations = append(newTolerations, podTolerations[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
newTolerations = append(newTolerations, *toleration)
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.Tolerations = newTolerations
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list.
|
||||||
|
// Returns true if something was updated, false otherwise.
|
||||||
|
func AddOrUpdateTolerationInPod(pod *v1.Pod, toleration *v1.Toleration) bool {
|
||||||
|
return AddOrUpdateTolerationInPodSpec(&pod.Spec, toleration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TolerationsTolerateTaint checks if taint is tolerated by any of the tolerations.
|
||||||
|
func TolerationsTolerateTaint(tolerations []v1.Toleration, taint *v1.Taint) bool {
|
||||||
|
for i := range tolerations {
|
||||||
|
if tolerations[i].ToleratesTaint(taint) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type taintsFilterFunc func(*v1.Taint) bool
|
||||||
|
|
||||||
|
// TolerationsTolerateTaintsWithFilter checks if given tolerations tolerates
|
||||||
|
// all the taints that apply to the filter in given taint list.
|
||||||
|
func TolerationsTolerateTaintsWithFilter(tolerations []v1.Toleration, taints []v1.Taint, applyFilter taintsFilterFunc) bool {
|
||||||
|
if len(taints) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range taints {
|
||||||
|
if applyFilter != nil && !applyFilter(&taints[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !TolerationsTolerateTaint(tolerations, &taints[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise.
|
||||||
|
func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) {
|
||||||
|
if len(taints) == 0 {
|
||||||
|
return true, []v1.Toleration{}
|
||||||
|
}
|
||||||
|
if len(tolerations) == 0 && len(taints) > 0 {
|
||||||
|
return false, []v1.Toleration{}
|
||||||
|
}
|
||||||
|
result := []v1.Toleration{}
|
||||||
|
for i := range taints {
|
||||||
|
tolerated := false
|
||||||
|
for j := range tolerations {
|
||||||
|
if tolerations[j].ToleratesTaint(&taints[i]) {
|
||||||
|
result = append(result, tolerations[j])
|
||||||
|
tolerated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !tolerated {
|
||||||
|
return false, []v1.Toleration{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAvoidPodsFromNodeAnnotations(annotations map[string]string) (v1.AvoidPods, error) {
|
||||||
|
var avoidPods v1.AvoidPods
|
||||||
|
if len(annotations) > 0 && annotations[v1.PreferAvoidPodsAnnotationKey] != "" {
|
||||||
|
err := json.Unmarshal([]byte(annotations[v1.PreferAvoidPodsAnnotationKey]), &avoidPods)
|
||||||
|
if err != nil {
|
||||||
|
return avoidPods, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avoidPods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersistentVolumeClass returns StorageClassName.
|
||||||
|
func GetPersistentVolumeClass(volume *v1.PersistentVolume) string {
|
||||||
|
// Use beta annotation first
|
||||||
|
if class, found := volume.Annotations[v1.BetaStorageClassAnnotation]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume.Spec.StorageClassName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was
|
||||||
|
// requested, it returns "".
|
||||||
|
func GetPersistentVolumeClaimClass(claim *v1.PersistentVolumeClaim) string {
|
||||||
|
// Use beta annotation first
|
||||||
|
if class, found := claim.Annotations[v1.BetaStorageClassAnnotation]; found {
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
|
||||||
|
if claim.Spec.StorageClassName != nil {
|
||||||
|
return *claim.Spec.StorageClassName
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopedResourceSelectorRequirementsAsSelector converts the ScopedResourceSelectorRequirement api type into a struct that implements
|
||||||
|
// labels.Selector.
|
||||||
|
func ScopedResourceSelectorRequirementsAsSelector(ssr v1.ScopedResourceSelectorRequirement) (labels.Selector, error) {
|
||||||
|
selector := labels.NewSelector()
|
||||||
|
var op selection.Operator
|
||||||
|
switch ssr.Operator {
|
||||||
|
case v1.ScopeSelectorOpIn:
|
||||||
|
op = selection.In
|
||||||
|
case v1.ScopeSelectorOpNotIn:
|
||||||
|
op = selection.NotIn
|
||||||
|
case v1.ScopeSelectorOpExists:
|
||||||
|
op = selection.Exists
|
||||||
|
case v1.ScopeSelectorOpDoesNotExist:
|
||||||
|
op = selection.DoesNotExist
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%q is not a valid scope selector operator", ssr.Operator)
|
||||||
|
}
|
||||||
|
r, err := labels.NewRequirement(string(ssr.ScopeName), op, ssr.Values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector = selector.Add(*r)
|
||||||
|
return selector, nil
|
||||||
|
}
|
||||||
19
vendor/k8s.io/kubernetes/pkg/kubelet/envvars/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/kubelet/envvars/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 envvars is the package that build the environment variables that kubernetes provides
|
||||||
|
// to the containers run by it.
|
||||||
|
package envvars // import "k8s.io/kubernetes/pkg/kubelet/envvars"
|
||||||
113
vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars.go
generated
vendored
Normal file
113
vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 envvars
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromServices builds environment variables that a container is started with,
|
||||||
|
// which tell the container where to find the services it may need, which are
|
||||||
|
// provided as an argument.
|
||||||
|
func FromServices(services []*v1.Service) []v1.EnvVar {
|
||||||
|
var result []v1.EnvVar
|
||||||
|
for i := range services {
|
||||||
|
service := services[i]
|
||||||
|
|
||||||
|
// ignore services where ClusterIP is "None" or empty
|
||||||
|
// the services passed to this method should be pre-filtered
|
||||||
|
// only services that have the cluster IP set should be included here
|
||||||
|
if !v1helper.IsServiceIPSet(service) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host
|
||||||
|
name := makeEnvVariableName(service.Name) + "_SERVICE_HOST"
|
||||||
|
result = append(result, v1.EnvVar{Name: name, Value: service.Spec.ClusterIP})
|
||||||
|
// First port - give it the backwards-compatible name
|
||||||
|
name = makeEnvVariableName(service.Name) + "_SERVICE_PORT"
|
||||||
|
result = append(result, v1.EnvVar{Name: name, Value: strconv.Itoa(int(service.Spec.Ports[0].Port))})
|
||||||
|
// All named ports (only the first may be unnamed, checked in validation)
|
||||||
|
for i := range service.Spec.Ports {
|
||||||
|
sp := &service.Spec.Ports[i]
|
||||||
|
if sp.Name != "" {
|
||||||
|
pn := name + "_" + makeEnvVariableName(sp.Name)
|
||||||
|
result = append(result, v1.EnvVar{Name: pn, Value: strconv.Itoa(int(sp.Port))})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Docker-compatible vars.
|
||||||
|
result = append(result, makeLinkVariables(service)...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEnvVariableName(str string) string {
|
||||||
|
// TODO: If we simplify to "all names are DNS1123Subdomains" this
|
||||||
|
// will need two tweaks:
|
||||||
|
// 1) Handle leading digits
|
||||||
|
// 2) Handle dots
|
||||||
|
return strings.ToUpper(strings.Replace(str, "-", "_", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLinkVariables(service *v1.Service) []v1.EnvVar {
|
||||||
|
prefix := makeEnvVariableName(service.Name)
|
||||||
|
all := []v1.EnvVar{}
|
||||||
|
for i := range service.Spec.Ports {
|
||||||
|
sp := &service.Spec.Ports[i]
|
||||||
|
|
||||||
|
protocol := string(v1.ProtocolTCP)
|
||||||
|
if sp.Protocol != "" {
|
||||||
|
protocol = string(sp.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort := net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(sp.Port)))
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
// Docker special-cases the first port.
|
||||||
|
all = append(all, v1.EnvVar{
|
||||||
|
Name: prefix + "_PORT",
|
||||||
|
Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
portPrefix := fmt.Sprintf("%s_PORT_%d_%s", prefix, sp.Port, strings.ToUpper(protocol))
|
||||||
|
all = append(all, []v1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: portPrefix,
|
||||||
|
Value: fmt.Sprintf("%s://%s", strings.ToLower(protocol), hostPort),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: portPrefix + "_PROTO",
|
||||||
|
Value: strings.ToLower(protocol),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: portPrefix + "_PORT",
|
||||||
|
Value: strconv.Itoa(int(sp.Port)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: portPrefix + "_ADDR",
|
||||||
|
Value: service.Spec.ClusterIP,
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
return all
|
||||||
|
}
|
||||||
77
vkubelet/env.go
Normal file → Executable file
77
vkubelet/env.go
Normal file → Executable file
@@ -8,10 +8,14 @@ import (
|
|||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
apivalidation "k8s.io/apimachinery/pkg/util/validation"
|
apivalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||||
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
fieldpath "k8s.io/kubernetes/pkg/fieldpath"
|
fieldpath "k8s.io/kubernetes/pkg/fieldpath"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/envvars"
|
||||||
|
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/log"
|
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||||
@@ -50,9 +54,12 @@ const (
|
|||||||
ReasonInvalidEnvironmentVariableNames = "InvalidEnvironmentVariableNames"
|
ReasonInvalidEnvironmentVariableNames = "InvalidEnvironmentVariableNames"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var masterServices = sets.NewString("kubernetes")
|
||||||
|
|
||||||
// populateEnvironmentVariables populates the environment of each container (and init container) in the specified pod.
|
// populateEnvironmentVariables populates the environment of each container (and init container) in the specified pod.
|
||||||
// TODO Make this the single exported function of a "pkg/environment" package in the future.
|
// TODO Make this the single exported function of a "pkg/environment" package in the future.
|
||||||
func populateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, rm *manager.ResourceManager, recorder record.EventRecorder) error {
|
func populateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, rm *manager.ResourceManager, recorder record.EventRecorder) error {
|
||||||
|
|
||||||
// Populate each init container's environment.
|
// Populate each init container's environment.
|
||||||
for idx := range pod.Spec.InitContainers {
|
for idx := range pod.Spec.InitContainers {
|
||||||
if err := populateContainerEnvironment(ctx, pod, &pod.Spec.InitContainers[idx], rm, recorder); err != nil {
|
if err := populateContainerEnvironment(ctx, pod, &pod.Spec.InitContainers[idx], rm, recorder); err != nil {
|
||||||
@@ -89,6 +96,53 @@ func populateContainerEnvironment(ctx context.Context, pod *corev1.Pod, containe
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getServiceEnvVarMap makes a map[string]string of env vars for services a
|
||||||
|
// pod in namespace ns should see.
|
||||||
|
// Based on getServiceEnvVarMap in kubelet_pods.go.
|
||||||
|
func getServiceEnvVarMap(rm *manager.ResourceManager, ns string, enableServiceLinks bool) (map[string]string, error) {
|
||||||
|
var (
|
||||||
|
serviceMap = make(map[string]*corev1.Service)
|
||||||
|
m = make(map[string]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
services, err := rm.ListServices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// project the services in namespace ns onto the master services
|
||||||
|
for i := range services {
|
||||||
|
service := services[i]
|
||||||
|
// ignore services where ClusterIP is "None" or empty
|
||||||
|
if !v1helper.IsServiceIPSet(service) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
serviceName := service.Name
|
||||||
|
|
||||||
|
// We always want to add environment variables for master kubernetes service
|
||||||
|
// from the default namespace, even if enableServiceLinks is false.
|
||||||
|
// We also add environment variables for other services in the same
|
||||||
|
// namespace, if enableServiceLinks is true.
|
||||||
|
if service.Namespace == metav1.NamespaceDefault && masterServices.Has(serviceName) {
|
||||||
|
if _, exists := serviceMap[serviceName]; !exists {
|
||||||
|
serviceMap[serviceName] = service
|
||||||
|
}
|
||||||
|
} else if service.Namespace == ns && enableServiceLinks {
|
||||||
|
serviceMap[serviceName] = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedServices := make([]*corev1.Service, 0, len(serviceMap))
|
||||||
|
for key := range serviceMap {
|
||||||
|
mappedServices = append(mappedServices, serviceMap[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range envvars.FromServices(mappedServices) {
|
||||||
|
m[e.Name] = e.Value
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// makeEnvironmentMapBasedOnEnvFrom returns a map representing the resolved environment of the specified container after being populated from the entries in the ".envFrom" field.
|
// makeEnvironmentMapBasedOnEnvFrom returns a map representing the resolved environment of the specified container after being populated from the entries in the ".envFrom" field.
|
||||||
func makeEnvironmentMapBasedOnEnvFrom(ctx context.Context, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (map[string]string, error) {
|
func makeEnvironmentMapBasedOnEnvFrom(ctx context.Context, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (map[string]string, error) {
|
||||||
// Create a map to hold the resulting environment.
|
// Create a map to hold the resulting environment.
|
||||||
@@ -347,6 +401,29 @@ loop:
|
|||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO If pod.Spec.EnableServiceLinks is nil then fail as per 1.14 kubelet.
|
||||||
|
enableServiceLinks := corev1.DefaultEnableServiceLinks
|
||||||
|
if pod.Spec.EnableServiceLinks != nil {
|
||||||
|
enableServiceLinks = *pod.Spec.EnableServiceLinks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that there is a race between Kubelet seeing the pod and kubelet seeing the service.
|
||||||
|
// To avoid this users can: (1) wait between starting a service and starting; or (2) detect
|
||||||
|
// missing service env var and exit and be restarted; or (3) use DNS instead of env vars
|
||||||
|
// and keep trying to resolve the DNS name of the service (recommended).
|
||||||
|
svcEnv, err := getServiceEnvVarMap(rm, pod.Namespace, enableServiceLinks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append service env vars.
|
||||||
|
for k, v := range svcEnv {
|
||||||
|
if _, present := res[k]; !present {
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return the populated environment.
|
// Return the populated environment.
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ func TestPopulatePodWithInitContainersUsingEnv(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +352,7 @@ func TestPopulatePodWithInitContainersUsingEnvWithFieldRef(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,6 +468,7 @@ func TestPopulatePodWithInitContainersUsingEnvFrom(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +547,7 @@ func TestEnvFromTwoConfigMapsAndOneSecret(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,6 +609,7 @@ func TestEnvFromConfigMapAndSecretWithInvalidKeys(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,6 +680,7 @@ func TestEnvOverridesEnvFrom(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,6 +757,7 @@ func TestEnvFromInexistentConfigMaps(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +814,7 @@ func TestEnvFromInexistentSecrets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,6 +867,7 @@ func TestEnvReferencingInexistentConfigMapKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,6 +917,7 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EnableServiceLinks: &bFalse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,3 +931,83 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
|
|||||||
assert.Check(t, is.Contains(event1, ReasonMandatorySecretNotFound))
|
assert.Check(t, is.Contains(event1, ReasonMandatorySecretNotFound))
|
||||||
assert.Check(t, is.Contains(event1, missingSecretName))
|
assert.Check(t, is.Contains(event1, missingSecretName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestServiceEnvVar tries to populate the environment of a container using services with ServiceLinks enabled and disabled.
|
||||||
|
func TestServiceEnvVar(t *testing.T) {
|
||||||
|
namespace2 := "namespace-02"
|
||||||
|
|
||||||
|
service1 := testutil.FakeService(metav1.NamespaceDefault, "kubernetes", "1.2.3.1", "TCP", 8081)
|
||||||
|
service2 := testutil.FakeService(namespace, "test", "1.2.3.3", "TCP", 8083)
|
||||||
|
// unused svc to show it isn't populated within a different namespace.
|
||||||
|
service3 := testutil.FakeService(namespace2, "unused", "1.2.3.4", "TCP", 8084)
|
||||||
|
|
||||||
|
rm := testutil.FakeResourceManager(service1, service2, service3)
|
||||||
|
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
|
||||||
|
|
||||||
|
pod := &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: "test-pod-name",
|
||||||
|
},
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Env: []corev1.EnvVar{
|
||||||
|
{Name: envVarName1, Value: envVarValue1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string // the name of the test case
|
||||||
|
enableServiceLinks *bool // enabling service links
|
||||||
|
expectedEnvs []corev1.EnvVar // a set of expected environment vars
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ServiceLinks disabled",
|
||||||
|
enableServiceLinks: &bFalse,
|
||||||
|
expectedEnvs: []corev1.EnvVar{
|
||||||
|
{Name: envVarName1, Value: envVarValue1},
|
||||||
|
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
|
||||||
|
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
|
||||||
|
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ServiceLinks enabled",
|
||||||
|
enableServiceLinks: &bTrue,
|
||||||
|
expectedEnvs: []corev1.EnvVar{
|
||||||
|
{Name: envVarName1, Value: envVarValue1},
|
||||||
|
{Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"},
|
||||||
|
{Name: "TEST_SERVICE_PORT", Value: "8083"},
|
||||||
|
{Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"},
|
||||||
|
{Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"},
|
||||||
|
{Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"},
|
||||||
|
{Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"},
|
||||||
|
{Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"},
|
||||||
|
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
|
||||||
|
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
|
||||||
|
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
|
||||||
|
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
pod.Spec.EnableServiceLinks = tc.enableServiceLinks
|
||||||
|
|
||||||
|
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
|
||||||
|
assert.NilError(t, err, "[%s]", tc.name)
|
||||||
|
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, tc.expectedEnvs, sortOpt))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user