Commit 49f639c0 authored by Hannes Hoerl's avatar Hannes Hoerl

Merge remote-tracking branch 'origin/master' into release-1.14

parents 1c2d649e 60d0ec57
...@@ -17546,6 +17546,10 @@ ...@@ -17546,6 +17546,10 @@
"io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": {
"description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.",
"properties": { "properties": {
"resourceVersion": {
"description": "Specifies the target ResourceVersion",
"type": "string"
},
"uid": { "uid": {
"description": "Specifies the target UID.", "description": "Specifies the target UID.",
"type": "string" "type": "string"
...@@ -19,16 +19,6 @@ http_archive( ...@@ -19,16 +19,6 @@ http_archive(
urls = mirror("https://github.com/kubernetes/repo-infra/archive/b461270ab6ccfb94ff2d78df96d26f669376d660.tar.gz"), urls = mirror("https://github.com/kubernetes/repo-infra/archive/b461270ab6ccfb94ff2d78df96d26f669376d660.tar.gz"),
) )
ETCD_VERSION = "3.3.10"
http_archive(
name = "com_coreos_etcd",
build_file = "@//third_party:etcd.BUILD",
sha256 = "1620a59150ec0a0124a65540e23891243feb2d9a628092fb1edcc23974724a45",
strip_prefix = "etcd-v%s-linux-amd64" % ETCD_VERSION,
urls = mirror("https://github.com/coreos/etcd/releases/download/v%s/etcd-v%s-linux-amd64.tar.gz" % (ETCD_VERSION, ETCD_VERSION)),
)
http_archive( http_archive(
name = "io_bazel_rules_go", name = "io_bazel_rules_go",
sha256 = "6776d68ebb897625dead17ae510eac3d5f6342367327875210df44dbe2aeeb19", sha256 = "6776d68ebb897625dead17ae510eac3d5f6342367327875210df44dbe2aeeb19",
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
load("//build:platforms.bzl", "SERVER_PLATFORMS") load("//build:platforms.bzl", "SERVER_PLATFORMS")
load("//build:workspace_mirror.bzl", "mirror") load("//build:workspace_mirror.bzl", "mirror")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("@io_bazel_rules_docker//container:container.bzl", "container_pull") load("@io_bazel_rules_docker//container:container.bzl", "container_pull")
CNI_VERSION = "0.6.0" CNI_VERSION = "0.6.0"
...@@ -35,6 +35,13 @@ _CRI_TARBALL_ARCH_SHA256 = { ...@@ -35,6 +35,13 @@ _CRI_TARBALL_ARCH_SHA256 = {
"s390x": "814aa9cd496be416612c2653097a1c9eb5784e38aa4889034b44ebf888709057", "s390x": "814aa9cd496be416612c2653097a1c9eb5784e38aa4889034b44ebf888709057",
} }
ETCD_VERSION = "3.3.10"
_ETCD_TARBALL_ARCH_SHA256 = {
"amd64": "1620a59150ec0a0124a65540e23891243feb2d9a628092fb1edcc23974724a45",
"arm64": "5ec97b0b872adce275b8130d19db314f7f2b803aeb24c4aae17a19e2d66853c4",
"ppc64le": "148fe96f0ec1813c5db9916199e96a913174304546bc8447a2d2f9fee4b8f6c2",
}
# Note that these are digests for the manifest list. We resolve the manifest # Note that these are digests for the manifest list. We resolve the manifest
# list to each of its platform-specific images in # list to each of its platform-specific images in
# debian_image_dependencies(). # debian_image_dependencies().
...@@ -48,6 +55,7 @@ def release_dependencies(): ...@@ -48,6 +55,7 @@ def release_dependencies():
cni_tarballs() cni_tarballs()
cri_tarballs() cri_tarballs()
debian_image_dependencies() debian_image_dependencies()
etcd_tarballs()
def cni_tarballs(): def cni_tarballs():
for arch, sha in _CNI_TARBALL_ARCH_SHA256.items(): for arch, sha in _CNI_TARBALL_ARCH_SHA256.items():
...@@ -92,3 +100,13 @@ def debian_image_dependencies(): ...@@ -92,3 +100,13 @@ def debian_image_dependencies():
registry = "k8s.gcr.io", registry = "k8s.gcr.io",
repository = "debian-hyperkube-base", repository = "debian-hyperkube-base",
) )
def etcd_tarballs():
for arch, sha in _ETCD_TARBALL_ARCH_SHA256.items():
http_archive(
name = "com_coreos_etcd_%s" % arch,
build_file = "@//third_party:etcd.BUILD",
sha256 = sha,
strip_prefix = "etcd-v%s-linux-%s" % (ETCD_VERSION, arch),
urls = mirror("https://github.com/coreos/etcd/releases/download/v%s/etcd-v%s-linux-%s.tar.gz" % (ETCD_VERSION, ETCD_VERSION, arch)),
)
...@@ -169,27 +169,47 @@ func (az *Cloud) UpdateLoadBalancer(ctx context.Context, clusterName string, ser ...@@ -169,27 +169,47 @@ func (az *Cloud) UpdateLoadBalancer(ctx context.Context, clusterName string, ser
func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error { func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error {
isInternal := requiresInternalLoadBalancer(service) isInternal := requiresInternalLoadBalancer(service)
serviceName := getServiceName(service) serviceName := getServiceName(service)
klog.V(5).Infof("delete(%s): START clusterName=%q", serviceName, clusterName) klog.V(5).Infof("Delete service (%s): START clusterName=%q", serviceName, clusterName)
ignoreErrors := func(err error) error {
if ignoreStatusNotFoundFromError(err) == nil {
klog.V(5).Infof("EnsureLoadBalancerDeleted: ignoring StatusNotFound error because the resource doesn't exist (%v)", err)
return nil
}
if ignoreStatusForbiddenFromError(err) == nil {
klog.V(5).Infof("EnsureLoadBalancerDeleted: ignoring StatusForbidden error (%v). This may be caused by wrong configuration via service annotations", err)
return nil
}
return err
}
serviceIPToCleanup, err := az.findServiceIPAddress(ctx, clusterName, service, isInternal) serviceIPToCleanup, err := az.findServiceIPAddress(ctx, clusterName, service, isInternal)
if err != nil { if ignoreErrors(err) != nil {
return err return err
} }
klog.V(2).Infof("EnsureLoadBalancerDeleted: reconciling security group for service %q with IP %q, wantLb = false", serviceName, serviceIPToCleanup) klog.V(2).Infof("EnsureLoadBalancerDeleted: reconciling security group for service %q with IP %q, wantLb = false", serviceName, serviceIPToCleanup)
if _, err := az.reconcileSecurityGroup(clusterName, service, &serviceIPToCleanup, false /* wantLb */); err != nil { if _, err := az.reconcileSecurityGroup(clusterName, service, &serviceIPToCleanup, false /* wantLb */); err != nil {
return err if ignoreErrors(err) != nil {
return err
}
} }
if _, err := az.reconcileLoadBalancer(clusterName, service, nil, false /* wantLb */); err != nil { if _, err := az.reconcileLoadBalancer(clusterName, service, nil, false /* wantLb */); err != nil {
return err if ignoreErrors(err) != nil {
return err
}
} }
if _, err := az.reconcilePublicIP(clusterName, service, nil, false /* wantLb */); err != nil { if _, err := az.reconcilePublicIP(clusterName, service, nil, false /* wantLb */); err != nil {
return err if ignoreErrors(err) != nil {
return err
}
} }
klog.V(2).Infof("delete(%s): FINISH", serviceName) klog.V(2).Infof("Delete service (%s): FINISH", serviceName)
return nil return nil
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package azure package azure
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
...@@ -308,3 +309,62 @@ func TestSubnet(t *testing.T) { ...@@ -308,3 +309,62 @@ func TestSubnet(t *testing.T) {
assert.Equal(t, c.expected, real, fmt.Sprintf("TestCase[%d]: %s", i, c.desc)) assert.Equal(t, c.expected, real, fmt.Sprintf("TestCase[%d]: %s", i, c.desc))
} }
} }
func TestEnsureLoadBalancerDeleted(t *testing.T) {
const vmCount = 8
const availabilitySetCount = 4
const serviceCount = 9
tests := []struct {
desc string
service v1.Service
expectCreateError bool
}{
{
desc: "external service should be created and deleted successfully",
service: getTestService("test1", v1.ProtocolTCP, 80),
},
{
desc: "internal service should be created and deleted successfully",
service: getInternalTestService("test2", 80),
},
{
desc: "annotated service with same resourceGroup should be created and deleted successfully",
service: getResourceGroupTestService("test3", "rg", "", 80),
},
{
desc: "annotated service with different resourceGroup shouldn't be created but should be deleted successfully",
service: getResourceGroupTestService("test4", "random-rg", "1.2.3.4", 80),
expectCreateError: true,
},
}
az := getTestCloud()
for i, c := range tests {
clusterResources := getClusterResources(az, vmCount, availabilitySetCount)
getTestSecurityGroup(az)
if c.service.Annotations[ServiceAnnotationLoadBalancerInternal] == "true" {
addTestSubnet(t, az, &c.service)
}
// create the service first.
lbStatus, err := az.EnsureLoadBalancer(context.TODO(), testClusterName, &c.service, clusterResources.nodes)
if c.expectCreateError {
assert.NotNil(t, err, "TestCase[%d]: %s", i, c.desc)
} else {
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
assert.NotNil(t, lbStatus, "TestCase[%d]: %s", i, c.desc)
result, err := az.LoadBalancerClient.List(context.TODO(), az.Config.ResourceGroup)
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
assert.Equal(t, len(result), 1, "TestCase[%d]: %s", i, c.desc)
assert.Equal(t, len(*result[0].LoadBalancingRules), 1, "TestCase[%d]: %s", i, c.desc)
}
// finally, delete it.
err = az.EnsureLoadBalancerDeleted(context.TODO(), testClusterName, &c.service)
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
result, err := az.LoadBalancerClient.List(context.Background(), az.Config.ResourceGroup)
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
assert.Equal(t, len(result), 0, "TestCase[%d]: %s", i, c.desc)
}
}
...@@ -1137,6 +1137,13 @@ func getInternalTestService(identifier string, requestedPorts ...int32) v1.Servi ...@@ -1137,6 +1137,13 @@ func getInternalTestService(identifier string, requestedPorts ...int32) v1.Servi
return svc return svc
} }
func getResourceGroupTestService(identifier, resourceGroup, loadBalancerIP string, requestedPorts ...int32) v1.Service {
svc := getTestService(identifier, v1.ProtocolTCP, requestedPorts...)
svc.Spec.LoadBalancerIP = loadBalancerIP
svc.Annotations[ServiceAnnotationLoadBalancerResourceGroup] = resourceGroup
return svc
}
func setLoadBalancerModeAnnotation(service *v1.Service, lbMode string) { func setLoadBalancerModeAnnotation(service *v1.Service, lbMode string) {
service.Annotations[ServiceAnnotationLoadBalancerMode] = lbMode service.Annotations[ServiceAnnotationLoadBalancerMode] = lbMode
} }
......
...@@ -71,6 +71,19 @@ func ignoreStatusNotFoundFromError(err error) error { ...@@ -71,6 +71,19 @@ func ignoreStatusNotFoundFromError(err error) error {
return err return err
} }
// ignoreStatusForbiddenFromError returns nil if the status code is StatusForbidden.
// This happens when AuthorizationFailed is reported from Azure API.
func ignoreStatusForbiddenFromError(err error) error {
if err == nil {
return nil
}
v, ok := err.(autorest.DetailedError)
if ok && v.StatusCode == http.StatusForbidden {
return nil
}
return err
}
/// getVirtualMachine calls 'VirtualMachinesClient.Get' with a timed cache /// getVirtualMachine calls 'VirtualMachinesClient.Get' with a timed cache
/// The service side has throttling control that delays responses if there're multiple requests onto certain vm /// The service side has throttling control that delays responses if there're multiple requests onto certain vm
/// resource request in short period. /// resource request in short period.
......
...@@ -369,6 +369,7 @@ var iptablesJumpChains = []iptablesJumpChain{ ...@@ -369,6 +369,7 @@ var iptablesJumpChains = []iptablesJumpChain{
{utiliptables.TableFilter, kubeExternalServicesChain, utiliptables.ChainInput, "kubernetes externally-visible service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}}, {utiliptables.TableFilter, kubeExternalServicesChain, utiliptables.ChainInput, "kubernetes externally-visible service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}},
{utiliptables.TableFilter, kubeServicesChain, utiliptables.ChainForward, "kubernetes service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}}, {utiliptables.TableFilter, kubeServicesChain, utiliptables.ChainForward, "kubernetes service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}},
{utiliptables.TableFilter, kubeServicesChain, utiliptables.ChainOutput, "kubernetes service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}}, {utiliptables.TableFilter, kubeServicesChain, utiliptables.ChainOutput, "kubernetes service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}},
{utiliptables.TableFilter, kubeServicesChain, utiliptables.ChainInput, "kubernetes service portals", []string{"-m", "conntrack", "--ctstate", "NEW"}},
{utiliptables.TableFilter, kubeForwardChain, utiliptables.ChainForward, "kubernetes forwarding rules", nil}, {utiliptables.TableFilter, kubeForwardChain, utiliptables.ChainForward, "kubernetes forwarding rules", nil},
{utiliptables.TableNAT, kubeServicesChain, utiliptables.ChainOutput, "kubernetes service portals", nil}, {utiliptables.TableNAT, kubeServicesChain, utiliptables.ChainOutput, "kubernetes service portals", nil},
{utiliptables.TableNAT, kubeServicesChain, utiliptables.ChainPrerouting, "kubernetes service portals", nil}, {utiliptables.TableNAT, kubeServicesChain, utiliptables.ChainPrerouting, "kubernetes service portals", nil},
...@@ -847,6 +848,7 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -847,6 +848,7 @@ func (proxier *Proxier) syncProxyRules() {
} }
writeLine(proxier.natRules, append(args, "-j", string(svcChain))...) writeLine(proxier.natRules, append(args, "-j", string(svcChain))...)
} else { } else {
// No endpoints.
writeLine(proxier.filterRules, writeLine(proxier.filterRules,
"-A", string(kubeServicesChain), "-A", string(kubeServicesChain),
"-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString), "-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString),
...@@ -917,6 +919,7 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -917,6 +919,7 @@ func (proxier *Proxier) syncProxyRules() {
// This covers cases like GCE load-balancers which get added to the local routing table. // This covers cases like GCE load-balancers which get added to the local routing table.
writeLine(proxier.natRules, append(dstLocalOnlyArgs, "-j", string(svcChain))...) writeLine(proxier.natRules, append(dstLocalOnlyArgs, "-j", string(svcChain))...)
} else { } else {
// No endpoints.
writeLine(proxier.filterRules, writeLine(proxier.filterRules,
"-A", string(kubeExternalServicesChain), "-A", string(kubeExternalServicesChain),
"-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString), "-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString),
...@@ -929,10 +932,10 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -929,10 +932,10 @@ func (proxier *Proxier) syncProxyRules() {
} }
// Capture load-balancer ingress. // Capture load-balancer ingress.
if hasEndpoints { fwChain := svcInfo.serviceFirewallChainName
fwChain := svcInfo.serviceFirewallChainName for _, ingress := range svcInfo.LoadBalancerStatus.Ingress {
for _, ingress := range svcInfo.LoadBalancerStatus.Ingress { if ingress.IP != "" {
if ingress.IP != "" { if hasEndpoints {
// create service firewall chain // create service firewall chain
if chain, ok := existingNATChains[fwChain]; ok { if chain, ok := existingNATChains[fwChain]; ok {
writeBytesLine(proxier.natChains, chain) writeBytesLine(proxier.natChains, chain)
...@@ -993,10 +996,19 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -993,10 +996,19 @@ func (proxier *Proxier) syncProxyRules() {
// If the packet was able to reach the end of firewall chain, then it did not get DNATed. // If the packet was able to reach the end of firewall chain, then it did not get DNATed.
// It means the packet cannot go thru the firewall, then mark it for DROP // It means the packet cannot go thru the firewall, then mark it for DROP
writeLine(proxier.natRules, append(args, "-j", string(KubeMarkDropChain))...) writeLine(proxier.natRules, append(args, "-j", string(KubeMarkDropChain))...)
} else {
// No endpoints.
writeLine(proxier.filterRules,
"-A", string(kubeServicesChain),
"-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString),
"-m", protocol, "-p", protocol,
"-d", utilproxy.ToCIDR(net.ParseIP(ingress.IP)),
"--dport", strconv.Itoa(svcInfo.Port),
"-j", "REJECT",
)
} }
} }
} }
// FIXME: do we need REJECT rules for load-balancer ingress if !hasEndpoints?
// Capture nodeports. If we had more than 2 rules it might be // Capture nodeports. If we had more than 2 rules it might be
// worthwhile to make a new per-service chain for nodeport rules, but // worthwhile to make a new per-service chain for nodeport rules, but
...@@ -1078,6 +1090,7 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -1078,6 +1090,7 @@ func (proxier *Proxier) syncProxyRules() {
writeLine(proxier.natRules, append(args, "-j", string(svcXlbChain))...) writeLine(proxier.natRules, append(args, "-j", string(svcXlbChain))...)
} }
} else { } else {
// No endpoints.
writeLine(proxier.filterRules, writeLine(proxier.filterRules,
"-A", string(kubeExternalServicesChain), "-A", string(kubeExternalServicesChain),
"-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString), "-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcNameString),
......
...@@ -147,6 +147,14 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp ...@@ -147,6 +147,14 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
) )
return nil, false, err return nil, false, err
} }
if options.Preconditions.ResourceVersion != nil && *options.Preconditions.ResourceVersion != namespace.ResourceVersion {
err = apierrors.NewConflict(
api.Resource("namespaces"),
name,
fmt.Errorf("Precondition failed: ResourceVersion in precondition: %v, ResourceVersion in object meta: %v", *options.Preconditions.ResourceVersion, namespace.ResourceVersion),
)
return nil, false, err
}
// upon first request to delete, we switch the phase to start namespace termination // upon first request to delete, we switch the phase to start namespace termination
// TODO: enhance graceful deletion's calls to DeleteStrategy to allow phase change and finalizer patterns // TODO: enhance graceful deletion's calls to DeleteStrategy to allow phase change and finalizer patterns
...@@ -156,7 +164,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp ...@@ -156,7 +164,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
return nil, false, err return nil, false, err
} }
preconditions := storage.Preconditions{UID: options.Preconditions.UID} preconditions := storage.Preconditions{UID: options.Preconditions.UID, ResourceVersion: options.Preconditions.ResourceVersion}
out := r.store.NewFunc() out := r.store.NewFunc()
err = r.store.Storage.GuaranteedUpdate( err = r.store.Storage.GuaranteedUpdate(
......
...@@ -92,6 +92,14 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp ...@@ -92,6 +92,14 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
) )
return nil, false, err return nil, false, err
} }
if options.Preconditions.ResourceVersion != nil && *options.Preconditions.ResourceVersion != crd.ResourceVersion {
err = apierrors.NewConflict(
apiextensions.Resource("customresourcedefinitions"),
name,
fmt.Errorf("Precondition failed: ResourceVersion in precondition: %v, ResourceVersion in object meta: %v", *options.Preconditions.ResourceVersion, crd.ResourceVersion),
)
return nil, false, err
}
// upon first request to delete, add our finalizer and then delegate // upon first request to delete, add our finalizer and then delegate
if crd.DeletionTimestamp.IsZero() { if crd.DeletionTimestamp.IsZero() {
...@@ -100,7 +108,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp ...@@ -100,7 +108,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
return nil, false, err return nil, false, err
} }
preconditions := storage.Preconditions{UID: options.Preconditions.UID} preconditions := storage.Preconditions{UID: options.Preconditions.UID, ResourceVersion: options.Preconditions.ResourceVersion}
out := r.Store.NewFunc() out := r.Store.NewFunc()
err = r.Store.Storage.GuaranteedUpdate( err = r.Store.Storage.GuaranteedUpdate(
......
...@@ -754,6 +754,10 @@ message Preconditions { ...@@ -754,6 +754,10 @@ message Preconditions {
// Specifies the target UID. // Specifies the target UID.
// +optional // +optional
optional string uid = 1; optional string uid = 1;
// Specifies the target ResourceVersion
// +optional
optional string resourceVersion = 2;
} }
// RootPaths lists the paths available at root. // RootPaths lists the paths available at root.
......
...@@ -228,6 +228,12 @@ func NewUIDPreconditions(uid string) *Preconditions { ...@@ -228,6 +228,12 @@ func NewUIDPreconditions(uid string) *Preconditions {
return &Preconditions{UID: &u} return &Preconditions{UID: &u}
} }
// NewRVDeletionPrecondition returns a DeleteOptions with a ResourceVersion precondition set.
func NewRVDeletionPrecondition(rv string) *DeleteOptions {
p := Preconditions{ResourceVersion: &rv}
return &DeleteOptions{Preconditions: &p}
}
// HasObjectMetaSystemFieldValues returns true if fields that are managed by the system on ObjectMeta have values. // HasObjectMetaSystemFieldValues returns true if fields that are managed by the system on ObjectMeta have values.
func HasObjectMetaSystemFieldValues(meta Object) bool { func HasObjectMetaSystemFieldValues(meta Object) bool {
return !meta.GetCreationTimestamp().Time.IsZero() || return !meta.GetCreationTimestamp().Time.IsZero() ||
......
...@@ -575,6 +575,9 @@ type Preconditions struct { ...@@ -575,6 +575,9 @@ type Preconditions struct {
// Specifies the target UID. // Specifies the target UID.
// +optional // +optional
UID *types.UID `json:"uid,omitempty" protobuf:"bytes,1,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` UID *types.UID `json:"uid,omitempty" protobuf:"bytes,1,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"`
// Specifies the target ResourceVersion
// +optional
ResourceVersion *string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
......
...@@ -294,8 +294,9 @@ func (PatchOptions) SwaggerDoc() map[string]string { ...@@ -294,8 +294,9 @@ func (PatchOptions) SwaggerDoc() map[string]string {
} }
var map_Preconditions = map[string]string{ var map_Preconditions = map[string]string{
"": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", "": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.",
"uid": "Specifies the target UID.", "uid": "Specifies the target UID.",
"resourceVersion": "Specifies the target ResourceVersion",
} }
func (Preconditions) SwaggerDoc() map[string]string { func (Preconditions) SwaggerDoc() map[string]string {
......
...@@ -831,6 +831,11 @@ func (in *Preconditions) DeepCopyInto(out *Preconditions) { ...@@ -831,6 +831,11 @@ func (in *Preconditions) DeepCopyInto(out *Preconditions) {
*out = new(types.UID) *out = new(types.UID)
**out = **in **out = **in
} }
if in.ResourceVersion != nil {
in, out := &in.ResourceVersion, &out.ResourceVersion
*out = new(string)
**out = **in
}
return return
} }
......
...@@ -7,14 +7,17 @@ go_library( ...@@ -7,14 +7,17 @@ go_library(
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager", importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library", "//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library", "//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library", "//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
], ],
) )
...@@ -41,6 +44,7 @@ go_test( ...@@ -41,6 +44,7 @@ go_test(
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
......
...@@ -20,14 +20,17 @@ import ( ...@@ -20,14 +20,17 @@ import (
"fmt" "fmt"
"time" "time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
openapiproto "k8s.io/kube-openapi/pkg/util/proto" openapiproto "k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/fieldpath" "sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge" "sigs.k8s.io/structured-merge-diff/merge"
"sigs.k8s.io/yaml"
) )
// FieldManager updates the managed fields and merge applied // FieldManager updates the managed fields and merge applied
...@@ -149,9 +152,20 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager ...@@ -149,9 +152,20 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err) return nil, fmt.Errorf("failed to decode managed fields: %v", err)
} }
// We can assume that patchObj is already on the proper version: // Check that the patch object has the same version as the live object
// it shouldn't have to be converted so that it's not defaulted. patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
// TODO (jennybuckley): Explicitly checkt that patchObj is in the proper version.
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
return nil, fmt.Errorf("error decoding YAML: %v", err)
}
if patchObj.GetAPIVersion() != f.groupVersion.String() {
return nil,
errors.NewBadRequest(
fmt.Sprintf("Incorrect version specified in apply patch. "+
"Specified patch version: %s, expected: %s",
patchObj.GetAPIVersion(), f.groupVersion.String()))
}
liveObjVersioned, err := f.toVersioned(liveObj) liveObjVersioned, err := f.toVersioned(liveObj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err) return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
......
...@@ -18,9 +18,11 @@ package fieldmanager_test ...@@ -18,9 +18,11 @@ package fieldmanager_test
import ( import (
"errors" "errors"
"net/http"
"testing" "testing"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
...@@ -72,7 +74,7 @@ func TestApplyStripsFields(t *testing.T) { ...@@ -72,7 +74,7 @@ func TestApplyStripsFields(t *testing.T) {
obj := &corev1.Pod{} obj := &corev1.Pod{}
newObj, err := f.Apply(obj, []byte(`{ newObj, err := f.Apply(obj, []byte(`{
"apiVersion": "v1", "apiVersion": "apps/v1",
"kind": "Deployment", "kind": "Deployment",
"metadata": { "metadata": {
"name": "b", "name": "b",
...@@ -85,7 +87,7 @@ func TestApplyStripsFields(t *testing.T) { ...@@ -85,7 +87,7 @@ func TestApplyStripsFields(t *testing.T) {
"managedFields": [{ "managedFields": [{
"manager": "apply", "manager": "apply",
"operation": "Apply", "operation": "Apply",
"apiVersion": "v1", "apiVersion": "apps/v1",
"fields": { "fields": {
"f:metadata": { "f:metadata": {
"f:labels": { "f:labels": {
...@@ -111,13 +113,46 @@ func TestApplyStripsFields(t *testing.T) { ...@@ -111,13 +113,46 @@ func TestApplyStripsFields(t *testing.T) {
} }
} }
func TestVersionCheck(t *testing.T) {
f := NewTestFieldManager(t)
obj := &corev1.Pod{}
// patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors
_, err := f.Apply(obj, []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
}`), "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
// patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error
_, err = f.Apply(obj, []byte(`{
"apiVersion": "apps/v2",
"kind": "Deployment",
}`), "fieldmanager_test", false)
if err == nil {
t.Fatalf("expected an error from mismatched patch and live versions")
}
switch typ := err.(type) {
default:
t.Fatalf("expected error to be of type %T was %T", apierrors.StatusError{}, typ)
case apierrors.APIStatus:
if typ.Status().Code != http.StatusBadRequest {
t.Fatalf("expected status code to be %d but was %d",
http.StatusBadRequest, typ.Status().Code)
}
}
}
func TestApplyDoesNotStripLabels(t *testing.T) { func TestApplyDoesNotStripLabels(t *testing.T) {
f := NewTestFieldManager(t) f := NewTestFieldManager(t)
obj := &corev1.Pod{} obj := &corev1.Pod{}
newObj, err := f.Apply(obj, []byte(`{ newObj, err := f.Apply(obj, []byte(`{
"apiVersion": "v1", "apiVersion": "apps/v1",
"kind": "Pod", "kind": "Pod",
"metadata": { "metadata": {
"labels": { "labels": {
......
...@@ -105,7 +105,12 @@ func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) { ...@@ -105,7 +105,12 @@ func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
return nil, fmt.Errorf("error decoding YAML: %v", err) return nil, fmt.Errorf("error decoding YAML: %v", err)
} }
return c.ObjectToTyped(unstructured) gvk := unstructured.GetObjectKind().GroupVersionKind()
t := c.parser.Type(gvk)
if t == nil {
return nil, fmt.Errorf("no corresponding type for %v", gvk)
}
return t.FromYAML(typed.YAMLObject(string(from)))
} }
func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) { func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
......
...@@ -68,7 +68,6 @@ metadata: ...@@ -68,7 +68,6 @@ metadata:
labels: labels:
app: nginx app: nginx
spec: spec:
replicas: 3
selector: selector:
matchLabels: matchLabels:
app: nginx app: nginx
...@@ -91,7 +90,6 @@ metadata: ...@@ -91,7 +90,6 @@ metadata:
labels: labels:
app: nginx app: nginx
spec: spec:
replicas: 3
selector: selector:
matchLabels: matchLabels:
app: nginx app: nginx
......
...@@ -448,6 +448,7 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj ...@@ -448,6 +448,7 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
storagePreconditions := &storage.Preconditions{} storagePreconditions := &storage.Preconditions{}
if preconditions := objInfo.Preconditions(); preconditions != nil { if preconditions := objInfo.Preconditions(); preconditions != nil {
storagePreconditions.UID = preconditions.UID storagePreconditions.UID = preconditions.UID
storagePreconditions.ResourceVersion = preconditions.ResourceVersion
} }
out := e.NewFunc() out := e.NewFunc()
...@@ -879,6 +880,7 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO ...@@ -879,6 +880,7 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
var preconditions storage.Preconditions var preconditions storage.Preconditions
if options.Preconditions != nil { if options.Preconditions != nil {
preconditions.UID = options.Preconditions.UID preconditions.UID = options.Preconditions.UID
preconditions.ResourceVersion = options.Preconditions.ResourceVersion
} }
graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, obj, options) graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, obj, options)
if err != nil { if err != nil {
......
...@@ -77,8 +77,13 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime. ...@@ -77,8 +77,13 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
return false, false, errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs) return false, false, errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "DeleteOptions"}, "", errs)
} }
// Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too. // Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too.
if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.GetUID() { if options.Preconditions != nil {
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.GetName(), fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.GetUID())) if options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.GetUID() {
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.GetName(), fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.GetUID()))
}
if options.Preconditions.ResourceVersion != nil && *options.Preconditions.ResourceVersion != objectMeta.GetResourceVersion() {
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.GetName(), fmt.Errorf("the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified", *options.Preconditions.ResourceVersion, objectMeta.GetResourceVersion()))
}
} }
gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy) gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy)
if !ok { if !ok {
......
...@@ -909,6 +909,45 @@ func (t *Tester) testDeleteWithUID(obj runtime.Object, createFn CreateFunc, getF ...@@ -909,6 +909,45 @@ func (t *Tester) testDeleteWithUID(obj runtime.Object, createFn CreateFunc, getF
} }
} }
// This test the fast-fail path. We test that the precondition gets verified
// again before deleting the object in tests of pkg/storage/etcd.
func (t *Tester) testDeleteWithResourceVersion(obj runtime.Object, createFn CreateFunc, getFn GetFunc, isNotFoundFn IsErrorFunc, opts metav1.DeleteOptions) {
ctx := t.TestContext()
foo := obj.DeepCopyObject()
t.setObjectMeta(foo, t.namer(1))
objectMeta := t.getObjectMetaOrFail(foo)
objectMeta.SetResourceVersion("RV0000")
if err := createFn(ctx, foo); err != nil {
t.Errorf("unexpected error: %v", err)
}
opts.Preconditions = metav1.NewRVDeletionPrecondition("RV1111").Preconditions
obj, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &opts)
if err == nil || !errors.IsConflict(err) {
t.Errorf("unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, object %s should not have been deleted immediately", objectMeta.GetName())
}
obj, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewRVDeletionPrecondition("RV0000"))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !t.returnDeletedObject {
if status, ok := obj.(*metav1.Status); !ok {
t.Errorf("expected status of delete, got %v", status)
} else if status.Status != metav1.StatusSuccess {
t.Errorf("expected success, got: %v", status.Status)
}
}
_, err = getFn(ctx, foo)
if err == nil || !isNotFoundFn(err) {
t.Errorf("unexpected error: %v", err)
}
}
// ============================================================================= // =============================================================================
// Graceful Deletion tests. // Graceful Deletion tests.
......
...@@ -98,6 +98,9 @@ type Preconditions struct { ...@@ -98,6 +98,9 @@ type Preconditions struct {
// Specifies the target UID. // Specifies the target UID.
// +optional // +optional
UID *types.UID `json:"uid,omitempty"` UID *types.UID `json:"uid,omitempty"`
// Specifies the target ResourceVersion
// +optional
ResourceVersion *string `json:"resourceVersion,omitempty"`
} }
// NewUIDPreconditions returns a Preconditions with UID set. // NewUIDPreconditions returns a Preconditions with UID set.
...@@ -125,8 +128,14 @@ func (p *Preconditions) Check(key string, obj runtime.Object) error { ...@@ -125,8 +128,14 @@ func (p *Preconditions) Check(key string, obj runtime.Object) error {
objMeta.GetUID()) objMeta.GetUID())
return NewInvalidObjError(key, err) return NewInvalidObjError(key, err)
} }
if p.ResourceVersion != nil && *p.ResourceVersion != objMeta.GetResourceVersion() {
err := fmt.Sprintf(
"Precondition failed: ResourceVersion in precondition: %v, ResourceVersion in object meta: %v",
*p.ResourceVersion,
objMeta.GetResourceVersion())
return NewInvalidObjError(key, err)
}
return nil return nil
} }
// Interface offers a common interface for object marshaling/unmarshaling operations and // Interface offers a common interface for object marshaling/unmarshaling operations and
......
...@@ -667,12 +667,26 @@ func (j *TestJig) pollIngressWithCert(ing *extensions.Ingress, address string, k ...@@ -667,12 +667,26 @@ func (j *TestJig) pollIngressWithCert(ing *extensions.Ingress, address string, k
} }
// WaitForIngress waits for the Ingress to get an address. // WaitForIngress waits for the Ingress to get an address.
// WaitForIngress returns when it gets the first 200 response
func (j *TestJig) WaitForIngress(waitForNodePort bool) { func (j *TestJig) WaitForIngress(waitForNodePort bool) {
if err := j.WaitForGivenIngressWithTimeout(j.Ingress, waitForNodePort, framework.LoadBalancerPollTimeout); err != nil { if err := j.WaitForGivenIngressWithTimeout(j.Ingress, waitForNodePort, framework.LoadBalancerPollTimeout); err != nil {
framework.Failf("error in waiting for ingress to get an address: %s", err) framework.Failf("error in waiting for ingress to get an address: %s", err)
} }
} }
// WaitForIngressToStable waits for the LB return 100 consecutive 200 responses.
func (j *TestJig) WaitForIngressToStable() {
if err := wait.Poll(10*time.Second, framework.LoadBalancerCreateTimeoutDefault, func() (bool, error) {
_, err := j.GetDistinctResponseFromIngress()
if err != nil {
return false, nil
}
return true, nil
}); err != nil {
framework.Failf("error in waiting for ingress to stablize: %v", err)
}
}
// WaitForGivenIngressWithTimeout waits till the ingress acquires an IP, // WaitForGivenIngressWithTimeout waits till the ingress acquires an IP,
// then waits for its hosts/urls to respond to a protocol check (either // then waits for its hosts/urls to respond to a protocol check (either
// http or https). If waitForNodePort is true, the NodePort of the Service // http or https). If waitForNodePort is true, the NodePort of the Service
......
...@@ -623,6 +623,7 @@ func (j *ServiceTestJig) waitForConditionOrFail(namespace, name string, timeout ...@@ -623,6 +623,7 @@ func (j *ServiceTestJig) waitForConditionOrFail(namespace, name string, timeout
// name as the jig and runs the "netexec" container. // name as the jig and runs the "netexec" container.
func (j *ServiceTestJig) newRCTemplate(namespace string) *v1.ReplicationController { func (j *ServiceTestJig) newRCTemplate(namespace string) *v1.ReplicationController {
var replicas int32 = 1 var replicas int32 = 1
var grace int64 = 3 // so we don't race with kube-proxy when scaling up/down
rc := &v1.ReplicationController{ rc := &v1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
...@@ -654,7 +655,7 @@ func (j *ServiceTestJig) newRCTemplate(namespace string) *v1.ReplicationControll ...@@ -654,7 +655,7 @@ func (j *ServiceTestJig) newRCTemplate(namespace string) *v1.ReplicationControll
}, },
}, },
}, },
TerminationGracePeriodSeconds: new(int64), TerminationGracePeriodSeconds: &grace,
}, },
}, },
}, },
...@@ -737,6 +738,28 @@ func (j *ServiceTestJig) RunOrFail(namespace string, tweak func(rc *v1.Replicati ...@@ -737,6 +738,28 @@ func (j *ServiceTestJig) RunOrFail(namespace string, tweak func(rc *v1.Replicati
return result return result
} }
func (j *ServiceTestJig) Scale(namespace string, replicas int) {
rc := j.Name
scale, err := j.Client.CoreV1().ReplicationControllers(namespace).GetScale(rc, metav1.GetOptions{})
if err != nil {
Failf("Failed to get scale for RC %q: %v", rc, err)
}
scale.Spec.Replicas = int32(replicas)
_, err = j.Client.CoreV1().ReplicationControllers(namespace).UpdateScale(rc, scale)
if err != nil {
Failf("Failed to scale RC %q: %v", rc, err)
}
pods, err := j.waitForPodsCreated(namespace, replicas)
if err != nil {
Failf("Failed waiting for pods: %v", err)
}
if err := j.waitForPodsReady(namespace, pods); err != nil {
Failf("Failed waiting for pods to be running: %v", err)
}
return
}
func (j *ServiceTestJig) waitForPdbReady(namespace string) error { func (j *ServiceTestJig) waitForPdbReady(namespace string) error {
timeout := 2 * time.Minute timeout := 2 * time.Minute
for start := time.Now(); time.Since(start) < timeout; time.Sleep(2 * time.Second) { for start := time.Now(); time.Since(start) < timeout; time.Sleep(2 * time.Second) {
...@@ -875,9 +898,19 @@ func (j *ServiceTestJig) TestReachableHTTP(host string, port int, timeout time.D ...@@ -875,9 +898,19 @@ func (j *ServiceTestJig) TestReachableHTTP(host string, port int, timeout time.D
} }
func (j *ServiceTestJig) TestReachableHTTPWithRetriableErrorCodes(host string, port int, retriableErrCodes []int, timeout time.Duration) { func (j *ServiceTestJig) TestReachableHTTPWithRetriableErrorCodes(host string, port int, retriableErrCodes []int, timeout time.Duration) {
if err := wait.PollImmediate(Poll, timeout, func() (bool, error) { pollfn := func() (bool, error) {
return TestReachableHTTPWithRetriableErrorCodes(host, port, "/echo?msg=hello", "hello", retriableErrCodes) result := PokeHTTP(host, port, "/echo?msg=hello",
}); err != nil { &HTTPPokeParams{
BodyContains: "hello",
RetriableCodes: retriableErrCodes,
})
if result.Status == HTTPSuccess {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
if err == wait.ErrWaitTimeout { if err == wait.ErrWaitTimeout {
Failf("Could not reach HTTP service through %v:%v after %v", host, port, timeout) Failf("Could not reach HTTP service through %v:%v after %v", host, port, timeout)
} else { } else {
...@@ -887,36 +920,87 @@ func (j *ServiceTestJig) TestReachableHTTPWithRetriableErrorCodes(host string, p ...@@ -887,36 +920,87 @@ func (j *ServiceTestJig) TestReachableHTTPWithRetriableErrorCodes(host string, p
} }
func (j *ServiceTestJig) TestNotReachableHTTP(host string, port int, timeout time.Duration) { func (j *ServiceTestJig) TestNotReachableHTTP(host string, port int, timeout time.Duration) {
if err := wait.PollImmediate(Poll, timeout, func() (bool, error) { return TestNotReachableHTTP(host, port) }); err != nil { pollfn := func() (bool, error) {
Failf("Could still reach HTTP service through %v:%v after %v: %v", host, port, timeout, err) result := PokeHTTP(host, port, "/", nil)
if result.Code == 0 {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
Failf("HTTP service %v:%v reachable after %v: %v", host, port, timeout, err)
}
}
func (j *ServiceTestJig) TestRejectedHTTP(host string, port int, timeout time.Duration) {
pollfn := func() (bool, error) {
result := PokeHTTP(host, port, "/", nil)
if result.Status == HTTPRefused {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
Failf("HTTP service %v:%v not rejected: %v", host, port, err)
} }
} }
func (j *ServiceTestJig) TestReachableUDP(host string, port int, timeout time.Duration) { func (j *ServiceTestJig) TestReachableUDP(host string, port int, timeout time.Duration) {
if err := wait.PollImmediate(Poll, timeout, func() (bool, error) { return TestReachableUDP(host, port, "echo hello", "hello") }); err != nil { pollfn := func() (bool, error) {
result := PokeUDP(host, port, "echo hello", &UDPPokeParams{
Timeout: 3 * time.Second,
Response: "hello",
})
if result.Status == UDPSuccess {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
Failf("Could not reach UDP service through %v:%v after %v: %v", host, port, timeout, err) Failf("Could not reach UDP service through %v:%v after %v: %v", host, port, timeout, err)
} }
} }
func (j *ServiceTestJig) TestNotReachableUDP(host string, port int, timeout time.Duration) { func (j *ServiceTestJig) TestNotReachableUDP(host string, port int, timeout time.Duration) {
if err := wait.PollImmediate(Poll, timeout, func() (bool, error) { return TestNotReachableUDP(host, port, "echo hello") }); err != nil { pollfn := func() (bool, error) {
Failf("Could still reach UDP service through %v:%v after %v: %v", host, port, timeout, err) result := PokeUDP(host, port, "echo hello", &UDPPokeParams{Timeout: 3 * time.Second})
if result.Status != UDPSuccess && result.Status != UDPError {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
Failf("UDP service %v:%v reachable after %v: %v", host, port, timeout, err)
}
}
func (j *ServiceTestJig) TestRejectedUDP(host string, port int, timeout time.Duration) {
pollfn := func() (bool, error) {
result := PokeUDP(host, port, "echo hello", &UDPPokeParams{Timeout: 3 * time.Second})
if result.Status == UDPRefused {
return true, nil
}
return false, nil // caller can retry
}
if err := wait.PollImmediate(Poll, timeout, pollfn); err != nil {
Failf("UDP service %v:%v not rejected: %v", host, port, err)
} }
} }
func (j *ServiceTestJig) GetHTTPContent(host string, port int, timeout time.Duration, url string) bytes.Buffer { func (j *ServiceTestJig) GetHTTPContent(host string, port int, timeout time.Duration, url string) bytes.Buffer {
var body bytes.Buffer var body bytes.Buffer
var err error
if pollErr := wait.PollImmediate(Poll, timeout, func() (bool, error) { if pollErr := wait.PollImmediate(Poll, timeout, func() (bool, error) {
var result bool result := PokeHTTP(host, port, url, nil)
result, err = TestReachableHTTPWithContent(host, port, url, "", &body) if result.Status == HTTPSuccess {
if err != nil { body.Write(result.Body)
Logf("Error hitting %v:%v%v, retrying: %v", host, port, url, err) return true, nil
return false, nil
} }
return result, nil return false, nil
}); pollErr != nil { }); pollErr != nil {
Failf("Could not reach HTTP service through %v:%v%v after %v: %v", host, port, url, timeout, err) Failf("Could not reach HTTP service through %v:%v%v after %v: %v", host, port, url, timeout, pollErr)
} }
return body return body
} }
...@@ -929,7 +1013,7 @@ func testHTTPHealthCheckNodePort(ip string, port int, request string) (bool, err ...@@ -929,7 +1013,7 @@ func testHTTPHealthCheckNodePort(ip string, port int, request string) (bool, err
return false, fmt.Errorf("Invalid input ip or port") return false, fmt.Errorf("Invalid input ip or port")
} }
Logf("Testing HTTP health check on %v", url) Logf("Testing HTTP health check on %v", url)
resp, err := httpGetNoConnectionPool(url) resp, err := httpGetNoConnectionPoolTimeout(url, 5*time.Second)
if err != nil { if err != nil {
Logf("Got error testing for reachability of %s: %v", url, err) Logf("Got error testing for reachability of %s: %v", url, err)
return false, err return false, err
......
...@@ -188,11 +188,11 @@ var _ = SIGDescribe("Firewall rule", func() { ...@@ -188,11 +188,11 @@ var _ = SIGDescribe("Firewall rule", func() {
}) })
func assertNotReachableHTTPTimeout(ip string, port int, timeout time.Duration) { func assertNotReachableHTTPTimeout(ip string, port int, timeout time.Duration) {
unreachable, err := framework.TestNotReachableHTTPTimeout(ip, port, timeout) result := framework.PokeHTTP(ip, port, "/", &framework.HTTPPokeParams{Timeout: timeout})
if err != nil { if result.Status == framework.HTTPError {
framework.Failf("Unexpected error checking for reachability of %s:%d: %v", ip, port, err) framework.Failf("Unexpected error checking for reachability of %s:%d: %v", ip, port, result.Error)
} }
if !unreachable { if result.Code != 0 {
framework.Failf("Was unexpectedly able to reach %s:%d", ip, port) framework.Failf("Was unexpectedly able to reach %s:%d", ip, port)
} }
} }
...@@ -529,9 +529,10 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -529,9 +529,10 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
_, err = f.ClientSet.CoreV1().Services(ns).Update(&svc) _, err = f.ClientSet.CoreV1().Services(ns).Update(&svc)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
wait.Poll(5*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) { err = wait.Poll(5*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) {
return gceController.BackendServiceUsingIG(jig.GetServicePorts(true)) return gceController.BackendServiceUsingIG(jig.GetServicePorts(false))
}) })
Expect(err).NotTo(HaveOccurred(), "Expect backend service to target IG, but failed to observe")
jig.WaitForIngress(true) jig.WaitForIngress(true)
By("Switch backend service to use NEG") By("Switch backend service to use NEG")
...@@ -542,9 +543,10 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -542,9 +543,10 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
_, err = f.ClientSet.CoreV1().Services(ns).Update(&svc) _, err = f.ClientSet.CoreV1().Services(ns).Update(&svc)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
wait.Poll(5*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) { err = wait.Poll(5*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) {
return gceController.BackendServiceUsingNEG(jig.GetServicePorts(false)) return gceController.BackendServiceUsingNEG(jig.GetServicePorts(false))
}) })
Expect(err).NotTo(HaveOccurred(), "Expect backend service to target NEG, but failed to observe")
jig.WaitForIngress(true) jig.WaitForIngress(true)
}) })
...@@ -556,7 +558,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -556,7 +558,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
svcPorts := jig.GetServicePorts(false) svcPorts := jig.GetServicePorts(false)
usingNEG, err := gceController.BackendServiceUsingNEG(svcPorts) usingNEG, err := gceController.BackendServiceUsingNEG(svcPorts)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usingNEG).To(BeTrue()) Expect(usingNEG).To(BeTrue(), "Expect backend service to be using NEG. But not.")
// ClusterIP ServicePorts have no NodePort // ClusterIP ServicePorts have no NodePort
for _, sp := range svcPorts { for _, sp := range svcPorts {
...@@ -574,18 +576,21 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -574,18 +576,21 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
_, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale) _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) { err = wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) {
res, err := jig.GetDistinctResponseFromIngress() res, err := jig.GetDistinctResponseFromIngress()
if err != nil { if err != nil {
return false, nil return false, nil
} }
framework.Logf("Expecting %d backends, got %d", num, res.Len())
return res.Len() == num, nil return res.Len() == num, nil
}) })
Expect(err).NotTo(HaveOccurred())
} }
By("Create a basic HTTP ingress using NEG") By("Create a basic HTTP ingress using NEG")
jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{}) jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{})
jig.WaitForIngress(true) jig.WaitForIngress(true)
jig.WaitForIngressToStable()
usingNEG, err := gceController.BackendServiceUsingNEG(jig.GetServicePorts(false)) usingNEG, err := gceController.BackendServiceUsingNEG(jig.GetServicePorts(false))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usingNEG).To(BeTrue()) Expect(usingNEG).To(BeTrue())
...@@ -611,6 +616,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -611,6 +616,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
By("Create a basic HTTP ingress using NEG") By("Create a basic HTTP ingress using NEG")
jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{}) jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{})
jig.WaitForIngress(true) jig.WaitForIngress(true)
jig.WaitForIngressToStable()
usingNEG, err := gceController.BackendServiceUsingNEG(jig.GetServicePorts(false)) usingNEG, err := gceController.BackendServiceUsingNEG(jig.GetServicePorts(false))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usingNEG).To(BeTrue()) Expect(usingNEG).To(BeTrue())
...@@ -621,13 +627,15 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -621,13 +627,15 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
scale.Spec.Replicas = int32(replicas) scale.Spec.Replicas = int32(replicas)
_, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale) _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
wait.Poll(10*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) {
err = wait.Poll(10*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) {
res, err := jig.GetDistinctResponseFromIngress() res, err := jig.GetDistinctResponseFromIngress()
if err != nil { if err != nil {
return false, nil return false, nil
} }
return res.Len() == replicas, nil return res.Len() == replicas, nil
}) })
Expect(err).NotTo(HaveOccurred())
By("Trigger rolling update and observe service disruption") By("Trigger rolling update and observe service disruption")
deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{}) deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
...@@ -637,7 +645,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -637,7 +645,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
deploy.Spec.Template.Spec.TerminationGracePeriodSeconds = &gracePeriod deploy.Spec.Template.Spec.TerminationGracePeriodSeconds = &gracePeriod
_, err = f.ClientSet.AppsV1().Deployments(ns).Update(deploy) _, err = f.ClientSet.AppsV1().Deployments(ns).Update(deploy)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
wait.Poll(10*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) { err = wait.Poll(10*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) {
res, err := jig.GetDistinctResponseFromIngress() res, err := jig.GetDistinctResponseFromIngress()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{}) deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
...@@ -655,6 +663,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -655,6 +663,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
return false, nil return false, nil
} }
}) })
Expect(err).NotTo(HaveOccurred())
}) })
It("should sync endpoints for both Ingress-referenced NEG and standalone NEG", func() { It("should sync endpoints for both Ingress-referenced NEG and standalone NEG", func() {
...@@ -669,7 +678,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -669,7 +678,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
_, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale) _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(name, scale)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) { err = wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) {
svc, err := f.ClientSet.CoreV1().Services(ns).Get(name, metav1.GetOptions{}) svc, err := f.ClientSet.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
...@@ -716,6 +725,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() { ...@@ -716,6 +725,7 @@ var _ = SIGDescribe("Loadbalancing: L7", func() {
return true, nil return true, nil
}) })
Expect(err).NotTo(HaveOccurred())
} }
By("Create a basic HTTP ingress using NEG") By("Create a basic HTTP ingress using NEG")
...@@ -1108,7 +1118,7 @@ func detectHttpVersionAndSchemeTest(f *framework.Framework, jig *ingress.TestJig ...@@ -1108,7 +1118,7 @@ func detectHttpVersionAndSchemeTest(f *framework.Framework, jig *ingress.TestJig
} }
func detectNegAnnotation(f *framework.Framework, jig *ingress.TestJig, gceController *gce.GCEIngressController, ns, name string, negs int) { func detectNegAnnotation(f *framework.Framework, jig *ingress.TestJig, gceController *gce.GCEIngressController, ns, name string, negs int) {
wait.Poll(5*time.Second, framework.LoadBalancerPollTimeout, func() (bool, error) { if err := wait.Poll(5*time.Second, negUpdateTimeout, func() (bool, error) {
svc, err := f.ClientSet.CoreV1().Services(ns).Get(name, metav1.GetOptions{}) svc, err := f.ClientSet.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, nil return false, nil
...@@ -1150,5 +1160,7 @@ func detectNegAnnotation(f *framework.Framework, jig *ingress.TestJig, gceContro ...@@ -1150,5 +1160,7 @@ func detectNegAnnotation(f *framework.Framework, jig *ingress.TestJig, gceContro
} }
return gceController.BackendServiceUsingNEG(jig.GetServicePorts(false)) return gceController.BackendServiceUsingNEG(jig.GetServicePorts(false))
}) }); err != nil {
Expect(err).NotTo(HaveOccurred())
}
} }
...@@ -791,11 +791,47 @@ var _ = SIGDescribe("Services", func() { ...@@ -791,11 +791,47 @@ var _ = SIGDescribe("Services", func() {
jig.TestReachableUDP(nodeIP, udpNodePort, framework.KubeProxyLagTimeout) jig.TestReachableUDP(nodeIP, udpNodePort, framework.KubeProxyLagTimeout)
By("hitting the TCP service's LoadBalancer") By("hitting the TCP service's LoadBalancer")
jig.TestReachableHTTP(tcpIngressIP, svcPort, loadBalancerCreateTimeout) // this may actually recreate the LB jig.TestReachableHTTP(tcpIngressIP, svcPort, loadBalancerCreateTimeout)
if loadBalancerSupportsUDP { if loadBalancerSupportsUDP {
By("hitting the UDP service's LoadBalancer") By("hitting the UDP service's LoadBalancer")
jig.TestReachableUDP(udpIngressIP, svcPort, loadBalancerCreateTimeout) // this may actually recreate the LB) jig.TestReachableUDP(udpIngressIP, svcPort, loadBalancerCreateTimeout)
}
By("Scaling the pods to 0")
jig.Scale(ns1, 0)
jig.Scale(ns2, 0)
By("looking for ICMP REJECT on the TCP service's NodePort")
jig.TestRejectedHTTP(nodeIP, tcpNodePort, framework.KubeProxyLagTimeout)
By("looking for ICMP REJECT on the UDP service's NodePort")
jig.TestRejectedUDP(nodeIP, udpNodePort, framework.KubeProxyLagTimeout)
By("looking for ICMP REJECT on the TCP service's LoadBalancer")
jig.TestRejectedHTTP(tcpIngressIP, svcPort, loadBalancerCreateTimeout)
if loadBalancerSupportsUDP {
By("looking for ICMP REJECT on the UDP service's LoadBalancer")
jig.TestRejectedUDP(udpIngressIP, svcPort, loadBalancerCreateTimeout)
}
By("Scaling the pods to 1")
jig.Scale(ns1, 1)
jig.Scale(ns2, 1)
By("hitting the TCP service's NodePort")
jig.TestReachableHTTP(nodeIP, tcpNodePort, framework.KubeProxyLagTimeout)
By("hitting the UDP service's NodePort")
jig.TestReachableUDP(nodeIP, udpNodePort, framework.KubeProxyLagTimeout)
By("hitting the TCP service's LoadBalancer")
jig.TestReachableHTTP(tcpIngressIP, svcPort, loadBalancerCreateTimeout)
if loadBalancerSupportsUDP {
By("hitting the UDP service's LoadBalancer")
jig.TestReachableUDP(udpIngressIP, svcPort, loadBalancerCreateTimeout)
} }
// Change the services back to ClusterIP. // Change the services back to ClusterIP.
...@@ -2063,14 +2099,18 @@ var _ = SIGDescribe("ESIPP [Slow] [DisabledForLargeClusters]", func() { ...@@ -2063,14 +2099,18 @@ var _ = SIGDescribe("ESIPP [Slow] [DisabledForLargeClusters]", func() {
for nodeName, nodeIPs := range endpointNodeMap { for nodeName, nodeIPs := range endpointNodeMap {
By(fmt.Sprintf("checking kube-proxy health check fails on node with endpoint (%s), public IP %s", nodeName, nodeIPs[0])) By(fmt.Sprintf("checking kube-proxy health check fails on node with endpoint (%s), public IP %s", nodeName, nodeIPs[0]))
var body bytes.Buffer var body bytes.Buffer
var result bool pollfn := func() (bool, error) {
var err error result := framework.PokeHTTP(nodeIPs[0], healthCheckNodePort, "/healthz", nil)
if pollErr := wait.PollImmediate(framework.Poll, framework.ServiceTestTimeout, func() (bool, error) { if result.Code == 0 {
result, err = framework.TestReachableHTTPWithContent(nodeIPs[0], healthCheckNodePort, "/healthz", "", &body) return true, nil
return !result, nil }
}); pollErr != nil { body.Reset()
framework.Failf("Kube-proxy still exposing health check on node %v:%v, after ESIPP was turned off. Last err %v, last body %v", body.Write(result.Body)
nodeName, healthCheckNodePort, err, body.String()) return false, nil
}
if pollErr := wait.PollImmediate(framework.Poll, framework.ServiceTestTimeout, pollfn); pollErr != nil {
framework.Failf("Kube-proxy still exposing health check on node %v:%v, after ESIPP was turned off. body %s",
nodeName, healthCheckNodePort, body.String())
} }
} }
......
...@@ -379,7 +379,7 @@ var _ = utils.SIGDescribe("CSI mock volume", func() { ...@@ -379,7 +379,7 @@ var _ = utils.SIGDescribe("CSI mock volume", func() {
_, _, pod3 := createPod() _, _, pod3 := createPod()
Expect(pod3).NotTo(BeNil(), "while creating third pod") Expect(pod3).NotTo(BeNil(), "while creating third pod")
err = waitForMaxVolumeCondition(pod3, m.cs) err = waitForMaxVolumeCondition(pod3, m.cs)
Expect(err).NotTo(HaveOccurred(), "while waiting for max volume condition") Expect(err).NotTo(HaveOccurred(), "while waiting for max volume condition on pod : %+v", pod3)
}) })
}) })
......
...@@ -367,7 +367,7 @@ func (g *gcePDCSIDriver) SkipUnsupportedTest(pattern testpatterns.TestPattern) { ...@@ -367,7 +367,7 @@ func (g *gcePDCSIDriver) SkipUnsupportedTest(pattern testpatterns.TestPattern) {
if pattern.FsType == "xfs" { if pattern.FsType == "xfs" {
framework.SkipUnlessNodeOSDistroIs("ubuntu", "custom") framework.SkipUnlessNodeOSDistroIs("ubuntu", "custom")
} }
if pattern.FeatureTag == "sig-windows" { if pattern.FeatureTag == "[sig-windows]" {
framework.Skipf("Skipping tests for windows since CSI does not support it yet") framework.Skipf("Skipping tests for windows since CSI does not support it yet")
} }
} }
...@@ -473,7 +473,7 @@ func (g *gcePDExternalCSIDriver) SkipUnsupportedTest(pattern testpatterns.TestPa ...@@ -473,7 +473,7 @@ func (g *gcePDExternalCSIDriver) SkipUnsupportedTest(pattern testpatterns.TestPa
if pattern.FsType == "xfs" { if pattern.FsType == "xfs" {
framework.SkipUnlessNodeOSDistroIs("ubuntu", "custom") framework.SkipUnlessNodeOSDistroIs("ubuntu", "custom")
} }
if pattern.FeatureTag == "sig-windows" { if pattern.FeatureTag == "[sig-windows]" {
framework.Skipf("Skipping tests for windows since CSI does not support it yet") framework.Skipf("Skipping tests for windows since CSI does not support it yet")
} }
} }
......
...@@ -1106,21 +1106,18 @@ var _ testsuites.DynamicPVTestDriver = &gcePdDriver{} ...@@ -1106,21 +1106,18 @@ var _ testsuites.DynamicPVTestDriver = &gcePdDriver{}
// InitGcePdDriver returns gcePdDriver that implements TestDriver interface // InitGcePdDriver returns gcePdDriver that implements TestDriver interface
func InitGcePdDriver() testsuites.TestDriver { func InitGcePdDriver() testsuites.TestDriver {
var supportedTypes sets.String // In current test structure, it first initialize the driver and then set up
var capFsGroup bool // the new framework, so we cannot get the correct OS here. So here set to
if framework.NodeOSDistroIs("windows") { // support all fs types including both linux and windows. We have code to check Node OS later
supportedTypes = sets.NewString("ntfs") // during test.
capFsGroup = false supportedTypes := sets.NewString(
} else { "", // Default fsType
supportedTypes = sets.NewString( "ext2",
"", // Default fsType "ext3",
"ext2", "ext4",
"ext3", "xfs",
"ext4", "ntfs",
"xfs", )
)
capFsGroup = true
}
return &gcePdDriver{ return &gcePdDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "gcepd", Name: "gcepd",
...@@ -1129,7 +1126,7 @@ func InitGcePdDriver() testsuites.TestDriver { ...@@ -1129,7 +1126,7 @@ func InitGcePdDriver() testsuites.TestDriver {
SupportedMountOption: sets.NewString("debug", "nouid32"), SupportedMountOption: sets.NewString("debug", "nouid32"),
Capabilities: map[testsuites.Capability]bool{ Capabilities: map[testsuites.Capability]bool{
testsuites.CapPersistence: true, testsuites.CapPersistence: true,
testsuites.CapFsGroup: capFsGroup, testsuites.CapFsGroup: true,
testsuites.CapBlock: true, testsuites.CapBlock: true,
testsuites.CapExec: true, testsuites.CapExec: true,
}, },
...@@ -1143,7 +1140,7 @@ func (g *gcePdDriver) GetDriverInfo() *testsuites.DriverInfo { ...@@ -1143,7 +1140,7 @@ func (g *gcePdDriver) GetDriverInfo() *testsuites.DriverInfo {
func (g *gcePdDriver) SkipUnsupportedTest(pattern testpatterns.TestPattern) { func (g *gcePdDriver) SkipUnsupportedTest(pattern testpatterns.TestPattern) {
framework.SkipUnlessProviderIs("gce", "gke") framework.SkipUnlessProviderIs("gce", "gke")
if pattern.FeatureTag == "sig-windows" { if pattern.FeatureTag == "[sig-windows]" {
framework.SkipUnlessNodeOSDistroIs("windows") framework.SkipUnlessNodeOSDistroIs("windows")
} }
} }
......
...@@ -152,21 +152,21 @@ var ( ...@@ -152,21 +152,21 @@ var (
Name: "Inline-volume (ntfs)", Name: "Inline-volume (ntfs)",
VolType: InlineVolume, VolType: InlineVolume,
FsType: "ntfs", FsType: "ntfs",
FeatureTag: "sig-windows", FeatureTag: "[sig-windows]",
} }
// NtfsPreprovisionedPV is TestPattern for "Pre-provisioned PV (ntfs)" // NtfsPreprovisionedPV is TestPattern for "Pre-provisioned PV (ntfs)"
NtfsPreprovisionedPV = TestPattern{ NtfsPreprovisionedPV = TestPattern{
Name: "Pre-provisioned PV (ntfs)", Name: "Pre-provisioned PV (ntfs)",
VolType: PreprovisionedPV, VolType: PreprovisionedPV,
FsType: "ntfs", FsType: "ntfs",
FeatureTag: "sig-windows", FeatureTag: "[sig-windows]",
} }
// NtfsDynamicPV is TestPattern for "Dynamic PV (xfs)" // NtfsDynamicPV is TestPattern for "Dynamic PV (xfs)"
NtfsDynamicPV = TestPattern{ NtfsDynamicPV = TestPattern{
Name: "Dynamic PV (ntfs)", Name: "Dynamic PV (ntfs)",
VolType: DynamicPV, VolType: DynamicPV,
FsType: "ntfs", FsType: "ntfs",
FeatureTag: "sig-windows", FeatureTag: "[sig-windows]",
} }
// Definitions for Filesystem volume mode // Definitions for Filesystem volume mode
......
...@@ -125,7 +125,7 @@ func (t *volumeIOTestSuite) defineTests(driver TestDriver, pattern testpatterns. ...@@ -125,7 +125,7 @@ func (t *volumeIOTestSuite) defineTests(driver TestDriver, pattern testpatterns.
fileSizes := createFileSizes(dInfo.MaxFileSize) fileSizes := createFileSizes(dInfo.MaxFileSize)
testFile := fmt.Sprintf("%s_io_test_%s", dInfo.Name, f.Namespace.Name) testFile := fmt.Sprintf("%s_io_test_%s", dInfo.Name, f.Namespace.Name)
var fsGroup *int64 var fsGroup *int64
if dInfo.Capabilities[CapFsGroup] { if !framework.NodeOSDistroIs("windows") && dInfo.Capabilities[CapFsGroup] {
fsGroupVal := int64(1234) fsGroupVal := int64(1234)
fsGroup = &fsGroupVal fsGroup = &fsGroupVal
} }
......
...@@ -152,7 +152,7 @@ func (t *volumesTestSuite) defineTests(driver TestDriver, pattern testpatterns.T ...@@ -152,7 +152,7 @@ func (t *volumesTestSuite) defineTests(driver TestDriver, pattern testpatterns.T
} }
config := convertTestConfig(l.config) config := convertTestConfig(l.config)
var fsGroup *int64 var fsGroup *int64
if dInfo.Capabilities[CapFsGroup] { if framework.NodeOSDistroIs("windows") && dInfo.Capabilities[CapFsGroup] {
fsGroupVal := int64(1234) fsGroupVal := int64(1234)
fsGroup = &fsGroupVal fsGroup = &fsGroupVal
} }
...@@ -185,7 +185,12 @@ func testScriptInPod( ...@@ -185,7 +185,12 @@ func testScriptInPod(
) )
suffix := generateSuffixForPodName(volumeType) suffix := generateSuffixForPodName(volumeType)
fileName := fmt.Sprintf("test-%s", suffix) fileName := fmt.Sprintf("test-%s", suffix)
content := fmt.Sprintf("ls %s", volPath) var content string
if framework.NodeOSDistroIs("windows") {
content = fmt.Sprintf("ls -n %s", volPath)
} else {
content = fmt.Sprintf("ls %s", volPath)
}
command := framework.GenerateWriteandExecuteScriptFileCmd(content, fileName, volPath) command := framework.GenerateWriteandExecuteScriptFileCmd(content, fileName, volPath)
pod := &v1.Pod{ pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
......
...@@ -45,17 +45,12 @@ const ( ...@@ -45,17 +45,12 @@ const (
) )
// Serial because the test restarts Kubelet // Serial because the test restarts Kubelet
var _ = framework.KubeDescribe("Device Plugin [Feature:DevicePlugin][NodeFeature:DevicePlugin][Serial]", func() {
f := framework.NewDefaultFramework("device-plugin-errors")
testDevicePlugin(f, false, pluginapi.DevicePluginPath)
})
var _ = framework.KubeDescribe("Device Plugin [Feature:DevicePluginProbe][NodeFeature:DevicePluginProbe][Serial]", func() { var _ = framework.KubeDescribe("Device Plugin [Feature:DevicePluginProbe][NodeFeature:DevicePluginProbe][Serial]", func() {
f := framework.NewDefaultFramework("device-plugin-errors") f := framework.NewDefaultFramework("device-plugin-errors")
testDevicePlugin(f, true, "/var/lib/kubelet/plugins_registry") testDevicePlugin(f, "/var/lib/kubelet/plugins_registry")
}) })
func testDevicePlugin(f *framework.Framework, enablePluginWatcher bool, pluginSockDir string) { func testDevicePlugin(f *framework.Framework, pluginSockDir string) {
pluginSockDir = filepath.Join(pluginSockDir) + "/" pluginSockDir = filepath.Join(pluginSockDir) + "/"
Context("DevicePlugin", func() { Context("DevicePlugin", func() {
By("Enabling support for Kubelet Plugins Watcher") By("Enabling support for Kubelet Plugins Watcher")
...@@ -63,7 +58,6 @@ func testDevicePlugin(f *framework.Framework, enablePluginWatcher bool, pluginSo ...@@ -63,7 +58,6 @@ func testDevicePlugin(f *framework.Framework, enablePluginWatcher bool, pluginSo
if initialConfig.FeatureGates == nil { if initialConfig.FeatureGates == nil {
initialConfig.FeatureGates = map[string]bool{} initialConfig.FeatureGates = map[string]bool{}
} }
initialConfig.FeatureGates[string(features.KubeletPluginsWatcher)] = enablePluginWatcher
initialConfig.FeatureGates[string(features.KubeletPodResources)] = true initialConfig.FeatureGates[string(features.KubeletPodResources)] = true
}) })
It("Verifies the Kubelet device plugin functionality.", func() { It("Verifies the Kubelet device plugin functionality.", func() {
......
...@@ -119,6 +119,19 @@ func TestApplyAlsoCreates(t *testing.T) { ...@@ -119,6 +119,19 @@ func TestApplyAlsoCreates(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to retrieve object: %v", err) t.Fatalf("Failed to retrieve object: %v", err)
} }
// Test that we can re apply with a different field manager and don't get conflicts
_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
Namespace("default").
Resource(tc.resource).
Name(tc.name).
Param("fieldManager", "apply_test_2").
Body([]byte(tc.body)).
Do().
Get()
if err != nil {
t.Fatalf("Failed to re-apply object using Apply patch: %v", err)
}
} }
} }
......
...@@ -4,6 +4,7 @@ load( ...@@ -4,6 +4,7 @@ load(
"@io_bazel_rules_go//go:def.bzl", "@io_bazel_rules_go//go:def.bzl",
"go_library", "go_library",
) )
load("//build:platforms.bzl", "go_platform_constraint")
go_library( go_library(
name = "go_default_library", name = "go_default_library",
...@@ -15,9 +16,23 @@ go_library( ...@@ -15,9 +16,23 @@ go_library(
"test_server.go", "test_server.go",
"util.go", "util.go",
], ],
data = [ data = select({
"@com_coreos_etcd//:etcd", go_platform_constraint(
], arch = "arm64",
os = "linux",
): [
"@com_coreos_etcd_arm64//:etcd",
],
go_platform_constraint(
arch = "ppc64le",
os = "linux",
): [
"@com_coreos_etcd_ppc64le//:etcd",
],
"//conditions:default": [
"@com_coreos_etcd_amd64//:etcd",
],
}),
importpath = "k8s.io/kubernetes/test/integration/framework", importpath = "k8s.io/kubernetes/test/integration/framework",
deps = [ deps = [
"//cmd/kube-apiserver/app:go_default_library", "//cmd/kube-apiserver/app:go_default_library",
......
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"k8s.io/klog" "k8s.io/klog"
...@@ -43,7 +44,8 @@ You can use 'hack/install-etcd.sh' to install a copy in third_party/. ...@@ -43,7 +44,8 @@ You can use 'hack/install-etcd.sh' to install a copy in third_party/.
// getEtcdPath returns a path to an etcd executable. // getEtcdPath returns a path to an etcd executable.
func getEtcdPath() (string, error) { func getEtcdPath() (string, error) {
bazelPath := filepath.Join(os.Getenv("RUNFILES_DIR"), "com_coreos_etcd/etcd") bazelPath := filepath.Join(os.Getenv("RUNFILES_DIR"), fmt.Sprintf("com_coreos_etcd_%s", runtime.GOARCH), "etcd")
p, err := exec.LookPath(bazelPath) p, err := exec.LookPath(bazelPath)
if err == nil { if err == nil {
return p, nil return p, nil
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment