commit
f44d561c1f
956
api/openapi-spec/swagger.json
generated
956
api/openapi-spec/swagger.json
generated
@ -12336,6 +12336,106 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"io.k8s.api.networking.v1alpha1.IPAddress": {
|
||||||
|
"description": "IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. An IP address can be represented in different formats, to guarantee the uniqueness of the IP, the name of the object is the IP address in canonical format, four decimal digits separated by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 Invalid: 10.01.2.3 or 2001:db8:0:0:0::1",
|
||||||
|
"properties": {
|
||||||
|
"apiVersion": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
|
||||||
|
"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddressSpec",
|
||||||
|
"description": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"io.k8s.api.networking.v1alpha1.IPAddressList": {
|
||||||
|
"description": "IPAddressList contains a list of IPAddress.",
|
||||||
|
"properties": {
|
||||||
|
"apiVersion": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"description": "items is the list of IPAddresses.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta",
|
||||||
|
"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"items"
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddressList",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"io.k8s.api.networking.v1alpha1.IPAddressSpec": {
|
||||||
|
"description": "IPAddressSpec describe the attributes in an IP Address.",
|
||||||
|
"properties": {
|
||||||
|
"parentRef": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.ParentReference",
|
||||||
|
"description": "ParentRef references the resource that an IPAddress is attached to. An IPAddress must reference a parent object."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"io.k8s.api.networking.v1alpha1.ParentReference": {
|
||||||
|
"description": "ParentReference describes a reference to a parent object.",
|
||||||
|
"properties": {
|
||||||
|
"group": {
|
||||||
|
"description": "Group is the group of the object being referenced.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Name is the name of the object being referenced.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"description": "Namespace is the namespace of the object being referenced.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"description": "Resource is the resource of the object being referenced.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"description": "UID is the uid of the object being referenced.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"io.k8s.api.node.v1.Overhead": {
|
"io.k8s.api.node.v1.Overhead": {
|
||||||
"description": "Overhead structure represents the resource overhead associated with running a pod.",
|
"description": "Overhead structure represents the resource overhead associated with running a pod.",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -71516,6 +71616,618 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apis/networking.k8s.io/v1alpha1/ipaddresses": {
|
||||||
|
"delete": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "delete collection of IPAddress",
|
||||||
|
"operationId": "deleteNetworkingV1alpha1CollectionIPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "continue",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
|
||||||
|
"in": "query",
|
||||||
|
"name": "dryRun",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "gracePeriodSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "labelSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "orphanDependents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "propagationPolicy",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersion",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersionMatch",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "sendInitialEvents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "timeoutSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "deletecollection",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "list or watch objects of kind IPAddress",
|
||||||
|
"operationId": "listNetworkingV1alpha1IPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "allowWatchBookmarks",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "continue",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "labelSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersion",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersionMatch",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "sendInitialEvents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "timeoutSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "watch",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf",
|
||||||
|
"application/json;stream=watch",
|
||||||
|
"application/vnd.kubernetes.protobuf;stream=watch"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddressList"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "list",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "If 'true', then the output is pretty printed.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "pretty",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "create an IPAddress",
|
||||||
|
"operationId": "createNetworkingV1alpha1IPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
|
||||||
|
"in": "query",
|
||||||
|
"name": "dryRun",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldManager",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldValidation",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"202": {
|
||||||
|
"description": "Accepted",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "post",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/apis/networking.k8s.io/v1alpha1/ipaddresses/{name}": {
|
||||||
|
"delete": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "delete an IPAddress",
|
||||||
|
"operationId": "deleteNetworkingV1alpha1IPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
|
||||||
|
"in": "query",
|
||||||
|
"name": "dryRun",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "gracePeriodSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "orphanDependents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "propagationPolicy",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"202": {
|
||||||
|
"description": "Accepted",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "delete",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "read the specified IPAddress",
|
||||||
|
"operationId": "readNetworkingV1alpha1IPAddress",
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "get",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "name of the IPAddress",
|
||||||
|
"in": "path",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "If 'true', then the output is pretty printed.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "pretty",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json-patch+json",
|
||||||
|
"application/merge-patch+json",
|
||||||
|
"application/strategic-merge-patch+json",
|
||||||
|
"application/apply-patch+yaml"
|
||||||
|
],
|
||||||
|
"description": "partially update the specified IPAddress",
|
||||||
|
"operationId": "patchNetworkingV1alpha1IPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
|
||||||
|
"in": "query",
|
||||||
|
"name": "dryRun",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldManager",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldValidation",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "force",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "patch",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "replace the specified IPAddress",
|
||||||
|
"operationId": "replaceNetworkingV1alpha1IPAddress",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "body",
|
||||||
|
"name": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
|
||||||
|
"in": "query",
|
||||||
|
"name": "dryRun",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldManager",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldValidation",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.api.networking.v1alpha1.IPAddress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "put",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apis/networking.k8s.io/v1alpha1/watch/clustercidrs": {
|
"/apis/networking.k8s.io/v1alpha1/watch/clustercidrs": {
|
||||||
"get": {
|
"get": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@ -71760,6 +72472,250 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/apis/networking.k8s.io/v1alpha1/watch/ipaddresses": {
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "watch individual changes to a list of IPAddress. deprecated: use the 'watch' parameter with a list operation instead.",
|
||||||
|
"operationId": "watchNetworkingV1alpha1IPAddressList",
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf",
|
||||||
|
"application/json;stream=watch",
|
||||||
|
"application/vnd.kubernetes.protobuf;stream=watch"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "watchlist",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "allowWatchBookmarks",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "continue",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "labelSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "If 'true', then the output is pretty printed.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "pretty",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersion",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersionMatch",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "sendInitialEvents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "timeoutSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "watch",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/apis/networking.k8s.io/v1alpha1/watch/ipaddresses/{name}": {
|
||||||
|
"get": {
|
||||||
|
"consumes": [
|
||||||
|
"*/*"
|
||||||
|
],
|
||||||
|
"description": "watch changes to an object of kind IPAddress. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.",
|
||||||
|
"operationId": "watchNetworkingV1alpha1IPAddress",
|
||||||
|
"produces": [
|
||||||
|
"application/json",
|
||||||
|
"application/yaml",
|
||||||
|
"application/vnd.kubernetes.protobuf",
|
||||||
|
"application/json;stream=watch",
|
||||||
|
"application/vnd.kubernetes.protobuf;stream=watch"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemes": [
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"networking_v1alpha1"
|
||||||
|
],
|
||||||
|
"x-kubernetes-action": "watch",
|
||||||
|
"x-kubernetes-group-version-kind": {
|
||||||
|
"group": "networking.k8s.io",
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"version": "v1alpha1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "allowWatchBookmarks",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "continue",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "fieldSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "labelSelector",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "name of the IPAddress",
|
||||||
|
"in": "path",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "If 'true', then the output is pretty printed.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "pretty",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersion",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset",
|
||||||
|
"in": "query",
|
||||||
|
"name": "resourceVersionMatch",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "sendInitialEvents",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "timeoutSeconds",
|
||||||
|
"type": "integer",
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
|
||||||
|
"in": "query",
|
||||||
|
"name": "watch",
|
||||||
|
"type": "boolean",
|
||||||
|
"uniqueItems": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"/apis/node.k8s.io/": {
|
"/apis/node.k8s.io/": {
|
||||||
"get": {
|
"get": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@ import (
|
|||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
|
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +36,10 @@ import (
|
|||||||
func validateClusterIPFlags(options *ServerRunOptions) []error {
|
func validateClusterIPFlags(options *ServerRunOptions) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
// maxCIDRBits is used to define the maximum CIDR size for the cluster ip(s)
|
// maxCIDRBits is used to define the maximum CIDR size for the cluster ip(s)
|
||||||
const maxCIDRBits = 20
|
maxCIDRBits := 20
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
|
maxCIDRBits = 64
|
||||||
|
}
|
||||||
|
|
||||||
// validate that primary has been processed by user provided values or it has been defaulted
|
// validate that primary has been processed by user provided values or it has been defaulted
|
||||||
if options.PrimaryServiceClusterIPRange.IP == nil {
|
if options.PrimaryServiceClusterIPRange.IP == nil {
|
||||||
|
@ -24,7 +24,10 @@ import (
|
|||||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
kubeapiserveradmission "k8s.io/apiserver/pkg/admission"
|
kubeapiserveradmission "k8s.io/apiserver/pkg/admission"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
basemetrics "k8s.io/component-base/metrics"
|
basemetrics "k8s.io/component-base/metrics"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
)
|
)
|
||||||
@ -61,6 +64,7 @@ func TestClusterServiceIPRange(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
options *ServerRunOptions
|
options *ServerRunOptions
|
||||||
expectErrors bool
|
expectErrors bool
|
||||||
|
gate bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no service cidr",
|
name: "no service cidr",
|
||||||
@ -87,6 +91,24 @@ func TestClusterServiceIPRange(t *testing.T) {
|
|||||||
expectErrors: true,
|
expectErrors: true,
|
||||||
options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
|
options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "service cidr IPv4 is too big but gate enbled",
|
||||||
|
expectErrors: false,
|
||||||
|
options: makeOptionsWithCIDRs("10.0.0.0/8", ""),
|
||||||
|
gate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service cidr IPv6 is too big but gate enbled",
|
||||||
|
expectErrors: false,
|
||||||
|
options: makeOptionsWithCIDRs("2001:db8::/64", ""),
|
||||||
|
gate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service cidr IPv6 is too big despuite gate enbled",
|
||||||
|
expectErrors: true,
|
||||||
|
options: makeOptionsWithCIDRs("2001:db8::/12", ""),
|
||||||
|
gate: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "dual-stack secondary cidr too big",
|
name: "dual-stack secondary cidr too big",
|
||||||
expectErrors: true,
|
expectErrors: true,
|
||||||
@ -122,6 +144,8 @@ func TestClusterServiceIPRange(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MultiCIDRServiceAllocator, tc.gate)()
|
||||||
|
|
||||||
errs := validateClusterIPFlags(tc.options)
|
errs := validateClusterIPFlags(tc.options)
|
||||||
if len(errs) > 0 && !tc.expectErrors {
|
if len(errs) > 0 && !tc.expectErrors {
|
||||||
t.Errorf("expected no errors, errors found %+v", errs)
|
t.Errorf("expected no errors, errors found %+v", errs)
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"k8s.io/api/imagepolicy/v1alpha1": "imagepolicyv1alpha1",
|
"k8s.io/api/imagepolicy/v1alpha1": "imagepolicyv1alpha1",
|
||||||
"k8s.io/api/networking/v1": "networkingv1",
|
"k8s.io/api/networking/v1": "networkingv1",
|
||||||
"k8s.io/api/networking/v1beta1": "networkingv1beta1",
|
"k8s.io/api/networking/v1beta1": "networkingv1beta1",
|
||||||
|
"k8s.io/api/networking/v1alpha1": "networkingv1alpha1",
|
||||||
"k8s.io/api/node/v1alpha1": "nodev1alpha1",
|
"k8s.io/api/node/v1alpha1": "nodev1alpha1",
|
||||||
"k8s.io/api/node/v1beta1": "nodev1beta1",
|
"k8s.io/api/node/v1beta1": "nodev1beta1",
|
||||||
"k8s.io/api/node/v1": "nodev1",
|
"k8s.io/api/node/v1": "nodev1",
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package fuzzer
|
package fuzzer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
fuzz "github.com/google/gofuzz"
|
fuzz "github.com/google/gofuzz"
|
||||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking"
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
@ -74,5 +76,35 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
func(obj *networking.IPAddress, c fuzz.Continue) {
|
||||||
|
c.FuzzNoCustom(obj) // fuzz self without calling this function again
|
||||||
|
// length in bytes of the IP Family: IPv4: 4 bytes IPv6: 16 bytes
|
||||||
|
boolean := []bool{false, true}
|
||||||
|
is6 := boolean[c.Rand.Intn(2)]
|
||||||
|
ip := generateRandomIP(is6, c)
|
||||||
|
obj.Name = ip
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateRandomIP(is6 bool, c fuzz.Continue) string {
|
||||||
|
n := 4
|
||||||
|
if is6 {
|
||||||
|
n = 16
|
||||||
|
}
|
||||||
|
bytes := make([]byte, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
bytes[i] = uint8(c.Rand.Intn(255))
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ok := netip.AddrFromSlice(bytes)
|
||||||
|
if ok {
|
||||||
|
return ip.String()
|
||||||
|
}
|
||||||
|
// this should not happen but is better to
|
||||||
|
// return a good IP address than nothing
|
||||||
|
if is6 {
|
||||||
|
return "2001:db8::1"
|
||||||
|
}
|
||||||
|
return "192.168.1.1"
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking"
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking/v1"
|
v1 "k8s.io/kubernetes/pkg/apis/networking/v1"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking/v1alpha1"
|
"k8s.io/kubernetes/pkg/apis/networking/v1alpha1"
|
||||||
"k8s.io/kubernetes/pkg/apis/networking/v1beta1"
|
"k8s.io/kubernetes/pkg/apis/networking/v1beta1"
|
||||||
)
|
)
|
||||||
|
@ -54,6 +54,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
&IngressClassList{},
|
&IngressClassList{},
|
||||||
&ClusterCIDR{},
|
&ClusterCIDR{},
|
||||||
&ClusterCIDRList{},
|
&ClusterCIDRList{},
|
||||||
|
&IPAddress{},
|
||||||
|
&IPAddressList{},
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package networking
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
)
|
)
|
||||||
@ -699,3 +700,55 @@ type ClusterCIDRList struct {
|
|||||||
// items is the list of ClusterCIDRs.
|
// items is the list of ClusterCIDRs.
|
||||||
Items []ClusterCIDR
|
Items []ClusterCIDR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs
|
||||||
|
// that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses.
|
||||||
|
// An IP address can be represented in different formats, to guarantee the uniqueness of the IP,
|
||||||
|
// the name of the object is the IP address in canonical format, four decimal digits separated
|
||||||
|
// by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6.
|
||||||
|
// Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1
|
||||||
|
// Invalid: 10.01.2.3 or 2001:db8:0:0:0::1
|
||||||
|
type IPAddress struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta
|
||||||
|
// +optional
|
||||||
|
Spec IPAddressSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressSpec describe the attributes in an IP Address,
|
||||||
|
type IPAddressSpec struct {
|
||||||
|
// ParentRef references the resource that an IPAddress is attached to.
|
||||||
|
// An IPAddress must reference a parent object.
|
||||||
|
// +required
|
||||||
|
ParentRef *ParentReference
|
||||||
|
}
|
||||||
|
type ParentReference struct {
|
||||||
|
// Group is the group of the object being referenced.
|
||||||
|
Group string
|
||||||
|
// Resource is the resource of the object being referenced.
|
||||||
|
Resource string
|
||||||
|
// Namespace is the namespace of the object being referenced.
|
||||||
|
Namespace string
|
||||||
|
// Name is the name of the object being referenced.
|
||||||
|
Name string
|
||||||
|
// UID is the uid of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
UID types.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// IPAddressList contains a list of IPAddress.
|
||||||
|
type IPAddressList struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta
|
||||||
|
|
||||||
|
// Items is the list of IPAddress
|
||||||
|
Items []IPAddress
|
||||||
|
}
|
||||||
|
137
pkg/apis/networking/v1alpha1/zz_generated.conversion.go
generated
137
pkg/apis/networking/v1alpha1/zz_generated.conversion.go
generated
@ -28,6 +28,7 @@ import (
|
|||||||
v1alpha1 "k8s.io/api/networking/v1alpha1"
|
v1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
core "k8s.io/kubernetes/pkg/apis/core"
|
core "k8s.io/kubernetes/pkg/apis/core"
|
||||||
networking "k8s.io/kubernetes/pkg/apis/networking"
|
networking "k8s.io/kubernetes/pkg/apis/networking"
|
||||||
)
|
)
|
||||||
@ -69,6 +70,46 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1alpha1.IPAddress)(nil), (*networking.IPAddress)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_IPAddress_To_networking_IPAddress(a.(*v1alpha1.IPAddress), b.(*networking.IPAddress), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*networking.IPAddress)(nil), (*v1alpha1.IPAddress)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_networking_IPAddress_To_v1alpha1_IPAddress(a.(*networking.IPAddress), b.(*v1alpha1.IPAddress), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1alpha1.IPAddressList)(nil), (*networking.IPAddressList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_IPAddressList_To_networking_IPAddressList(a.(*v1alpha1.IPAddressList), b.(*networking.IPAddressList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*networking.IPAddressList)(nil), (*v1alpha1.IPAddressList)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_networking_IPAddressList_To_v1alpha1_IPAddressList(a.(*networking.IPAddressList), b.(*v1alpha1.IPAddressList), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1alpha1.IPAddressSpec)(nil), (*networking.IPAddressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec(a.(*v1alpha1.IPAddressSpec), b.(*networking.IPAddressSpec), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*networking.IPAddressSpec)(nil), (*v1alpha1.IPAddressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec(a.(*networking.IPAddressSpec), b.(*v1alpha1.IPAddressSpec), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*v1alpha1.ParentReference)(nil), (*networking.ParentReference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_ParentReference_To_networking_ParentReference(a.(*v1alpha1.ParentReference), b.(*networking.ParentReference), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*networking.ParentReference)(nil), (*v1alpha1.ParentReference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_networking_ParentReference_To_v1alpha1_ParentReference(a.(*networking.ParentReference), b.(*v1alpha1.ParentReference), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,3 +186,99 @@ func autoConvert_networking_ClusterCIDRSpec_To_v1alpha1_ClusterCIDRSpec(in *netw
|
|||||||
func Convert_networking_ClusterCIDRSpec_To_v1alpha1_ClusterCIDRSpec(in *networking.ClusterCIDRSpec, out *v1alpha1.ClusterCIDRSpec, s conversion.Scope) error {
|
func Convert_networking_ClusterCIDRSpec_To_v1alpha1_ClusterCIDRSpec(in *networking.ClusterCIDRSpec, out *v1alpha1.ClusterCIDRSpec, s conversion.Scope) error {
|
||||||
return autoConvert_networking_ClusterCIDRSpec_To_v1alpha1_ClusterCIDRSpec(in, out, s)
|
return autoConvert_networking_ClusterCIDRSpec_To_v1alpha1_ClusterCIDRSpec(in, out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_IPAddress_To_networking_IPAddress(in *v1alpha1.IPAddress, out *networking.IPAddress, s conversion.Scope) error {
|
||||||
|
out.ObjectMeta = in.ObjectMeta
|
||||||
|
if err := Convert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_IPAddress_To_networking_IPAddress is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_IPAddress_To_networking_IPAddress(in *v1alpha1.IPAddress, out *networking.IPAddress, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_IPAddress_To_networking_IPAddress(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_networking_IPAddress_To_v1alpha1_IPAddress(in *networking.IPAddress, out *v1alpha1.IPAddress, s conversion.Scope) error {
|
||||||
|
out.ObjectMeta = in.ObjectMeta
|
||||||
|
if err := Convert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_networking_IPAddress_To_v1alpha1_IPAddress is an autogenerated conversion function.
|
||||||
|
func Convert_networking_IPAddress_To_v1alpha1_IPAddress(in *networking.IPAddress, out *v1alpha1.IPAddress, s conversion.Scope) error {
|
||||||
|
return autoConvert_networking_IPAddress_To_v1alpha1_IPAddress(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_IPAddressList_To_networking_IPAddressList(in *v1alpha1.IPAddressList, out *networking.IPAddressList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]networking.IPAddress)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_IPAddressList_To_networking_IPAddressList is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_IPAddressList_To_networking_IPAddressList(in *v1alpha1.IPAddressList, out *networking.IPAddressList, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_IPAddressList_To_networking_IPAddressList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_networking_IPAddressList_To_v1alpha1_IPAddressList(in *networking.IPAddressList, out *v1alpha1.IPAddressList, s conversion.Scope) error {
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
out.Items = *(*[]v1alpha1.IPAddress)(unsafe.Pointer(&in.Items))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_networking_IPAddressList_To_v1alpha1_IPAddressList is an autogenerated conversion function.
|
||||||
|
func Convert_networking_IPAddressList_To_v1alpha1_IPAddressList(in *networking.IPAddressList, out *v1alpha1.IPAddressList, s conversion.Scope) error {
|
||||||
|
return autoConvert_networking_IPAddressList_To_v1alpha1_IPAddressList(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec(in *v1alpha1.IPAddressSpec, out *networking.IPAddressSpec, s conversion.Scope) error {
|
||||||
|
out.ParentRef = (*networking.ParentReference)(unsafe.Pointer(in.ParentRef))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec(in *v1alpha1.IPAddressSpec, out *networking.IPAddressSpec, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_IPAddressSpec_To_networking_IPAddressSpec(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec(in *networking.IPAddressSpec, out *v1alpha1.IPAddressSpec, s conversion.Scope) error {
|
||||||
|
out.ParentRef = (*v1alpha1.ParentReference)(unsafe.Pointer(in.ParentRef))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec is an autogenerated conversion function.
|
||||||
|
func Convert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec(in *networking.IPAddressSpec, out *v1alpha1.IPAddressSpec, s conversion.Scope) error {
|
||||||
|
return autoConvert_networking_IPAddressSpec_To_v1alpha1_IPAddressSpec(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_ParentReference_To_networking_ParentReference(in *v1alpha1.ParentReference, out *networking.ParentReference, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resource = in.Resource
|
||||||
|
out.Namespace = in.Namespace
|
||||||
|
out.Name = in.Name
|
||||||
|
out.UID = types.UID(in.UID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_ParentReference_To_networking_ParentReference is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_ParentReference_To_networking_ParentReference(in *v1alpha1.ParentReference, out *networking.ParentReference, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_ParentReference_To_networking_ParentReference(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_networking_ParentReference_To_v1alpha1_ParentReference(in *networking.ParentReference, out *v1alpha1.ParentReference, s conversion.Scope) error {
|
||||||
|
out.Group = in.Group
|
||||||
|
out.Resource = in.Resource
|
||||||
|
out.Namespace = in.Namespace
|
||||||
|
out.Name = in.Name
|
||||||
|
out.UID = types.UID(in.UID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_networking_ParentReference_To_v1alpha1_ParentReference is an autogenerated conversion function.
|
||||||
|
func Convert_networking_ParentReference_To_v1alpha1_ParentReference(in *networking.ParentReference, out *v1alpha1.ParentReference, s conversion.Scope) error {
|
||||||
|
return autoConvert_networking_ParentReference_To_v1alpha1_ParentReference(in, out, s)
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ package validation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
@ -741,3 +742,77 @@ func validateClusterCIDRUpdateSpec(update, old *networking.ClusterCIDRSpec, fldP
|
|||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateIPAddressName validates that the name is the decimal representation of an IP address.
|
||||||
|
// IPAddress does not support generating names, prefix is not considered.
|
||||||
|
func ValidateIPAddressName(name string, prefix bool) []string {
|
||||||
|
var errs []string
|
||||||
|
ip, err := netip.ParseAddr(name)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
} else if ip.String() != name {
|
||||||
|
errs = append(errs, "not a valid ip in canonical format")
|
||||||
|
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateIPAddress(ipAddress *networking.IPAddress) field.ErrorList {
|
||||||
|
allErrs := apivalidation.ValidateObjectMeta(&ipAddress.ObjectMeta, false, ValidateIPAddressName, field.NewPath("metadata"))
|
||||||
|
errs := validateIPAddressParentReference(ipAddress.Spec.ParentRef, field.NewPath("spec"))
|
||||||
|
allErrs = append(allErrs, errs...)
|
||||||
|
return allErrs
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateIPAddressParentReference ensures that the IPAddress ParenteReference exists and is valid.
|
||||||
|
func validateIPAddressParentReference(params *networking.ParentReference, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
if params == nil {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), ""))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
fldPath = fldPath.Child("parentRef")
|
||||||
|
// group is required but the Core group used by Services is the empty value, so it can not be enforced
|
||||||
|
if params.Group != "" {
|
||||||
|
for _, msg := range validation.IsDNS1123Subdomain(params.Group) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), params.Group, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resource is required
|
||||||
|
if params.Resource == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("resource"), ""))
|
||||||
|
} else {
|
||||||
|
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Resource) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("resource"), params.Resource, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// name is required
|
||||||
|
if params.Name == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||||
|
} else {
|
||||||
|
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// namespace is optional
|
||||||
|
if params.Namespace != "" {
|
||||||
|
for _, msg := range pathvalidation.IsValidPathSegmentName(params.Namespace) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), params.Namespace, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateIPAddressUpdate tests if an update to an IPAddress is valid.
|
||||||
|
func ValidateIPAddressUpdate(update, old *networking.IPAddress) field.ErrorList {
|
||||||
|
var allErrs field.ErrorList
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
@ -2195,3 +2195,215 @@ func TestValidateClusterConfigUpdate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateIPAddress(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
expectedErrors int
|
||||||
|
ipAddress *networking.IPAddress
|
||||||
|
}{
|
||||||
|
"empty-ipaddress-bad-name": {
|
||||||
|
expectedErrors: 1,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty-ipaddress-bad-name-no-parent-reference": {
|
||||||
|
expectedErrors: 2,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"good-ipaddress": {
|
||||||
|
expectedErrors: 0,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"good-ipaddress-gateway": {
|
||||||
|
expectedErrors: 0,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "gateway.networking.k8s.io",
|
||||||
|
Resource: "gateway",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"good-ipv6address": {
|
||||||
|
expectedErrors: 0,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "2001:4860:4860::8888",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"non-canonica-ipv6address": {
|
||||||
|
expectedErrors: 1,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "2001:4860:4860:0::8888",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"missing-ipaddress-reference": {
|
||||||
|
expectedErrors: 1,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wrong-ipaddress-reference": {
|
||||||
|
expectedErrors: 1,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "custom.resource.com",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo$%&",
|
||||||
|
Namespace: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"wrong-ipaddress-reference-multiple-errors": {
|
||||||
|
expectedErrors: 4,
|
||||||
|
ipAddress: &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: ".cust@m.resource.com",
|
||||||
|
Resource: "",
|
||||||
|
Name: "",
|
||||||
|
Namespace: "bar$$$$$%@",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
errs := ValidateIPAddress(testCase.ipAddress)
|
||||||
|
if len(errs) != testCase.expectedErrors {
|
||||||
|
t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateIPAddressUpdate(t *testing.T) {
|
||||||
|
old := &networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.1.1",
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "custom.resource.com",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
new func(svc *networking.IPAddress) *networking.IPAddress
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful update, no changes",
|
||||||
|
new: func(old *networking.IPAddress) *networking.IPAddress {
|
||||||
|
out := old.DeepCopy()
|
||||||
|
return out
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Failed update, update spec.ParentRef",
|
||||||
|
new: func(svc *networking.IPAddress) *networking.IPAddress {
|
||||||
|
out := svc.DeepCopy()
|
||||||
|
out.Spec.ParentRef = &networking.ParentReference{
|
||||||
|
Group: "custom.resource.com",
|
||||||
|
Resource: "Gateway",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}, expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Failed update, delete spec.ParentRef",
|
||||||
|
new: func(svc *networking.IPAddress) *networking.IPAddress {
|
||||||
|
out := svc.DeepCopy()
|
||||||
|
out.Spec.ParentRef = nil
|
||||||
|
return out
|
||||||
|
}, expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
err := ValidateIPAddressUpdate(testCase.new(old), old)
|
||||||
|
if !testCase.expectErr && err != nil {
|
||||||
|
t.Errorf("ValidateIPAddressUpdate must be successful for test '%s', got %v", testCase.name, err)
|
||||||
|
}
|
||||||
|
if testCase.expectErr && err == nil {
|
||||||
|
t.Errorf("ValidateIPAddressUpdate must return error for test: %s, but got nil", testCase.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
97
pkg/apis/networking/zz_generated.deepcopy.go
generated
97
pkg/apis/networking/zz_generated.deepcopy.go
generated
@ -154,6 +154,87 @@ func (in *HTTPIngressRuleValue) DeepCopy() *HTTPIngressRuleValue {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddress) DeepCopyInto(out *IPAddress) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddress.
|
||||||
|
func (in *IPAddress) DeepCopy() *IPAddress {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddress)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *IPAddress) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddressList) DeepCopyInto(out *IPAddressList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]IPAddress, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressList.
|
||||||
|
func (in *IPAddressList) DeepCopy() *IPAddressList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddressList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *IPAddressList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.ParentRef != nil {
|
||||||
|
in, out := &in.ParentRef, &out.ParentRef
|
||||||
|
*out = new(ParentReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec.
|
||||||
|
func (in *IPAddressSpec) DeepCopy() *IPAddressSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddressSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *IPBlock) DeepCopyInto(out *IPBlock) {
|
func (in *IPBlock) DeepCopyInto(out *IPBlock) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@ -816,6 +897,22 @@ func (in *NetworkPolicyStatus) DeepCopy() *NetworkPolicyStatus {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ParentReference) DeepCopyInto(out *ParentReference) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentReference.
|
||||||
|
func (in *ParentReference) DeepCopy() *ParentReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ParentReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ServiceBackendPort) DeepCopyInto(out *ServiceBackendPort) {
|
func (in *ServiceBackendPort) DeepCopyInto(out *ServiceBackendPort) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -33,9 +33,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/kubernetes/pkg/controlplane/reconcilers"
|
"k8s.io/kubernetes/pkg/controlplane/reconcilers"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/rangeallocation"
|
"k8s.io/kubernetes/pkg/registry/core/rangeallocation"
|
||||||
corerest "k8s.io/kubernetes/pkg/registry/core/rest"
|
corerest "k8s.io/kubernetes/pkg/registry/core/rest"
|
||||||
servicecontroller "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller"
|
servicecontroller "k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller"
|
||||||
@ -52,7 +55,8 @@ const (
|
|||||||
// controller loops, which manage creating the "kubernetes" service and
|
// controller loops, which manage creating the "kubernetes" service and
|
||||||
// provide the IP repair check on service IPs
|
// provide the IP repair check on service IPs
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
|
informers informers.SharedInformerFactory
|
||||||
|
|
||||||
ServiceClusterIPRegistry rangeallocation.RangeRegistry
|
ServiceClusterIPRegistry rangeallocation.RangeRegistry
|
||||||
ServiceClusterIPRange net.IPNet
|
ServiceClusterIPRange net.IPNet
|
||||||
@ -98,7 +102,8 @@ func (c *completedConfig) NewBootstrapController(legacyRESTStorage corerest.Lega
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Controller{
|
return &Controller{
|
||||||
client: client,
|
client: client,
|
||||||
|
informers: c.ExtraConfig.VersionedInformers,
|
||||||
|
|
||||||
EndpointReconciler: c.ExtraConfig.EndpointReconcilerConfig.Reconciler,
|
EndpointReconciler: c.ExtraConfig.EndpointReconcilerConfig.Reconciler,
|
||||||
EndpointInterval: c.ExtraConfig.EndpointReconcilerConfig.Interval,
|
EndpointInterval: c.ExtraConfig.EndpointReconcilerConfig.Interval,
|
||||||
@ -150,7 +155,6 @@ func (c *Controller) Start() {
|
|||||||
klog.Errorf("Error removing old endpoints from kubernetes service: %v", err)
|
klog.Errorf("Error removing old endpoints from kubernetes service: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval, c.client.CoreV1(), c.client.EventsV1(), &c.ServiceClusterIPRange, c.ServiceClusterIPRegistry, &c.SecondaryServiceClusterIPRange, c.SecondaryServiceClusterIPRegistry)
|
|
||||||
repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.client.CoreV1(), c.client.EventsV1(), c.ServiceNodePortRange, c.ServiceNodePortRegistry)
|
repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.client.CoreV1(), c.client.EventsV1(), c.ServiceNodePortRange, c.ServiceNodePortRegistry)
|
||||||
|
|
||||||
// We start both repairClusterIPs and repairNodePorts to ensure repair
|
// We start both repairClusterIPs and repairNodePorts to ensure repair
|
||||||
@ -163,15 +167,37 @@ func (c *Controller) Start() {
|
|||||||
// than 1 minute for backward compatibility of failing the whole
|
// than 1 minute for backward compatibility of failing the whole
|
||||||
// apiserver if we can't repair them.
|
// apiserver if we can't repair them.
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(1)
|
||||||
|
|
||||||
runRepairClusterIPs := func(stopCh chan struct{}) {
|
|
||||||
repairClusterIPs.RunUntil(wg.Done, stopCh)
|
|
||||||
}
|
|
||||||
runRepairNodePorts := func(stopCh chan struct{}) {
|
runRepairNodePorts := func(stopCh chan struct{}) {
|
||||||
repairNodePorts.RunUntil(wg.Done, stopCh)
|
repairNodePorts.RunUntil(wg.Done, stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
var runRepairClusterIPs func(stopCh chan struct{})
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
|
repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval,
|
||||||
|
c.client.CoreV1(),
|
||||||
|
c.client.EventsV1(),
|
||||||
|
&c.ServiceClusterIPRange,
|
||||||
|
c.ServiceClusterIPRegistry,
|
||||||
|
&c.SecondaryServiceClusterIPRange,
|
||||||
|
c.SecondaryServiceClusterIPRegistry)
|
||||||
|
runRepairClusterIPs = func(stopCh chan struct{}) {
|
||||||
|
repairClusterIPs.RunUntil(wg.Done, stopCh)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repairClusterIPs := servicecontroller.NewRepairIPAddress(c.ServiceClusterIPInterval,
|
||||||
|
c.client,
|
||||||
|
&c.ServiceClusterIPRange,
|
||||||
|
&c.SecondaryServiceClusterIPRange,
|
||||||
|
c.informers.Core().V1().Services(),
|
||||||
|
c.informers.Networking().V1alpha1().IPAddresses(),
|
||||||
|
)
|
||||||
|
runRepairClusterIPs = func(stopCh chan struct{}) {
|
||||||
|
repairClusterIPs.RunUntil(wg.Done, stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
c.runner = async.NewRunner(c.RunKubernetesService, runRepairClusterIPs, runRepairNodePorts)
|
c.runner = async.NewRunner(c.RunKubernetesService, runRepairClusterIPs, runRepairNodePorts)
|
||||||
c.runner.Start()
|
c.runner.Start()
|
||||||
|
|
||||||
|
@ -590,6 +590,7 @@ func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generi
|
|||||||
ExtendExpiration: c.ExtraConfig.ExtendExpiration,
|
ExtendExpiration: c.ExtraConfig.ExtendExpiration,
|
||||||
ServiceAccountMaxExpiration: c.ExtraConfig.ServiceAccountMaxExpiration,
|
ServiceAccountMaxExpiration: c.ExtraConfig.ServiceAccountMaxExpiration,
|
||||||
APIAudiences: c.GenericConfig.Authentication.APIAudiences,
|
APIAudiences: c.GenericConfig.Authentication.APIAudiences,
|
||||||
|
Informers: c.ExtraConfig.VersionedInformers,
|
||||||
}
|
}
|
||||||
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)
|
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -154,6 +154,7 @@ func TestLegacyRestStorageStrategies(t *testing.T) {
|
|||||||
ServiceIPRange: apiserverCfg.ExtraConfig.ServiceIPRange,
|
ServiceIPRange: apiserverCfg.ExtraConfig.ServiceIPRange,
|
||||||
ServiceNodePortRange: apiserverCfg.ExtraConfig.ServiceNodePortRange,
|
ServiceNodePortRange: apiserverCfg.ExtraConfig.ServiceNodePortRange,
|
||||||
LoopbackClientConfig: apiserverCfg.GenericConfig.LoopbackClientConfig,
|
LoopbackClientConfig: apiserverCfg.GenericConfig.LoopbackClientConfig,
|
||||||
|
Informers: apiserverCfg.ExtraConfig.VersionedInformers,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, apiGroupInfo, err := storageProvider.NewLegacyRESTStorage(serverstorage.NewResourceConfig(), apiserverCfg.GenericConfig.RESTOptionsGetter)
|
_, apiGroupInfo, err := storageProvider.NewLegacyRESTStorage(serverstorage.NewResourceConfig(), apiserverCfg.GenericConfig.RESTOptionsGetter)
|
||||||
|
@ -554,6 +554,13 @@ const (
|
|||||||
// Enables the MultiCIDR Range allocator.
|
// Enables the MultiCIDR Range allocator.
|
||||||
MultiCIDRRangeAllocator featuregate.Feature = "MultiCIDRRangeAllocator"
|
MultiCIDRRangeAllocator featuregate.Feature = "MultiCIDRRangeAllocator"
|
||||||
|
|
||||||
|
// owner: @aojea
|
||||||
|
// kep: https://kep.k8s.io/1880
|
||||||
|
// alpha: v1.27
|
||||||
|
//
|
||||||
|
// Enables the dynamic configuration of Service IP ranges
|
||||||
|
MultiCIDRServiceAllocator featuregate.Feature = "MultiCIDRServiceAllocator"
|
||||||
|
|
||||||
// owner: @rikatz
|
// owner: @rikatz
|
||||||
// kep: https://kep.k8s.io/2943
|
// kep: https://kep.k8s.io/2943
|
||||||
// alpha: v1.24
|
// alpha: v1.24
|
||||||
@ -1041,6 +1048,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
MultiCIDRRangeAllocator: {Default: false, PreRelease: featuregate.Alpha},
|
MultiCIDRRangeAllocator: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
MultiCIDRServiceAllocator: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
NetworkPolicyStatus: {Default: false, PreRelease: featuregate.Alpha},
|
NetworkPolicyStatus: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
NewVolumeManagerReconstruction: {Default: true, PreRelease: featuregate.Beta},
|
NewVolumeManagerReconstruction: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
167
pkg/generated/openapi/zz_generated.openapi.go
generated
167
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -734,6 +734,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||||||
"k8s.io/api/networking/v1alpha1.ClusterCIDR": schema_k8sio_api_networking_v1alpha1_ClusterCIDR(ref),
|
"k8s.io/api/networking/v1alpha1.ClusterCIDR": schema_k8sio_api_networking_v1alpha1_ClusterCIDR(ref),
|
||||||
"k8s.io/api/networking/v1alpha1.ClusterCIDRList": schema_k8sio_api_networking_v1alpha1_ClusterCIDRList(ref),
|
"k8s.io/api/networking/v1alpha1.ClusterCIDRList": schema_k8sio_api_networking_v1alpha1_ClusterCIDRList(ref),
|
||||||
"k8s.io/api/networking/v1alpha1.ClusterCIDRSpec": schema_k8sio_api_networking_v1alpha1_ClusterCIDRSpec(ref),
|
"k8s.io/api/networking/v1alpha1.ClusterCIDRSpec": schema_k8sio_api_networking_v1alpha1_ClusterCIDRSpec(ref),
|
||||||
|
"k8s.io/api/networking/v1alpha1.IPAddress": schema_k8sio_api_networking_v1alpha1_IPAddress(ref),
|
||||||
|
"k8s.io/api/networking/v1alpha1.IPAddressList": schema_k8sio_api_networking_v1alpha1_IPAddressList(ref),
|
||||||
|
"k8s.io/api/networking/v1alpha1.IPAddressSpec": schema_k8sio_api_networking_v1alpha1_IPAddressSpec(ref),
|
||||||
|
"k8s.io/api/networking/v1alpha1.ParentReference": schema_k8sio_api_networking_v1alpha1_ParentReference(ref),
|
||||||
"k8s.io/api/networking/v1beta1.HTTPIngressPath": schema_k8sio_api_networking_v1beta1_HTTPIngressPath(ref),
|
"k8s.io/api/networking/v1beta1.HTTPIngressPath": schema_k8sio_api_networking_v1beta1_HTTPIngressPath(ref),
|
||||||
"k8s.io/api/networking/v1beta1.HTTPIngressRuleValue": schema_k8sio_api_networking_v1beta1_HTTPIngressRuleValue(ref),
|
"k8s.io/api/networking/v1beta1.HTTPIngressRuleValue": schema_k8sio_api_networking_v1beta1_HTTPIngressRuleValue(ref),
|
||||||
"k8s.io/api/networking/v1beta1.Ingress": schema_k8sio_api_networking_v1beta1_Ingress(ref),
|
"k8s.io/api/networking/v1beta1.Ingress": schema_k8sio_api_networking_v1beta1_Ingress(ref),
|
||||||
@ -36714,6 +36718,169 @@ func schema_k8sio_api_networking_v1alpha1_ClusterCIDRSpec(ref common.ReferenceCa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func schema_k8sio_api_networking_v1alpha1_IPAddress(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. An IP address can be represented in different formats, to guarantee the uniqueness of the IP, the name of the object is the IP address in canonical format, four decimal digits separated by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 Invalid: 10.01.2.3 or 2001:db8:0:0:0::1",
|
||||||
|
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: "spec is the desired state of the IPAddress. 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/api/networking/v1alpha1.IPAddressSpec"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"k8s.io/api/networking/v1alpha1.IPAddressSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_k8sio_api_networking_v1alpha1_IPAddressList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "IPAddressList contains a list of IPAddress.",
|
||||||
|
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.ListMeta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "items is the list of IPAddresses.",
|
||||||
|
Type: []string{"array"},
|
||||||
|
Items: &spec.SchemaOrArray{
|
||||||
|
Schema: &spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("k8s.io/api/networking/v1alpha1.IPAddress"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"items"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"k8s.io/api/networking/v1alpha1.IPAddress", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_k8sio_api_networking_v1alpha1_IPAddressSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "IPAddressSpec describe the attributes in an IP Address.",
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"parentRef": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "ParentRef references the resource that an IPAddress is attached to. An IPAddress must reference a parent object.",
|
||||||
|
Ref: ref("k8s.io/api/networking/v1alpha1.ParentReference"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"k8s.io/api/networking/v1alpha1.ParentReference"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_k8sio_api_networking_v1alpha1_ParentReference(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "ParentReference describes a reference to a parent object.",
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"group": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Group is the group of the object being referenced.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Resource is the resource of the object being referenced.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Namespace is the namespace of the object being referenced.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Name is the name of the object being referenced.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "UID is the uid of the object being referenced.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func schema_k8sio_api_networking_v1beta1_HTTPIngressPath(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
func schema_k8sio_api_networking_v1beta1_HTTPIngressPath(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
return common.OpenAPIDefinition{
|
return common.OpenAPIDefinition{
|
||||||
Schema: spec.Schema{
|
Schema: spec.Schema{
|
||||||
|
@ -68,9 +68,10 @@ func NewStorageFactoryConfig() *StorageFactoryConfig {
|
|||||||
//
|
//
|
||||||
// TODO (https://github.com/kubernetes/kubernetes/issues/108451): remove the override in 1.25.
|
// TODO (https://github.com/kubernetes/kubernetes/issues/108451): remove the override in 1.25.
|
||||||
// apisstorage.Resource("csistoragecapacities").WithVersion("v1beta1"),
|
// apisstorage.Resource("csistoragecapacities").WithVersion("v1beta1"),
|
||||||
networking.Resource("clustercidrs").WithVersion("v1alpha1"),
|
|
||||||
admissionregistration.Resource("validatingadmissionpolicies").WithVersion("v1alpha1"),
|
admissionregistration.Resource("validatingadmissionpolicies").WithVersion("v1alpha1"),
|
||||||
admissionregistration.Resource("validatingadmissionpolicybindings").WithVersion("v1alpha1"),
|
admissionregistration.Resource("validatingadmissionpolicybindings").WithVersion("v1alpha1"),
|
||||||
|
networking.Resource("clustercidrs").WithVersion("v1alpha1"),
|
||||||
|
networking.Resource("ipaddresses").WithVersion("v1alpha1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StorageFactoryConfig{
|
return &StorageFactoryConfig{
|
||||||
|
@ -645,6 +645,14 @@ func AddHandlers(h printers.PrintHandler) {
|
|||||||
}
|
}
|
||||||
_ = h.TableHandler(podSchedulingCtxColumnDefinitions, printPodSchedulingContext)
|
_ = h.TableHandler(podSchedulingCtxColumnDefinitions, printPodSchedulingContext)
|
||||||
_ = h.TableHandler(podSchedulingCtxColumnDefinitions, printPodSchedulingContextList)
|
_ = h.TableHandler(podSchedulingCtxColumnDefinitions, printPodSchedulingContextList)
|
||||||
|
|
||||||
|
ipAddressColumnDefinitions := []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
|
||||||
|
{Name: "ParentRef", Type: "string", Description: networkingv1alpha1.IPAddressSpec{}.SwaggerDoc()["parentRef"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
h.TableHandler(ipAddressColumnDefinitions, printIPAddress)
|
||||||
|
h.TableHandler(ipAddressColumnDefinitions, printIPAddressList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass ports=nil for all ports.
|
// Pass ports=nil for all ports.
|
||||||
@ -2779,6 +2787,41 @@ func printClusterCIDRList(list *networking.ClusterCIDRList, options printers.Gen
|
|||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printIPAddress(obj *networking.IPAddress, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||||
|
row := metav1.TableRow{
|
||||||
|
Object: runtime.RawExtension{Object: obj},
|
||||||
|
}
|
||||||
|
|
||||||
|
parentRefName := "<none>"
|
||||||
|
if obj.Spec.ParentRef != nil {
|
||||||
|
gr := schema.GroupResource{
|
||||||
|
Group: obj.Spec.ParentRef.Group,
|
||||||
|
Resource: obj.Spec.ParentRef.Resource,
|
||||||
|
}
|
||||||
|
parentRefName = strings.ToLower(gr.String())
|
||||||
|
if obj.Spec.ParentRef.Namespace != "" {
|
||||||
|
parentRefName += "/" + obj.Spec.ParentRef.Namespace
|
||||||
|
}
|
||||||
|
parentRefName += "/" + obj.Spec.ParentRef.Name
|
||||||
|
}
|
||||||
|
age := translateTimestampSince(obj.CreationTimestamp)
|
||||||
|
row.Cells = append(row.Cells, obj.Name, parentRefName, age)
|
||||||
|
|
||||||
|
return []metav1.TableRow{row}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIPAddressList(list *networking.IPAddressList, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||||
|
rows := make([]metav1.TableRow, 0, len(list.Items))
|
||||||
|
for i := range list.Items {
|
||||||
|
r, err := printIPAddress(&list.Items[i], options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows = append(rows, r...)
|
||||||
|
}
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
func printScale(obj *autoscaling.Scale, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
func printScale(obj *autoscaling.Scale, options printers.GenerateOptions) ([]metav1.TableRow, error) {
|
||||||
row := metav1.TableRow{
|
row := metav1.TableRow{
|
||||||
Object: runtime.RawExtension{Object: obj},
|
Object: runtime.RawExtension{Object: obj},
|
||||||
|
@ -6482,3 +6482,83 @@ func TestPrintClusterCIDRList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrintIPAddress(t *testing.T) {
|
||||||
|
ip := networking.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.2.2",
|
||||||
|
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "mygroup",
|
||||||
|
Resource: "myresource",
|
||||||
|
Namespace: "mynamespace",
|
||||||
|
Name: "myname",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// Columns: Name, ParentRef, Age
|
||||||
|
expected := []metav1.TableRow{{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "10y"}}}
|
||||||
|
|
||||||
|
rows, err := printIPAddress(&ip, printers.GenerateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error generating table rows for IPAddress: %#v", err)
|
||||||
|
}
|
||||||
|
rows[0].Object.Object = nil
|
||||||
|
if !reflect.DeepEqual(expected, rows) {
|
||||||
|
t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expected, rows))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintIPAddressList(t *testing.T) {
|
||||||
|
ipList := networking.IPAddressList{
|
||||||
|
Items: []networking.IPAddress{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "192.168.2.2",
|
||||||
|
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "mygroup",
|
||||||
|
Resource: "myresource",
|
||||||
|
Namespace: "mynamespace",
|
||||||
|
Name: "myname",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "2001:db8::2",
|
||||||
|
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-5, 0, 0)},
|
||||||
|
},
|
||||||
|
Spec: networking.IPAddressSpec{
|
||||||
|
ParentRef: &networking.ParentReference{
|
||||||
|
Group: "mygroup2",
|
||||||
|
Resource: "myresource2",
|
||||||
|
Namespace: "mynamespace2",
|
||||||
|
Name: "myname2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// Columns: Name, ParentRef, Age
|
||||||
|
expected := []metav1.TableRow{
|
||||||
|
{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "10y"}},
|
||||||
|
{Cells: []interface{}{"2001:db8::2", "myresource2.mygroup2/mynamespace2/myname2", "5y1d"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := printIPAddressList(&ipList, printers.GenerateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error generating table rows for IPAddress: %#v", err)
|
||||||
|
}
|
||||||
|
for i := range rows {
|
||||||
|
rows[i].Object.Object = nil
|
||||||
|
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, rows) {
|
||||||
|
t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expected, rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -36,11 +36,15 @@ import (
|
|||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/etcd3"
|
"k8s.io/apiserver/pkg/storage/etcd3"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
networkingv1alpha1client "k8s.io/client-go/kubernetes/typed/networking/v1alpha1"
|
||||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/cluster/ports"
|
"k8s.io/kubernetes/pkg/cluster/ports"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/componentstatus"
|
"k8s.io/kubernetes/pkg/registry/core/componentstatus"
|
||||||
configmapstore "k8s.io/kubernetes/pkg/registry/core/configmap/storage"
|
configmapstore "k8s.io/kubernetes/pkg/registry/core/configmap/storage"
|
||||||
@ -90,6 +94,7 @@ type LegacyRESTStorageProvider struct {
|
|||||||
APIAudiences authenticator.Audiences
|
APIAudiences authenticator.Audiences
|
||||||
|
|
||||||
LoopbackClientConfig *restclient.Config
|
LoopbackClientConfig *restclient.Config
|
||||||
|
Informers informers.SharedInformerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacyRESTStorage returns stateful information about particular instances of REST storage to
|
// LegacyRESTStorage returns stateful information about particular instances of REST storage to
|
||||||
@ -196,41 +201,64 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(apiResourceConfigSource
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
|
||||||
}
|
}
|
||||||
|
var serviceClusterIPAllocator, secondaryServiceClusterIPAllocator ipallocator.Interface
|
||||||
|
|
||||||
serviceClusterIPAllocator, err := ipallocator.New(&serviceClusterIPRange, func(max int, rangeSpec string, offset int) (allocator.Interface, error) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
var mem allocator.Snapshottable
|
serviceClusterIPAllocator, err = ipallocator.New(&serviceClusterIPRange, func(max int, rangeSpec string, offset int) (allocator.Interface, error) {
|
||||||
mem = allocator.NewAllocationMapWithOffset(max, rangeSpec, offset)
|
var mem allocator.Snapshottable
|
||||||
// TODO etcdallocator package to return a storage interface via the storageFactory
|
mem = allocator.NewAllocationMapWithOffset(max, rangeSpec, offset)
|
||||||
etcd, err := serviceallocator.NewEtcd(mem, "/ranges/serviceips", serviceStorageConfig.ForResource(api.Resource("serviceipallocations")))
|
// TODO etcdallocator package to return a storage interface via the storageFactory
|
||||||
|
etcd, err := serviceallocator.NewEtcd(mem, "/ranges/serviceips", serviceStorageConfig.ForResource(api.Resource("serviceipallocations")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serviceClusterIPRegistry = etcd
|
||||||
|
return etcd, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster IP allocator: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
networkingv1alphaClient, err := networkingv1alpha1client.NewForConfig(c.LoopbackClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
|
||||||
|
}
|
||||||
|
serviceClusterIPAllocator, err = ipallocator.NewIPAllocator(&serviceClusterIPRange, networkingv1alphaClient, c.Informers.Networking().V1alpha1().IPAddresses())
|
||||||
|
if err != nil {
|
||||||
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster IP allocator: %v", err)
|
||||||
}
|
}
|
||||||
serviceClusterIPRegistry = etcd
|
|
||||||
return etcd, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster IP allocator: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceClusterIPAllocator.EnableMetrics()
|
serviceClusterIPAllocator.EnableMetrics()
|
||||||
restStorage.ServiceClusterIPAllocator = serviceClusterIPRegistry
|
restStorage.ServiceClusterIPAllocator = serviceClusterIPRegistry
|
||||||
|
|
||||||
// allocator for secondary service ip range
|
// allocator for secondary service ip range
|
||||||
var secondaryServiceClusterIPAllocator ipallocator.Interface
|
|
||||||
if c.SecondaryServiceIPRange.IP != nil {
|
if c.SecondaryServiceIPRange.IP != nil {
|
||||||
var secondaryServiceClusterIPRegistry rangeallocation.RangeRegistry
|
var secondaryServiceClusterIPRegistry rangeallocation.RangeRegistry
|
||||||
secondaryServiceClusterIPAllocator, err = ipallocator.New(&c.SecondaryServiceIPRange, func(max int, rangeSpec string, offset int) (allocator.Interface, error) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
var mem allocator.Snapshottable
|
secondaryServiceClusterIPAllocator, err = ipallocator.New(&c.SecondaryServiceIPRange, func(max int, rangeSpec string, offset int) (allocator.Interface, error) {
|
||||||
mem = allocator.NewAllocationMapWithOffset(max, rangeSpec, offset)
|
var mem allocator.Snapshottable
|
||||||
// TODO etcdallocator package to return a storage interface via the storageFactory
|
mem = allocator.NewAllocationMapWithOffset(max, rangeSpec, offset)
|
||||||
etcd, err := serviceallocator.NewEtcd(mem, "/ranges/secondaryserviceips", serviceStorageConfig.ForResource(api.Resource("serviceipallocations")))
|
// TODO etcdallocator package to return a storage interface via the storageFactory
|
||||||
|
etcd, err := serviceallocator.NewEtcd(mem, "/ranges/secondaryserviceips", serviceStorageConfig.ForResource(api.Resource("serviceipallocations")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secondaryServiceClusterIPRegistry = etcd
|
||||||
|
return etcd, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster secondary IP allocator: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
networkingv1alphaClient, err := networkingv1alpha1client.NewForConfig(c.LoopbackClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
|
||||||
|
}
|
||||||
|
secondaryServiceClusterIPAllocator, err = ipallocator.NewIPAllocator(&c.SecondaryServiceIPRange, networkingv1alphaClient, c.Informers.Networking().V1alpha1().IPAddresses())
|
||||||
|
if err != nil {
|
||||||
|
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster secondary IP allocator: %v", err)
|
||||||
}
|
}
|
||||||
secondaryServiceClusterIPRegistry = etcd
|
|
||||||
return etcd, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, fmt.Errorf("cannot create cluster secondary IP allocator: %v", err)
|
|
||||||
}
|
}
|
||||||
secondaryServiceClusterIPAllocator.EnableMetrics()
|
secondaryServiceClusterIPAllocator.EnableMetrics()
|
||||||
restStorage.SecondaryServiceClusterIPAllocator = secondaryServiceClusterIPRegistry
|
restStorage.SecondaryServiceClusterIPAllocator = secondaryServiceClusterIPRegistry
|
||||||
|
590
pkg/registry/core/service/ipallocator/controller/repairip.go
Normal file
590
pkg/registry/core/service/ipallocator/controller/repairip.go
Normal file
@ -0,0 +1,590 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||||
|
networkinginformers "k8s.io/client-go/informers/networking/v1alpha1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
corelisters "k8s.io/client-go/listers/core/v1"
|
||||||
|
networkinglisters "k8s.io/client-go/listers/networking/v1alpha1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/client-go/util/retry"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
|
"k8s.io/utils/clock"
|
||||||
|
netutils "k8s.io/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxRetries is the number of times a service will be retried before it is dropped out of the queue.
|
||||||
|
// With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the
|
||||||
|
// sequence of delays between successive queuings of a service.
|
||||||
|
//
|
||||||
|
// 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s
|
||||||
|
maxRetries = 15
|
||||||
|
workers = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repair is a controller loop that examines all service ClusterIP allocations and logs any errors,
|
||||||
|
// and then creates the accurate list of IPAddresses objects with all allocated ClusterIPs.
|
||||||
|
//
|
||||||
|
// Handles:
|
||||||
|
// * Duplicate ClusterIP assignments caused by operator action or undetected race conditions
|
||||||
|
// * Allocations to services that were not actually created due to a crash or powerloss
|
||||||
|
// * Migrates old versions of Kubernetes services into the new ipallocator automatically
|
||||||
|
// creating the corresponding IPAddress objects
|
||||||
|
// * IPAddress objects with wrong references or labels
|
||||||
|
//
|
||||||
|
// Logs about:
|
||||||
|
// * ClusterIPs that do not match the currently configured range
|
||||||
|
//
|
||||||
|
// There is a one-to-one relation between Service ClusterIPs and IPAddresses.
|
||||||
|
// The bidirectional relation is achieved using the following fields:
|
||||||
|
// Service.Spec.Cluster == IPAddress.Name AND IPAddress.ParentRef == Service
|
||||||
|
//
|
||||||
|
// The controller use two reconcile loops, one for Services and other for IPAddress.
|
||||||
|
// The Service reconcile loop verifies the bidirectional relation exists and is correct.
|
||||||
|
// 1. Service_X [ClusterIP_X] <------> IPAddress_X [Ref:Service_X] ok
|
||||||
|
// 2. Service_Y [ClusterIP_Y] <------> IPAddress_Y [Ref:GatewayA] !ok, wrong reference
|
||||||
|
// 3. Service_Z [ClusterIP_Z] <------> !ok, missing IPAddress
|
||||||
|
// 4. Service_A [ClusterIP_A] <------> IPAddress_A [Ref:Service_B] !ok, duplicate IPAddress
|
||||||
|
// Service_B [ClusterIP_A] <------> only one service can verify the relation
|
||||||
|
// The IPAddress reconcile loop checks there are no orphan IPAddresses, the rest of the
|
||||||
|
// cases are covered by the Services loop
|
||||||
|
// 1. <------> IPAddress_Z [Ref:Service_C] !ok, orphan IPAddress
|
||||||
|
|
||||||
|
type RepairIPAddress struct {
|
||||||
|
client kubernetes.Interface
|
||||||
|
interval time.Duration
|
||||||
|
|
||||||
|
networkByFamily map[netutils.IPFamily]*net.IPNet // networks we operate on, by their family
|
||||||
|
|
||||||
|
serviceLister corelisters.ServiceLister
|
||||||
|
servicesSynced cache.InformerSynced
|
||||||
|
|
||||||
|
ipAddressLister networkinglisters.IPAddressLister
|
||||||
|
ipAddressSynced cache.InformerSynced
|
||||||
|
|
||||||
|
svcQueue workqueue.RateLimitingInterface
|
||||||
|
ipQueue workqueue.RateLimitingInterface
|
||||||
|
workerLoopPeriod time.Duration
|
||||||
|
|
||||||
|
broadcaster events.EventBroadcaster
|
||||||
|
recorder events.EventRecorder
|
||||||
|
clock clock.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepair creates a controller that periodically ensures that all clusterIPs are uniquely allocated across the cluster
|
||||||
|
// and generates informational warnings for a cluster that is not in sync.
|
||||||
|
func NewRepairIPAddress(interval time.Duration,
|
||||||
|
client kubernetes.Interface,
|
||||||
|
network *net.IPNet,
|
||||||
|
secondaryNetwork *net.IPNet,
|
||||||
|
serviceInformer coreinformers.ServiceInformer,
|
||||||
|
ipAddressInformer networkinginformers.IPAddressInformer) *RepairIPAddress {
|
||||||
|
eventBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
|
||||||
|
recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, "ipallocator-repair-controller")
|
||||||
|
|
||||||
|
networkByFamily := make(map[netutils.IPFamily]*net.IPNet)
|
||||||
|
primary := netutils.IPFamilyOfCIDR(network)
|
||||||
|
networkByFamily[primary] = network
|
||||||
|
if secondaryNetwork != nil {
|
||||||
|
secondary := netutils.IPFamilyOfCIDR(secondaryNetwork)
|
||||||
|
networkByFamily[secondary] = secondaryNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &RepairIPAddress{
|
||||||
|
interval: interval,
|
||||||
|
client: client,
|
||||||
|
networkByFamily: networkByFamily,
|
||||||
|
serviceLister: serviceInformer.Lister(),
|
||||||
|
servicesSynced: serviceInformer.Informer().HasSynced,
|
||||||
|
ipAddressLister: ipAddressInformer.Lister(),
|
||||||
|
ipAddressSynced: ipAddressInformer.Informer().HasSynced,
|
||||||
|
svcQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "services"),
|
||||||
|
ipQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ipaddresses"),
|
||||||
|
workerLoopPeriod: time.Second,
|
||||||
|
broadcaster: eventBroadcaster,
|
||||||
|
recorder: recorder,
|
||||||
|
clock: clock.RealClock{},
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||||
|
if err == nil {
|
||||||
|
r.svcQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateFunc: func(old interface{}, new interface{}) {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(new)
|
||||||
|
if err == nil {
|
||||||
|
r.svcQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
|
||||||
|
// key function.
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err == nil {
|
||||||
|
r.svcQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, interval)
|
||||||
|
|
||||||
|
ipAddressInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||||
|
if err == nil {
|
||||||
|
r.ipQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpdateFunc: func(old interface{}, new interface{}) {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(new)
|
||||||
|
if err == nil {
|
||||||
|
r.ipQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
|
||||||
|
// key function.
|
||||||
|
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||||
|
if err == nil {
|
||||||
|
r.ipQueue.Add(key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, interval)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunUntil starts the controller until the provided ch is closed.
|
||||||
|
func (r *RepairIPAddress) RunUntil(onFirstSuccess func(), stopCh chan struct{}) {
|
||||||
|
defer r.ipQueue.ShutDown()
|
||||||
|
defer r.svcQueue.ShutDown()
|
||||||
|
r.broadcaster.StartRecordingToSink(stopCh)
|
||||||
|
defer r.broadcaster.Shutdown()
|
||||||
|
|
||||||
|
klog.Info("Starting ipallocator-repair-controller")
|
||||||
|
defer klog.Info("Shutting down ipallocator-repair-controller")
|
||||||
|
|
||||||
|
if !cache.WaitForNamedCacheSync("ipallocator-repair-controller", stopCh, r.ipAddressSynced, r.servicesSynced) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First sync goes through all the Services and IPAddresses in the cache,
|
||||||
|
// once synced, it signals the main loop and works using the handlers, since
|
||||||
|
// it's less expensive and more optimal.
|
||||||
|
if err := r.runOnce(); err != nil {
|
||||||
|
runtime.HandleError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onFirstSuccess()
|
||||||
|
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go wait.Until(r.ipWorker, r.workerLoopPeriod, stopCh)
|
||||||
|
go wait.Until(r.svcWorker, r.workerLoopPeriod, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-stopCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// runOnce verifies the state of the ClusterIP allocations and returns an error if an unrecoverable problem occurs.
|
||||||
|
func (r *RepairIPAddress) runOnce() error {
|
||||||
|
return retry.RetryOnConflict(retry.DefaultBackoff, r.doRunOnce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doRunOnce verifies the state of the ClusterIP allocations and returns an error if an unrecoverable problem occurs.
|
||||||
|
func (r *RepairIPAddress) doRunOnce() error {
|
||||||
|
services, err := r.serviceLister.List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to refresh the service IP block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every Service's ClusterIP, and rebuild the state as we think it should be.
|
||||||
|
for _, svc := range services {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(svc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = r.syncService(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have checked that every Service has its corresponding IP.
|
||||||
|
// Check that there is no IP created by the allocator without
|
||||||
|
// a Service associated.
|
||||||
|
ipLabelSelector := labels.Set(map[string]string{
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
}).AsSelectorPreValidated()
|
||||||
|
ipAddresses, err := r.ipAddressLister.List(ipLabelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to refresh the IPAddress block: %v", err)
|
||||||
|
}
|
||||||
|
// Check every IPAddress matches the corresponding Service, and rebuild the state as we think it should be.
|
||||||
|
for _, ipAddress := range ipAddresses {
|
||||||
|
key, err := cache.MetaNamespaceKeyFunc(ipAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = r.syncIPAddress(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) svcWorker() {
|
||||||
|
for r.processNextWorkSvc() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) processNextWorkSvc() bool {
|
||||||
|
eKey, quit := r.svcQueue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer r.svcQueue.Done(eKey)
|
||||||
|
|
||||||
|
err := r.syncService(eKey.(string))
|
||||||
|
r.handleSvcErr(err, eKey)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) handleSvcErr(err error, key interface{}) {
|
||||||
|
if err == nil {
|
||||||
|
r.svcQueue.Forget(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.svcQueue.NumRequeues(key) < maxRetries {
|
||||||
|
klog.V(2).InfoS("Error syncing Service, retrying", "service", key, "err", err)
|
||||||
|
r.svcQueue.AddRateLimited(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Warningf("Dropping Service %q out of the queue: %v", key, err)
|
||||||
|
r.svcQueue.Forget(key)
|
||||||
|
runtime.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncServices reconcile the Service ClusterIPs to verify that each one has the corresponding IPAddress object associated
|
||||||
|
func (r *RepairIPAddress) syncService(key string) error {
|
||||||
|
var syncError error
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
svc, err := r.serviceLister.Services(namespace).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
// nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !helper.IsServiceIPSet(svc) {
|
||||||
|
// didn't need a ClusterIP
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, clusterIP := range svc.Spec.ClusterIPs {
|
||||||
|
ip := netutils.ParseIPSloppy(clusterIP)
|
||||||
|
if ip == nil {
|
||||||
|
// ClusterIP is corrupt, ClusterIPs are already validated, but double checking here
|
||||||
|
// in case there are some inconsistencies with the parsers
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPNotValid", "ClusterIPValidation", "Cluster IP %s is not a valid IP; please recreate Service", ip)
|
||||||
|
runtime.HandleError(fmt.Errorf("the ClusterIP %s for Service %s/%s is not a valid IP; please recreate Service", ip, svc.Namespace, svc.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
family := netutils.IPFamilyOf(ip)
|
||||||
|
v1Family := getFamilyByIP(ip)
|
||||||
|
network, ok := r.networkByFamily[family]
|
||||||
|
if !ok {
|
||||||
|
// this service is using an IPFamily no longer configured on cluster
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPNotValid", "ClusterIPValidation", "Cluster IP %s(%s) is of ip family that is no longer configured on cluster; please recreate Service", ip, v1Family)
|
||||||
|
runtime.HandleError(fmt.Errorf("the ClusterIP [%v]: %s for Service %s/%s is of ip family that is no longer configured on cluster; please recreate Service", v1Family, ip, svc.Namespace, svc.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !network.Contains(ip) {
|
||||||
|
// ClusterIP is out of range
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPOutOfRange", "ClusterIPAllocation", "Cluster IP [%v]: %s is not within the configured Service CIDR %s; please recreate service", v1Family, ip, network.String())
|
||||||
|
runtime.HandleError(fmt.Errorf("the ClusterIP [%v]: %s for Service %s/%s is not within the service CIDR %s; please recreate", v1Family, ip, svc.Namespace, svc.Name, network.String()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the IPAddress object associated to the ClusterIP
|
||||||
|
ipAddress, err := r.ipAddressLister.Get(ip.String())
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
// ClusterIP doesn't seem to be allocated, create it.
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPNotAllocated", "ClusterIPAllocation", "Cluster IP [%v]: %s is not allocated; repairing", v1Family, ip)
|
||||||
|
runtime.HandleError(fmt.Errorf("the ClusterIP [%v]: %s for Service %s/%s is not allocated; repairing", v1Family, ip, svc.Namespace, svc.Name))
|
||||||
|
_, err := r.client.NetworkingV1alpha1().IPAddresses().Create(context.Background(), newIPAddress(ip.String(), svc), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "UnknownError", "ClusterIPAllocation", "Unable to allocate ClusterIP [%v]: %s due to an unknown error", v1Family, ip)
|
||||||
|
return fmt.Errorf("unable to allocate ClusterIP [%v]: %s for Service %s/%s due to an unknown error, will retry later: %v", v1Family, ip, svc.Namespace, svc.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress that belongs to a Service must reference a Service
|
||||||
|
if ipAddress.Spec.ParentRef.Group != "" ||
|
||||||
|
ipAddress.Spec.ParentRef.Resource != "services" {
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPNotAllocated", "ClusterIPAllocation", "the ClusterIP [%v]: %s for Service %s/%s has a wrong reference; repairing", v1Family, ip, svc.Namespace, svc.Name)
|
||||||
|
if err := r.recreateIPAddress(ipAddress.Name, svc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress that belongs to a Service must reference the current Service
|
||||||
|
if ipAddress.Spec.ParentRef.Namespace != svc.Namespace ||
|
||||||
|
ipAddress.Spec.ParentRef.Name != svc.Name {
|
||||||
|
// verify that there are no two Services with the same IP, otherwise
|
||||||
|
// it will keep deleting and recreating the same IPAddress changing the reference
|
||||||
|
refService, err := r.serviceLister.Services(ipAddress.Spec.ParentRef.Namespace).Get(ipAddress.Spec.ParentRef.Name)
|
||||||
|
if err != nil {
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPNotAllocated", "ClusterIPAllocation", "the ClusterIP [%v]: %s for Service %s/%s has a wrong reference; repairing", v1Family, ip, svc.Namespace, svc.Name)
|
||||||
|
if err := r.recreateIPAddress(ipAddress.Name, svc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the IPAddress is duplicate but current Service is not the referenced, it has to be recreated
|
||||||
|
for _, clusterIP := range refService.Spec.ClusterIPs {
|
||||||
|
if ipAddress.Name == clusterIP {
|
||||||
|
r.recorder.Eventf(svc, nil, v1.EventTypeWarning, "ClusterIPAlreadyAllocated", "ClusterIPAllocation", "Cluster IP [%v]:%s was assigned to multiple services; please recreate service", family, ip)
|
||||||
|
runtime.HandleError(fmt.Errorf("the cluster IP [%v]:%s for service %s/%s was assigned to other services %s/%s; please recreate", family, ip, svc.Namespace, svc.Name, refService.Namespace, refService.Name))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress must have the corresponding labels assigned by the allocator
|
||||||
|
if !verifyIPAddressLabels(ipAddress) {
|
||||||
|
if err := r.recreateIPAddress(ipAddress.Name, svc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return syncError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) recreateIPAddress(name string, svc *v1.Service) error {
|
||||||
|
err := r.client.NetworkingV1alpha1().IPAddresses().Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = r.client.NetworkingV1alpha1().IPAddresses().Create(context.Background(), newIPAddress(name, svc), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) ipWorker() {
|
||||||
|
for r.processNextWorkIp() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) processNextWorkIp() bool {
|
||||||
|
eKey, quit := r.ipQueue.Get()
|
||||||
|
if quit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer r.ipQueue.Done(eKey)
|
||||||
|
|
||||||
|
err := r.syncIPAddress(eKey.(string))
|
||||||
|
r.handleIpErr(err, eKey)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RepairIPAddress) handleIpErr(err error, key interface{}) {
|
||||||
|
if err == nil {
|
||||||
|
r.ipQueue.Forget(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ipQueue.NumRequeues(key) < maxRetries {
|
||||||
|
klog.V(2).InfoS("Error syncing Service, retrying", "service", key, "err", err)
|
||||||
|
r.ipQueue.AddRateLimited(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Warningf("Dropping Service %q out of the queue: %v", key, err)
|
||||||
|
r.ipQueue.Forget(key)
|
||||||
|
runtime.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncIPAddress verify that the IPAddress that are owned by the ipallocator controller reference an existing Service
|
||||||
|
// to avoid leaking IPAddresses. IPAddresses that are owned by other controllers are not processed to avoid hotloops.
|
||||||
|
// IPAddress that reference Services and are part of the ClusterIP are validated in the syncService loop.
|
||||||
|
func (r *RepairIPAddress) syncIPAddress(key string) error {
|
||||||
|
ipAddress, err := r.ipAddressLister.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
// nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// not mananged by this controller
|
||||||
|
if !managedByController(ipAddress) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not reference a Service but created by the service allocator, something else have changed it, delete it
|
||||||
|
if ipAddress.Spec.ParentRef.Group != "" ||
|
||||||
|
ipAddress.Spec.ParentRef.Resource != "services" {
|
||||||
|
runtime.HandleError(fmt.Errorf("IPAddress %s appears to have been modified, not referencing a Service %v: cleaning up", ipAddress.Name, ipAddress.Spec.ParentRef))
|
||||||
|
r.recorder.Eventf(ipAddress, nil, v1.EventTypeWarning, "IPAddressNotAllocated", "IPAddressAllocation", "IPAddress %s appears to have been modified, not referencing a Service %v: cleaning up", ipAddress.Name, ipAddress.Spec.ParentRef)
|
||||||
|
err := r.client.NetworkingV1alpha1().IPAddresses().Delete(context.Background(), ipAddress.Name, metav1.DeleteOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := r.serviceLister.Services(ipAddress.Spec.ParentRef.Namespace).Get(ipAddress.Spec.ParentRef.Name)
|
||||||
|
if apierrors.IsNotFound(err) {
|
||||||
|
// cleaning all IPAddress without an owner reference IF the time since it was created is greater than 60 seconds (default timeout value on the kube-apiserver)
|
||||||
|
// This is required because during the Service creation there is a time that the IPAddress object exists but the Service is still being created
|
||||||
|
// Assume that CreationTimestamp exists.
|
||||||
|
ipLifetime := r.clock.Now().Sub(ipAddress.CreationTimestamp.Time)
|
||||||
|
gracePeriod := 60 * time.Second
|
||||||
|
if ipLifetime > gracePeriod {
|
||||||
|
runtime.HandleError(fmt.Errorf("IPAddress %s appears to have leaked: cleaning up", ipAddress.Name))
|
||||||
|
r.recorder.Eventf(ipAddress, nil, v1.EventTypeWarning, "IPAddressNotAllocated", "IPAddressAllocation", "IPAddress: %s for Service %s/%s appears to have leaked: cleaning up", ipAddress.Name, ipAddress.Spec.ParentRef.Namespace, ipAddress.Spec.ParentRef.Name)
|
||||||
|
err := r.client.NetworkingV1alpha1().IPAddresses().Delete(context.Background(), ipAddress.Name, metav1.DeleteOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// requeue after the grace period
|
||||||
|
r.ipQueue.AddAfter(key, gracePeriod-ipLifetime)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
runtime.HandleError(fmt.Errorf("unable to get parent Service for IPAddress %s due to an unknown error: %v", ipAddress, err))
|
||||||
|
r.recorder.Eventf(ipAddress, nil, v1.EventTypeWarning, "UnknownError", "IPAddressAllocation", "Unable to get parent Service for IPAddress %s due to an unknown error", ipAddress)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// The service exists, we have checked in previous loop that all Service to IPAddress are correct
|
||||||
|
// but we also have to check the reverse, that the IPAddress to Service relation is correct
|
||||||
|
for _, clusterIP := range svc.Spec.ClusterIPs {
|
||||||
|
if ipAddress.Name == clusterIP {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime.HandleError(fmt.Errorf("the IPAddress: %s for Service %s/%s has a wrong reference %#v; cleaning up", ipAddress.Name, svc.Name, svc.Namespace, ipAddress.Spec.ParentRef))
|
||||||
|
r.recorder.Eventf(ipAddress, nil, v1.EventTypeWarning, "IPAddressWrongReference", "IPAddressAllocation", "IPAddress: %s for Service %s/%s has a wrong reference; cleaning up", ipAddress.Name, svc.Namespace, svc.Name)
|
||||||
|
err = r.client.NetworkingV1alpha1().IPAddresses().Delete(context.Background(), ipAddress.Name, metav1.DeleteOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func newIPAddress(name string, svc *v1.Service) *networkingv1alpha1.IPAddress {
|
||||||
|
family := string(v1.IPv4Protocol)
|
||||||
|
if netutils.IsIPv6String(name) {
|
||||||
|
family = string(v1.IPv6Protocol)
|
||||||
|
}
|
||||||
|
return &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: family,
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: serviceToRef(svc),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceToRef(svc *v1.Service) *networkingv1alpha1.ParentReference {
|
||||||
|
if svc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Namespace: svc.Namespace,
|
||||||
|
Name: svc.Name,
|
||||||
|
UID: svc.UID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFamilyByIP(ip net.IP) v1.IPFamily {
|
||||||
|
if netutils.IsIPv6(ip) {
|
||||||
|
return v1.IPv6Protocol
|
||||||
|
}
|
||||||
|
return v1.IPv4Protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
// managedByController returns true if the controller of the provided
|
||||||
|
// EndpointSlices is the EndpointSlice controller.
|
||||||
|
func managedByController(ip *networkingv1alpha1.IPAddress) bool {
|
||||||
|
managedBy, ok := ip.Labels[networkingv1alpha1.LabelManagedBy]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return managedBy == ipallocator.ControllerName
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyIPAddressLabels(ip *networkingv1alpha1.IPAddress) bool {
|
||||||
|
labelFamily, ok := ip.Labels[networkingv1alpha1.LabelIPAddressFamily]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
family := string(v1.IPv4Protocol)
|
||||||
|
if netutils.IsIPv6String(ip.Name) {
|
||||||
|
family = string(v1.IPv6Protocol)
|
||||||
|
}
|
||||||
|
if family != labelFamily {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return managedByController(ip)
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
k8stesting "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/events"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
|
netutils "k8s.io/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
serviceCIDRv4 = "10.0.0.0/16"
|
||||||
|
serviceCIDRv6 = "2001:db8::/64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeRepair struct {
|
||||||
|
*RepairIPAddress
|
||||||
|
serviceStore cache.Store
|
||||||
|
ipAddressStore cache.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeRepair() (*fake.Clientset, *fakeRepair) {
|
||||||
|
fakeClient := fake.NewSimpleClientset()
|
||||||
|
|
||||||
|
informerFactory := informers.NewSharedInformerFactory(fakeClient, 0*time.Second)
|
||||||
|
serviceInformer := informerFactory.Core().V1().Services()
|
||||||
|
serviceIndexer := serviceInformer.Informer().GetIndexer()
|
||||||
|
|
||||||
|
ipInformer := informerFactory.Networking().V1alpha1().IPAddresses()
|
||||||
|
ipIndexer := ipInformer.Informer().GetIndexer()
|
||||||
|
|
||||||
|
fakeClient.PrependReactor("create", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
ip := action.(k8stesting.CreateAction).GetObject().(*networkingv1alpha1.IPAddress)
|
||||||
|
err := ipIndexer.Add(ip)
|
||||||
|
return false, ip, err
|
||||||
|
}))
|
||||||
|
fakeClient.PrependReactor("update", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
ip := action.(k8stesting.UpdateAction).GetObject().(*networkingv1alpha1.IPAddress)
|
||||||
|
return false, ip, fmt.Errorf("IPAddress is inmutable after creation")
|
||||||
|
}))
|
||||||
|
fakeClient.PrependReactor("delete", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
ip := action.(k8stesting.DeleteAction).GetName()
|
||||||
|
err := ipIndexer.Delete(ip)
|
||||||
|
return false, &networkingv1alpha1.IPAddress{}, err
|
||||||
|
}))
|
||||||
|
|
||||||
|
_, primary, err := netutils.ParseCIDRSloppy(serviceCIDRv4)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, secondary, err := netutils.ParseCIDRSloppy(serviceCIDRv6)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r := NewRepairIPAddress(0*time.Second,
|
||||||
|
fakeClient,
|
||||||
|
primary,
|
||||||
|
secondary,
|
||||||
|
serviceInformer,
|
||||||
|
ipInformer,
|
||||||
|
)
|
||||||
|
return fakeClient, &fakeRepair{r, serviceIndexer, ipIndexer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepairServiceIP(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
svcs []*v1.Service
|
||||||
|
ipAddresses []*networkingv1alpha1.IPAddress
|
||||||
|
expectedIPs []string
|
||||||
|
actions [][]string // verb and resource
|
||||||
|
events []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no changes needed single stack",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
|
||||||
|
},
|
||||||
|
expectedIPs: []string{"10.0.1.1"},
|
||||||
|
actions: [][]string{},
|
||||||
|
events: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes needed dual stack",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
|
||||||
|
newIPAddress("2001:db8::10", newService("test-svc", []string{"2001:db8::10"})),
|
||||||
|
},
|
||||||
|
expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
|
||||||
|
actions: [][]string{},
|
||||||
|
events: []string{},
|
||||||
|
},
|
||||||
|
// these two cases simulate migrating from bitmaps to IPAddress objects
|
||||||
|
{
|
||||||
|
name: "create IPAddress single stack",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
|
||||||
|
expectedIPs: []string{"10.0.1.1"},
|
||||||
|
actions: [][]string{{"create", "ipaddresses"}},
|
||||||
|
events: []string{"Warning ClusterIPNotAllocated Cluster IP [IPv4]: 10.0.1.1 is not allocated; repairing"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create IPAddresses dual stack",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
|
||||||
|
expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
|
||||||
|
actions: [][]string{{"create", "ipaddresses"}, {"create", "ipaddresses"}},
|
||||||
|
events: []string{
|
||||||
|
"Warning ClusterIPNotAllocated Cluster IP [IPv4]: 10.0.1.1 is not allocated; repairing",
|
||||||
|
"Warning ClusterIPNotAllocated Cluster IP [IPv6]: 2001:db8::10 is not allocated; repairing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile IPAddress single stack wrong reference",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
|
||||||
|
},
|
||||||
|
expectedIPs: []string{"10.0.1.1"},
|
||||||
|
actions: [][]string{{"delete", "ipaddresses"}, {"create", "ipaddresses"}},
|
||||||
|
events: []string{"Warning ClusterIPNotAllocated the ClusterIP [IPv4]: 10.0.1.1 for Service bar/test-svc has a wrong reference; repairing"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile IPAddresses dual stack",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1", "2001:db8::10"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
|
||||||
|
newIPAddress("2001:db8::10", newService("test-svc2", []string{"2001:db8::10"})),
|
||||||
|
},
|
||||||
|
expectedIPs: []string{"10.0.1.1", "2001:db8::10"},
|
||||||
|
actions: [][]string{{"delete", "ipaddresses"}, {"create", "ipaddresses"}, {"delete", "ipaddresses"}, {"create", "ipaddresses"}},
|
||||||
|
events: []string{
|
||||||
|
"Warning ClusterIPNotAllocated the ClusterIP [IPv4]: 10.0.1.1 for Service bar/test-svc has a wrong reference; repairing",
|
||||||
|
"Warning ClusterIPNotAllocated the ClusterIP [IPv6]: 2001:db8::10 for Service bar/test-svc has a wrong reference; repairing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one IP out of range",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"192.168.1.1", "2001:db8::10"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("192.168.1.1", newService("test-svc", []string{"192.168.1.1"})),
|
||||||
|
newIPAddress("2001:db8::10", newService("test-svc", []string{"2001:db8::10"})),
|
||||||
|
},
|
||||||
|
expectedIPs: []string{"2001:db8::10"},
|
||||||
|
actions: [][]string{},
|
||||||
|
events: []string{"Warning ClusterIPOutOfRange Cluster IP [IPv4]: 192.168.1.1 is not within the configured Service CIDR 10.0.0.0/16; please recreate service"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one IP orphan",
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
|
||||||
|
},
|
||||||
|
actions: [][]string{{"delete", "ipaddresses"}},
|
||||||
|
events: []string{"Warning IPAddressNotAllocated IPAddress: 10.0.1.1 for Service bar/test-svc appears to have leaked: cleaning up"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Two IPAddresses referencing the same service",
|
||||||
|
svcs: []*v1.Service{newService("test-svc", []string{"10.0.1.1"})},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc", []string{"10.0.1.1"})),
|
||||||
|
newIPAddress("10.0.1.2", newService("test-svc", []string{"10.0.1.1"})),
|
||||||
|
},
|
||||||
|
actions: [][]string{{"delete", "ipaddresses"}},
|
||||||
|
events: []string{"Warning IPAddressWrongReference IPAddress: 10.0.1.2 for Service bar/test-svc has a wrong reference; cleaning up"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Two Services with same ClusterIP",
|
||||||
|
svcs: []*v1.Service{
|
||||||
|
newService("test-svc", []string{"10.0.1.1"}),
|
||||||
|
newService("test-svc2", []string{"10.0.1.1"}),
|
||||||
|
},
|
||||||
|
ipAddresses: []*networkingv1alpha1.IPAddress{
|
||||||
|
newIPAddress("10.0.1.1", newService("test-svc2", []string{"10.0.1.1"})),
|
||||||
|
},
|
||||||
|
events: []string{"Warning ClusterIPAlreadyAllocated Cluster IP [4]:10.0.1.1 was assigned to multiple services; please recreate service"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
c, r := newFakeRepair()
|
||||||
|
// override for testing
|
||||||
|
r.servicesSynced = func() bool { return true }
|
||||||
|
r.ipAddressSynced = func() bool { return true }
|
||||||
|
recorder := events.NewFakeRecorder(100)
|
||||||
|
r.recorder = recorder
|
||||||
|
for _, svc := range test.svcs {
|
||||||
|
err := r.serviceStore.Add(svc)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error trying to add Service %v object: %v", svc, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range test.ipAddresses {
|
||||||
|
ip.CreationTimestamp = metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
err := r.ipAddressStore.Add(ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error trying to add IPAddress %s object: %v", ip, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.runOnce()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range test.expectedIPs {
|
||||||
|
_, err := r.ipAddressLister.Get(ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error trying to get IPAddress %s object: %v", ip, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectAction(t, c.Actions(), test.actions)
|
||||||
|
expectEvents(t, recorder.Events, test.events)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepairIPAddress_syncIPAddress(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ip *networkingv1alpha1.IPAddress
|
||||||
|
actions [][]string // verb and resource
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "correct ipv4 address",
|
||||||
|
ip: &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "10.0.1.1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv4Protocol),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
},
|
||||||
|
CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct ipv6 address",
|
||||||
|
ip: &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "2001:db8::11",
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
},
|
||||||
|
CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not managed by this controller",
|
||||||
|
ip: &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "2001:db8::11",
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
|
||||||
|
networkingv1alpha1.LabelManagedBy: "controller-foo",
|
||||||
|
},
|
||||||
|
CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "networking.gateway.k8s.io",
|
||||||
|
Resource: "gateway",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "out of range",
|
||||||
|
ip: &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "fd00:db8::11",
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
},
|
||||||
|
CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "leaked ip",
|
||||||
|
ip: &networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "10.0.1.1",
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(v1.IPv6Protocol),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ipallocator.ControllerName,
|
||||||
|
},
|
||||||
|
CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Name: "noexist",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: [][]string{{"delete", "ipaddresses"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, r := newFakeRepair()
|
||||||
|
err := r.ipAddressStore.Add(tt.ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = r.serviceStore.Add(newService("foo", []string{tt.ip.Name}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// override for testing
|
||||||
|
r.servicesSynced = func() bool { return true }
|
||||||
|
r.ipAddressSynced = func() bool { return true }
|
||||||
|
recorder := events.NewFakeRecorder(100)
|
||||||
|
r.recorder = recorder
|
||||||
|
if err := r.syncIPAddress(tt.ip.Name); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("RepairIPAddress.syncIPAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
expectAction(t, c.Actions(), tt.actions)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newService(name string, ips []string) *v1.Service {
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
svc := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: name},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
ClusterIP: ips[0],
|
||||||
|
ClusterIPs: ips,
|
||||||
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectAction(t *testing.T, actions []k8stesting.Action, expected [][]string) {
|
||||||
|
t.Helper()
|
||||||
|
if len(actions) != len(expected) {
|
||||||
|
t.Fatalf("Expected at least %d actions, got %d \ndiff: %v", len(expected), len(actions), cmp.Diff(expected, actions))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, action := range actions {
|
||||||
|
verb := expected[i][0]
|
||||||
|
if action.GetVerb() != verb {
|
||||||
|
t.Errorf("Expected action %d verb to be %s, got %s", i, verb, action.GetVerb())
|
||||||
|
}
|
||||||
|
resource := expected[i][1]
|
||||||
|
if action.GetResource().Resource != resource {
|
||||||
|
t.Errorf("Expected action %d resource to be %s, got %s", i, resource, action.GetResource().Resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectEvents(t *testing.T, actual <-chan string, expected []string) {
|
||||||
|
t.Helper()
|
||||||
|
c := time.After(wait.ForeverTestTimeout)
|
||||||
|
for _, e := range expected {
|
||||||
|
select {
|
||||||
|
case a := <-actual:
|
||||||
|
if e != a {
|
||||||
|
t.Errorf("Expected event %q, got %q", e, a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-c:
|
||||||
|
t.Errorf("Expected event %q, got nothing", e)
|
||||||
|
// continue iterating to print all expected events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case a := <-actual:
|
||||||
|
t.Errorf("Unexpected event: %q", a)
|
||||||
|
default:
|
||||||
|
return // No more events, as expected.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
566
pkg/registry/core/service/ipallocator/ipallocator.go
Normal file
566
pkg/registry/core/service/ipallocator/ipallocator.go
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 ipallocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
networkingv1alpha1informers "k8s.io/client-go/informers/networking/v1alpha1"
|
||||||
|
networkingv1alpha1client "k8s.io/client-go/kubernetes/typed/networking/v1alpha1"
|
||||||
|
networkingv1alpha1listers "k8s.io/client-go/listers/networking/v1alpha1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
netutils "k8s.io/utils/net"
|
||||||
|
utiltrace "k8s.io/utils/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ControllerName = "ipallocator.k8s.io"
|
||||||
|
|
||||||
|
// Allocator implements current ipallocator interface using IPAddress API object
|
||||||
|
// and an informer as backend.
|
||||||
|
type Allocator struct {
|
||||||
|
cidr *net.IPNet
|
||||||
|
prefix netip.Prefix
|
||||||
|
firstAddress netip.Addr // first IP address within the range
|
||||||
|
offsetAddress netip.Addr // IP address that delimits the upper and lower subranges
|
||||||
|
lastAddress netip.Addr // last IP address within the range
|
||||||
|
family api.IPFamily // family is the IP family of this range
|
||||||
|
|
||||||
|
rangeOffset int // subdivides the assigned IP range to prefer dynamic allocation from the upper range
|
||||||
|
size uint64 // cap the total number of IPs available to maxInt64
|
||||||
|
|
||||||
|
client networkingv1alpha1client.NetworkingV1alpha1Interface
|
||||||
|
ipAddressLister networkingv1alpha1listers.IPAddressLister
|
||||||
|
ipAddressSynced cache.InformerSynced
|
||||||
|
|
||||||
|
// metrics is a metrics recorder that can be disabled
|
||||||
|
metrics metricsRecorderInterface
|
||||||
|
metricLabel string
|
||||||
|
|
||||||
|
rand *rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Interface = &Allocator{}
|
||||||
|
|
||||||
|
// NewIPAllocator returns an IP allocator associated to a network range
|
||||||
|
// that use the IPAddress objectto track the assigned IP addresses,
|
||||||
|
// using an informer cache as storage.
|
||||||
|
func NewIPAllocator(
|
||||||
|
cidr *net.IPNet,
|
||||||
|
client networkingv1alpha1client.NetworkingV1alpha1Interface,
|
||||||
|
ipAddressInformer networkingv1alpha1informers.IPAddressInformer,
|
||||||
|
) (*Allocator, error) {
|
||||||
|
prefix, err := netip.ParsePrefix(cidr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix.Addr().Is6() && prefix.Bits() < 64 {
|
||||||
|
return nil, fmt.Errorf("shortest allowed prefix length for service CIDR is 64, got %d", prefix.Bits())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use the utils/net function once is available
|
||||||
|
size := hostsPerNetwork(cidr)
|
||||||
|
var family api.IPFamily
|
||||||
|
if netutils.IsIPv6CIDR(cidr) {
|
||||||
|
family = api.IPv6Protocol
|
||||||
|
} else {
|
||||||
|
family = api.IPv4Protocol
|
||||||
|
}
|
||||||
|
// Caching the first, offset and last addresses allows to optimize
|
||||||
|
// the search loops by using the netip.Addr iterator instead
|
||||||
|
// of having to do conversions with IP addresses.
|
||||||
|
// Don't allocate the network's ".0" address.
|
||||||
|
ipFirst := prefix.Masked().Addr().Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Use the broadcast address as last address for IPv6
|
||||||
|
ipLast, err := broadcastAddress(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// For IPv4 don't use the network's broadcast address
|
||||||
|
if family == api.IPv4Protocol {
|
||||||
|
ipLast = ipLast.Prev()
|
||||||
|
}
|
||||||
|
// KEP-3070: Reserve Service IP Ranges For Dynamic and Static IP Allocation
|
||||||
|
// calculate the subrange offset
|
||||||
|
rangeOffset := calculateRangeOffset(cidr)
|
||||||
|
offsetAddress, err := addOffsetAddress(ipFirst, uint64(rangeOffset))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a := &Allocator{
|
||||||
|
cidr: cidr,
|
||||||
|
prefix: prefix,
|
||||||
|
firstAddress: ipFirst,
|
||||||
|
lastAddress: ipLast,
|
||||||
|
rangeOffset: rangeOffset,
|
||||||
|
offsetAddress: offsetAddress,
|
||||||
|
size: size,
|
||||||
|
family: family,
|
||||||
|
client: client,
|
||||||
|
ipAddressLister: ipAddressInformer.Lister(),
|
||||||
|
ipAddressSynced: ipAddressInformer.Informer().HasSynced,
|
||||||
|
metrics: &emptyMetricsRecorder{}, // disabled by default
|
||||||
|
metricLabel: cidr.String(),
|
||||||
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Allocator) createIPAddress(name string, svc *api.Service, scope string) error {
|
||||||
|
ipAddress := networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Labels: map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(a.IPFamily()),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ControllerName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: serviceToRef(svc),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := a.client.IPAddresses().Create(context.Background(), &ipAddress, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
// update metrics
|
||||||
|
a.metrics.incrementAllocationErrors(a.metricLabel, scope)
|
||||||
|
if apierrors.IsAlreadyExists(err) {
|
||||||
|
return ErrAllocated
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// update metrics
|
||||||
|
a.metrics.incrementAllocations(a.metricLabel, scope)
|
||||||
|
a.metrics.setAllocated(a.metricLabel, a.Used())
|
||||||
|
a.metrics.setAvailable(a.metricLabel, a.Free())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate attempts to reserve the provided IP. ErrNotInRange or
|
||||||
|
// ErrAllocated will be returned if the IP is not valid for this range
|
||||||
|
// or has already been reserved. ErrFull will be returned if there
|
||||||
|
// are no addresses left.
|
||||||
|
// Only for testing, it will fail to create the IPAddress object because
|
||||||
|
// the Service reference is required.
|
||||||
|
func (a *Allocator) Allocate(ip net.IP) error {
|
||||||
|
return a.AllocateService(nil, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllocateService attempts to reserve the provided IP. ErrNotInRange or
|
||||||
|
// ErrAllocated will be returned if the IP is not valid for this range
|
||||||
|
// or has already been reserved. ErrFull will be returned if there
|
||||||
|
// are no addresses left.
|
||||||
|
func (a *Allocator) AllocateService(svc *api.Service, ip net.IP) error {
|
||||||
|
return a.allocateService(svc, ip, dryRunFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Allocator) allocateService(svc *api.Service, ip net.IP, dryRun bool) error {
|
||||||
|
if !a.ipAddressSynced() {
|
||||||
|
return fmt.Errorf("allocator not ready")
|
||||||
|
}
|
||||||
|
addr, err := netip.ParseAddr(ip.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check address is within the range of available addresses
|
||||||
|
if addr.Less(a.firstAddress) || // requested address is lower than the first address in the subnet
|
||||||
|
a.lastAddress.Less(addr) { // the last address in the subnet is lower than the requested address
|
||||||
|
if !dryRun {
|
||||||
|
// update metrics
|
||||||
|
a.metrics.incrementAllocationErrors(a.metricLabel, "static")
|
||||||
|
}
|
||||||
|
return &ErrNotInRange{ip, a.prefix.String()}
|
||||||
|
}
|
||||||
|
if dryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.createIPAddress(ip.String(), svc, "static")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllocateNext return an IP address that wasn't allocated yet.
|
||||||
|
// Only for testing, it will fail to create the IPAddress object because
|
||||||
|
// the Service reference is required.
|
||||||
|
func (a *Allocator) AllocateNext() (net.IP, error) {
|
||||||
|
return a.AllocateNextService(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllocateNext return an IP address that wasn't allocated yet.
|
||||||
|
func (a *Allocator) AllocateNextService(svc *api.Service) (net.IP, error) {
|
||||||
|
return a.allocateNextService(svc, dryRunFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocateNextService tries to allocate a free IP address within the subnet.
|
||||||
|
// If the subnet is big enough, it partitions the subnet into two subranges,
|
||||||
|
// delimited by a.rangeOffset.
|
||||||
|
// It tries to allocate a free IP address from the upper subnet first and
|
||||||
|
// falls back to the lower subnet.
|
||||||
|
// It starts allocating from a random IP within each range.
|
||||||
|
func (a *Allocator) allocateNextService(svc *api.Service, dryRun bool) (net.IP, error) {
|
||||||
|
if !a.ipAddressSynced() {
|
||||||
|
return nil, fmt.Errorf("allocator not ready")
|
||||||
|
}
|
||||||
|
if dryRun {
|
||||||
|
// Don't bother finding a free value. It's racy and not worth the
|
||||||
|
// effort to plumb any further.
|
||||||
|
return a.CIDR().IP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := utiltrace.New("allocate dynamic ClusterIP address")
|
||||||
|
defer trace.LogIfLong(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// rand.Int63n panics for n <= 0 so we need to avoid problems when
|
||||||
|
// converting from uint64 to int64
|
||||||
|
rangeSize := a.size - uint64(a.rangeOffset)
|
||||||
|
var offset uint64
|
||||||
|
switch {
|
||||||
|
case rangeSize >= math.MaxInt64:
|
||||||
|
offset = rand.Uint64()
|
||||||
|
case rangeSize == 0:
|
||||||
|
return net.IP{}, ErrFull
|
||||||
|
default:
|
||||||
|
offset = uint64(a.rand.Int63n(int64(rangeSize)))
|
||||||
|
}
|
||||||
|
iterator := ipIterator(a.offsetAddress, a.lastAddress, offset)
|
||||||
|
ip, err := a.allocateFromRange(iterator, svc)
|
||||||
|
if err == nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
// check the lower range
|
||||||
|
if a.rangeOffset != 0 {
|
||||||
|
offset = uint64(a.rand.Intn(a.rangeOffset))
|
||||||
|
iterator = ipIterator(a.firstAddress, a.offsetAddress.Prev(), offset)
|
||||||
|
ip, err = a.allocateFromRange(iterator, svc)
|
||||||
|
if err == nil {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update metrics
|
||||||
|
a.metrics.incrementAllocationErrors(a.metricLabel, "dynamic")
|
||||||
|
return net.IP{}, ErrFull
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP iterator allows to iterate over all the IP addresses
|
||||||
|
// in a range defined by the start and last address.
|
||||||
|
// It starts iterating at the address position defined by the offset.
|
||||||
|
// It returns an invalid address to indicate it hasfinished.
|
||||||
|
func ipIterator(first netip.Addr, last netip.Addr, offset uint64) func() netip.Addr {
|
||||||
|
// There are no modulo operations for IP addresses
|
||||||
|
modulo := func(addr netip.Addr) netip.Addr {
|
||||||
|
if addr.Compare(last) == 1 {
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
next := func(addr netip.Addr) netip.Addr {
|
||||||
|
return modulo(addr.Next())
|
||||||
|
}
|
||||||
|
start, err := addOffsetAddress(first, offset)
|
||||||
|
if err != nil {
|
||||||
|
return func() netip.Addr { return netip.Addr{} }
|
||||||
|
}
|
||||||
|
start = modulo(start)
|
||||||
|
ip := start
|
||||||
|
seen := false
|
||||||
|
return func() netip.Addr {
|
||||||
|
value := ip
|
||||||
|
// is the last or the first iteration
|
||||||
|
if value == start {
|
||||||
|
if seen {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
seen = true
|
||||||
|
}
|
||||||
|
ip = next(ip)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocateFromRange allocates an empty IP address from the range of
|
||||||
|
// IPs between the first and last address (both included), starting
|
||||||
|
// from the start address.
|
||||||
|
// TODO: this is a linear search, it can be optimized.
|
||||||
|
func (a *Allocator) allocateFromRange(iterator func() netip.Addr, svc *api.Service) (net.IP, error) {
|
||||||
|
for {
|
||||||
|
ip := iterator()
|
||||||
|
if !ip.IsValid() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
name := ip.String()
|
||||||
|
_, err := a.ipAddressLister.Get(name)
|
||||||
|
// continue if ip already exist
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !apierrors.IsNotFound(err) {
|
||||||
|
klog.Infof("unexpected error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// address is not present on the cache, try to allocate it
|
||||||
|
err = a.createIPAddress(name, svc, "dynamic")
|
||||||
|
// an error can happen if there is a race and our informer was not updated
|
||||||
|
// swallow the error and try with the next IP address
|
||||||
|
if err != nil {
|
||||||
|
klog.Infof("can not create IPAddress %s: %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return ip.AsSlice(), nil
|
||||||
|
}
|
||||||
|
return net.IP{}, ErrFull
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the IP back to the pool. Releasing an
|
||||||
|
// unallocated IP or an IP out of the range is a no-op and
|
||||||
|
// returns no error.
|
||||||
|
func (a *Allocator) Release(ip net.IP) error {
|
||||||
|
return a.release(ip, dryRunFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Allocator) release(ip net.IP, dryRun bool) error {
|
||||||
|
if !a.ipAddressSynced() {
|
||||||
|
return fmt.Errorf("allocator not ready")
|
||||||
|
}
|
||||||
|
if dryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := ip.String()
|
||||||
|
// Try to Delete the IPAddress independently of the cache state.
|
||||||
|
// The error is ignored for compatibility reasons.
|
||||||
|
err := a.client.IPAddresses().Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||||
|
if err == nil {
|
||||||
|
// update metrics
|
||||||
|
a.metrics.setAllocated(a.metricLabel, a.Used())
|
||||||
|
a.metrics.setAvailable(a.metricLabel, a.Free())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
klog.Infof("error releasing ip %s : %v", name, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach executes the function on each allocated IP
|
||||||
|
// This is required to satisfy the Allocator Interface only
|
||||||
|
func (a *Allocator) ForEach(f func(net.IP)) {
|
||||||
|
ipLabelSelector := labels.Set(map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(a.IPFamily()),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ControllerName,
|
||||||
|
}).AsSelectorPreValidated()
|
||||||
|
ips, err := a.ipAddressLister.List(ipLabelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, ip := range ips {
|
||||||
|
f(netutils.ParseIPSloppy(ip.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Allocator) CIDR() net.IPNet {
|
||||||
|
return *a.cidr
|
||||||
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
func (a *Allocator) Has(ip net.IP) bool {
|
||||||
|
// convert IP to name
|
||||||
|
name := ip.String()
|
||||||
|
ipAddress, err := a.client.IPAddresses().Get(context.Background(), name, metav1.GetOptions{})
|
||||||
|
if err != nil || len(ipAddress.Name) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Allocator) IPFamily() api.IPFamily {
|
||||||
|
return a.family
|
||||||
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
func (a *Allocator) Used() int {
|
||||||
|
ipLabelSelector := labels.Set(map[string]string{
|
||||||
|
networkingv1alpha1.LabelIPAddressFamily: string(a.IPFamily()),
|
||||||
|
networkingv1alpha1.LabelManagedBy: ControllerName,
|
||||||
|
}).AsSelectorPreValidated()
|
||||||
|
ips, err := a.ipAddressLister.List(ipLabelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
func (a *Allocator) Free() int {
|
||||||
|
return int(a.size) - a.Used()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy
|
||||||
|
func (a *Allocator) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// DryRun
|
||||||
|
func (a *Allocator) DryRun() Interface {
|
||||||
|
return dryRunAllocator{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableMetrics
|
||||||
|
func (a *Allocator) EnableMetrics() {
|
||||||
|
registerMetrics()
|
||||||
|
a.metrics = &metricsRecorder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dryRunRange is a shim to satisfy Interface without persisting state.
|
||||||
|
type dryRunAllocator struct {
|
||||||
|
real *Allocator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) Allocate(ip net.IP) error {
|
||||||
|
return dry.real.allocateService(nil, ip, dryRunTrue)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) AllocateNext() (net.IP, error) {
|
||||||
|
return dry.real.allocateNextService(nil, dryRunTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) Release(ip net.IP) error {
|
||||||
|
return dry.real.release(ip, dryRunTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) ForEach(cb func(net.IP)) {
|
||||||
|
dry.real.ForEach(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) CIDR() net.IPNet {
|
||||||
|
return dry.real.CIDR()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) IPFamily() api.IPFamily {
|
||||||
|
return dry.real.IPFamily()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) DryRun() Interface {
|
||||||
|
return dry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) Has(ip net.IP) bool {
|
||||||
|
return dry.real.Has(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dry dryRunAllocator) EnableMetrics() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// addOffsetAddress returns the address at the provided offset within the subnet
|
||||||
|
// TODO: move it to k8s.io/utils/net, this is the same as current AddIPOffset()
|
||||||
|
// but using netip.Addr instead of net.IP
|
||||||
|
func addOffsetAddress(address netip.Addr, offset uint64) (netip.Addr, error) {
|
||||||
|
addressBig := big.NewInt(0).SetBytes(address.AsSlice())
|
||||||
|
r := big.NewInt(0).Add(addressBig, big.NewInt(int64(offset)))
|
||||||
|
addr, ok := netip.AddrFromSlice(r.Bytes())
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}, fmt.Errorf("invalid address %v", r.Bytes())
|
||||||
|
}
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostsPerNetwork returns the number of available hosts in a subnet.
|
||||||
|
// The max number is limited by the size of an uint64.
|
||||||
|
// Number of hosts is calculated with the formula:
|
||||||
|
// IPv4: 2^x – 2, not consider network and broadcast address
|
||||||
|
// IPv6: 2^x - 1, not consider network address
|
||||||
|
// where x is the number of host bits in the subnet.
|
||||||
|
func hostsPerNetwork(subnet *net.IPNet) uint64 {
|
||||||
|
ones, bits := subnet.Mask.Size()
|
||||||
|
// this checks that we are not overflowing an int64
|
||||||
|
if bits-ones >= 64 {
|
||||||
|
return math.MaxUint64
|
||||||
|
}
|
||||||
|
max := uint64(1) << uint(bits-ones)
|
||||||
|
// Don't use the network's ".0" address,
|
||||||
|
if max == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
max--
|
||||||
|
if netutils.IsIPv4CIDR(subnet) {
|
||||||
|
// Don't use the IPv4 network's broadcast address
|
||||||
|
if max == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
max--
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcastAddress returns the broadcast address of the subnet
|
||||||
|
// The broadcast address is obtained by setting all the host bits
|
||||||
|
// in a subnet to 1.
|
||||||
|
// network 192.168.0.0/24 : subnet bits 24 host bits 32 - 24 = 8
|
||||||
|
// broadcast address 192.168.0.255
|
||||||
|
func broadcastAddress(subnet netip.Prefix) (netip.Addr, error) {
|
||||||
|
base := subnet.Masked().Addr()
|
||||||
|
bytes := base.AsSlice()
|
||||||
|
// get all the host bits from the subnet
|
||||||
|
n := 8*len(bytes) - subnet.Bits()
|
||||||
|
// set all the host bits to 1
|
||||||
|
for i := len(bytes) - 1; i >= 0 && n > 0; i-- {
|
||||||
|
if n >= 8 {
|
||||||
|
bytes[i] = 0xff
|
||||||
|
n -= 8
|
||||||
|
} else {
|
||||||
|
mask := ^uint8(0) >> (8 - n)
|
||||||
|
bytes[i] |= mask
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, ok := netip.AddrFromSlice(bytes)
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}, fmt.Errorf("invalid address %v", bytes)
|
||||||
|
}
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceToRef obtain the Service Parent Reference
|
||||||
|
func serviceToRef(svc *api.Service) *networkingv1alpha1.ParentReference {
|
||||||
|
if svc == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "",
|
||||||
|
Resource: "services",
|
||||||
|
Namespace: svc.Namespace,
|
||||||
|
Name: svc.Name,
|
||||||
|
UID: svc.UID,
|
||||||
|
}
|
||||||
|
}
|
921
pkg/registry/core/service/ipallocator/ipallocator_test.go
Normal file
921
pkg/registry/core/service/ipallocator/ipallocator_test.go
Normal file
@ -0,0 +1,921 @@
|
|||||||
|
/*
|
||||||
|
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 ipallocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
k8stesting "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/component-base/metrics/testutil"
|
||||||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
netutils "k8s.io/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestAllocator(cidr *net.IPNet) (*Allocator, error) {
|
||||||
|
client := fake.NewSimpleClientset()
|
||||||
|
|
||||||
|
informerFactory := informers.NewSharedInformerFactory(client, 0*time.Second)
|
||||||
|
ipInformer := informerFactory.Networking().V1alpha1().IPAddresses()
|
||||||
|
ipStore := ipInformer.Informer().GetIndexer()
|
||||||
|
|
||||||
|
client.PrependReactor("create", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
ip := action.(k8stesting.CreateAction).GetObject().(*networkingv1alpha1.IPAddress)
|
||||||
|
_, exists, err := ipStore.GetByKey(ip.Name)
|
||||||
|
if exists && err != nil {
|
||||||
|
return false, nil, fmt.Errorf("ip already exist")
|
||||||
|
}
|
||||||
|
ip.Generation = 1
|
||||||
|
err = ipStore.Add(ip)
|
||||||
|
return false, ip, err
|
||||||
|
}))
|
||||||
|
client.PrependReactor("delete", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
|
||||||
|
name := action.(k8stesting.DeleteAction).GetName()
|
||||||
|
obj, exists, err := ipStore.GetByKey(name)
|
||||||
|
ip := &networkingv1alpha1.IPAddress{}
|
||||||
|
if exists && err == nil {
|
||||||
|
ip = obj.(*networkingv1alpha1.IPAddress)
|
||||||
|
err = ipStore.Delete(ip)
|
||||||
|
}
|
||||||
|
return false, ip, err
|
||||||
|
}))
|
||||||
|
|
||||||
|
c, err := NewIPAllocator(cidr, client.NetworkingV1alpha1(), ipInformer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.ipAddressSynced = func() bool { return true }
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateIPAllocator(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cidr string
|
||||||
|
family api.IPFamily
|
||||||
|
free int
|
||||||
|
released string
|
||||||
|
outOfRange []string
|
||||||
|
alreadyAllocated string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IPv4",
|
||||||
|
cidr: "192.168.1.0/24",
|
||||||
|
free: 254,
|
||||||
|
released: "192.168.1.5",
|
||||||
|
outOfRange: []string{
|
||||||
|
"192.168.0.1", // not in 192.168.1.0/24
|
||||||
|
"192.168.1.0", // reserved (base address)
|
||||||
|
"192.168.1.255", // reserved (broadcast address)
|
||||||
|
"192.168.2.2", // not in 192.168.1.0/24
|
||||||
|
},
|
||||||
|
alreadyAllocated: "192.168.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6",
|
||||||
|
cidr: "2001:db8:1::/116",
|
||||||
|
free: 4095,
|
||||||
|
released: "2001:db8:1::5",
|
||||||
|
outOfRange: []string{
|
||||||
|
"2001:db8::1", // not in 2001:db8:1::/48
|
||||||
|
"2001:db8:1::", // reserved (base address)
|
||||||
|
"2001:db8:2::2", // not in 2001:db8:1::/48
|
||||||
|
},
|
||||||
|
alreadyAllocated: "2001:db8:1::1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy(tc.cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
if f := r.Free(); f != tc.free {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f := r.Used(); f != 0 {
|
||||||
|
t.Errorf("[%s]: wrong used: expected %d, got %d", tc.name, 0, f)
|
||||||
|
}
|
||||||
|
found := sets.NewString()
|
||||||
|
count := 0
|
||||||
|
for r.Free() > 0 {
|
||||||
|
ip, err := r.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("[%s] error @ free: %d used: %d count: %d: %v", tc.name, r.Free(), r.Used(), count, err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
//if !cidr.Contains(ip) {
|
||||||
|
// t.Fatalf("[%s] allocated %s which is outside of %s", tc.name, ip, cidr)
|
||||||
|
//}
|
||||||
|
if found.Has(ip.String()) {
|
||||||
|
t.Fatalf("[%s] allocated %s twice @ %d", tc.name, ip, count)
|
||||||
|
}
|
||||||
|
found.Insert(ip.String())
|
||||||
|
}
|
||||||
|
if _, err := r.AllocateNext(); err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found.Has(tc.released) {
|
||||||
|
t.Fatalf("not allocated address to be releases %s found %d", tc.released, len(found))
|
||||||
|
}
|
||||||
|
released := netutils.ParseIPSloppy(tc.released)
|
||||||
|
if err := r.Release(released); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 1 {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f)
|
||||||
|
}
|
||||||
|
if f := r.Used(); f != (tc.free - 1) {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f)
|
||||||
|
}
|
||||||
|
ip, err := r.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !released.Equal(ip) {
|
||||||
|
t.Errorf("[%s] unexpected %s : %s", tc.name, ip, released)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Release(released); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, outOfRange := range tc.outOfRange {
|
||||||
|
err = r.Allocate(netutils.ParseIPSloppy(outOfRange))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("unexpacted allocating of %s", outOfRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := r.Allocate(netutils.ParseIPSloppy(tc.alreadyAllocated)); err == nil {
|
||||||
|
t.Fatalf("unexpected allocation of %s", tc.alreadyAllocated)
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 1 {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f)
|
||||||
|
}
|
||||||
|
if f := r.Used(); f != (tc.free - 1) {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f)
|
||||||
|
}
|
||||||
|
if err := r.Allocate(released); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 0 {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 0, f)
|
||||||
|
}
|
||||||
|
if f := r.Used(); f != tc.free {
|
||||||
|
t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateTinyIPAllocator(t *testing.T) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/32")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
|
||||||
|
if f := r.Free(); f != 0 {
|
||||||
|
t.Errorf("free: %d", f)
|
||||||
|
}
|
||||||
|
if _, err := r.AllocateNext(); err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateReservedIPAllocator(t *testing.T) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/25")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
// allocate all addresses on the dynamic block
|
||||||
|
// subnet /25 = 128 ; dynamic block size is min(max(16,128/16),256) = 16
|
||||||
|
dynamicOffset := calculateRangeOffset(cidr)
|
||||||
|
dynamicBlockSize := int(r.size) - dynamicOffset
|
||||||
|
for i := 0; i < dynamicBlockSize; i++ {
|
||||||
|
_, err := r.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error trying to allocate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := dynamicOffset; i < int(r.size); i++ {
|
||||||
|
ip := fmt.Sprintf("192.168.1.%d", i+1)
|
||||||
|
if !r.Has(netutils.ParseIPSloppy(ip)) {
|
||||||
|
t.Errorf("IP %s expected to be allocated", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != dynamicOffset {
|
||||||
|
t.Errorf("expected %d free addresses, got %d", dynamicOffset, f)
|
||||||
|
}
|
||||||
|
// allocate all addresses on the static block
|
||||||
|
for i := 0; i < dynamicOffset; i++ {
|
||||||
|
ip := fmt.Sprintf("192.168.1.%d", i+1)
|
||||||
|
if err := r.Allocate(netutils.ParseIPSloppy(ip)); err != nil {
|
||||||
|
t.Errorf("Unexpected error trying to allocate IP %s: %v", ip, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 0 {
|
||||||
|
t.Errorf("expected free equal to 0 got: %d", f)
|
||||||
|
}
|
||||||
|
// release one address in the allocated block and another a new one randomly
|
||||||
|
if err := r.Release(netutils.ParseIPSloppy("192.168.1.10")); err != nil {
|
||||||
|
t.Fatalf("Unexpected error trying to release ip 192.168.1.10: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := r.AllocateNext(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 0 {
|
||||||
|
t.Errorf("expected free equal to 0 got: %d", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateSmallIPAllocator(t *testing.T) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.240/30")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
|
||||||
|
if f := r.Free(); f != 2 {
|
||||||
|
t.Errorf("expected free equal to 2 got: %d", f)
|
||||||
|
}
|
||||||
|
found := sets.NewString()
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
ip, err := r.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error allocating %s try %d : %v", ip, i, err)
|
||||||
|
}
|
||||||
|
if found.Has(ip.String()) {
|
||||||
|
t.Fatalf("address %s has been already allocated", ip)
|
||||||
|
}
|
||||||
|
found.Insert(ip.String())
|
||||||
|
}
|
||||||
|
for s := range found {
|
||||||
|
if !r.Has(netutils.ParseIPSloppy(s)) {
|
||||||
|
t.Fatalf("missing: %s", s)
|
||||||
|
}
|
||||||
|
if err := r.Allocate(netutils.ParseIPSloppy(s)); err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f := r.Free(); f != 0 {
|
||||||
|
t.Errorf("expected free equal to 0 got: %d", f)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
if ip, err := r.AllocateNext(); err == nil {
|
||||||
|
t.Fatalf("suddenly became not-full: %s", ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForEachIPAllocator(t *testing.T) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/24")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testCases := []sets.String{
|
||||||
|
sets.NewString(),
|
||||||
|
sets.NewString("192.168.1.1"),
|
||||||
|
sets.NewString("192.168.1.1", "192.168.1.254"),
|
||||||
|
sets.NewString("192.168.1.1", "192.168.1.128", "192.168.1.254"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
|
||||||
|
for ips := range tc {
|
||||||
|
ip := netutils.ParseIPSloppy(ips)
|
||||||
|
if err := r.Allocate(ip); err != nil {
|
||||||
|
t.Errorf("[%d] error allocating IP %v: %v", i, ip, err)
|
||||||
|
}
|
||||||
|
if !r.Has(ip) {
|
||||||
|
t.Errorf("[%d] expected IP %v allocated", i, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calls := sets.NewString()
|
||||||
|
r.ForEach(func(ip net.IP) {
|
||||||
|
calls.Insert(ip.String())
|
||||||
|
})
|
||||||
|
if len(calls) != len(tc) {
|
||||||
|
t.Errorf("[%d] expected %d calls, got %d", i, len(tc), len(calls))
|
||||||
|
}
|
||||||
|
if !calls.Equal(tc) {
|
||||||
|
t.Errorf("[%d] expected calls to equal testcase: %v vs %v", i, calls.List(), tc.List())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPAllocatorClusterIPMetrics(t *testing.T) {
|
||||||
|
clearMetrics()
|
||||||
|
// create IPv4 allocator
|
||||||
|
cidrIPv4 := "10.0.0.0/24"
|
||||||
|
_, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4)
|
||||||
|
a, err := newTestAllocator(clusterCIDRv4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.EnableMetrics()
|
||||||
|
// create IPv6 allocator
|
||||||
|
cidrIPv6 := "2001:db8::/112"
|
||||||
|
_, clusterCIDRv6, _ := netutils.ParseCIDRSloppy(cidrIPv6)
|
||||||
|
b, err := newTestAllocator(clusterCIDRv6)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating CidrSet: %v", err)
|
||||||
|
}
|
||||||
|
b.EnableMetrics()
|
||||||
|
|
||||||
|
// Check initial state
|
||||||
|
em := testMetrics{
|
||||||
|
free: 0,
|
||||||
|
used: 0,
|
||||||
|
allocated: 0,
|
||||||
|
errors: 0,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
em = testMetrics{
|
||||||
|
free: 0,
|
||||||
|
used: 0,
|
||||||
|
allocated: 0,
|
||||||
|
errors: 0,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv6, em)
|
||||||
|
|
||||||
|
// allocate 2 IPv4 addresses
|
||||||
|
found := sets.NewString()
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
ip, err := a.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if found.Has(ip.String()) {
|
||||||
|
t.Fatalf("already reserved: %s", ip)
|
||||||
|
}
|
||||||
|
found.Insert(ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
em = testMetrics{
|
||||||
|
free: 252,
|
||||||
|
used: 2,
|
||||||
|
allocated: 2,
|
||||||
|
errors: 0,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
|
||||||
|
// try to allocate the same IP addresses
|
||||||
|
for s := range found {
|
||||||
|
if !a.Has(netutils.ParseIPSloppy(s)) {
|
||||||
|
t.Fatalf("missing: %s", s)
|
||||||
|
}
|
||||||
|
if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
em = testMetrics{
|
||||||
|
free: 252,
|
||||||
|
used: 2,
|
||||||
|
allocated: 2,
|
||||||
|
errors: 2,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
|
||||||
|
// release the addresses allocated
|
||||||
|
for s := range found {
|
||||||
|
if !a.Has(netutils.ParseIPSloppy(s)) {
|
||||||
|
t.Fatalf("missing: %s", s)
|
||||||
|
}
|
||||||
|
if err := a.Release(netutils.ParseIPSloppy(s)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
em = testMetrics{
|
||||||
|
free: 254,
|
||||||
|
used: 0,
|
||||||
|
allocated: 2,
|
||||||
|
errors: 2,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
|
||||||
|
// allocate 264 addresses for each allocator
|
||||||
|
// the full range and 10 more (254 + 10 = 264) for IPv4
|
||||||
|
for i := 0; i < 264; i++ {
|
||||||
|
a.AllocateNext()
|
||||||
|
b.AllocateNext()
|
||||||
|
}
|
||||||
|
em = testMetrics{
|
||||||
|
free: 0,
|
||||||
|
used: 254,
|
||||||
|
allocated: 256, // this is a counter, we already had 2 allocations and we did 254 more
|
||||||
|
errors: 12,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
em = testMetrics{
|
||||||
|
free: 65271, // IPv6 clusterIP range is capped to 2^16 and consider the broadcast address as valid
|
||||||
|
used: 264,
|
||||||
|
allocated: 264,
|
||||||
|
errors: 0,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv6, em)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPAllocatorClusterIPAllocatedMetrics(t *testing.T) {
|
||||||
|
clearMetrics()
|
||||||
|
// create IPv4 allocator
|
||||||
|
cidrIPv4 := "10.0.0.0/25"
|
||||||
|
_, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4)
|
||||||
|
a, err := newTestAllocator(clusterCIDRv4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a.EnableMetrics()
|
||||||
|
|
||||||
|
em := testMetrics{
|
||||||
|
free: 0,
|
||||||
|
used: 0,
|
||||||
|
allocated: 0,
|
||||||
|
errors: 0,
|
||||||
|
}
|
||||||
|
expectMetrics(t, cidrIPv4, em)
|
||||||
|
|
||||||
|
// allocate 2 dynamic IPv4 addresses
|
||||||
|
found := sets.NewString()
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
ip, err := a.AllocateNext()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if found.Has(ip.String()) {
|
||||||
|
t.Fatalf("already reserved: %s", ip)
|
||||||
|
}
|
||||||
|
found.Insert(ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic_allocated, err := testutil.GetCounterMetricValue(clusterIPAllocations.WithLabelValues(cidrIPv4, "dynamic"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get %s value, err: %v", clusterIPAllocations.Name, err)
|
||||||
|
}
|
||||||
|
if dynamic_allocated != 2 {
|
||||||
|
t.Fatalf("Expected 2 received %f", dynamic_allocated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to allocate the same IP addresses
|
||||||
|
for s := range found {
|
||||||
|
if !a.Has(netutils.ParseIPSloppy(s)) {
|
||||||
|
t.Fatalf("missing: %s", s)
|
||||||
|
}
|
||||||
|
if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_errors, err := testutil.GetCounterMetricValue(clusterIPAllocationErrors.WithLabelValues(cidrIPv4, "static"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get %s value, err: %v", clusterIPAllocationErrors.Name, err)
|
||||||
|
}
|
||||||
|
if static_errors != 2 {
|
||||||
|
t.Fatalf("Expected 2 received %f", dynamic_allocated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_addOffsetAddress(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
address netip.Addr
|
||||||
|
offset uint64
|
||||||
|
want netip.Addr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "IPv4 offset 0",
|
||||||
|
address: netip.MustParseAddr("192.168.0.0"),
|
||||||
|
offset: 0,
|
||||||
|
want: netip.MustParseAddr("192.168.0.0"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 offset 0 not nibble boundary",
|
||||||
|
address: netip.MustParseAddr("192.168.0.11"),
|
||||||
|
offset: 0,
|
||||||
|
want: netip.MustParseAddr("192.168.0.11"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 offset 1",
|
||||||
|
address: netip.MustParseAddr("192.168.0.0"),
|
||||||
|
offset: 1,
|
||||||
|
want: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 offset 1 not nibble boundary",
|
||||||
|
address: netip.MustParseAddr("192.168.0.11"),
|
||||||
|
offset: 1,
|
||||||
|
want: netip.MustParseAddr("192.168.0.12"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 offset 1",
|
||||||
|
address: netip.MustParseAddr("fd00:1:2:3::"),
|
||||||
|
offset: 1,
|
||||||
|
want: netip.MustParseAddr("fd00:1:2:3::1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 offset 1 not nibble boundary",
|
||||||
|
address: netip.MustParseAddr("fd00:1:2:3::a"),
|
||||||
|
offset: 1,
|
||||||
|
want: netip.MustParseAddr("fd00:1:2:3::b"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 offset last",
|
||||||
|
address: netip.MustParseAddr("192.168.0.0"),
|
||||||
|
offset: 255,
|
||||||
|
want: netip.MustParseAddr("192.168.0.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 offset last",
|
||||||
|
address: netip.MustParseAddr("fd00:1:2:3::"),
|
||||||
|
offset: 0x7FFFFFFFFFFFFFFF,
|
||||||
|
want: netip.MustParseAddr("fd00:1:2:3:7FFF:FFFF:FFFF:FFFF"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv4 offset middle",
|
||||||
|
address: netip.MustParseAddr("192.168.0.0"),
|
||||||
|
offset: 128,
|
||||||
|
want: netip.MustParseAddr("192.168.0.128"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 offset 255",
|
||||||
|
address: netip.MustParseAddr("2001:db8:1::101"),
|
||||||
|
offset: 255,
|
||||||
|
want: netip.MustParseAddr("2001:db8:1::200"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6 offset 1025",
|
||||||
|
address: netip.MustParseAddr("fd00:1:2:3::"),
|
||||||
|
offset: 1025,
|
||||||
|
want: netip.MustParseAddr("fd00:1:2:3::401"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := addOffsetAddress(tt.address, tt.offset)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) || err != nil {
|
||||||
|
t.Errorf("offsetAddress() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
// double check to avoid mistakes on the hardcoded values
|
||||||
|
// avoid large numbers or it will timeout the test
|
||||||
|
if tt.offset < 2048 {
|
||||||
|
want := tt.address
|
||||||
|
var i uint64
|
||||||
|
for i = 0; i < tt.offset; i++ {
|
||||||
|
want = want.Next()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) || err != nil {
|
||||||
|
t.Errorf("offsetAddress() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_broadcastAddress(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
subnet netip.Prefix
|
||||||
|
want netip.Addr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ipv4",
|
||||||
|
subnet: netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
want: netip.MustParseAddr("192.168.0.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv4 no nibble boundary",
|
||||||
|
subnet: netip.MustParsePrefix("10.0.0.0/12"),
|
||||||
|
want: netip.MustParseAddr("10.15.255.255"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv6",
|
||||||
|
subnet: netip.MustParsePrefix("fd00:1:2:3::/64"),
|
||||||
|
want: netip.MustParseAddr("fd00:1:2:3:FFFF:FFFF:FFFF:FFFF"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got, err := broadcastAddress(tt.subnet); !reflect.DeepEqual(got, tt.want) || err != nil {
|
||||||
|
t.Errorf("broadcastAddress() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_hostsPerNetwork(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cidr string
|
||||||
|
addrs uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "supported IPv4 cidr",
|
||||||
|
cidr: "192.168.1.0/24",
|
||||||
|
addrs: 254,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single IPv4 host",
|
||||||
|
cidr: "192.168.1.0/32",
|
||||||
|
addrs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "small IPv4 cidr",
|
||||||
|
cidr: "192.168.1.0/31",
|
||||||
|
addrs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large IPv4 cidr",
|
||||||
|
cidr: "0.0.0.0/1",
|
||||||
|
addrs: math.MaxInt32 - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full IPv4 range",
|
||||||
|
cidr: "0.0.0.0/0",
|
||||||
|
addrs: math.MaxUint32 - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "supported IPv6 cidr",
|
||||||
|
cidr: "2001:db2::/112",
|
||||||
|
addrs: 65535,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single IPv6 host",
|
||||||
|
cidr: "2001:db8::/128",
|
||||||
|
addrs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "small IPv6 cidr",
|
||||||
|
cidr: "2001:db8::/127",
|
||||||
|
addrs: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "largest IPv6 for Int64",
|
||||||
|
cidr: "2001:db8::/65",
|
||||||
|
addrs: math.MaxInt64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "largest IPv6 for Uint64",
|
||||||
|
cidr: "2001:db8::/64",
|
||||||
|
addrs: math.MaxUint64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very large IPv6 cidr",
|
||||||
|
cidr: "2001:db8::/1",
|
||||||
|
addrs: math.MaxUint64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy(tc.cidr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse cidr for test %s, unexpected error: '%s'", tc.name, err)
|
||||||
|
}
|
||||||
|
if size := hostsPerNetwork(cidr); size != tc.addrs {
|
||||||
|
t.Errorf("test %s failed. %s should have a range size of %d, got %d",
|
||||||
|
tc.name, tc.cidr, tc.addrs, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ipIterator(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
first netip.Addr
|
||||||
|
last netip.Addr
|
||||||
|
offset uint64
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "start from first address small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 0,
|
||||||
|
want: []string{"192.168.0.1", "192.168.0.2"},
|
||||||
|
}, {
|
||||||
|
name: "start from last address small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 1,
|
||||||
|
want: []string{"192.168.0.2", "192.168.0.1"},
|
||||||
|
}, {
|
||||||
|
name: "start from offset out of range address small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 10,
|
||||||
|
want: []string{"192.168.0.1", "192.168.0.2"},
|
||||||
|
}, {
|
||||||
|
name: "start from first address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 0,
|
||||||
|
want: []string{"192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7"},
|
||||||
|
}, {
|
||||||
|
name: "start from middle address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 2,
|
||||||
|
want: []string{"192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7", "192.168.0.1", "192.168.0.2"},
|
||||||
|
}, {
|
||||||
|
name: "start from last address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 6,
|
||||||
|
want: []string{"192.168.0.7", "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := []string{}
|
||||||
|
iterator := ipIterator(tt.first, tt.last, tt.offset)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ip := iterator()
|
||||||
|
if !ip.IsValid() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
got = append(got, ip.String())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ipIterator() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
// check the iterator is fully stopped
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if ip := iterator(); ip.IsValid() {
|
||||||
|
t.Errorf("iterator should not return more addresses: %v", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ipIterator_Number(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
first netip.Addr
|
||||||
|
last netip.Addr
|
||||||
|
offset uint64
|
||||||
|
want uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "start from first address small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 0,
|
||||||
|
want: 2,
|
||||||
|
}, {
|
||||||
|
name: "start from last address small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 1,
|
||||||
|
want: 2,
|
||||||
|
}, {
|
||||||
|
name: "start from offset out of range small range",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
offset: 10,
|
||||||
|
want: 2,
|
||||||
|
}, {
|
||||||
|
name: "start from first address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 0,
|
||||||
|
want: 7,
|
||||||
|
}, {
|
||||||
|
name: "start from middle address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 2,
|
||||||
|
want: 7,
|
||||||
|
}, {
|
||||||
|
name: "start from last address",
|
||||||
|
first: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
last: netip.MustParseAddr("192.168.0.7"),
|
||||||
|
offset: 6,
|
||||||
|
want: 7,
|
||||||
|
}, {
|
||||||
|
name: "start from first address large range",
|
||||||
|
first: netip.MustParseAddr("2001:db8:1::101"),
|
||||||
|
last: netip.MustParseAddr("2001:db8:1::fff"),
|
||||||
|
offset: 0,
|
||||||
|
want: 3839,
|
||||||
|
}, {
|
||||||
|
name: "start from address in the middle",
|
||||||
|
first: netip.MustParseAddr("2001:db8:1::101"),
|
||||||
|
last: netip.MustParseAddr("2001:db8:1::fff"),
|
||||||
|
offset: 255,
|
||||||
|
want: 3839,
|
||||||
|
}, {
|
||||||
|
name: "start from last address",
|
||||||
|
first: netip.MustParseAddr("2001:db8:1::101"),
|
||||||
|
last: netip.MustParseAddr("2001:db8:1::fff"),
|
||||||
|
offset: 3838,
|
||||||
|
want: 3839,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var got uint64
|
||||||
|
iterator := ipIterator(tt.first, tt.last, tt.offset)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ip := iterator()
|
||||||
|
if !ip.IsValid() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
got++
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("ipIterator() = %d, want %d", got, tt.want)
|
||||||
|
}
|
||||||
|
// check the iterator is fully stopped
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if ip := iterator(); ip.IsValid() {
|
||||||
|
t.Errorf("iterator should not return more addresses: %v", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkIPAllocatorAllocateNextIPv4Size1048574(b *testing.B) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("10.0.0.0/12")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
r.AllocateNext()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkIPAllocatorAllocateNextIPv6Size65535(b *testing.B) {
|
||||||
|
_, cidr, err := netutils.ParseCIDRSloppy("fd00::/120")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
r, err := newTestAllocator(cidr)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Destroy()
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
r.AllocateNext()
|
||||||
|
}
|
||||||
|
}
|
@ -18,14 +18,17 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
@ -399,7 +402,18 @@ func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]st
|
|||||||
allocator = allocator.DryRun()
|
allocator = allocator.DryRun()
|
||||||
}
|
}
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
allocatedIP, err := allocator.AllocateNext()
|
var allocatedIP net.IP
|
||||||
|
var err error
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
|
svcAllocator, ok := allocator.(*ipallocator.Allocator)
|
||||||
|
if ok {
|
||||||
|
allocatedIP, err = svcAllocator.AllocateNextService(service)
|
||||||
|
} else {
|
||||||
|
allocatedIP, err = allocator.AllocateNext()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allocatedIP, err = allocator.AllocateNext()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return allocated, errors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP: %v", err))
|
return allocated, errors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP: %v", err))
|
||||||
}
|
}
|
||||||
@ -409,7 +423,18 @@ func (al *Allocators) allocIPs(service *api.Service, toAlloc map[api.IPFamily]st
|
|||||||
if parsedIP == nil {
|
if parsedIP == nil {
|
||||||
return allocated, errors.NewInternalError(fmt.Errorf("failed to parse service IP %q", ip))
|
return allocated, errors.NewInternalError(fmt.Errorf("failed to parse service IP %q", ip))
|
||||||
}
|
}
|
||||||
if err := allocator.Allocate(parsedIP); err != nil {
|
var err error
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
|
||||||
|
svcAllocator, ok := allocator.(*ipallocator.Allocator)
|
||||||
|
if ok {
|
||||||
|
err = svcAllocator.AllocateService(service, parsedIP)
|
||||||
|
} else {
|
||||||
|
err = allocator.Allocate(parsedIP)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = allocator.Allocate(parsedIP)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIPs"), service.Spec.ClusterIPs, fmt.Sprintf("failed to allocate IP %v: %v", ip, err))}
|
el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIPs"), service.Spec.ClusterIPs, fmt.Sprintf("failed to allocate IP %v: %v", ip, err))}
|
||||||
return allocated, errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
return allocated, errors.NewInvalid(api.Kind("Service"), service.Name, el)
|
||||||
}
|
}
|
||||||
|
17
pkg/registry/networking/ipaddress/doc.go
Normal file
17
pkg/registry/networking/ipaddress/doc.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
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 ipaddress // import "k8s.io/kubernetes/pkg/registry/networking/ipaddress"
|
63
pkg/registry/networking/ipaddress/storage/storage.go
Normal file
63
pkg/registry/networking/ipaddress/storage/storage.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"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"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
|
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/networking/ipaddress"
|
||||||
|
)
|
||||||
|
|
||||||
|
// REST implements a RESTStorage for IPAddress against etcd
|
||||||
|
type REST struct {
|
||||||
|
*genericregistry.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewREST returns a RESTStorage object that will work against endpoint slices.
|
||||||
|
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, error) {
|
||||||
|
store := &genericregistry.Store{
|
||||||
|
NewFunc: func() runtime.Object { return &networking.IPAddress{} },
|
||||||
|
NewListFunc: func() runtime.Object { return &networking.IPAddressList{} },
|
||||||
|
DefaultQualifiedResource: networking.Resource("ipaddresses"),
|
||||||
|
SingularQualifiedResource: networking.Resource("ipaddress"),
|
||||||
|
|
||||||
|
CreateStrategy: ipaddress.Strategy,
|
||||||
|
UpdateStrategy: ipaddress.Strategy,
|
||||||
|
DeleteStrategy: ipaddress.Strategy,
|
||||||
|
|
||||||
|
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
|
||||||
|
}
|
||||||
|
options := &generic.StoreOptions{RESTOptions: optsGetter}
|
||||||
|
if err := store.CompleteWithOptions(options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &REST{store}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement ShortNamesProvider.
|
||||||
|
var _ rest.ShortNamesProvider = &REST{}
|
||||||
|
|
||||||
|
// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
|
||||||
|
func (r *REST) ShortNames() []string {
|
||||||
|
return []string{"ip"}
|
||||||
|
}
|
110
pkg/registry/networking/ipaddress/strategy.go
Normal file
110
pkg/registry/networking/ipaddress/strategy.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
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 ipaddress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/networking"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/networking/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ipAddressStrategy implements verification logic for Replication.
|
||||||
|
type ipAddressStrategy struct {
|
||||||
|
runtime.ObjectTyper
|
||||||
|
names.NameGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
// noopNameGenerator does not generate names, it just returns the base.
|
||||||
|
type noopNameGenerator struct{}
|
||||||
|
|
||||||
|
func (noopNameGenerator) GenerateName(base string) string {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy is the default logic that applies when creating and updating Replication IPAddress objects.
|
||||||
|
var Strategy = ipAddressStrategy{legacyscheme.Scheme, noopNameGenerator{}}
|
||||||
|
|
||||||
|
// Strategy should implement rest.RESTCreateStrategy
|
||||||
|
var _ rest.RESTCreateStrategy = Strategy
|
||||||
|
|
||||||
|
// Strategy should implement rest.RESTUpdateStrategy
|
||||||
|
var _ rest.RESTUpdateStrategy = Strategy
|
||||||
|
|
||||||
|
// NamespaceScoped returns false because all IPAddresses is cluster scoped.
|
||||||
|
func (ipAddressStrategy) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForCreate clears the status of an IPAddress before creation.
|
||||||
|
func (ipAddressStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
|
_ = obj.(*networking.IPAddress)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
|
func (ipAddressStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
|
newIPAddress := obj.(*networking.IPAddress)
|
||||||
|
oldIPAddress := old.(*networking.IPAddress)
|
||||||
|
|
||||||
|
_, _ = newIPAddress, oldIPAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates a new IPAddress.
|
||||||
|
func (ipAddressStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
|
ipAddress := obj.(*networking.IPAddress)
|
||||||
|
err := validation.ValidateIPAddress(ipAddress)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonicalize normalizes the object after validation.
|
||||||
|
func (ipAddressStrategy) Canonicalize(obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowCreateOnUpdate is false for IPAddress; this means POST is needed to create one.
|
||||||
|
func (ipAddressStrategy) AllowCreateOnUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
|
func (ipAddressStrategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList {
|
||||||
|
newIPAddress := new.(*networking.IPAddress)
|
||||||
|
oldIPAddress := old.(*networking.IPAddress)
|
||||||
|
errList := validation.ValidateIPAddress(newIPAddress)
|
||||||
|
errList = append(errList, validation.ValidateIPAddressUpdate(newIPAddress, oldIPAddress)...)
|
||||||
|
return errList
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowUnconditionalUpdate is the default update policy for IPAddress objects.
|
||||||
|
func (ipAddressStrategy) AllowUnconditionalUpdate() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
|
func (ipAddressStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
|
func (ipAddressStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||||
|
return nil
|
||||||
|
}
|
17
pkg/registry/networking/ipaddress/strategy_test.go
Normal file
17
pkg/registry/networking/ipaddress/strategy_test.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
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 ipaddress
|
@ -28,6 +28,7 @@ import (
|
|||||||
clustercidrstore "k8s.io/kubernetes/pkg/registry/networking/clustercidr/storage"
|
clustercidrstore "k8s.io/kubernetes/pkg/registry/networking/clustercidr/storage"
|
||||||
ingressstore "k8s.io/kubernetes/pkg/registry/networking/ingress/storage"
|
ingressstore "k8s.io/kubernetes/pkg/registry/networking/ingress/storage"
|
||||||
ingressclassstore "k8s.io/kubernetes/pkg/registry/networking/ingressclass/storage"
|
ingressclassstore "k8s.io/kubernetes/pkg/registry/networking/ingressclass/storage"
|
||||||
|
ipaddressstore "k8s.io/kubernetes/pkg/registry/networking/ipaddress/storage"
|
||||||
networkpolicystore "k8s.io/kubernetes/pkg/registry/networking/networkpolicy/storage"
|
networkpolicystore "k8s.io/kubernetes/pkg/registry/networking/networkpolicy/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,6 +100,14 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstora
|
|||||||
storage[resource] = clusterCIDRCStorage
|
storage[resource] = clusterCIDRCStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ipaddress
|
||||||
|
if resource := "ipaddresses"; apiResourceConfigSource.ResourceEnabled(networkingapiv1alpha1.SchemeGroupVersion.WithResource(resource)) {
|
||||||
|
ipAddressStorage, err := ipaddressstore.NewREST(restOptionsGetter)
|
||||||
|
if err != nil {
|
||||||
|
return storage, err
|
||||||
|
}
|
||||||
|
storage[resource] = ipAddressStorage
|
||||||
|
}
|
||||||
return storage, nil
|
return storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1011
staging/src/k8s.io/api/networking/v1alpha1/generated.pb.go
generated
1011
staging/src/k8s.io/api/networking/v1alpha1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -92,3 +92,64 @@ message ClusterCIDRSpec {
|
|||||||
optional string ipv6 = 4;
|
optional string ipv6 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs
|
||||||
|
// that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses.
|
||||||
|
// An IP address can be represented in different formats, to guarantee the uniqueness of the IP,
|
||||||
|
// the name of the object is the IP address in canonical format, four decimal digits separated
|
||||||
|
// by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6.
|
||||||
|
// Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1
|
||||||
|
// Invalid: 10.01.2.3 or 2001:db8:0:0:0::1
|
||||||
|
message IPAddress {
|
||||||
|
// Standard object's metadata.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||||
|
|
||||||
|
// spec is the desired state of the IPAddress.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||||
|
// +optional
|
||||||
|
optional IPAddressSpec spec = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressList contains a list of IPAddress.
|
||||||
|
message IPAddressList {
|
||||||
|
// Standard object's metadata.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||||
|
|
||||||
|
// items is the list of IPAddresses.
|
||||||
|
repeated IPAddress items = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressSpec describe the attributes in an IP Address.
|
||||||
|
message IPAddressSpec {
|
||||||
|
// ParentRef references the resource that an IPAddress is attached to.
|
||||||
|
// An IPAddress must reference a parent object.
|
||||||
|
// +required
|
||||||
|
optional ParentReference parentRef = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentReference describes a reference to a parent object.
|
||||||
|
message ParentReference {
|
||||||
|
// Group is the group of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
optional string group = 1;
|
||||||
|
|
||||||
|
// Resource is the resource of the object being referenced.
|
||||||
|
// +required
|
||||||
|
optional string resource = 2;
|
||||||
|
|
||||||
|
// Namespace is the namespace of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
optional string namespace = 3;
|
||||||
|
|
||||||
|
// Name is the name of the object being referenced.
|
||||||
|
// +required
|
||||||
|
optional string name = 4;
|
||||||
|
|
||||||
|
// UID is the uid of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
optional string uid = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -22,12 +22,17 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GroupName is the group name use in this package.
|
// GroupName is the group name used in this package.
|
||||||
const GroupName = "networking.k8s.io"
|
const GroupName = "networking.k8s.io"
|
||||||
|
|
||||||
// SchemeGroupVersion is group version used to register these objects.
|
// SchemeGroupVersion is group version used to register objects in this package.
|
||||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||||
|
|
||||||
|
// Kind takes an unqualified kind and returns a Group qualified GroupKind.
|
||||||
|
func Kind(kind string) schema.GroupKind {
|
||||||
|
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||||
|
}
|
||||||
|
|
||||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
|
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
|
||||||
func Resource(resource string) schema.GroupResource {
|
func Resource(resource string) schema.GroupResource {
|
||||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
@ -49,8 +54,9 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&ClusterCIDR{},
|
&ClusterCIDR{},
|
||||||
&ClusterCIDRList{},
|
&ClusterCIDRList{},
|
||||||
|
&IPAddress{},
|
||||||
|
&IPAddressList{},
|
||||||
)
|
)
|
||||||
// Add the watch version that applies.
|
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package v1alpha1
|
|||||||
import (
|
import (
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
@ -95,3 +96,68 @@ type ClusterCIDRList struct {
|
|||||||
// items is the list of ClusterCIDRs.
|
// items is the list of ClusterCIDRs.
|
||||||
Items []ClusterCIDR `json:"items" protobuf:"bytes,2,rep,name=items"`
|
Items []ClusterCIDR `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +genclient
|
||||||
|
// +genclient:nonNamespaced
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
// +k8s:prerelease-lifecycle-gen:introduced=1.27
|
||||||
|
|
||||||
|
// IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs
|
||||||
|
// that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses.
|
||||||
|
// An IP address can be represented in different formats, to guarantee the uniqueness of the IP,
|
||||||
|
// the name of the object is the IP address in canonical format, four decimal digits separated
|
||||||
|
// by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6.
|
||||||
|
// Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1
|
||||||
|
// Invalid: 10.01.2.3 or 2001:db8:0:0:0::1
|
||||||
|
type IPAddress struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// Standard object's metadata.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
// spec is the desired state of the IPAddress.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||||
|
// +optional
|
||||||
|
Spec IPAddressSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressSpec describe the attributes in an IP Address.
|
||||||
|
type IPAddressSpec struct {
|
||||||
|
// ParentRef references the resource that an IPAddress is attached to.
|
||||||
|
// An IPAddress must reference a parent object.
|
||||||
|
// +required
|
||||||
|
ParentRef *ParentReference `json:"parentRef,omitempty" protobuf:"bytes,1,opt,name=parentRef"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentReference describes a reference to a parent object.
|
||||||
|
type ParentReference struct {
|
||||||
|
// Group is the group of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"`
|
||||||
|
// Resource is the resource of the object being referenced.
|
||||||
|
// +required
|
||||||
|
Resource string `json:"resource,omitempty" protobuf:"bytes,2,opt,name=resource"`
|
||||||
|
// Namespace is the namespace of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
|
||||||
|
// Name is the name of the object being referenced.
|
||||||
|
// +required
|
||||||
|
Name string `json:"name,omitempty" protobuf:"bytes,4,opt,name=name"`
|
||||||
|
// UID is the uid of the object being referenced.
|
||||||
|
// +optional
|
||||||
|
UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
// +k8s:prerelease-lifecycle-gen:introduced=1.27
|
||||||
|
|
||||||
|
// IPAddressList contains a list of IPAddress.
|
||||||
|
type IPAddressList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// Standard object's metadata.
|
||||||
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
// items is the list of IPAddresses.
|
||||||
|
Items []IPAddress `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||||
|
}
|
||||||
|
@ -59,4 +59,46 @@ func (ClusterCIDRSpec) SwaggerDoc() map[string]string {
|
|||||||
return map_ClusterCIDRSpec
|
return map_ClusterCIDRSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var map_IPAddress = map[string]string{
|
||||||
|
"": "IPAddress represents a single IP of a single IP Family. The object is designed to be used by APIs that operate on IP addresses. The object is used by the Service core API for allocation of IP addresses. An IP address can be represented in different formats, to guarantee the uniqueness of the IP, the name of the object is the IP address in canonical format, four decimal digits separated by dots suppressing leading zeros for IPv4 and the representation defined by RFC 5952 for IPv6. Valid: 192.168.1.5 or 2001:db8::1 or 2001:db8:aaaa:bbbb:cccc:dddd:eeee:1 Invalid: 10.01.2.3 or 2001:db8:0:0:0::1",
|
||||||
|
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||||
|
"spec": "spec is the desired state of the IPAddress. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPAddress) SwaggerDoc() map[string]string {
|
||||||
|
return map_IPAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
var map_IPAddressList = map[string]string{
|
||||||
|
"": "IPAddressList contains a list of IPAddress.",
|
||||||
|
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||||
|
"items": "items is the list of IPAddresses.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPAddressList) SwaggerDoc() map[string]string {
|
||||||
|
return map_IPAddressList
|
||||||
|
}
|
||||||
|
|
||||||
|
var map_IPAddressSpec = map[string]string{
|
||||||
|
"": "IPAddressSpec describe the attributes in an IP Address.",
|
||||||
|
"parentRef": "ParentRef references the resource that an IPAddress is attached to. An IPAddress must reference a parent object.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IPAddressSpec) SwaggerDoc() map[string]string {
|
||||||
|
return map_IPAddressSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
var map_ParentReference = map[string]string{
|
||||||
|
"": "ParentReference describes a reference to a parent object.",
|
||||||
|
"group": "Group is the group of the object being referenced.",
|
||||||
|
"resource": "Resource is the resource of the object being referenced.",
|
||||||
|
"namespace": "Namespace is the namespace of the object being referenced.",
|
||||||
|
"name": "Name is the name of the object being referenced.",
|
||||||
|
"uid": "UID is the uid of the object being referenced.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ParentReference) SwaggerDoc() map[string]string {
|
||||||
|
return map_ParentReference
|
||||||
|
}
|
||||||
|
|
||||||
// AUTO-GENERATED FUNCTIONS END HERE
|
// AUTO-GENERATED FUNCTIONS END HERE
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 v1alpha1
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// TODO: Use IPFamily as field with a field selector,And the value is set based on
|
||||||
|
// the name at create time and immutable.
|
||||||
|
// LabelIPAddressFamily is used to indicate the IP family of a Kubernetes IPAddress.
|
||||||
|
// This label simplify dual-stack client operations allowing to obtain the list of
|
||||||
|
// IP addresses filtered by family.
|
||||||
|
LabelIPAddressFamily = "ipaddress.kubernetes.io/ip-family"
|
||||||
|
// LabelManagedBy is used to indicate the controller or entity that manages
|
||||||
|
// an IPAddress. This label aims to enable different IPAddress
|
||||||
|
// objects to be managed by different controllers or entities within the
|
||||||
|
// same cluster. It is highly recommended to configure this label for all
|
||||||
|
// IPAddress objects.
|
||||||
|
LabelManagedBy = "ipaddress.kubernetes.io/managed-by"
|
||||||
|
)
|
@ -106,3 +106,100 @@ func (in *ClusterCIDRSpec) DeepCopy() *ClusterCIDRSpec {
|
|||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddress) DeepCopyInto(out *IPAddress) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddress.
|
||||||
|
func (in *IPAddress) DeepCopy() *IPAddress {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddress)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *IPAddress) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddressList) DeepCopyInto(out *IPAddressList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]IPAddress, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressList.
|
||||||
|
func (in *IPAddressList) DeepCopy() *IPAddressList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddressList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *IPAddressList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.ParentRef != nil {
|
||||||
|
in, out := &in.ParentRef, &out.ParentRef
|
||||||
|
*out = new(ParentReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec.
|
||||||
|
func (in *IPAddressSpec) DeepCopy() *IPAddressSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IPAddressSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ParentReference) DeepCopyInto(out *ParentReference) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParentReference.
|
||||||
|
func (in *ParentReference) DeepCopy() *ParentReference {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ParentReference)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
@ -56,3 +56,39 @@ func (in *ClusterCIDRList) APILifecycleDeprecated() (major, minor int) {
|
|||||||
func (in *ClusterCIDRList) APILifecycleRemoved() (major, minor int) {
|
func (in *ClusterCIDRList) APILifecycleRemoved() (major, minor int) {
|
||||||
return 1, 31
|
return 1, 31
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 *IPAddress) APILifecycleIntroduced() (major, minor int) {
|
||||||
|
return 1, 27
|
||||||
|
}
|
||||||
|
|
||||||
|
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
|
||||||
|
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
|
||||||
|
func (in *IPAddress) APILifecycleDeprecated() (major, minor int) {
|
||||||
|
return 1, 30
|
||||||
|
}
|
||||||
|
|
||||||
|
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
|
||||||
|
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
|
||||||
|
func (in *IPAddress) APILifecycleRemoved() (major, minor int) {
|
||||||
|
return 1, 33
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *IPAddressList) APILifecycleIntroduced() (major, minor int) {
|
||||||
|
return 1, 27
|
||||||
|
}
|
||||||
|
|
||||||
|
// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.
|
||||||
|
// It is controlled by "k8s:prerelease-lifecycle-gen:deprecated" tags in types.go or "k8s:prerelease-lifecycle-gen:introduced" plus three minor.
|
||||||
|
func (in *IPAddressList) APILifecycleDeprecated() (major, minor int) {
|
||||||
|
return 1, 30
|
||||||
|
}
|
||||||
|
|
||||||
|
// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.
|
||||||
|
// It is controlled by "k8s:prerelease-lifecycle-gen:removed" tags in types.go or "k8s:prerelease-lifecycle-gen:deprecated" plus three minor.
|
||||||
|
func (in *IPAddressList) APILifecycleRemoved() (major, minor int) {
|
||||||
|
return 1, 33
|
||||||
|
}
|
||||||
|
55
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.json
vendored
Normal file
55
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.json
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"kind": "IPAddress",
|
||||||
|
"apiVersion": "networking.k8s.io/v1alpha1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "nameValue",
|
||||||
|
"generateName": "generateNameValue",
|
||||||
|
"namespace": "namespaceValue",
|
||||||
|
"selfLink": "selfLinkValue",
|
||||||
|
"uid": "uidValue",
|
||||||
|
"resourceVersion": "resourceVersionValue",
|
||||||
|
"generation": 7,
|
||||||
|
"creationTimestamp": "2008-01-01T01:01:01Z",
|
||||||
|
"deletionTimestamp": "2009-01-01T01:01:01Z",
|
||||||
|
"deletionGracePeriodSeconds": 10,
|
||||||
|
"labels": {
|
||||||
|
"labelsKey": "labelsValue"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"annotationsKey": "annotationsValue"
|
||||||
|
},
|
||||||
|
"ownerReferences": [
|
||||||
|
{
|
||||||
|
"apiVersion": "apiVersionValue",
|
||||||
|
"kind": "kindValue",
|
||||||
|
"name": "nameValue",
|
||||||
|
"uid": "uidValue",
|
||||||
|
"controller": true,
|
||||||
|
"blockOwnerDeletion": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"finalizers": [
|
||||||
|
"finalizersValue"
|
||||||
|
],
|
||||||
|
"managedFields": [
|
||||||
|
{
|
||||||
|
"manager": "managerValue",
|
||||||
|
"operation": "operationValue",
|
||||||
|
"apiVersion": "apiVersionValue",
|
||||||
|
"time": "2004-01-01T01:01:01Z",
|
||||||
|
"fieldsType": "fieldsTypeValue",
|
||||||
|
"fieldsV1": {},
|
||||||
|
"subresource": "subresourceValue"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"parentRef": {
|
||||||
|
"group": "groupValue",
|
||||||
|
"resource": "resourceValue",
|
||||||
|
"namespace": "namespaceValue",
|
||||||
|
"name": "nameValue",
|
||||||
|
"uid": "uidValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.pb
vendored
Normal file
BIN
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.pb
vendored
Normal file
Binary file not shown.
41
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.yaml
vendored
Normal file
41
staging/src/k8s.io/api/testdata/HEAD/networking.k8s.io.v1alpha1.IPAddress.yaml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1alpha1
|
||||||
|
kind: IPAddress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
annotationsKey: annotationsValue
|
||||||
|
creationTimestamp: "2008-01-01T01:01:01Z"
|
||||||
|
deletionGracePeriodSeconds: 10
|
||||||
|
deletionTimestamp: "2009-01-01T01:01:01Z"
|
||||||
|
finalizers:
|
||||||
|
- finalizersValue
|
||||||
|
generateName: generateNameValue
|
||||||
|
generation: 7
|
||||||
|
labels:
|
||||||
|
labelsKey: labelsValue
|
||||||
|
managedFields:
|
||||||
|
- apiVersion: apiVersionValue
|
||||||
|
fieldsType: fieldsTypeValue
|
||||||
|
fieldsV1: {}
|
||||||
|
manager: managerValue
|
||||||
|
operation: operationValue
|
||||||
|
subresource: subresourceValue
|
||||||
|
time: "2004-01-01T01:01:01Z"
|
||||||
|
name: nameValue
|
||||||
|
namespace: namespaceValue
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: apiVersionValue
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: kindValue
|
||||||
|
name: nameValue
|
||||||
|
uid: uidValue
|
||||||
|
resourceVersion: resourceVersionValue
|
||||||
|
selfLink: selfLinkValue
|
||||||
|
uid: uidValue
|
||||||
|
spec:
|
||||||
|
parentRef:
|
||||||
|
group: groupValue
|
||||||
|
name: nameValue
|
||||||
|
namespace: namespaceValue
|
||||||
|
resource: resourceValue
|
||||||
|
uid: uidValue
|
@ -10125,6 +10125,47 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||||||
type:
|
type:
|
||||||
scalar: numeric
|
scalar: numeric
|
||||||
default: 0
|
default: 0
|
||||||
|
- name: io.k8s.api.networking.v1alpha1.IPAddress
|
||||||
|
map:
|
||||||
|
fields:
|
||||||
|
- name: apiVersion
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: kind
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: metadata
|
||||||
|
type:
|
||||||
|
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta
|
||||||
|
default: {}
|
||||||
|
- name: spec
|
||||||
|
type:
|
||||||
|
namedType: io.k8s.api.networking.v1alpha1.IPAddressSpec
|
||||||
|
default: {}
|
||||||
|
- name: io.k8s.api.networking.v1alpha1.IPAddressSpec
|
||||||
|
map:
|
||||||
|
fields:
|
||||||
|
- name: parentRef
|
||||||
|
type:
|
||||||
|
namedType: io.k8s.api.networking.v1alpha1.ParentReference
|
||||||
|
- name: io.k8s.api.networking.v1alpha1.ParentReference
|
||||||
|
map:
|
||||||
|
fields:
|
||||||
|
- name: group
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: name
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: namespace
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: resource
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
|
- name: uid
|
||||||
|
type:
|
||||||
|
scalar: string
|
||||||
- name: io.k8s.api.networking.v1beta1.HTTPIngressPath
|
- name: io.k8s.api.networking.v1beta1.HTTPIngressPath
|
||||||
map:
|
map:
|
||||||
fields:
|
fields:
|
||||||
|
@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
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 applyconfiguration-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
managedfields "k8s.io/apimachinery/pkg/util/managedfields"
|
||||||
|
internal "k8s.io/client-go/applyconfigurations/internal"
|
||||||
|
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAddressApplyConfiguration represents an declarative configuration of the IPAddress type for use
|
||||||
|
// with apply.
|
||||||
|
type IPAddressApplyConfiguration struct {
|
||||||
|
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||||
|
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||||
|
Spec *IPAddressSpecApplyConfiguration `json:"spec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress constructs an declarative configuration of the IPAddress type for use with
|
||||||
|
// apply.
|
||||||
|
func IPAddress(name string) *IPAddressApplyConfiguration {
|
||||||
|
b := &IPAddressApplyConfiguration{}
|
||||||
|
b.WithName(name)
|
||||||
|
b.WithKind("IPAddress")
|
||||||
|
b.WithAPIVersion("networking.k8s.io/v1alpha1")
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIPAddress extracts the applied configuration owned by fieldManager from
|
||||||
|
// iPAddress. If no managedFields are found in iPAddress for fieldManager, a
|
||||||
|
// IPAddressApplyConfiguration is returned with only the Name, Namespace (if applicable),
|
||||||
|
// APIVersion and Kind populated. It is possible that no managed fields were found for because other
|
||||||
|
// field managers have taken ownership of all the fields previously owned by fieldManager, or because
|
||||||
|
// the fieldManager never owned fields any fields.
|
||||||
|
// iPAddress must be a unmodified IPAddress API object that was retrieved from the Kubernetes API.
|
||||||
|
// ExtractIPAddress provides a way to perform a extract/modify-in-place/apply workflow.
|
||||||
|
// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously
|
||||||
|
// applied if another fieldManager has updated or force applied any of the previously applied fields.
|
||||||
|
// Experimental!
|
||||||
|
func ExtractIPAddress(iPAddress *networkingv1alpha1.IPAddress, fieldManager string) (*IPAddressApplyConfiguration, error) {
|
||||||
|
return extractIPAddress(iPAddress, fieldManager, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIPAddressStatus is the same as ExtractIPAddress except
|
||||||
|
// that it extracts the status subresource applied configuration.
|
||||||
|
// Experimental!
|
||||||
|
func ExtractIPAddressStatus(iPAddress *networkingv1alpha1.IPAddress, fieldManager string) (*IPAddressApplyConfiguration, error) {
|
||||||
|
return extractIPAddress(iPAddress, fieldManager, "status")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractIPAddress(iPAddress *networkingv1alpha1.IPAddress, fieldManager string, subresource string) (*IPAddressApplyConfiguration, error) {
|
||||||
|
b := &IPAddressApplyConfiguration{}
|
||||||
|
err := managedfields.ExtractInto(iPAddress, internal.Parser().Type("io.k8s.api.networking.v1alpha1.IPAddress"), fieldManager, b, subresource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.WithName(iPAddress.Name)
|
||||||
|
|
||||||
|
b.WithKind("IPAddress")
|
||||||
|
b.WithAPIVersion("networking.k8s.io/v1alpha1")
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKind sets the Kind field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Kind field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithKind(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.Kind = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the APIVersion field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithAPIVersion(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.APIVersion = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName sets the Name field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Name field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithName(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.Name = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the GenerateName field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithGenerateName(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.GenerateName = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamespace sets the Namespace field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Namespace field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithNamespace(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.Namespace = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUID sets the UID field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the UID field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithUID(value types.UID) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.UID = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the ResourceVersion field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithResourceVersion(value string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.ResourceVersion = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGeneration sets the Generation field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Generation field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithGeneration(value int64) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.Generation = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the CreationTimestamp field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithCreationTimestamp(value metav1.Time) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.CreationTimestamp = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.DeletionTimestamp = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
b.DeletionGracePeriodSeconds = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLabels puts the entries into the Labels field in the declarative configuration
|
||||||
|
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the entries provided by each call will be put on the Labels field,
|
||||||
|
// overwriting an existing map entries in Labels field with the same key.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithLabels(entries map[string]string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
if b.Labels == nil && len(entries) > 0 {
|
||||||
|
b.Labels = make(map[string]string, len(entries))
|
||||||
|
}
|
||||||
|
for k, v := range entries {
|
||||||
|
b.Labels[k] = v
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAnnotations puts the entries into the Annotations field in the declarative configuration
|
||||||
|
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the entries provided by each call will be put on the Annotations field,
|
||||||
|
// overwriting an existing map entries in Annotations field with the same key.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithAnnotations(entries map[string]string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
if b.Annotations == nil && len(entries) > 0 {
|
||||||
|
b.Annotations = make(map[string]string, len(entries))
|
||||||
|
}
|
||||||
|
for k, v := range entries {
|
||||||
|
b.Annotations[k] = v
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
|
||||||
|
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||||
|
// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
for i := range values {
|
||||||
|
if values[i] == nil {
|
||||||
|
panic("nil value passed to WithOwnerReferences")
|
||||||
|
}
|
||||||
|
b.OwnerReferences = append(b.OwnerReferences, *values[i])
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
|
||||||
|
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||||
|
// If called multiple times, values provided by each call will be appended to the Finalizers field.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithFinalizers(values ...string) *IPAddressApplyConfiguration {
|
||||||
|
b.ensureObjectMetaApplyConfigurationExists()
|
||||||
|
for i := range values {
|
||||||
|
b.Finalizers = append(b.Finalizers, values[i])
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *IPAddressApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
|
||||||
|
if b.ObjectMetaApplyConfiguration == nil {
|
||||||
|
b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpec sets the Spec field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Spec field is set to the value of the last call.
|
||||||
|
func (b *IPAddressApplyConfiguration) WithSpec(value *IPAddressSpecApplyConfiguration) *IPAddressApplyConfiguration {
|
||||||
|
b.Spec = value
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
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 applyconfiguration-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// IPAddressSpecApplyConfiguration represents an declarative configuration of the IPAddressSpec type for use
|
||||||
|
// with apply.
|
||||||
|
type IPAddressSpecApplyConfiguration struct {
|
||||||
|
ParentRef *ParentReferenceApplyConfiguration `json:"parentRef,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressSpecApplyConfiguration constructs an declarative configuration of the IPAddressSpec type for use with
|
||||||
|
// apply.
|
||||||
|
func IPAddressSpec() *IPAddressSpecApplyConfiguration {
|
||||||
|
return &IPAddressSpecApplyConfiguration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithParentRef sets the ParentRef field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the ParentRef field is set to the value of the last call.
|
||||||
|
func (b *IPAddressSpecApplyConfiguration) WithParentRef(value *ParentReferenceApplyConfiguration) *IPAddressSpecApplyConfiguration {
|
||||||
|
b.ParentRef = value
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
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 applyconfiguration-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParentReferenceApplyConfiguration represents an declarative configuration of the ParentReference type for use
|
||||||
|
// with apply.
|
||||||
|
type ParentReferenceApplyConfiguration struct {
|
||||||
|
Group *string `json:"group,omitempty"`
|
||||||
|
Resource *string `json:"resource,omitempty"`
|
||||||
|
Namespace *string `json:"namespace,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
UID *types.UID `json:"uid,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentReferenceApplyConfiguration constructs an declarative configuration of the ParentReference type for use with
|
||||||
|
// apply.
|
||||||
|
func ParentReference() *ParentReferenceApplyConfiguration {
|
||||||
|
return &ParentReferenceApplyConfiguration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGroup sets the Group field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Group field is set to the value of the last call.
|
||||||
|
func (b *ParentReferenceApplyConfiguration) WithGroup(value string) *ParentReferenceApplyConfiguration {
|
||||||
|
b.Group = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResource sets the Resource field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Resource field is set to the value of the last call.
|
||||||
|
func (b *ParentReferenceApplyConfiguration) WithResource(value string) *ParentReferenceApplyConfiguration {
|
||||||
|
b.Resource = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamespace sets the Namespace field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Namespace field is set to the value of the last call.
|
||||||
|
func (b *ParentReferenceApplyConfiguration) WithNamespace(value string) *ParentReferenceApplyConfiguration {
|
||||||
|
b.Namespace = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName sets the Name field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the Name field is set to the value of the last call.
|
||||||
|
func (b *ParentReferenceApplyConfiguration) WithName(value string) *ParentReferenceApplyConfiguration {
|
||||||
|
b.Name = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUID sets the UID field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the UID field is set to the value of the last call.
|
||||||
|
func (b *ParentReferenceApplyConfiguration) WithUID(value types.UID) *ParentReferenceApplyConfiguration {
|
||||||
|
b.UID = &value
|
||||||
|
return b
|
||||||
|
}
|
@ -1289,6 +1289,12 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
|||||||
return &applyconfigurationsnetworkingv1alpha1.ClusterCIDRApplyConfiguration{}
|
return &applyconfigurationsnetworkingv1alpha1.ClusterCIDRApplyConfiguration{}
|
||||||
case networkingv1alpha1.SchemeGroupVersion.WithKind("ClusterCIDRSpec"):
|
case networkingv1alpha1.SchemeGroupVersion.WithKind("ClusterCIDRSpec"):
|
||||||
return &applyconfigurationsnetworkingv1alpha1.ClusterCIDRSpecApplyConfiguration{}
|
return &applyconfigurationsnetworkingv1alpha1.ClusterCIDRSpecApplyConfiguration{}
|
||||||
|
case networkingv1alpha1.SchemeGroupVersion.WithKind("IPAddress"):
|
||||||
|
return &applyconfigurationsnetworkingv1alpha1.IPAddressApplyConfiguration{}
|
||||||
|
case networkingv1alpha1.SchemeGroupVersion.WithKind("IPAddressSpec"):
|
||||||
|
return &applyconfigurationsnetworkingv1alpha1.IPAddressSpecApplyConfiguration{}
|
||||||
|
case networkingv1alpha1.SchemeGroupVersion.WithKind("ParentReference"):
|
||||||
|
return &applyconfigurationsnetworkingv1alpha1.ParentReferenceApplyConfiguration{}
|
||||||
|
|
||||||
// Group=networking.k8s.io, Version=v1beta1
|
// Group=networking.k8s.io, Version=v1beta1
|
||||||
case networkingv1beta1.SchemeGroupVersion.WithKind("HTTPIngressPath"):
|
case networkingv1beta1.SchemeGroupVersion.WithKind("HTTPIngressPath"):
|
||||||
|
@ -289,6 +289,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
|||||||
// Group=networking.k8s.io, Version=v1alpha1
|
// Group=networking.k8s.io, Version=v1alpha1
|
||||||
case networkingv1alpha1.SchemeGroupVersion.WithResource("clustercidrs"):
|
case networkingv1alpha1.SchemeGroupVersion.WithResource("clustercidrs"):
|
||||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha1().ClusterCIDRs().Informer()}, nil
|
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha1().ClusterCIDRs().Informer()}, nil
|
||||||
|
case networkingv1alpha1.SchemeGroupVersion.WithResource("ipaddresses"):
|
||||||
|
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1alpha1().IPAddresses().Informer()}, nil
|
||||||
|
|
||||||
// Group=networking.k8s.io, Version=v1beta1
|
// Group=networking.k8s.io, Version=v1beta1
|
||||||
case networkingv1beta1.SchemeGroupVersion.WithResource("ingresses"):
|
case networkingv1beta1.SchemeGroupVersion.WithResource("ingresses"):
|
||||||
|
@ -26,6 +26,8 @@ import (
|
|||||||
type Interface interface {
|
type Interface interface {
|
||||||
// ClusterCIDRs returns a ClusterCIDRInformer.
|
// ClusterCIDRs returns a ClusterCIDRInformer.
|
||||||
ClusterCIDRs() ClusterCIDRInformer
|
ClusterCIDRs() ClusterCIDRInformer
|
||||||
|
// IPAddresses returns a IPAddressInformer.
|
||||||
|
IPAddresses() IPAddressInformer
|
||||||
}
|
}
|
||||||
|
|
||||||
type version struct {
|
type version struct {
|
||||||
@ -43,3 +45,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
|
|||||||
func (v *version) ClusterCIDRs() ClusterCIDRInformer {
|
func (v *version) ClusterCIDRs() ClusterCIDRInformer {
|
||||||
return &clusterCIDRInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
return &clusterCIDRInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPAddresses returns a IPAddressInformer.
|
||||||
|
func (v *version) IPAddresses() IPAddressInformer {
|
||||||
|
return &iPAddressInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
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 informer-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
time "time"
|
||||||
|
|
||||||
|
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
internalinterfaces "k8s.io/client-go/informers/internalinterfaces"
|
||||||
|
kubernetes "k8s.io/client-go/kubernetes"
|
||||||
|
v1alpha1 "k8s.io/client-go/listers/networking/v1alpha1"
|
||||||
|
cache "k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAddressInformer provides access to a shared informer and lister for
|
||||||
|
// IPAddresses.
|
||||||
|
type IPAddressInformer interface {
|
||||||
|
Informer() cache.SharedIndexInformer
|
||||||
|
Lister() v1alpha1.IPAddressLister
|
||||||
|
}
|
||||||
|
|
||||||
|
type iPAddressInformer struct {
|
||||||
|
factory internalinterfaces.SharedInformerFactory
|
||||||
|
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPAddressInformer constructs a new informer for IPAddress type.
|
||||||
|
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||||
|
// one. This reduces memory footprint and number of connections to the server.
|
||||||
|
func NewIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||||
|
return NewFilteredIPAddressInformer(client, resyncPeriod, indexers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilteredIPAddressInformer constructs a new informer for IPAddress type.
|
||||||
|
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||||
|
// one. This reduces memory footprint and number of connections to the server.
|
||||||
|
func NewFilteredIPAddressInformer(client kubernetes.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||||
|
return cache.NewSharedIndexInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.NetworkingV1alpha1().IPAddresses().List(context.TODO(), options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.NetworkingV1alpha1().IPAddresses().Watch(context.TODO(), options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&networkingv1alpha1.IPAddress{},
|
||||||
|
resyncPeriod,
|
||||||
|
indexers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *iPAddressInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||||
|
return NewFilteredIPAddressInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *iPAddressInformer) Informer() cache.SharedIndexInformer {
|
||||||
|
return f.factory.InformerFor(&networkingv1alpha1.IPAddress{}, f.defaultInformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *iPAddressInformer) Lister() v1alpha1.IPAddressLister {
|
||||||
|
return v1alpha1.NewIPAddressLister(f.Informer().GetIndexer())
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
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 client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
json "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
v1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
labels "k8s.io/apimachinery/pkg/labels"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
networkingv1alpha1 "k8s.io/client-go/applyconfigurations/networking/v1alpha1"
|
||||||
|
testing "k8s.io/client-go/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeIPAddresses implements IPAddressInterface
|
||||||
|
type FakeIPAddresses struct {
|
||||||
|
Fake *FakeNetworkingV1alpha1
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipaddressesResource = v1alpha1.SchemeGroupVersion.WithResource("ipaddresses")
|
||||||
|
|
||||||
|
var ipaddressesKind = v1alpha1.SchemeGroupVersion.WithKind("IPAddress")
|
||||||
|
|
||||||
|
// Get takes name of the iPAddress, and returns the corresponding iPAddress object, and an error if there is any.
|
||||||
|
func (c *FakeIPAddresses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootGetAction(ipaddressesResource, name), &v1alpha1.IPAddress{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List takes label and field selectors, and returns the list of IPAddresses that match those selectors.
|
||||||
|
func (c *FakeIPAddresses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPAddressList, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootListAction(ipaddressesResource, ipaddressesKind, opts), &v1alpha1.IPAddressList{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||||
|
if label == nil {
|
||||||
|
label = labels.Everything()
|
||||||
|
}
|
||||||
|
list := &v1alpha1.IPAddressList{ListMeta: obj.(*v1alpha1.IPAddressList).ListMeta}
|
||||||
|
for _, item := range obj.(*v1alpha1.IPAddressList).Items {
|
||||||
|
if label.Matches(labels.Set(item.Labels)) {
|
||||||
|
list.Items = append(list.Items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a watch.Interface that watches the requested iPAddresses.
|
||||||
|
func (c *FakeIPAddresses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||||
|
return c.Fake.
|
||||||
|
InvokesWatch(testing.NewRootWatchAction(ipaddressesResource, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes the representation of a iPAddress and creates it. Returns the server's representation of the iPAddress, and an error, if there is any.
|
||||||
|
func (c *FakeIPAddresses) Create(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.CreateOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootCreateAction(ipaddressesResource, iPAddress), &v1alpha1.IPAddress{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes the representation of a iPAddress and updates it. Returns the server's representation of the iPAddress, and an error, if there is any.
|
||||||
|
func (c *FakeIPAddresses) Update(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.UpdateOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootUpdateAction(ipaddressesResource, iPAddress), &v1alpha1.IPAddress{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes name of the iPAddress and deletes it. Returns an error if one occurs.
|
||||||
|
func (c *FakeIPAddresses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||||
|
_, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootDeleteActionWithOptions(ipaddressesResource, name, opts), &v1alpha1.IPAddress{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCollection deletes a collection of objects.
|
||||||
|
func (c *FakeIPAddresses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||||
|
action := testing.NewRootDeleteCollectionAction(ipaddressesResource, listOpts)
|
||||||
|
|
||||||
|
_, err := c.Fake.Invokes(action, &v1alpha1.IPAddressList{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch applies the patch and returns the patched iPAddress.
|
||||||
|
func (c *FakeIPAddresses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootPatchSubresourceAction(ipaddressesResource, name, pt, data, subresources...), &v1alpha1.IPAddress{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply takes the given apply declarative configuration, applies it and returns the applied iPAddress.
|
||||||
|
func (c *FakeIPAddresses) Apply(ctx context.Context, iPAddress *networkingv1alpha1.IPAddressApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
if iPAddress == nil {
|
||||||
|
return nil, fmt.Errorf("iPAddress provided to Apply must not be nil")
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(iPAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := iPAddress.Name
|
||||||
|
if name == nil {
|
||||||
|
return nil, fmt.Errorf("iPAddress.Name must be provided to Apply")
|
||||||
|
}
|
||||||
|
obj, err := c.Fake.
|
||||||
|
Invokes(testing.NewRootPatchSubresourceAction(ipaddressesResource, *name, types.ApplyPatchType, data), &v1alpha1.IPAddress{})
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), err
|
||||||
|
}
|
@ -32,6 +32,10 @@ func (c *FakeNetworkingV1alpha1) ClusterCIDRs() v1alpha1.ClusterCIDRInterface {
|
|||||||
return &FakeClusterCIDRs{c}
|
return &FakeClusterCIDRs{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FakeNetworkingV1alpha1) IPAddresses() v1alpha1.IPAddressInterface {
|
||||||
|
return &FakeIPAddresses{c}
|
||||||
|
}
|
||||||
|
|
||||||
// RESTClient returns a RESTClient that is used to communicate
|
// RESTClient returns a RESTClient that is used to communicate
|
||||||
// with API server by this client implementation.
|
// with API server by this client implementation.
|
||||||
func (c *FakeNetworkingV1alpha1) RESTClient() rest.Interface {
|
func (c *FakeNetworkingV1alpha1) RESTClient() rest.Interface {
|
||||||
|
@ -19,3 +19,5 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
type ClusterCIDRExpansion interface{}
|
type ClusterCIDRExpansion interface{}
|
||||||
|
|
||||||
|
type IPAddressExpansion interface{}
|
||||||
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
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 client-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
json "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
watch "k8s.io/apimachinery/pkg/watch"
|
||||||
|
networkingv1alpha1 "k8s.io/client-go/applyconfigurations/networking/v1alpha1"
|
||||||
|
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
rest "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAddressesGetter has a method to return a IPAddressInterface.
|
||||||
|
// A group's client should implement this interface.
|
||||||
|
type IPAddressesGetter interface {
|
||||||
|
IPAddresses() IPAddressInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddressInterface has methods to work with IPAddress resources.
|
||||||
|
type IPAddressInterface interface {
|
||||||
|
Create(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.CreateOptions) (*v1alpha1.IPAddress, error)
|
||||||
|
Update(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.UpdateOptions) (*v1alpha1.IPAddress, error)
|
||||||
|
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||||
|
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||||
|
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.IPAddress, error)
|
||||||
|
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.IPAddressList, error)
|
||||||
|
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||||
|
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAddress, err error)
|
||||||
|
Apply(ctx context.Context, iPAddress *networkingv1alpha1.IPAddressApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.IPAddress, err error)
|
||||||
|
IPAddressExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
// iPAddresses implements IPAddressInterface
|
||||||
|
type iPAddresses struct {
|
||||||
|
client rest.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// newIPAddresses returns a IPAddresses
|
||||||
|
func newIPAddresses(c *NetworkingV1alpha1Client) *iPAddresses {
|
||||||
|
return &iPAddresses{
|
||||||
|
client: c.RESTClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes name of the iPAddress, and returns the corresponding iPAddress object, and an error if there is any.
|
||||||
|
func (c *iPAddresses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
result = &v1alpha1.IPAddress{}
|
||||||
|
err = c.client.Get().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
Name(name).
|
||||||
|
VersionedParams(&options, scheme.ParameterCodec).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List takes label and field selectors, and returns the list of IPAddresses that match those selectors.
|
||||||
|
func (c *iPAddresses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IPAddressList, err error) {
|
||||||
|
var timeout time.Duration
|
||||||
|
if opts.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
result = &v1alpha1.IPAddressList{}
|
||||||
|
err = c.client.Get().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch returns a watch.Interface that watches the requested iPAddresses.
|
||||||
|
func (c *iPAddresses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||||
|
var timeout time.Duration
|
||||||
|
if opts.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
opts.Watch = true
|
||||||
|
return c.client.Get().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Watch(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes the representation of a iPAddress and creates it. Returns the server's representation of the iPAddress, and an error, if there is any.
|
||||||
|
func (c *iPAddresses) Create(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.CreateOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
result = &v1alpha1.IPAddress{}
|
||||||
|
err = c.client.Post().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Body(iPAddress).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes the representation of a iPAddress and updates it. Returns the server's representation of the iPAddress, and an error, if there is any.
|
||||||
|
func (c *iPAddresses) Update(ctx context.Context, iPAddress *v1alpha1.IPAddress, opts v1.UpdateOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
result = &v1alpha1.IPAddress{}
|
||||||
|
err = c.client.Put().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
Name(iPAddress.Name).
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Body(iPAddress).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes name of the iPAddress and deletes it. Returns an error if one occurs.
|
||||||
|
func (c *iPAddresses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||||
|
return c.client.Delete().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
Name(name).
|
||||||
|
Body(&opts).
|
||||||
|
Do(ctx).
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCollection deletes a collection of objects.
|
||||||
|
func (c *iPAddresses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||||
|
var timeout time.Duration
|
||||||
|
if listOpts.TimeoutSeconds != nil {
|
||||||
|
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
|
||||||
|
}
|
||||||
|
return c.client.Delete().
|
||||||
|
Resource("ipaddresses").
|
||||||
|
VersionedParams(&listOpts, scheme.ParameterCodec).
|
||||||
|
Timeout(timeout).
|
||||||
|
Body(&opts).
|
||||||
|
Do(ctx).
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch applies the patch and returns the patched iPAddress.
|
||||||
|
func (c *iPAddresses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
result = &v1alpha1.IPAddress{}
|
||||||
|
err = c.client.Patch(pt).
|
||||||
|
Resource("ipaddresses").
|
||||||
|
Name(name).
|
||||||
|
SubResource(subresources...).
|
||||||
|
VersionedParams(&opts, scheme.ParameterCodec).
|
||||||
|
Body(data).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply takes the given apply declarative configuration, applies it and returns the applied iPAddress.
|
||||||
|
func (c *iPAddresses) Apply(ctx context.Context, iPAddress *networkingv1alpha1.IPAddressApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.IPAddress, err error) {
|
||||||
|
if iPAddress == nil {
|
||||||
|
return nil, fmt.Errorf("iPAddress provided to Apply must not be nil")
|
||||||
|
}
|
||||||
|
patchOpts := opts.ToPatchOptions()
|
||||||
|
data, err := json.Marshal(iPAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := iPAddress.Name
|
||||||
|
if name == nil {
|
||||||
|
return nil, fmt.Errorf("iPAddress.Name must be provided to Apply")
|
||||||
|
}
|
||||||
|
result = &v1alpha1.IPAddress{}
|
||||||
|
err = c.client.Patch(types.ApplyPatchType).
|
||||||
|
Resource("ipaddresses").
|
||||||
|
Name(*name).
|
||||||
|
VersionedParams(&patchOpts, scheme.ParameterCodec).
|
||||||
|
Body(data).
|
||||||
|
Do(ctx).
|
||||||
|
Into(result)
|
||||||
|
return
|
||||||
|
}
|
@ -29,6 +29,7 @@ import (
|
|||||||
type NetworkingV1alpha1Interface interface {
|
type NetworkingV1alpha1Interface interface {
|
||||||
RESTClient() rest.Interface
|
RESTClient() rest.Interface
|
||||||
ClusterCIDRsGetter
|
ClusterCIDRsGetter
|
||||||
|
IPAddressesGetter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkingV1alpha1Client is used to interact with features provided by the networking.k8s.io group.
|
// NetworkingV1alpha1Client is used to interact with features provided by the networking.k8s.io group.
|
||||||
@ -40,6 +41,10 @@ func (c *NetworkingV1alpha1Client) ClusterCIDRs() ClusterCIDRInterface {
|
|||||||
return newClusterCIDRs(c)
|
return newClusterCIDRs(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *NetworkingV1alpha1Client) IPAddresses() IPAddressInterface {
|
||||||
|
return newIPAddresses(c)
|
||||||
|
}
|
||||||
|
|
||||||
// NewForConfig creates a new NetworkingV1alpha1Client for the given config.
|
// NewForConfig creates a new NetworkingV1alpha1Client for the given config.
|
||||||
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||||
// where httpClient was generated with rest.HTTPClientFor(c).
|
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||||
|
@ -21,3 +21,7 @@ package v1alpha1
|
|||||||
// ClusterCIDRListerExpansion allows custom methods to be added to
|
// ClusterCIDRListerExpansion allows custom methods to be added to
|
||||||
// ClusterCIDRLister.
|
// ClusterCIDRLister.
|
||||||
type ClusterCIDRListerExpansion interface{}
|
type ClusterCIDRListerExpansion interface{}
|
||||||
|
|
||||||
|
// IPAddressListerExpansion allows custom methods to be added to
|
||||||
|
// IPAddressLister.
|
||||||
|
type IPAddressListerExpansion interface{}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
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 lister-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1alpha1 "k8s.io/api/networking/v1alpha1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPAddressLister helps list IPAddresses.
|
||||||
|
// All objects returned here must be treated as read-only.
|
||||||
|
type IPAddressLister interface {
|
||||||
|
// List lists all IPAddresses in the indexer.
|
||||||
|
// Objects returned here must be treated as read-only.
|
||||||
|
List(selector labels.Selector) (ret []*v1alpha1.IPAddress, err error)
|
||||||
|
// Get retrieves the IPAddress from the index for a given name.
|
||||||
|
// Objects returned here must be treated as read-only.
|
||||||
|
Get(name string) (*v1alpha1.IPAddress, error)
|
||||||
|
IPAddressListerExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
// iPAddressLister implements the IPAddressLister interface.
|
||||||
|
type iPAddressLister struct {
|
||||||
|
indexer cache.Indexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPAddressLister returns a new IPAddressLister.
|
||||||
|
func NewIPAddressLister(indexer cache.Indexer) IPAddressLister {
|
||||||
|
return &iPAddressLister{indexer: indexer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all IPAddresses in the indexer.
|
||||||
|
func (s *iPAddressLister) List(selector labels.Selector) (ret []*v1alpha1.IPAddress, err error) {
|
||||||
|
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||||
|
ret = append(ret, m.(*v1alpha1.IPAddress))
|
||||||
|
})
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves the IPAddress from the index for a given name.
|
||||||
|
func (s *iPAddressLister) Get(name string) (*v1alpha1.IPAddress, error) {
|
||||||
|
obj, exists, err := s.indexer.GetByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.NewNotFound(v1alpha1.Resource("ipaddress"), name)
|
||||||
|
}
|
||||||
|
return obj.(*v1alpha1.IPAddress), nil
|
||||||
|
}
|
@ -216,6 +216,7 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]ResourceDescr
|
|||||||
{Group: networkingv1.GroupName, Kind: "Ingress"}: &IngressDescriber{c},
|
{Group: networkingv1.GroupName, Kind: "Ingress"}: &IngressDescriber{c},
|
||||||
{Group: networkingv1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c},
|
{Group: networkingv1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c},
|
||||||
{Group: networkingv1alpha1.GroupName, Kind: "ClusterCIDR"}: &ClusterCIDRDescriber{c},
|
{Group: networkingv1alpha1.GroupName, Kind: "ClusterCIDR"}: &ClusterCIDRDescriber{c},
|
||||||
|
{Group: networkingv1alpha1.GroupName, Kind: "IPAddress"}: &IPAddressDescriber{c},
|
||||||
{Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c},
|
{Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c},
|
||||||
{Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
|
{Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
|
||||||
{Group: batchv1beta1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
|
{Group: batchv1beta1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
|
||||||
@ -2853,6 +2854,46 @@ func (c *ClusterCIDRDescriber) describeClusterCIDRV1alpha1(cc *networkingv1alpha
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPAddressDescriber generates information about an IPAddress.
|
||||||
|
type IPAddressDescriber struct {
|
||||||
|
client clientset.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPAddressDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
|
||||||
|
var events *corev1.EventList
|
||||||
|
|
||||||
|
ipV1alpha1, err := c.client.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), name, metav1.GetOptions{})
|
||||||
|
if err == nil {
|
||||||
|
if describerSettings.ShowEvents {
|
||||||
|
events, _ = searchEvents(c.client.CoreV1(), ipV1alpha1, describerSettings.ChunkSize)
|
||||||
|
}
|
||||||
|
return c.describeIPAddressV1alpha1(ipV1alpha1, events)
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IPAddressDescriber) describeIPAddressV1alpha1(ip *networkingv1alpha1.IPAddress, events *corev1.EventList) (string, error) {
|
||||||
|
return tabbedString(func(out io.Writer) error {
|
||||||
|
w := NewPrefixWriter(out)
|
||||||
|
w.Write(LEVEL_0, "Name:\t%v\n", ip.Name)
|
||||||
|
printLabelsMultiline(w, "Labels", ip.Labels)
|
||||||
|
printAnnotationsMultiline(w, "Annotations", ip.Annotations)
|
||||||
|
|
||||||
|
if ip.Spec.ParentRef != nil {
|
||||||
|
w.Write(LEVEL_0, "Parent Reference:\n")
|
||||||
|
w.Write(LEVEL_1, "Group:\t%v\n", ip.Spec.ParentRef.Group)
|
||||||
|
w.Write(LEVEL_1, "Resource:\t%v\n", ip.Spec.ParentRef.Resource)
|
||||||
|
w.Write(LEVEL_1, "Namespace:\t%v\n", ip.Spec.ParentRef.Namespace)
|
||||||
|
w.Write(LEVEL_1, "Name:\t%v\n", ip.Spec.ParentRef.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if events != nil {
|
||||||
|
DescribeEvents(events, w)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceDescriber generates information about a service.
|
// ServiceDescriber generates information about a service.
|
||||||
type ServiceDescriber struct {
|
type ServiceDescriber struct {
|
||||||
clientset.Interface
|
clientset.Interface
|
||||||
|
@ -5686,6 +5686,54 @@ Events: <none>` + "\n",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDescribeIPAddress(t *testing.T) {
|
||||||
|
|
||||||
|
testcases := map[string]struct {
|
||||||
|
input *fake.Clientset
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
"IPAddress v1alpha1": {
|
||||||
|
input: fake.NewSimpleClientset(&networkingv1alpha1.IPAddress{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo.123",
|
||||||
|
},
|
||||||
|
Spec: networkingv1alpha1.IPAddressSpec{
|
||||||
|
ParentRef: &networkingv1alpha1.ParentReference{
|
||||||
|
Group: "mygroup",
|
||||||
|
Resource: "myresource",
|
||||||
|
Namespace: "mynamespace",
|
||||||
|
Name: "myname",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
output: `Name: foo.123
|
||||||
|
Labels: <none>
|
||||||
|
Annotations: <none>
|
||||||
|
Parent Reference:
|
||||||
|
Group: mygroup
|
||||||
|
Resource: myresource
|
||||||
|
Namespace: mynamespace
|
||||||
|
Name: myname
|
||||||
|
Events: <none>` + "\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testcases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
c := &describeClient{T: t, Namespace: "foo", Interface: tc.input}
|
||||||
|
d := IPAddressDescriber{c}
|
||||||
|
out, err := d.Describe("bar", "foo.123", DescriberSettings{ShowEvents: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if out != tc.output {
|
||||||
|
t.Errorf("expected :\n%s\nbut got output:\n%s diff:\n%s", tc.output, out, cmp.Diff(tc.output, out))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestControllerRef(t *testing.T) {
|
func TestControllerRef(t *testing.T) {
|
||||||
var replicas int32 = 1
|
var replicas int32 = 1
|
||||||
f := fake.NewSimpleClientset(
|
f := fake.NewSimpleClientset(
|
||||||
|
@ -695,77 +695,6 @@ func TestAPIServerService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceAlloc(t *testing.T) {
|
|
||||||
// Create an IPv4 single stack control-plane
|
|
||||||
serviceCIDR := "192.168.0.0/29"
|
|
||||||
|
|
||||||
client, _, tearDownFn := framework.StartTestServer(t, framework.TestServerSetup{
|
|
||||||
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
|
||||||
opts.ServiceClusterIPRanges = serviceCIDR
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer tearDownFn()
|
|
||||||
|
|
||||||
svc := func(i int) *corev1.Service {
|
|
||||||
return &corev1.Service{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: fmt.Sprintf("svc-%v", i),
|
|
||||||
},
|
|
||||||
Spec: corev1.ServiceSpec{
|
|
||||||
Type: corev1.ServiceTypeClusterIP,
|
|
||||||
Ports: []corev1.ServicePort{
|
|
||||||
{Port: 80},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the default "kubernetes" service is created.
|
|
||||||
if err := wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
|
|
||||||
_, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{})
|
|
||||||
if err != nil && !apierrors.IsNotFound(err) {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return !apierrors.IsNotFound(err), nil
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("creating kubernetes service timed out")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make 5 more services to take up all IPs
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{}); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make another service. It will fail because we're out of cluster IPs
|
|
||||||
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(8), metav1.CreateOptions{}); err != nil {
|
|
||||||
if !strings.Contains(err.Error(), "range is full") {
|
|
||||||
t.Errorf("unexpected error text: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
svcs, err := client.CoreV1().Services(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected success, and error getting the services: %v", err)
|
|
||||||
}
|
|
||||||
allIPs := []string{}
|
|
||||||
for _, s := range svcs.Items {
|
|
||||||
allIPs = append(allIPs, s.Spec.ClusterIP)
|
|
||||||
}
|
|
||||||
t.Fatalf("unexpected creation success. The following IPs exist: %#v. It should only be possible to allocate 2 IP addresses in this cluster.\n\n%#v", allIPs, svcs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the first service.
|
|
||||||
if err := client.CoreV1().Services(metav1.NamespaceDefault).Delete(context.TODO(), svc(1).ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
|
|
||||||
t.Fatalf("got unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This time creating the second service should work.
|
|
||||||
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(8), metav1.CreateOptions{}); err != nil {
|
|
||||||
t.Fatalf("got unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestUpdateNodeObjects represents a simple version of the behavior of node checkins at steady
|
// TestUpdateNodeObjects represents a simple version of the behavior of node checkins at steady
|
||||||
// state. This test allows for easy profiling of a realistic primary scenario for baseline CPU
|
// state. This test allows for easy profiling of a realistic primary scenario for baseline CPU
|
||||||
// in very large clusters. It is disabled by default - start a kube-apiserver and pass
|
// in very large clusters. It is disabled by default - start a kube-apiserver and pass
|
||||||
|
@ -242,6 +242,13 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
|
|||||||
},
|
},
|
||||||
// --
|
// --
|
||||||
|
|
||||||
|
// k8s.io/kubernetes/pkg/apis/networking/v1alpha1
|
||||||
|
gvr("networking.k8s.io", "v1alpha1", "ipaddresses"): {
|
||||||
|
Stub: `{"metadata": {"name": "192.168.1.2"}, "spec": {"parentRef": {"resource": "services","name": "test", "namespace": "ns"}}}`,
|
||||||
|
ExpectedEtcdPath: "/registry/ipaddresses/192.168.1.2",
|
||||||
|
},
|
||||||
|
// --
|
||||||
|
|
||||||
// k8s.io/kubernetes/pkg/apis/policy/v1
|
// k8s.io/kubernetes/pkg/apis/policy/v1
|
||||||
gvr("policy", "v1", "poddisruptionbudgets"): {
|
gvr("policy", "v1", "poddisruptionbudgets"): {
|
||||||
Stub: `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
Stub: `{"metadata": {"name": "pdbv1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
||||||
@ -481,6 +488,7 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
|
|||||||
ExpectedEtcdPath: "/registry/storageversions/sv1.test",
|
ExpectedEtcdPath: "/registry/storageversions/sv1.test",
|
||||||
},
|
},
|
||||||
// --
|
// --
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add csinodes
|
// add csinodes
|
||||||
|
379
test/integration/servicecidr/allocator_test.go
Normal file
379
test/integration/servicecidr/allocator_test.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 servicecidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
netutils "k8s.io/utils/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceAlloc(t *testing.T) {
|
||||||
|
// Create an IPv4 single stack control-plane
|
||||||
|
serviceCIDR := "192.168.0.0/29"
|
||||||
|
|
||||||
|
client, _, tearDownFn := framework.StartTestServer(t, framework.TestServerSetup{
|
||||||
|
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
||||||
|
opts.ServiceClusterIPRanges = serviceCIDR
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
svc := func(i int) *v1.Service {
|
||||||
|
return &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("svc-%v", i),
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{Port: 80},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the default "kubernetes" service is created.
|
||||||
|
if err := wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
|
||||||
|
_, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !apierrors.IsNotFound(err), nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("creating kubernetes service timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make 5 more services to take up all IPs
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make another service. It will fail because we're out of cluster IPs
|
||||||
|
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(8), metav1.CreateOptions{}); err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "range is full") {
|
||||||
|
t.Errorf("unexpected error text: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
svcs, err := client.CoreV1().Services(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected success, and error getting the services: %v", err)
|
||||||
|
}
|
||||||
|
allIPs := []string{}
|
||||||
|
for _, s := range svcs.Items {
|
||||||
|
allIPs = append(allIPs, s.Spec.ClusterIP)
|
||||||
|
}
|
||||||
|
t.Fatalf("unexpected creation success. The following IPs exist: %#v. It should only be possible to allocate 2 IP addresses in this cluster.\n\n%#v", allIPs, svcs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the first service.
|
||||||
|
if err := client.CoreV1().Services(metav1.NamespaceDefault).Delete(context.TODO(), svc(1).ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
|
||||||
|
t.Fatalf("got unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This time creating the second service should work.
|
||||||
|
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(8), metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatalf("got unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceAllocIPAddress(t *testing.T) {
|
||||||
|
// Create an IPv6 single stack control-plane with a large range
|
||||||
|
serviceCIDR := "2001:db8::/64"
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MultiCIDRServiceAllocator, true)()
|
||||||
|
|
||||||
|
client, _, tearDownFn := framework.StartTestServer(t, framework.TestServerSetup{
|
||||||
|
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
|
||||||
|
opts.ServiceClusterIPRanges = serviceCIDR
|
||||||
|
opts.GenericServerRunOptions.AdvertiseAddress = netutils.ParseIPSloppy("2001:db8::10")
|
||||||
|
opts.APIEnablement.RuntimeConfig.Set("networking.k8s.io/v1alpha1=true")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
svc := func(i int) *v1.Service {
|
||||||
|
return &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("svc-%v", i),
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{Port: 80},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the default "kubernetes" service is created.
|
||||||
|
if err := wait.Poll(250*time.Millisecond, time.Minute, func() (bool, error) {
|
||||||
|
_, err := client.CoreV1().Services(metav1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{})
|
||||||
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !apierrors.IsNotFound(err), nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("creating kubernetes service timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create 5 random services and check that the Services have an IP associated
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
svc, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = client.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), svc.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a service in the top of the range to verify we can allocate in the whole range
|
||||||
|
// because it is not reasonable to create 2^64 services
|
||||||
|
lastSvc := svc(8)
|
||||||
|
lastSvc.Spec.ClusterIP = "2001:db8::ffff:ffff:ffff:ffff"
|
||||||
|
if _, err := client.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), lastSvc, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Errorf("unexpected error text: %v", err)
|
||||||
|
}
|
||||||
|
_, err := client.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), lastSvc.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateService(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MultiCIDRServiceAllocator, true)()
|
||||||
|
//logs.GlogSetter("7")
|
||||||
|
|
||||||
|
etcdOptions := framework.SharedEtcd()
|
||||||
|
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
|
||||||
|
s := kubeapiservertesting.StartTestServerOrDie(t,
|
||||||
|
apiServerOptions,
|
||||||
|
[]string{
|
||||||
|
"--runtime-config=networking.k8s.io/v1alpha1=true",
|
||||||
|
"--service-cluster-ip-range=10.0.0.0/24",
|
||||||
|
"--advertise-address=10.1.1.1",
|
||||||
|
"--disable-admission-plugins=ServiceAccount",
|
||||||
|
},
|
||||||
|
etcdOptions)
|
||||||
|
defer s.TearDownFn()
|
||||||
|
serviceName := "test-old-service"
|
||||||
|
namespace := "old-service-ns"
|
||||||
|
// Create a service and store it in etcd
|
||||||
|
svc := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: serviceName,
|
||||||
|
Namespace: namespace,
|
||||||
|
CreationTimestamp: metav1.Now(),
|
||||||
|
UID: "08675309-9376-9376-9376-086753099999",
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.11",
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "test-port",
|
||||||
|
Port: 81,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
svcJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), svc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed creating service JSON: %v", err)
|
||||||
|
}
|
||||||
|
key := "/" + etcdOptions.Prefix + "/services/specs/" + namespace + "/" + serviceName
|
||||||
|
if _, err := s.EtcdClient.Put(context.Background(), key, string(svcJSON)); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Logf("Service stored in etcd %v", string(svcJSON))
|
||||||
|
|
||||||
|
kubeclient, err := kubernetes.NewForConfig(s.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
ns := framework.CreateNamespaceOrDie(kubeclient, namespace, t)
|
||||||
|
defer framework.DeleteNamespaceOrDie(kubeclient, ns, t)
|
||||||
|
|
||||||
|
// TODO: Understand why the Service can not be obtained with a List, it only works if we trigger an event
|
||||||
|
// by updating the Service.
|
||||||
|
_, err = kubeclient.CoreV1().Services(namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
|
||||||
|
// The repair loop must create the IP address associated
|
||||||
|
_, err = kubeclient.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), svc.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkewedAllocators(t *testing.T) {
|
||||||
|
svc := func(i int) *v1.Service {
|
||||||
|
return &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("svc-%v", i),
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{Port: 80},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
etcdOptions := framework.SharedEtcd()
|
||||||
|
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
|
||||||
|
// s1 uses IPAddress allocator
|
||||||
|
s1 := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions,
|
||||||
|
[]string{
|
||||||
|
"--runtime-config=networking.k8s.io/v1alpha1=true",
|
||||||
|
"--service-cluster-ip-range=10.0.0.0/24",
|
||||||
|
"--disable-admission-plugins=ServiceAccount",
|
||||||
|
fmt.Sprintf("--feature-gates=%s=true", features.MultiCIDRServiceAllocator)},
|
||||||
|
etcdOptions)
|
||||||
|
defer s1.TearDownFn()
|
||||||
|
|
||||||
|
kubeclient1, err := kubernetes.NewForConfig(s1.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create 5 random services and check that the Services have an IP associated
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
service, err := kubeclient1.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = kubeclient1.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), service.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// s2 uses bitmap allocator
|
||||||
|
s2 := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions,
|
||||||
|
[]string{
|
||||||
|
"--runtime-config=networking.k8s.io/v1alpha1=false",
|
||||||
|
"--service-cluster-ip-range=10.0.0.0/24",
|
||||||
|
"--disable-admission-plugins=ServiceAccount",
|
||||||
|
fmt.Sprintf("--feature-gates=%s=false", features.MultiCIDRServiceAllocator)},
|
||||||
|
etcdOptions)
|
||||||
|
defer s2.TearDownFn()
|
||||||
|
|
||||||
|
kubeclient2, err := kubernetes.NewForConfig(s2.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create 5 random services and check that the Services have an IP associated
|
||||||
|
for i := 5; i < 10; i++ {
|
||||||
|
service, err := kubeclient2.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wait.PollImmediate(1*time.Second, 10*time.Second, func() (bool, error) {
|
||||||
|
// The repair loop must create the IP address associated
|
||||||
|
_, err = kubeclient1.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), service.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagsIPAllocator(t *testing.T) {
|
||||||
|
svc := func(i int) *v1.Service {
|
||||||
|
return &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("svc-%v", i),
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{Port: 80},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
etcdOptions := framework.SharedEtcd()
|
||||||
|
apiServerOptions := kubeapiservertesting.NewDefaultTestServerOptions()
|
||||||
|
// s1 uses IPAddress allocator
|
||||||
|
s1 := kubeapiservertesting.StartTestServerOrDie(t, apiServerOptions,
|
||||||
|
[]string{
|
||||||
|
"--runtime-config=networking.k8s.io/v1alpha1=true",
|
||||||
|
"--service-cluster-ip-range=10.0.0.0/24",
|
||||||
|
fmt.Sprintf("--feature-gates=%s=true", features.MultiCIDRServiceAllocator)},
|
||||||
|
etcdOptions)
|
||||||
|
defer s1.TearDownFn()
|
||||||
|
|
||||||
|
kubeclient1, err := kubernetes.NewForConfig(s1.ClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create 5 random services and check that the Services have an IP associated
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
service, err := kubeclient1.CoreV1().Services(metav1.NamespaceDefault).Create(context.TODO(), svc(i), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = kubeclient1.NetworkingV1alpha1().IPAddresses().Get(context.TODO(), service.Spec.ClusterIP, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
test/integration/servicecidr/main_test.go
Normal file
27
test/integration/servicecidr/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 servicecidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
framework.EtcdMain(m.Run)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user