SSA: add integration tests

test/integration/apiserver/apply covers the behavior of server-side-apply (SSA)
for official APIs. But there seem to be no integration tests which cover the
semantic of SSA like adding/removing/updating entries in a list map. This adds
such a test.

It needs an API which is under control of the test and uses
k8s.io/apimachinery/pkg/apis/testapigroup for that purpose, with some issues
fixed (OpenAPI code generation complained) and a new list map added.

Registering that API group in the apiserver needs a REST storage and
strategy. The API group only gets added in the test. However, the production
code has to know about it. In particular,
pkg/generated/openapi/zz_generated.openapi.go has to describe it.
This commit is contained in:
Patrick Ohly
2025-07-08 10:36:53 +02:00
parent f130a825c2
commit 3357e8fc05
18 changed files with 1710 additions and 76 deletions

View File

@@ -120,6 +120,12 @@ const (
repairLoopInterval = 3 * time.Minute
)
var (
// AdditionalStorageProvidersForTests allows tests to inject additional test-only API groups.
// Only meant for use in integration tests.
AdditionalStorageProvidersForTests func(client *kubernetes.Clientset) []controlplaneapiserver.RESTStorageProvider
)
// Extra defines extra configuration for kube-apiserver
type Extra struct {
EndpointReconcilerConfig EndpointReconcilerConfig
@@ -401,7 +407,7 @@ func (c CompletedConfig) StorageProviders(client *kubernetes.Clientset) ([]contr
// with specific priorities.
// TODO: describe the priority all the way down in the RESTStorageProviders and plumb it back through the various discovery
// handlers that we have.
return []controlplaneapiserver.RESTStorageProvider{
providers := []controlplaneapiserver.RESTStorageProvider{
legacyRESTStorageProvider,
apiserverinternalrest.StorageProvider{},
authenticationrest.RESTStorageProvider{Authenticator: c.ControlPlane.Generic.Authentication.Authenticator, APIAudiences: c.ControlPlane.Generic.Authentication.APIAudiences},
@@ -425,7 +431,13 @@ func (c CompletedConfig) StorageProviders(client *kubernetes.Clientset) ([]contr
admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: client.Discovery()},
eventsrest.RESTStorageProvider{TTL: c.ControlPlane.EventTTL},
resourcerest.RESTStorageProvider{NamespaceClient: client.CoreV1().Namespaces()},
}, nil
}
if AdditionalStorageProvidersForTests != nil {
providers = append(providers, AdditionalStorageProvidersForTests(client)...)
}
return providers, nil
}
var (

View File

@@ -1163,6 +1163,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/apimachinery/pkg/apis/meta/v1.UpdateOptions": schema_pkg_apis_meta_v1_UpdateOptions(ref),
"k8s.io/apimachinery/pkg/apis/meta/v1.WatchEvent": schema_pkg_apis_meta_v1_WatchEvent(ref),
"k8s.io/apimachinery/pkg/apis/meta/v1beta1.PartialObjectMetadataList": schema_pkg_apis_meta_v1beta1_PartialObjectMetadataList(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.Carp": schema_pkg_apis_testapigroup_v1_Carp(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpCondition": schema_pkg_apis_testapigroup_v1_CarpCondition(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpInfo": schema_pkg_apis_testapigroup_v1_CarpInfo(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpList": schema_pkg_apis_testapigroup_v1_CarpList(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpSpec": schema_pkg_apis_testapigroup_v1_CarpSpec(ref),
"k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpStatus": schema_pkg_apis_testapigroup_v1_CarpStatus(ref),
"k8s.io/apimachinery/pkg/runtime.RawExtension": schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref),
"k8s.io/apimachinery/pkg/runtime.TypeMeta": schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref),
"k8s.io/apimachinery/pkg/runtime.Unknown": schema_k8sio_apimachinery_pkg_runtime_Unknown(ref),
@@ -59659,6 +59665,417 @@ func schema_pkg_apis_meta_v1beta1_PartialObjectMetadataList(ref common.Reference
}
}
func schema_pkg_apis_testapigroup_v1_Carp(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Carp is a collection of containers, used as either input (create, update) or as output (list, get).",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Description: "Specification of the desired behavior of the carp. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Description: "Most recently observed status of the carp. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpStatus"),
},
},
},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpSpec", "k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpStatus"},
}
}
func schema_pkg_apis_testapigroup_v1_CarpCondition(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Description: "Type is the type of the condition. Currently only Ready. More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"status": {
SchemaProps: spec.SchemaProps{
Description: "Status is the status of the condition. Can be True, False, Unknown. More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"lastProbeTime": {
SchemaProps: spec.SchemaProps{
Description: "Last time we probed the condition.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"lastTransitionTime": {
SchemaProps: spec.SchemaProps{
Description: "Last time the condition transitioned from one status to another.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"reason": {
SchemaProps: spec.SchemaProps{
Description: "Unique, one-word, CamelCase reason for the condition's last transition.",
Type: []string{"string"},
Format: "",
},
},
"message": {
SchemaProps: spec.SchemaProps{
Description: "Human-readable message indicating details about last transition.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"type", "status"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
func schema_pkg_apis_testapigroup_v1_CarpInfo(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"a": {
SchemaProps: spec.SchemaProps{
Description: "A is the first map key.",
Default: 0,
Type: []string{"integer"},
Format: "int64",
},
},
"b": {
SchemaProps: spec.SchemaProps{
Description: "B is the second map key.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"data": {
SchemaProps: spec.SchemaProps{
Description: "Some data for each pair of A and B.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"a", "b", "data"},
},
},
}
}
func schema_pkg_apis_testapigroup_v1_CarpList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "CarpList is a list of Carps.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Description: "List of carps. More info: http://kubernetes.io/docs/user-guide/carps",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/testapigroup/v1.Carp"),
},
},
},
},
},
},
Required: []string{"items"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/testapigroup/v1.Carp"},
}
}
func schema_pkg_apis_testapigroup_v1_CarpSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "CarpSpec is a description of a carp",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"restartPolicy": {
SchemaProps: spec.SchemaProps{
Description: "Restart policy for all containers within the carp. One of Always, OnFailure, Never. Default to Always. More info: http://kubernetes.io/docs/user-guide/carp-states#restartpolicy",
Type: []string{"string"},
Format: "",
},
},
"terminationGracePeriodSeconds": {
SchemaProps: spec.SchemaProps{
Description: "Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the carp are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.",
Type: []string{"integer"},
Format: "int64",
},
},
"activeDeadlineSeconds": {
SchemaProps: spec.SchemaProps{
Description: "Optional duration in seconds the carp may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.",
Type: []string{"integer"},
Format: "int64",
},
},
"nodeSelector": {
SchemaProps: spec.SchemaProps{
Description: "NodeSelector is a selector which must be true for the carp to fit on a node. Selector which must match a node's labels for the carp to be scheduled on that node. More info: http://kubernetes.io/docs/user-guide/node-selection/README",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"serviceAccountName": {
SchemaProps: spec.SchemaProps{
Description: "ServiceAccountName is the name of the ServiceAccount to use to run this carp. More info: https://kubernetes.io/docs/concepts/security/service-accounts/",
Type: []string{"string"},
Format: "",
},
},
"deprecatedServiceAccount": {
SchemaProps: spec.SchemaProps{
Description: "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.",
Type: []string{"string"},
Format: "",
},
},
"nodeName": {
SchemaProps: spec.SchemaProps{
Description: "NodeName is a request to schedule this carp onto a specific node. If it is non-empty, the scheduler simply schedules this carp onto that node, assuming that it fits resource requirements.",
Type: []string{"string"},
Format: "",
},
},
"hostNetwork": {
SchemaProps: spec.SchemaProps{
Description: "Host networking requested for this carp. Use the host's network namespace. Default to false.",
Type: []string{"boolean"},
Format: "",
},
},
"hostPID": {
SchemaProps: spec.SchemaProps{
Description: "Use the host's pid namespace. Optional: Default to false.",
Type: []string{"boolean"},
Format: "",
},
},
"hostIPC": {
SchemaProps: spec.SchemaProps{
Description: "Use the host's ipc namespace. Optional: Default to false.",
Type: []string{"boolean"},
Format: "",
},
},
"hostname": {
SchemaProps: spec.SchemaProps{
Description: "Specifies the hostname of the Carp If not specified, the carp's hostname will be set to a system-defined value.",
Type: []string{"string"},
Format: "",
},
},
"subdomain": {
SchemaProps: spec.SchemaProps{
Description: "If specified, the fully qualified Carp hostname will be \"<hostname>.<subdomain>.<carp namespace>.svc.<cluster domain>\". If not specified, the carp will not have a domainname at all.",
Type: []string{"string"},
Format: "",
},
},
"schedulerName": {
SchemaProps: spec.SchemaProps{
Description: "If specified, the carp will be dispatched by specified scheduler. If not specified, the carp will be dispatched by default scheduler.",
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_testapigroup_v1_CarpStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "CarpStatus represents information about the status of a carp. Status may trail the actual state of a system.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"phase": {
SchemaProps: spec.SchemaProps{
Description: "Current condition of the carp. More info: http://kubernetes.io/docs/user-guide/carp-states#carp-phase",
Type: []string{"string"},
Format: "",
},
},
"conditions": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"type",
},
"x-kubernetes-list-type": "map",
"x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge",
},
},
SchemaProps: spec.SchemaProps{
Description: "Current service state of carp. More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpCondition"),
},
},
},
},
},
"message": {
SchemaProps: spec.SchemaProps{
Description: "A human readable message indicating details about why the carp is in this condition.",
Type: []string{"string"},
Format: "",
},
},
"reason": {
SchemaProps: spec.SchemaProps{
Description: "A brief CamelCase message indicating details about why the carp is in this state. e.g. 'DiskPressure'",
Type: []string{"string"},
Format: "",
},
},
"hostIP": {
SchemaProps: spec.SchemaProps{
Description: "IP address of the host to which the carp is assigned. Empty if not yet scheduled.",
Type: []string{"string"},
Format: "",
},
},
"carpIP": {
SchemaProps: spec.SchemaProps{
Description: "IP address allocated to the carp. Routable at least within the cluster. Empty if not yet allocated.",
Type: []string{"string"},
Format: "",
},
},
"startTime": {
SchemaProps: spec.SchemaProps{
Description: "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the carp.",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"infos": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"a",
"b",
},
"x-kubernetes-list-type": "map",
},
},
SchemaProps: spec.SchemaProps{
Description: "Carp infos are provided by different clients, hence the map type.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpInfo"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.Time", "k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpCondition", "k8s.io/apimachinery/pkg/apis/testapigroup/v1.CarpInfo"},
}
}
func schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -0,0 +1,108 @@
/*
Copyright 2022 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 storage
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/testapigroup"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/testapigroup/carp"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
)
// REST implements a RESTStorage for Carps.
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against Carps.
func NewREST(optsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (*REST, *StatusREST, error) {
if nsClient == nil {
return nil, nil, fmt.Errorf("namespace client is required")
}
strategy := carp.NewStrategy(nsClient)
store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &testapigroup.Carp{} },
NewListFunc: func() runtime.Object { return &testapigroup.CarpList{} },
PredicateFunc: carp.Match,
DefaultQualifiedResource: testapigroup.Resource("carps"),
SingularQualifiedResource: testapigroup.Resource("carp"),
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ReturnDeletedObject: true,
ResetFieldsStrategy: strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: carp.GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
return nil, nil, err
}
statusStore := *store
statusStrategy := carp.NewStatusStrategy(strategy)
statusStore.UpdateStrategy = statusStrategy
statusStore.ResetFieldsStrategy = statusStrategy
rest := &REST{store}
return rest, &StatusREST{store: &statusStore}, nil
}
// StatusREST implements the REST endpoint for changing the status of a Carp.
type StatusREST struct {
store *genericregistry.Store
}
// New creates a new Carp object.
func (r *StatusREST) New() runtime.Object {
return &testapigroup.Carp{}
}
func (r *StatusREST) Destroy() {
// Given that underlying store is shared with REST,
// we don't destroy it here explicitly.
}
// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return r.store.Get(ctx, name, options)
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@@ -0,0 +1,168 @@
/*
Copyright 2022 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 carp
import (
"context"
"errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/testapigroup"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
)
// carpStrategy implements behavior for Carp objects
type carpStrategy struct {
runtime.ObjectTyper
names.NameGenerator
nsClient v1.NamespaceInterface
}
// NewStrategy is the default logic that applies when creating and updating Carp objects.
func NewStrategy(nsClient v1.NamespaceInterface) *carpStrategy {
return &carpStrategy{
legacyscheme.Scheme,
names.SimpleNameGenerator,
nsClient,
}
}
func (*carpStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy and
// should not be modified by the user. For a new Carp that is the
// status.
func (*carpStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"testapigroup.apimachinery.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
func (*carpStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
claim := obj.(*testapigroup.Carp)
// Status must not be set by user on create.
claim.Status = testapigroup.CarpStatus{}
}
func (s *carpStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return nil
}
func (*carpStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nil
}
func (*carpStrategy) Canonicalize(obj runtime.Object) {
}
func (*carpStrategy) AllowCreateOnUpdate() bool {
return false
}
func (*carpStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newClaim := obj.(*testapigroup.Carp)
oldClaim := old.(*testapigroup.Carp)
newClaim.Status = oldClaim.Status
}
func (s *carpStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return nil
}
func (*carpStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
func (*carpStrategy) AllowUnconditionalUpdate() bool {
return true
}
type carpStatusStrategy struct {
*carpStrategy
}
// NewStatusStrategy creates a strategy for operating the status object.
func NewStatusStrategy(carpStrategy *carpStrategy) *carpStatusStrategy {
return &carpStatusStrategy{carpStrategy}
}
// GetResetFields returns the set of fields that get reset by the strategy and
// should not be modified by the user. For a status update that is the spec.
func (*carpStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"testapigroup.apimachinery.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (*carpStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newClaim := obj.(*testapigroup.Carp)
oldClaim := old.(*testapigroup.Carp)
newClaim.Spec = oldClaim.Spec
metav1.ResetObjectMetaForStatus(&newClaim.ObjectMeta, &oldClaim.ObjectMeta)
}
func (r *carpStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return nil
}
// WarningsOnUpdate returns warnings for the given update.
func (*carpStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil
}
// Match returns a generic matcher for a given label and field selector.
func Match(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
return storage.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: GetAttrs,
}
}
// GetAttrs returns labels and fields of a given object for filtering purposes.
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
claim, ok := obj.(*testapigroup.Carp)
if !ok {
return nil, nil, errors.New("not a carp")
}
return labels.Set(claim.Labels), toSelectableFields(claim), nil
}
// toSelectableFields returns a field set that represents the object
func toSelectableFields(claim *testapigroup.Carp) fields.Set {
fields := generic.ObjectMetaFieldsSet(&claim.ObjectMeta, true)
return fields
}

View File

@@ -0,0 +1,66 @@
/*
Copyright 2022 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 rest
import (
"k8s.io/apimachinery/pkg/apis/testapigroup"
testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
serverstorage "k8s.io/apiserver/pkg/server/storage"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
carpstore "k8s.io/kubernetes/pkg/registry/testapigroup/carp/storage"
)
type RESTStorageProvider struct {
NamespaceClient v1.NamespaceInterface
}
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(testapigroup.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
// If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities.
// TODO refactor the plumbing to provide the information in the APIGroupInfo
if storageMap, err := p.v1Storage(apiResourceConfigSource, restOptionsGetter, p.NamespaceClient); err != nil {
return genericapiserver.APIGroupInfo{}, err
} else if len(storageMap) > 0 {
apiGroupInfo.VersionedResourcesStorageMap[testapigroupv1.SchemeGroupVersion.Version] = storageMap
}
return apiGroupInfo, nil
}
func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (map[string]rest.Storage, error) {
storage := map[string]rest.Storage{}
if resource := "carps"; apiResourceConfigSource.ResourceEnabled(testapigroupv1.SchemeGroupVersion.WithResource(resource)) {
resourceClaimStorage, resourceClaimStatusStorage, err := carpstore.NewREST(restOptionsGetter, nsClient)
if err != nil {
return nil, err
}
storage[resource] = resourceClaimStorage
storage[resource+"/status"] = resourceClaimStatusStorage
}
return storage, nil
}
func (p RESTStorageProvider) GroupName() string {
return testapigroup.GroupName
}

View File

@@ -46,6 +46,7 @@ func Resource(resource string) schema.GroupResource {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Carp{},
&CarpList{},
)
return nil
}

View File

@@ -68,6 +68,13 @@ type CarpStatus struct {
// This is before the Kubelet pulled the container image(s) for the carp.
// +optional
StartTime *metav1.Time
// Carp infos are provided by different clients, hence the map type.
//
// +listType=map
// +listKey=a
// +listKey=b
Infos []CarpInfo
}
type CarpCondition struct {
@@ -83,6 +90,18 @@ type CarpCondition struct {
Message string
}
type CarpInfo struct {
// A is the first map key.
// +required
A int64
// B is the second map key.
// +required
B string
// Some data for each pair of A and B.
Data string
}
// CarpSpec is a description of a carp
type CarpSpec struct {
// +optional

View File

@@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/testapigroup
// +k8s:openapi-gen=false
// +k8s:defaulter-gen=TypeMeta
// +k8s:prerelease-lifecycle-gen=true
// +groupName=testapigroup.apimachinery.k8s.io
package v1

View File

@@ -101,10 +101,38 @@ func (m *CarpCondition) XXX_DiscardUnknown() {
var xxx_messageInfo_CarpCondition proto.InternalMessageInfo
func (m *CarpInfo) Reset() { *m = CarpInfo{} }
func (*CarpInfo) ProtoMessage() {}
func (*CarpInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_83e19b543dd132db, []int{2}
}
func (m *CarpInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *CarpInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (m *CarpInfo) XXX_Merge(src proto.Message) {
xxx_messageInfo_CarpInfo.Merge(m, src)
}
func (m *CarpInfo) XXX_Size() int {
return m.Size()
}
func (m *CarpInfo) XXX_DiscardUnknown() {
xxx_messageInfo_CarpInfo.DiscardUnknown(m)
}
var xxx_messageInfo_CarpInfo proto.InternalMessageInfo
func (m *CarpList) Reset() { *m = CarpList{} }
func (*CarpList) ProtoMessage() {}
func (*CarpList) Descriptor() ([]byte, []int) {
return fileDescriptor_83e19b543dd132db, []int{2}
return fileDescriptor_83e19b543dd132db, []int{3}
}
func (m *CarpList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -132,7 +160,7 @@ var xxx_messageInfo_CarpList proto.InternalMessageInfo
func (m *CarpSpec) Reset() { *m = CarpSpec{} }
func (*CarpSpec) ProtoMessage() {}
func (*CarpSpec) Descriptor() ([]byte, []int) {
return fileDescriptor_83e19b543dd132db, []int{3}
return fileDescriptor_83e19b543dd132db, []int{4}
}
func (m *CarpSpec) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -160,7 +188,7 @@ var xxx_messageInfo_CarpSpec proto.InternalMessageInfo
func (m *CarpStatus) Reset() { *m = CarpStatus{} }
func (*CarpStatus) ProtoMessage() {}
func (*CarpStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_83e19b543dd132db, []int{4}
return fileDescriptor_83e19b543dd132db, []int{5}
}
func (m *CarpStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -188,6 +216,7 @@ var xxx_messageInfo_CarpStatus proto.InternalMessageInfo
func init() {
proto.RegisterType((*Carp)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.Carp")
proto.RegisterType((*CarpCondition)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpCondition")
proto.RegisterType((*CarpInfo)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpInfo")
proto.RegisterType((*CarpList)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpList")
proto.RegisterType((*CarpSpec)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpSpec")
proto.RegisterMapType((map[string]string)(nil), "k8s.io.apimachinery.pkg.apis.testapigroup.v1.CarpSpec.NodeSelectorEntry")
@@ -199,72 +228,76 @@ func init() {
}
var fileDescriptor_83e19b543dd132db = []byte{
// 1037 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xc1, 0x6e, 0xdb, 0x46,
0x13, 0x36, 0x2d, 0xc9, 0x96, 0xd6, 0x56, 0x62, 0x6f, 0x62, 0x80, 0xbf, 0x81, 0x48, 0x8e, 0x0f,
0x86, 0xff, 0xc2, 0xa5, 0x62, 0xa3, 0x09, 0xdc, 0xa6, 0x40, 0x11, 0xda, 0x45, 0xa5, 0xc2, 0x71,
0x84, 0x95, 0x81, 0x14, 0x45, 0x0f, 0x59, 0x51, 0x5b, 0x8a, 0x95, 0xc8, 0x25, 0x76, 0x57, 0x2a,
0x74, 0x2b, 0xfa, 0x04, 0x7d, 0x88, 0xde, 0x7a, 0xee, 0x03, 0xf4, 0x50, 0xc0, 0xc7, 0x1c, 0x73,
0x12, 0x6a, 0xf5, 0x2d, 0x7c, 0x2a, 0x76, 0xb9, 0xa4, 0x48, 0x4b, 0x55, 0xa3, 0xdc, 0xb8, 0x33,
0xdf, 0xf7, 0xcd, 0xec, 0xce, 0x68, 0x46, 0xe0, 0xf3, 0xde, 0x29, 0xb7, 0x3c, 0x5a, 0xc3, 0xa1,
0xe7, 0x63, 0xa7, 0xeb, 0x05, 0x84, 0x8d, 0x6a, 0x61, 0xcf, 0x95, 0x06, 0x5e, 0x13, 0x84, 0x0b,
0x1c, 0x7a, 0x2e, 0xa3, 0x83, 0xb0, 0x36, 0x3c, 0xae, 0xb9, 0x24, 0x20, 0x0c, 0x0b, 0xd2, 0xb1,
0x42, 0x46, 0x05, 0x85, 0x47, 0x11, 0xdb, 0x4a, 0xb3, 0xad, 0xb0, 0xe7, 0x4a, 0x03, 0xb7, 0xd2,
0x6c, 0x6b, 0x78, 0xbc, 0xfb, 0xb1, 0xeb, 0x89, 0xee, 0xa0, 0x6d, 0x39, 0xd4, 0xaf, 0xb9, 0xd4,
0xa5, 0x35, 0x25, 0xd2, 0x1e, 0x7c, 0xaf, 0x4e, 0xea, 0xa0, 0xbe, 0x22, 0xf1, 0xdd, 0x4f, 0x16,
0xa6, 0xe6, 0x13, 0x81, 0xe7, 0xa4, 0xb4, 0x5b, 0xfb, 0x37, 0x16, 0x1b, 0x04, 0xc2, 0xf3, 0xc9,
0x0c, 0xe1, 0xd9, 0x7f, 0x11, 0xb8, 0xd3, 0x25, 0x3e, 0xbe, 0xcb, 0xdb, 0xff, 0x75, 0x15, 0xe4,
0xcf, 0x30, 0x0b, 0xe1, 0x1b, 0x50, 0x94, 0xc9, 0x74, 0xb0, 0xc0, 0xa6, 0xb1, 0x67, 0x1c, 0x6e,
0x9c, 0x3c, 0xb1, 0x16, 0xbe, 0x8b, 0x44, 0x5b, 0xc3, 0x63, 0xeb, 0x55, 0xfb, 0x07, 0xe2, 0x88,
0x97, 0x44, 0x60, 0x1b, 0x5e, 0x8f, 0xab, 0x2b, 0x93, 0x71, 0x15, 0x4c, 0x6d, 0x28, 0x51, 0x85,
0xdf, 0x80, 0x3c, 0x0f, 0x89, 0x63, 0xae, 0x2a, 0xf5, 0x67, 0xd6, 0x32, 0xaf, 0x6e, 0xc9, 0x1c,
0x5b, 0x21, 0x71, 0xec, 0x4d, 0x1d, 0x23, 0x2f, 0x4f, 0x48, 0x29, 0xc2, 0x37, 0x60, 0x8d, 0x0b,
0x2c, 0x06, 0xdc, 0xcc, 0x29, 0xed, 0xd3, 0x0f, 0xd0, 0x56, 0x7c, 0xfb, 0x9e, 0x56, 0x5f, 0x8b,
0xce, 0x48, 0xeb, 0xee, 0xff, 0x9e, 0x03, 0x65, 0x09, 0x3b, 0xa3, 0x41, 0xc7, 0x13, 0x1e, 0x0d,
0xe0, 0x53, 0x90, 0x17, 0xa3, 0x90, 0xa8, 0xb7, 0x2a, 0xd9, 0x8f, 0xe3, 0xac, 0xae, 0x46, 0x21,
0xb9, 0x1d, 0x57, 0xb7, 0x33, 0x60, 0x69, 0x44, 0x0a, 0x0e, 0x3f, 0x4d, 0x52, 0x5d, 0xcd, 0x10,
0x75, 0xc0, 0xdb, 0x71, 0xf5, 0x7e, 0x42, 0xcb, 0xe6, 0x00, 0x5d, 0x50, 0xee, 0x63, 0x2e, 0x9a,
0x8c, 0xb6, 0xc9, 0x95, 0xe7, 0x13, 0x7d, 0xd9, 0x8f, 0xde, 0xaf, 0x4c, 0x92, 0x61, 0xef, 0xe8,
0x68, 0xe5, 0x8b, 0xb4, 0x10, 0xca, 0xea, 0xc2, 0x21, 0x80, 0xd2, 0x70, 0xc5, 0x70, 0xc0, 0xa3,
0xfc, 0x65, 0xb4, 0xfc, 0xd2, 0xd1, 0x76, 0x75, 0x34, 0x78, 0x31, 0xa3, 0x86, 0xe6, 0x44, 0x80,
0x07, 0x60, 0x8d, 0x11, 0xcc, 0x69, 0x60, 0x16, 0xd4, 0xdb, 0x24, 0xc5, 0x40, 0xca, 0x8a, 0xb4,
0x17, 0xfe, 0x1f, 0xac, 0xfb, 0x84, 0x73, 0xec, 0x12, 0x73, 0x4d, 0x01, 0xef, 0x6b, 0xe0, 0xfa,
0xcb, 0xc8, 0x8c, 0x62, 0xff, 0xfe, 0x1f, 0x06, 0x28, 0xca, 0x52, 0x5c, 0x78, 0x5c, 0xc0, 0xef,
0x66, 0x5a, 0xdc, 0x7a, 0xbf, 0xdb, 0x48, 0xb6, 0x6a, 0xf0, 0x2d, 0x1d, 0xa8, 0x18, 0x5b, 0x52,
0xed, 0xfd, 0x1a, 0x14, 0x3c, 0x41, 0x7c, 0x59, 0xd8, 0xdc, 0xe1, 0xc6, 0xc9, 0xc9, 0xf2, 0x3d,
0x68, 0x97, 0xb5, 0x7c, 0xa1, 0x21, 0x85, 0x50, 0xa4, 0xb7, 0xff, 0xe7, 0x7a, 0x74, 0x07, 0xd9,
0xf0, 0xf0, 0x02, 0x94, 0x99, 0xa4, 0x32, 0xd1, 0xa4, 0x7d, 0xcf, 0x19, 0xa9, 0x26, 0x28, 0xd9,
0x07, 0x71, 0x61, 0x51, 0xda, 0x79, 0x7b, 0xd7, 0x80, 0xb2, 0x64, 0xe8, 0x82, 0x47, 0x82, 0x30,
0xdf, 0x0b, 0xb0, 0x2c, 0xc2, 0x57, 0x0c, 0x3b, 0xa4, 0x49, 0x98, 0x47, 0x3b, 0x2d, 0xe2, 0xd0,
0xa0, 0xc3, 0x55, 0xd1, 0x73, 0xf6, 0xe3, 0xc9, 0xb8, 0xfa, 0xe8, 0x6a, 0x11, 0x10, 0x2d, 0xd6,
0x81, 0xaf, 0xc0, 0x0e, 0x76, 0x84, 0x37, 0x24, 0xe7, 0x04, 0x77, 0xfa, 0x5e, 0x40, 0xe2, 0x00,
0x05, 0x15, 0xe0, 0x7f, 0x93, 0x71, 0x75, 0xe7, 0xc5, 0x3c, 0x00, 0x9a, 0xcf, 0x83, 0x3f, 0x1b,
0x60, 0x33, 0xa0, 0x1d, 0xd2, 0x22, 0x7d, 0xe2, 0x08, 0xca, 0xcc, 0x75, 0xf5, 0xea, 0xf5, 0x0f,
0x9b, 0x2a, 0xd6, 0x65, 0x4a, 0xea, 0xcb, 0x40, 0xb0, 0x91, 0xfd, 0x50, 0xbf, 0xe8, 0x66, 0xda,
0x85, 0x32, 0x31, 0xe1, 0xd7, 0x00, 0x72, 0xc2, 0x86, 0x9e, 0x43, 0x5e, 0x38, 0x0e, 0x1d, 0x04,
0xe2, 0x12, 0xfb, 0xc4, 0x2c, 0xaa, 0x8a, 0x24, 0xcd, 0xdf, 0x9a, 0x41, 0xa0, 0x39, 0x2c, 0x58,
0x07, 0xf7, 0xb2, 0x56, 0xb3, 0xa4, 0x74, 0xf6, 0xb4, 0x8e, 0x79, 0x4e, 0x42, 0x46, 0x1c, 0x39,
0xba, 0xb3, 0x8a, 0xe8, 0x0e, 0x0f, 0x1e, 0x81, 0xa2, 0xcc, 0x52, 0xe5, 0x02, 0x94, 0x46, 0xd2,
0xb6, 0x97, 0xda, 0x8e, 0x12, 0x04, 0x7c, 0x0a, 0x36, 0xba, 0x94, 0x8b, 0x4b, 0x22, 0x7e, 0xa4,
0xac, 0x67, 0x6e, 0xec, 0x19, 0x87, 0x45, 0xfb, 0x81, 0x26, 0x6c, 0xd4, 0xa7, 0x2e, 0x94, 0xc6,
0xc9, 0xdf, 0xa0, 0x3c, 0x36, 0x1b, 0xe7, 0xe6, 0xa6, 0xa2, 0x24, 0xbf, 0xc1, 0x7a, 0x64, 0x46,
0xb1, 0x3f, 0x86, 0x36, 0x9a, 0x67, 0x66, 0x79, 0x16, 0xda, 0x68, 0x9e, 0xa1, 0xd8, 0x2f, 0x53,
0x97, 0x9f, 0x81, 0x4c, 0x7d, 0x2b, 0x9b, 0x7a, 0x5d, 0xdb, 0x51, 0x82, 0x80, 0x35, 0x50, 0xe2,
0x83, 0x76, 0x87, 0xfa, 0xd8, 0x0b, 0xcc, 0x6d, 0x05, 0xdf, 0xd6, 0xf0, 0x52, 0x2b, 0x76, 0xa0,
0x29, 0x06, 0x3e, 0x07, 0x65, 0xb9, 0x06, 0x3b, 0x83, 0x3e, 0x61, 0x2a, 0xc6, 0x03, 0x45, 0x4a,
0xa6, 0x62, 0x2b, 0x76, 0xaa, 0x37, 0xca, 0x62, 0x77, 0xbf, 0x00, 0xdb, 0x33, 0x5d, 0x02, 0xb7,
0x40, 0xae, 0x47, 0x46, 0xd1, 0x12, 0x40, 0xf2, 0x13, 0x3e, 0x04, 0x85, 0x21, 0xee, 0x0f, 0x48,
0x34, 0xdf, 0x51, 0x74, 0xf8, 0x6c, 0xf5, 0xd4, 0xd8, 0xff, 0x2d, 0x07, 0xc0, 0x74, 0xd5, 0xc0,
0x27, 0xa0, 0x10, 0x76, 0x31, 0x8f, 0x37, 0x48, 0xdc, 0x2f, 0x85, 0xa6, 0x34, 0xde, 0x8e, 0xab,
0x25, 0x89, 0x55, 0x07, 0x14, 0x01, 0x21, 0x05, 0xc0, 0x89, 0x77, 0x43, 0x3c, 0x66, 0x9e, 0x2f,
0xdf, 0xf0, 0xc9, 0x7e, 0x99, 0xee, 0xeb, 0xc4, 0xc4, 0x51, 0x2a, 0x44, 0x7a, 0xd0, 0xe6, 0x16,
0x0f, 0xda, 0xd4, 0xec, 0xce, 0x2f, 0x9c, 0xdd, 0x07, 0x60, 0x2d, 0x2a, 0xf6, 0xdd, 0x19, 0x1f,
0xf5, 0x02, 0xd2, 0x5e, 0x89, 0x73, 0x30, 0x0b, 0x1b, 0x4d, 0x3d, 0xe2, 0x13, 0xdc, 0x99, 0xb2,
0x22, 0xed, 0x85, 0xaf, 0x41, 0x49, 0x0d, 0x34, 0xb5, 0xa2, 0xd6, 0x97, 0x5e, 0x51, 0x65, 0xd5,
0x2b, 0xb1, 0x00, 0x9a, 0x6a, 0xd9, 0xe8, 0xfa, 0xa6, 0xb2, 0xf2, 0xf6, 0xa6, 0xb2, 0xf2, 0xee,
0xa6, 0xb2, 0xf2, 0xd3, 0xa4, 0x62, 0x5c, 0x4f, 0x2a, 0xc6, 0xdb, 0x49, 0xc5, 0x78, 0x37, 0xa9,
0x18, 0x7f, 0x4d, 0x2a, 0xc6, 0x2f, 0x7f, 0x57, 0x56, 0xbe, 0x3d, 0x5a, 0xe6, 0x8f, 0xe7, 0x3f,
0x01, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xd1, 0x13, 0x90, 0xa7, 0x0a, 0x00, 0x00,
// 1103 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x4f, 0x4f, 0x1b, 0x47,
0x14, 0x67, 0xb1, 0x0d, 0xf6, 0x80, 0x9b, 0x30, 0x09, 0xea, 0x16, 0x29, 0x36, 0xf1, 0x01, 0xd1,
0x8a, 0xae, 0x03, 0x6a, 0x22, 0xda, 0x54, 0xaa, 0x58, 0xa8, 0x0a, 0x15, 0x21, 0xd6, 0x18, 0x29,
0x55, 0x1b, 0x55, 0x19, 0xef, 0x0e, 0xcb, 0x16, 0xef, 0xce, 0x6a, 0x66, 0xec, 0xca, 0xb7, 0xaa,
0xa7, 0x1e, 0xfb, 0x21, 0xfa, 0x15, 0xfa, 0x01, 0x7a, 0xe3, 0x98, 0x63, 0x7a, 0xb1, 0x8a, 0xfb,
0x2d, 0x38, 0x55, 0x33, 0x3b, 0xbb, 0x5e, 0x63, 0xe3, 0xc6, 0xdc, 0x3c, 0xef, 0xfd, 0x7e, 0xbf,
0xf7, 0x3c, 0xf3, 0xfe, 0x2c, 0xf8, 0xf2, 0x62, 0x97, 0x5b, 0x3e, 0xad, 0xe3, 0xc8, 0x0f, 0xb0,
0x73, 0xee, 0x87, 0x84, 0xf5, 0xea, 0xd1, 0x85, 0x27, 0x0d, 0xbc, 0x2e, 0x08, 0x17, 0x38, 0xf2,
0x3d, 0x46, 0x3b, 0x51, 0xbd, 0xbb, 0x5d, 0xf7, 0x48, 0x48, 0x18, 0x16, 0xc4, 0xb5, 0x22, 0x46,
0x05, 0x85, 0x5b, 0x31, 0xdb, 0xca, 0xb2, 0xad, 0xe8, 0xc2, 0x93, 0x06, 0x6e, 0x65, 0xd9, 0x56,
0x77, 0x7b, 0xed, 0x53, 0xcf, 0x17, 0xe7, 0x9d, 0x96, 0xe5, 0xd0, 0xa0, 0xee, 0x51, 0x8f, 0xd6,
0x95, 0x48, 0xab, 0x73, 0xa6, 0x4e, 0xea, 0xa0, 0x7e, 0xc5, 0xe2, 0x6b, 0x9f, 0x4d, 0x4d, 0x2d,
0x20, 0x02, 0x4f, 0x48, 0x69, 0xad, 0x7e, 0x1b, 0x8b, 0x75, 0x42, 0xe1, 0x07, 0x64, 0x8c, 0xf0,
0xec, 0xff, 0x08, 0xdc, 0x39, 0x27, 0x01, 0xbe, 0xc9, 0xab, 0xfd, 0x31, 0x0f, 0xf2, 0xfb, 0x98,
0x45, 0xf0, 0x0d, 0x28, 0xca, 0x64, 0x5c, 0x2c, 0xb0, 0x69, 0xac, 0x1b, 0x9b, 0x4b, 0x3b, 0x4f,
0xac, 0xa9, 0xf7, 0x22, 0xd1, 0x56, 0x77, 0xdb, 0x7a, 0xd9, 0xfa, 0x89, 0x38, 0xe2, 0x05, 0x11,
0xd8, 0x86, 0x97, 0xfd, 0xea, 0xdc, 0xa0, 0x5f, 0x05, 0x43, 0x1b, 0x4a, 0x55, 0xe1, 0x77, 0x20,
0xcf, 0x23, 0xe2, 0x98, 0xf3, 0x4a, 0xfd, 0x99, 0x35, 0xcb, 0xad, 0x5b, 0x32, 0xc7, 0x66, 0x44,
0x1c, 0x7b, 0x59, 0xc7, 0xc8, 0xcb, 0x13, 0x52, 0x8a, 0xf0, 0x0d, 0x58, 0xe0, 0x02, 0x8b, 0x0e,
0x37, 0x73, 0x4a, 0x7b, 0xf7, 0x0e, 0xda, 0x8a, 0x6f, 0x7f, 0xa0, 0xd5, 0x17, 0xe2, 0x33, 0xd2,
0xba, 0xb5, 0x3f, 0x73, 0xa0, 0x2c, 0x61, 0xfb, 0x34, 0x74, 0x7d, 0xe1, 0xd3, 0x10, 0x3e, 0x05,
0x79, 0xd1, 0x8b, 0x88, 0xba, 0xab, 0x92, 0xfd, 0x38, 0xc9, 0xea, 0xb4, 0x17, 0x91, 0xeb, 0x7e,
0x75, 0x65, 0x04, 0x2c, 0x8d, 0x48, 0xc1, 0xe1, 0xe7, 0x69, 0xaa, 0xf3, 0x23, 0x44, 0x1d, 0xf0,
0xba, 0x5f, 0xbd, 0x97, 0xd2, 0x46, 0x73, 0x80, 0x1e, 0x28, 0xb7, 0x31, 0x17, 0x0d, 0x46, 0x5b,
0xe4, 0xd4, 0x0f, 0x88, 0xfe, 0xb3, 0x9f, 0xbc, 0xdf, 0x33, 0x49, 0x86, 0xbd, 0xaa, 0xa3, 0x95,
0x8f, 0xb3, 0x42, 0x68, 0x54, 0x17, 0x76, 0x01, 0x94, 0x86, 0x53, 0x86, 0x43, 0x1e, 0xe7, 0x2f,
0xa3, 0xe5, 0x67, 0x8e, 0xb6, 0xa6, 0xa3, 0xc1, 0xe3, 0x31, 0x35, 0x34, 0x21, 0x02, 0xdc, 0x00,
0x0b, 0x8c, 0x60, 0x4e, 0x43, 0xb3, 0xa0, 0xee, 0x26, 0x7d, 0x0c, 0xa4, 0xac, 0x48, 0x7b, 0xe1,
0xc7, 0x60, 0x31, 0x20, 0x9c, 0x63, 0x8f, 0x98, 0x0b, 0x0a, 0x78, 0x4f, 0x03, 0x17, 0x5f, 0xc4,
0x66, 0x94, 0xf8, 0x6b, 0x3f, 0x82, 0xa2, 0x7c, 0x89, 0xa3, 0xf0, 0x8c, 0xc2, 0x0f, 0x81, 0x11,
0x97, 0x76, 0xce, 0x2e, 0x69, 0x82, 0xb1, 0x87, 0x0c, 0x2c, 0x1d, 0x2d, 0xfd, 0x1c, 0xa9, 0xc3,
0x46, 0x46, 0x0b, 0xae, 0x83, 0xbc, 0xea, 0x87, 0x9c, 0xf2, 0xa5, 0x95, 0x77, 0x80, 0x05, 0x46,
0xca, 0x53, 0xfb, 0xcb, 0x88, 0x03, 0x1c, 0xfb, 0x5c, 0xc0, 0xd7, 0x63, 0x2d, 0x64, 0xbd, 0xdf,
0x6d, 0x49, 0xb6, 0x6a, 0xa0, 0xfb, 0x3a, 0x44, 0x31, 0xb1, 0x64, 0xda, 0xe7, 0x15, 0x28, 0xf8,
0x82, 0x04, 0xb2, 0x70, 0x72, 0x9b, 0x4b, 0x3b, 0x3b, 0xb3, 0xd7, 0xb8, 0x5d, 0xd6, 0xf2, 0x85,
0x23, 0x29, 0x84, 0x62, 0xbd, 0xda, 0xdf, 0x8b, 0xf1, 0x7f, 0x90, 0x0d, 0x05, 0x8f, 0x41, 0x99,
0x49, 0x2a, 0x13, 0x0d, 0xda, 0xf6, 0x9d, 0x9e, 0xfe, 0xef, 0x1b, 0x49, 0xe1, 0xa0, 0xac, 0xf3,
0xfa, 0xa6, 0x01, 0x8d, 0x92, 0xa1, 0x07, 0x1e, 0x09, 0xc2, 0x02, 0x3f, 0xc4, 0xf2, 0x91, 0xbf,
0x61, 0xd8, 0x21, 0x0d, 0xc2, 0x7c, 0xea, 0x36, 0x89, 0x43, 0x43, 0x97, 0xab, 0xa2, 0xca, 0xd9,
0x8f, 0x07, 0xfd, 0xea, 0xa3, 0xd3, 0x69, 0x40, 0x34, 0x5d, 0x07, 0xbe, 0x04, 0xab, 0xd8, 0x11,
0x7e, 0x97, 0x1c, 0x10, 0xec, 0xb6, 0xfd, 0x90, 0x24, 0x01, 0x0a, 0x2a, 0xc0, 0x47, 0x83, 0x7e,
0x75, 0x75, 0x6f, 0x12, 0x00, 0x4d, 0xe6, 0xc1, 0x5f, 0x0d, 0xb0, 0x1c, 0x52, 0x97, 0x34, 0x49,
0x9b, 0x38, 0x82, 0x32, 0x73, 0x51, 0xdd, 0xfa, 0xe1, 0xdd, 0xa6, 0x96, 0x75, 0x92, 0x91, 0xfa,
0x3a, 0x14, 0xac, 0x67, 0x3f, 0xd4, 0x37, 0xba, 0x9c, 0x75, 0xa1, 0x91, 0x98, 0xf0, 0x5b, 0x00,
0x39, 0x61, 0x5d, 0xdf, 0x21, 0x7b, 0x8e, 0x43, 0x3b, 0xa1, 0x38, 0xc1, 0x01, 0x31, 0x8b, 0xea,
0x45, 0xd2, 0xe6, 0x6a, 0x8e, 0x21, 0xd0, 0x04, 0x16, 0x7c, 0x0d, 0x4c, 0x97, 0x44, 0x8c, 0x38,
0x72, 0xf8, 0x8f, 0x72, 0xcc, 0x92, 0x52, 0x5c, 0xd7, 0x8a, 0xe6, 0xc1, 0x2d, 0x38, 0x74, 0xab,
0x02, 0xdc, 0x02, 0x45, 0x99, 0xb9, 0xca, 0x0f, 0x28, 0xb5, 0xb4, 0x94, 0x4f, 0xb4, 0x1d, 0xa5,
0x08, 0xf8, 0x14, 0x2c, 0x9d, 0x53, 0x2e, 0x4e, 0x88, 0xf8, 0x99, 0xb2, 0x0b, 0x73, 0x69, 0xdd,
0xd8, 0x2c, 0xda, 0x0f, 0x34, 0x61, 0xe9, 0x70, 0xe8, 0x42, 0x59, 0x9c, 0xec, 0x7b, 0x79, 0x6c,
0x1c, 0x1d, 0x98, 0xcb, 0x8a, 0x92, 0xf6, 0xfd, 0x61, 0x6c, 0x46, 0x89, 0x3f, 0x81, 0x1e, 0x35,
0xf6, 0xcd, 0xf2, 0x38, 0xf4, 0xa8, 0xb1, 0x8f, 0x12, 0xbf, 0x4c, 0x5d, 0xfe, 0x0c, 0x65, 0xea,
0xf7, 0x47, 0x53, 0x3f, 0xd4, 0x76, 0x94, 0x22, 0x60, 0x1d, 0x94, 0x78, 0xa7, 0xe5, 0xd2, 0x00,
0xfb, 0xa1, 0xb9, 0xa2, 0xe0, 0x2b, 0x1a, 0x5e, 0x6a, 0x26, 0x0e, 0x34, 0xc4, 0xc0, 0xe7, 0xa0,
0x2c, 0x57, 0xaf, 0xdb, 0x69, 0x13, 0xa6, 0xae, 0xe7, 0x81, 0x22, 0xa5, 0x93, 0xb8, 0x99, 0x75,
0xa2, 0x51, 0xec, 0xda, 0x57, 0x60, 0x65, 0xac, 0x72, 0xe0, 0x7d, 0x90, 0xbb, 0x20, 0xbd, 0x78,
0xf1, 0x20, 0xf9, 0x13, 0x3e, 0x04, 0x85, 0x2e, 0x6e, 0x77, 0x48, 0x3c, 0xc4, 0x50, 0x7c, 0xf8,
0x62, 0x7e, 0xd7, 0xa8, 0xfd, 0x96, 0x07, 0x60, 0xb8, 0xde, 0xe0, 0x13, 0x50, 0x88, 0xce, 0x31,
0x4f, 0xb6, 0x56, 0x52, 0x43, 0x85, 0x86, 0x34, 0x5e, 0xf7, 0xab, 0x25, 0x89, 0x55, 0x07, 0x14,
0x03, 0x21, 0x05, 0xc0, 0x49, 0xf6, 0x51, 0x32, 0x7a, 0x9e, 0xcf, 0xde, 0x04, 0xe9, 0x4e, 0x1b,
0x7e, 0x23, 0xa4, 0x26, 0x8e, 0x32, 0x21, 0xb2, 0xc3, 0x3d, 0x37, 0x7d, 0xb8, 0x67, 0xf6, 0x45,
0x7e, 0xea, 0xbe, 0xd8, 0x00, 0x0b, 0xf1, 0x63, 0xdf, 0xdc, 0x2b, 0x71, 0x2d, 0x20, 0xed, 0x95,
0x38, 0x47, 0x2e, 0x8b, 0x86, 0x5e, 0x2b, 0x29, 0x4e, 0xad, 0x90, 0x06, 0xd2, 0x5e, 0xf8, 0x0a,
0x94, 0xd4, 0x90, 0x53, 0x6b, 0x71, 0x71, 0xe6, 0xb5, 0x58, 0x56, 0xb5, 0x92, 0x08, 0xa0, 0xa1,
0x16, 0xfc, 0x01, 0x14, 0xfc, 0xf0, 0x8c, 0x72, 0xb3, 0xa8, 0xee, 0xf9, 0x0e, 0x9f, 0x48, 0x72,
0xd1, 0x65, 0xc6, 0xbc, 0x14, 0x43, 0xb1, 0xa6, 0x8d, 0x2e, 0xaf, 0x2a, 0x73, 0x6f, 0xaf, 0x2a,
0x73, 0xef, 0xae, 0x2a, 0x73, 0xbf, 0x0c, 0x2a, 0xc6, 0xe5, 0xa0, 0x62, 0xbc, 0x1d, 0x54, 0x8c,
0x77, 0x83, 0x8a, 0xf1, 0xcf, 0xa0, 0x62, 0xfc, 0xfe, 0x6f, 0x65, 0xee, 0xfb, 0xad, 0x59, 0xbe,
0xa4, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xce, 0x5e, 0x7f, 0xf0, 0x78, 0x0b, 0x00, 0x00,
}
func (m *Carp) Marshal() (dAtA []byte, err error) {
@@ -383,6 +416,42 @@ func (m *CarpCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *CarpInfo) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *CarpInfo) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *CarpInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
i -= len(m.Data)
copy(dAtA[i:], m.Data)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Data)))
i--
dAtA[i] = 0x1a
i -= len(m.B)
copy(dAtA[i:], m.B)
i = encodeVarintGenerated(dAtA, i, uint64(len(m.B)))
i--
dAtA[i] = 0x12
i = encodeVarintGenerated(dAtA, i, uint64(m.A))
i--
dAtA[i] = 0x8
return len(dAtA) - i, nil
}
func (m *CarpList) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@@ -572,6 +641,20 @@ func (m *CarpStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Infos) > 0 {
for iNdEx := len(m.Infos) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Infos[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGenerated(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x42
}
}
if m.StartTime != nil {
{
size, err := m.StartTime.MarshalToSizedBuffer(dAtA[:i])
@@ -673,6 +756,20 @@ func (m *CarpCondition) Size() (n int) {
return n
}
func (m *CarpInfo) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
n += 1 + sovGenerated(uint64(m.A))
l = len(m.B)
n += 1 + l + sovGenerated(uint64(l))
l = len(m.Data)
n += 1 + l + sovGenerated(uint64(l))
return n
}
func (m *CarpList) Size() (n int) {
if m == nil {
return 0
@@ -756,6 +853,12 @@ func (m *CarpStatus) Size() (n int) {
l = m.StartTime.Size()
n += 1 + l + sovGenerated(uint64(l))
}
if len(m.Infos) > 0 {
for _, e := range m.Infos {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@@ -792,6 +895,18 @@ func (this *CarpCondition) String() string {
}, "")
return s
}
func (this *CarpInfo) String() string {
if this == nil {
return "nil"
}
s := strings.Join([]string{`&CarpInfo{`,
`A:` + fmt.Sprintf("%v", this.A) + `,`,
`B:` + fmt.Sprintf("%v", this.B) + `,`,
`Data:` + fmt.Sprintf("%v", this.Data) + `,`,
`}`,
}, "")
return s
}
func (this *CarpList) String() string {
if this == nil {
return "nil"
@@ -849,6 +964,11 @@ func (this *CarpStatus) String() string {
repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "CarpCondition", "CarpCondition", 1), `&`, ``, 1) + ","
}
repeatedStringForConditions += "}"
repeatedStringForInfos := "[]CarpInfo{"
for _, f := range this.Infos {
repeatedStringForInfos += strings.Replace(strings.Replace(f.String(), "CarpInfo", "CarpInfo", 1), `&`, ``, 1) + ","
}
repeatedStringForInfos += "}"
s := strings.Join([]string{`&CarpStatus{`,
`Phase:` + fmt.Sprintf("%v", this.Phase) + `,`,
`Conditions:` + repeatedStringForConditions + `,`,
@@ -857,6 +977,7 @@ func (this *CarpStatus) String() string {
`HostIP:` + fmt.Sprintf("%v", this.HostIP) + `,`,
`CarpIP:` + fmt.Sprintf("%v", this.CarpIP) + `,`,
`StartTime:` + strings.Replace(fmt.Sprintf("%v", this.StartTime), "Time", "v1.Time", 1) + `,`,
`Infos:` + repeatedStringForInfos + `,`,
`}`,
}, "")
return s
@@ -1262,6 +1383,139 @@ func (m *CarpCondition) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *CarpInfo) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: CarpInfo: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: CarpInfo: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field A", wireType)
}
m.A = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.A |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field B", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.B = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Data = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGenerated
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *CarpList) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@@ -2139,6 +2393,40 @@ func (m *CarpStatus) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Infos", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Infos = append(m.Infos, CarpInfo{})
if err := m.Infos[len(m.Infos)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@@ -77,6 +77,19 @@ message CarpCondition {
optional string message = 6;
}
message CarpInfo {
// A is the first map key.
// +required
optional int64 a = 1;
// B is the second map key.
// +required
optional string b = 2;
// Some data for each pair of A and B.
optional string data = 3;
}
// CarpList is a list of Carps.
message CarpList {
// Standard list metadata.
@@ -129,7 +142,7 @@ message CarpSpec {
// Deprecated: Use serviceAccountName instead.
// +k8s:conversion-gen=false
// +optional
optional string serviceAccount = 9;
optional string deprecatedServiceAccount = 9;
// NodeName is a request to schedule this carp onto a specific node. If it is non-empty,
// the scheduler simply schedules this carp onto that node, assuming that it fits resource
@@ -168,7 +181,7 @@ message CarpSpec {
// If specified, the carp will be dispatched by specified scheduler.
// If not specified, the carp will be dispatched by default scheduler.
// +optional
optional string schedulername = 19;
optional string schedulerName = 19;
}
// CarpStatus represents information about the status of a carp. Status may trail the actual
@@ -181,6 +194,10 @@ message CarpStatus {
// Current service state of carp.
// More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions
// +patchStrategy=merge
// +patchMergeKey=type
// +listType=map
// +listMapKey=type
// +optional
repeated CarpCondition conditions = 2;
@@ -206,5 +223,12 @@ message CarpStatus {
// This is before the Kubelet pulled the container image(s) for the carp.
// +optional
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time startTime = 7;
// Carp infos are provided by different clients, hence the map type.
//
// +listType=map
// +listMapKey=a
// +listMapKey=b
repeated CarpInfo infos = 8;
}

View File

@@ -57,6 +57,7 @@ func init() {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Carp{},
&CarpList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -28,6 +28,7 @@ type (
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.1
// Carp is a collection of containers, used as either input (create, update) or as output (list, get).
type Carp struct {
@@ -60,8 +61,12 @@ type CarpStatus struct {
Phase CarpPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=CarpPhase"`
// Current service state of carp.
// More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions
// +patchStrategy=merge
// +patchMergeKey=type
// +listType=map
// +listMapKey=type
// +optional
Conditions []CarpCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"`
Conditions []CarpCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,opt,name=conditions"`
// A human readable message indicating details about why the carp is in this condition.
// +optional
Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"`
@@ -82,6 +87,13 @@ type CarpStatus struct {
// This is before the Kubelet pulled the container image(s) for the carp.
// +optional
StartTime *metav1.Time `json:"startTime,omitempty" protobuf:"bytes,7,opt,name=startTime"`
// Carp infos are provided by different clients, hence the map type.
//
// +listType=map
// +listMapKey=a
// +listMapKey=b
Infos []CarpInfo `json:"infos,omitempty" protobuf:"bytes,8,rep,name=infos"`
}
type CarpCondition struct {
@@ -107,6 +119,18 @@ type CarpCondition struct {
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}
type CarpInfo struct {
// A is the first map key.
// +required
A int64 `json:"a" protobuf:"bytes,1,name=a"`
// B is the second map key.
// +required
B string `json:"b" protobuf:"bytes,2,name=b"`
// Some data for each pair of A and B.
Data string `json:"data" protobuf:"bytes,3,name=data"`
}
// CarpSpec is a description of a carp
type CarpSpec struct {
// Restart policy for all containers within the carp.
@@ -143,7 +167,7 @@ type CarpSpec struct {
// Deprecated: Use serviceAccountName instead.
// +k8s:conversion-gen=false
// +optional
DeprecatedServiceAccount string `json:"serviceAccount,omitempty" protobuf:"bytes,9,opt,name=serviceAccount"`
DeprecatedServiceAccount string `json:"deprecatedServiceAccount,omitempty" protobuf:"bytes,9,opt,name=deprecatedServiceAccount"`
// NodeName is a request to schedule this carp onto a specific node. If it is non-empty,
// the scheduler simply schedules this carp onto that node, assuming that it fits resource
@@ -176,10 +200,11 @@ type CarpSpec struct {
// If specified, the carp will be dispatched by specified scheduler.
// If not specified, the carp will be dispatched by default scheduler.
// +optional
SchedulerName string `json:"schedulername,omitempty" protobuf:"bytes,19,opt,name=schedulername"`
SchedulerName string `json:"schedulerName,omitempty" protobuf:"bytes,19,opt,name=schedulerName"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:prerelease-lifecycle-gen:introduced=1.1
// CarpList is a list of Carps.
type CarpList struct {

View File

@@ -57,6 +57,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CarpInfo)(nil), (*testapigroup.CarpInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_CarpInfo_To_testapigroup_CarpInfo(a.(*CarpInfo), b.(*testapigroup.CarpInfo), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*testapigroup.CarpInfo)(nil), (*CarpInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_testapigroup_CarpInfo_To_v1_CarpInfo(a.(*testapigroup.CarpInfo), b.(*CarpInfo), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CarpList)(nil), (*testapigroup.CarpList)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_CarpList_To_testapigroup_CarpList(a.(*CarpList), b.(*testapigroup.CarpList), scope)
}); err != nil {
@@ -152,6 +162,30 @@ func Convert_testapigroup_CarpCondition_To_v1_CarpCondition(in *testapigroup.Car
return autoConvert_testapigroup_CarpCondition_To_v1_CarpCondition(in, out, s)
}
func autoConvert_v1_CarpInfo_To_testapigroup_CarpInfo(in *CarpInfo, out *testapigroup.CarpInfo, s conversion.Scope) error {
out.A = in.A
out.B = in.B
out.Data = in.Data
return nil
}
// Convert_v1_CarpInfo_To_testapigroup_CarpInfo is an autogenerated conversion function.
func Convert_v1_CarpInfo_To_testapigroup_CarpInfo(in *CarpInfo, out *testapigroup.CarpInfo, s conversion.Scope) error {
return autoConvert_v1_CarpInfo_To_testapigroup_CarpInfo(in, out, s)
}
func autoConvert_testapigroup_CarpInfo_To_v1_CarpInfo(in *testapigroup.CarpInfo, out *CarpInfo, s conversion.Scope) error {
out.A = in.A
out.B = in.B
out.Data = in.Data
return nil
}
// Convert_testapigroup_CarpInfo_To_v1_CarpInfo is an autogenerated conversion function.
func Convert_testapigroup_CarpInfo_To_v1_CarpInfo(in *testapigroup.CarpInfo, out *CarpInfo, s conversion.Scope) error {
return autoConvert_testapigroup_CarpInfo_To_v1_CarpInfo(in, out, s)
}
func autoConvert_v1_CarpList_To_testapigroup_CarpList(in *CarpList, out *testapigroup.CarpList, s conversion.Scope) error {
out.ListMeta = in.ListMeta
if in.Items != nil {
@@ -242,6 +276,7 @@ func autoConvert_v1_CarpStatus_To_testapigroup_CarpStatus(in *CarpStatus, out *t
out.HostIP = in.HostIP
out.CarpIP = in.CarpIP
out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime))
out.Infos = *(*[]testapigroup.CarpInfo)(unsafe.Pointer(&in.Infos))
return nil
}
@@ -258,6 +293,7 @@ func autoConvert_testapigroup_CarpStatus_To_v1_CarpStatus(in *testapigroup.CarpS
out.HostIP = in.HostIP
out.CarpIP = in.CarpIP
out.StartTime = (*metav1.Time)(unsafe.Pointer(in.StartTime))
out.Infos = *(*[]CarpInfo)(unsafe.Pointer(&in.Infos))
return nil
}

View File

@@ -71,6 +71,22 @@ func (in *CarpCondition) DeepCopy() *CarpCondition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CarpInfo) DeepCopyInto(out *CarpInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpInfo.
func (in *CarpInfo) DeepCopy() *CarpInfo {
if in == nil {
return nil
}
out := new(CarpInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CarpList) DeepCopyInto(out *CarpList) {
*out = *in
@@ -151,6 +167,11 @@ func (in *CarpStatus) DeepCopyInto(out *CarpStatus) {
in, out := &in.StartTime, &out.StartTime
*out = (*in).DeepCopy()
}
if in.Infos != nil {
in, out := &in.Infos, &out.Infos
*out = make([]CarpInfo, len(*in))
copy(*out, *in)
}
return
}

View File

@@ -0,0 +1,34 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by prerelease-lifecycle-gen. DO NOT EDIT.
package v1
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *Carp) APILifecycleIntroduced() (major, minor int) {
return 1, 1
}
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
func (in *CarpList) APILifecycleIntroduced() (major, minor int) {
return 1, 1
}

View File

@@ -71,6 +71,22 @@ func (in *CarpCondition) DeepCopy() *CarpCondition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CarpInfo) DeepCopyInto(out *CarpInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpInfo.
func (in *CarpInfo) DeepCopy() *CarpInfo {
if in == nil {
return nil
}
out := new(CarpInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CarpList) DeepCopyInto(out *CarpList) {
*out = *in
@@ -151,6 +167,11 @@ func (in *CarpStatus) DeepCopyInto(out *CarpStatus) {
in, out := &in.StartTime, &out.StartTime
*out = (*in).DeepCopy()
}
if in.Infos != nil {
in, out := &in.Infos, &out.Infos
*out = make([]CarpInfo, len(*in))
copy(*out, *in)
}
return
}

View File

@@ -0,0 +1,364 @@
/*
Copyright 2025 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 dra
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/apis/testapigroup/install"
testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"k8s.io/client-go/kubernetes"
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/controlplane"
controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver"
testapigrouprest "k8s.io/kubernetes/pkg/registry/testapigroup/rest"
"k8s.io/kubernetes/test/integration/framework"
"k8s.io/kubernetes/test/utils/ktesting"
"sigs.k8s.io/yaml"
)
func init() {
install.Install(legacyscheme.Scheme)
}
func TestApply(t *testing.T) {
tCtx := ktesting.Init(t)
etcdOptions := framework.SharedEtcd()
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
apiServerFlags := framework.DefaultTestServerFlags()
runtimeConfigs := []string{"testapigroup.apimachinery.k8s.io/v1=true"}
apiServerFlags = append(apiServerFlags, "--runtime-config="+strings.Join(runtimeConfigs, ","))
// Sanity check. Not protected against concurrent access, but that's
// okay: integration tests are also run with race detection, so that
// would catch it.
if controlplane.AdditionalStorageProvidersForTests != nil {
t.Fatal("cannot set AdditionalStorageProvidersForTests, already set")
}
t.Cleanup(func() {
controlplane.AdditionalStorageProvidersForTests = nil
})
controlplane.AdditionalStorageProvidersForTests = func(client *kubernetes.Clientset) []controlplaneapiserver.RESTStorageProvider {
return []controlplaneapiserver.RESTStorageProvider{
testapigrouprest.RESTStorageProvider{NamespaceClient: client.CoreV1().Namespaces()},
}
}
server := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions, apiServerFlags, etcdOptions)
tCtx.CleanupCtx(func(tCtx ktesting.TContext) {
tCtx.Log("Stopping the apiserver...")
server.TearDownFn()
})
tCtx = ktesting.WithRESTConfig(tCtx, server.ClientConfig)
// More sub-tests could be added here. Currently there's only one.
tCtx.Run("optional-list-map-key", testOptionalListMapKey)
}
func testOptionalListMapKey(tCtx ktesting.TContext) {
requireManagedFields := func(what string, obj *unstructured.Unstructured, expectedManagedFields any) {
tCtx.Helper()
actualManagedFields, _, _ := unstructured.NestedFieldCopy(obj.Object, "metadata", "managedFields")
// Strip non-deterministic time.
if actualManagedFields != nil {
managers := actualManagedFields.([]any)
for i := range managers {
unstructured.RemoveNestedField(managers[i].(map[string]any), "time")
}
}
require.Equal(tCtx, dump(expectedManagedFields), dump(actualManagedFields), fmt.Sprintf("%s:\n%s", what, dump(obj)))
}
requireInfos := func(what string, obj *unstructured.Unstructured, expectedInfos []testapigroupv1.CarpInfo) {
tCtx.Helper()
actualInfos, _, _ := unstructured.NestedFieldCopy(obj.Object, "status", "infos")
require.Equal(tCtx, dump(expectedInfos), dump(actualInfos), fmt.Sprintf("%s:\n%s", what, dump(obj)))
}
carp := &unstructured.Unstructured{}
name := "test-carp"
namespace := createTestNamespace(tCtx, nil)
carp.SetName(name)
carp.SetNamespace(namespace)
client := tCtx.Dynamic().Resource(testapigroupv1.SchemeGroupVersion.WithResource("carps")).Namespace(namespace)
// Create with no fields in spec -> managed fields still empty.
carp, err := client.Create(tCtx, carp, metav1.CreateOptions{FieldManager: "creator"})
tCtx.ExpectNoError(err, "create carp")
requireManagedFields("after creation", carp, nil)
// Set infos with "A: 1, B: x" and "A: 2, B: x".
carp, err = client.ApplyStatus(tCtx, name, parseObj(tCtx, `
kind: Carp
apiVersion: testapigroup.apimachinery.k8s.io/v1
status:
infos:
- a: 1
b: "x"
data: status1_a1_bx
- a: 2
b: "x"
data: status1_a2_bx
`),
metav1.ApplyOptions{FieldManager: "status1"})
tCtx.ExpectNoError(err, "add status #1")
requireManagedFields("add status #1", carp, parseAny(tCtx, `
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
k:{"a":2,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status1
operation: Apply
subresource: status
`))
requireInfos("add status #1", carp, []testapigroupv1.CarpInfo{{A: 1, B: "x", Data: "status1_a1_bx"}, {A: 2, B: "x", Data: "status1_a2_bx"}})
// Second status infos with "A: 1, B: y" and "A: 2, B: y".
carp, err = client.ApplyStatus(tCtx, name, parseObj(tCtx, `
kind: Carp
apiVersion: testapigroup.apimachinery.k8s.io/v1
status:
infos:
- a: 1
b: "y"
data: status2_a1_by
- a: 2
b: "y"
data: status2_a2_by
`),
metav1.ApplyOptions{FieldManager: "status2"})
tCtx.ExpectNoError(err, "add status #2")
requireManagedFields("add status #2", carp, parseAny(tCtx, `
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
k:{"a":2,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status1
operation: Apply
subresource: status
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
k:{"a":2,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status2
operation: Apply
subresource: status
`))
requireInfos("add status #2", carp, []testapigroupv1.CarpInfo{{A: 1, B: "x", Data: "status1_a1_bx"}, {A: 2, B: "x", Data: "status1_a2_bx"}, {A: 1, B: "y", Data: "status2_a1_by"}, {A: 2, B: "y", Data: "status2_a2_by"}})
// Remove one entry of first field manager.
carp, err = client.ApplyStatus(tCtx, name, parseObj(tCtx, `
kind: Carp
apiVersion: testapigroup.apimachinery.k8s.io/v1
status:
infos:
- a: 1
b: "x"
data: status1_a1_bx
`),
metav1.ApplyOptions{FieldManager: "status1"})
tCtx.ExpectNoError(err, "remove status #1")
requireManagedFields("remove status #1", carp, parseAny(tCtx, `
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status1
operation: Apply
subresource: status
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
k:{"a":2,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status2
operation: Apply
subresource: status
`))
requireInfos("remove status #1", carp, []testapigroupv1.CarpInfo{{A: 1, B: "x", Data: "status1_a1_bx"}, {A: 1, B: "y", Data: "status2_a1_by"}, {A: 2, B: "y", Data: "status2_a2_by"}})
// Update one entry of second field manager.
carp, err = client.ApplyStatus(tCtx, name, parseObj(tCtx, `
kind: Carp
apiVersion: testapigroup.apimachinery.k8s.io/v1
status:
infos:
- a: 1
b: "y"
data: status2_a1_by
- a: 2
b: "y"
data: status2_a2_by_updated
`),
metav1.ApplyOptions{FieldManager: "status2"})
tCtx.ExpectNoError(err, "update status #2")
requireManagedFields("update status #2", carp, parseAny(tCtx, `
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"x"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status1
operation: Apply
subresource: status
- apiVersion: testapigroup.apimachinery.k8s.io/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:infos:
k:{"a":1,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
k:{"a":2,"b":"y"}:
.: {}
f:a: {}
f:b: {}
f:data: {}
manager: status2
operation: Apply
subresource: status
`))
requireInfos("update status #2", carp, []testapigroupv1.CarpInfo{{A: 1, B: "x", Data: "status1_a1_bx"}, {A: 1, B: "y", Data: "status2_a1_by"}, {A: 2, B: "y", Data: "status2_a2_by_updated"}})
}
func nestedFieldNoCopy(obj *unstructured.Unstructured, fields ...string) fieldLookupResult {
field, found, err := unstructured.NestedFieldNoCopy(obj.Object, fields...)
return fieldLookupResult{
field: field,
found: found,
err: err,
}
}
type fieldLookupResult struct {
field any
found bool
err error
}
// createTestNamespace creates a namespace with a name that is derived from the
// current test name:
// - Non-alpha-numeric characters replaced by hyphen.
// - Truncated in the middle to make it short enough for GenerateName.
// - Hyphen plus random suffix added by the apiserver.
func createTestNamespace(tCtx ktesting.TContext, labels map[string]string) string {
tCtx.Helper()
name := regexp.MustCompile(`[^[:alnum:]_-]`).ReplaceAllString(tCtx.Name(), "-")
name = strings.ToLower(name)
if len(name) > 63 {
name = name[:30] + "--" + name[len(name)-30:]
}
ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: name + "-"}}
ns.Labels = labels
ns, err := tCtx.Client().CoreV1().Namespaces().Create(tCtx, ns, metav1.CreateOptions{})
tCtx.ExpectNoError(err, "create test namespace")
tCtx.CleanupCtx(func(tCtx ktesting.TContext) {
tCtx.ExpectNoError(tCtx.Client().CoreV1().Namespaces().Delete(tCtx, ns.Name, metav1.DeleteOptions{}), "delete test namespace")
})
return ns.Name
}
func dump(in any) string {
out, err := yaml.Marshal(in)
if err != nil {
return err.Error()
}
return string(out)
}
func parseObj(tCtx ktesting.TContext, data string) *unstructured.Unstructured {
tCtx.Helper()
var obj unstructured.Unstructured
err := yaml.Unmarshal([]byte(data), &obj)
tCtx.ExpectNoError(err, data)
return &obj
}
func parseAny(tCtx ktesting.TContext, data string) any {
tCtx.Helper()
var result any
err := yaml.Unmarshal([]byte(data), &result)
tCtx.ExpectNoError(err, data)
return result
}

View File

@@ -0,0 +1,27 @@
/*
Copyright 2025 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 dra
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}