diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index ff5ff3ee30652589ac1d322c0a9f64726d26a570..7273eb818bb9cb282d8323ed3a8085a74daa727b 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -6827,6 +6827,735 @@ } } }, + "/apis/network.kubesphere.io/v1alpha1/ippools": { + "get": { + "description": "list or watch objects of kind IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "listNetworkKubesphereIoV1alpha1IPPool", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "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. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.", + "name": "allowWatchBookmarks", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolList" + } + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "post": { + "description": "create an IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "createNetworkKubesphereIoV1alpha1IPPool", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "fieldManager", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "delete": { + "description": "delete collection of IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "deleteNetworkKubesphereIoV1alpha1CollectionIPPool", + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "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. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.", + "name": "allowWatchBookmarks", + "in": "query" + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/network.kubesphere.io/v1alpha1/ippools/{name}": { + "get": { + "description": "read the specified IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "readNetworkKubesphereIoV1alpha1IPPool", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "put": { + "description": "replace the specified IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "replaceNetworkKubesphereIoV1alpha1IPPool", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "fieldManager", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "delete": { + "description": "delete an IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "deleteNetworkKubesphereIoV1alpha1IPPool", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "gracePeriodSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "orphanDependents", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "propagationPolicy", + "in": "query" + } + ], + "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" + } + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "patch": { + "description": "partially update the specified IPPool", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "patchNetworkKubesphereIoV1alpha1IPPool", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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).", + "name": "fieldManager", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "force", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the IPPool", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/network.kubesphere.io/v1alpha1/ippools/{name}/status": { + "get": { + "description": "read status of the specified IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "readNetworkKubesphereIoV1alpha1IPPoolStatus", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "put": { + "description": "replace status of the specified IPPool", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "replaceNetworkKubesphereIoV1alpha1IPPoolStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "fieldManager", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "patch": { + "description": "partially update status of the specified IPPool", + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "patchNetworkKubesphereIoV1alpha1IPPoolStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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).", + "name": "fieldManager", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "force", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the IPPool", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, "/apis/network.kubesphere.io/v1alpha1/namespacenetworkpolicies": { "get": { "description": "list or watch objects of kind NamespaceNetworkPolicy", @@ -7475,15 +8204,200 @@ "patch": { "description": "partially update status of the specified NamespaceNetworkPolicy", "consumes": [ - "application/json-patch+json", - "application/merge-patch+json", - "application/strategic-merge-patch+json", - "application/apply-patch+yaml" + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json", + "application/apply-patch+yaml" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "patchNetworkKubesphereIoV1alpha1NamespaceNetworkPolicyStatus", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + { + "uniqueItems": true, + "type": "string", + "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", + "name": "dryRun", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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).", + "name": "fieldManager", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "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.", + "name": "force", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.NamespaceNetworkPolicy" + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "NamespaceNetworkPolicy" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "description": "name of the NamespaceNetworkPolicy", + "name": "name", + "in": "path", + "required": true + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + } + ] + }, + "/apis/network.kubesphere.io/v1alpha1/watch/ippools": { + "get": { + "description": "watch individual changes to a list of IPPool. deprecated: use the 'watch' parameter with a list operation instead.", + "consumes": [ + "*/*" + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "schemes": [ + "https" + ], + "tags": [ + "networkKubesphereIo_v1alpha1" + ], + "operationId": "watchNetworkKubesphereIoV1alpha1IPPoolList", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" + } + } + }, + "x-kubernetes-action": "watchlist", + "x-kubernetes-group-version-kind": { + "group": "network.kubesphere.io", + "version": "v1alpha1", + "kind": "IPPool" + } + }, + "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "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. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.", + "name": "allowWatchBookmarks", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "If 'true', then the output is pretty printed.", + "name": "pretty", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" + } + ] + }, + "/apis/network.kubesphere.io/v1alpha1/watch/ippools/{name}": { + "get": { + "description": "watch changes to an object of kind IPPool. deprecated: use the 'watch' parameter with a list operation instead, filtered to a single item with the 'fieldSelector' parameter.", + "consumes": [ + "*/*" ], "produces": [ "application/json", "application/yaml", - "application/vnd.kubernetes.protobuf" + "application/vnd.kubernetes.protobuf", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf;stream=watch" ], "schemes": [ "https" @@ -7491,58 +8405,62 @@ "tags": [ "networkKubesphereIo_v1alpha1" ], - "operationId": "patchNetworkKubesphereIoV1alpha1NamespaceNetworkPolicyStatus", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - { - "uniqueItems": true, - "type": "string", - "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", - "name": "dryRun", - "in": "query" - }, - { - "uniqueItems": true, - "type": "string", - "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).", - "name": "fieldManager", - "in": "query" - }, - { - "uniqueItems": true, - "type": "boolean", - "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.", - "name": "force", - "in": "query" - } - ], + "operationId": "watchNetworkKubesphereIoV1alpha1IPPool", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.NamespaceNetworkPolicy" + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" } } }, - "x-kubernetes-action": "patch", + "x-kubernetes-action": "watch", "x-kubernetes-group-version-kind": { "group": "network.kubesphere.io", "version": "v1alpha1", - "kind": "NamespaceNetworkPolicy" + "kind": "IPPool" } }, "parameters": [ + { + "uniqueItems": true, + "type": "boolean", + "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. If the feature gate WatchBookmarks is not enabled in apiserver, this field is ignored.", + "name": "allowWatchBookmarks", + "in": "query" + }, { "uniqueItems": true, "type": "string", - "description": "name of the NamespaceNetworkPolicy", + "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.", + "name": "continue", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "name": "fieldSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "name": "labelSelector", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "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.", + "name": "limit", + "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "name of the IPPool", "name": "name", "in": "path", "required": true @@ -7553,6 +8471,27 @@ "description": "If 'true', then the output is pretty printed.", "name": "pretty", "in": "query" + }, + { + "uniqueItems": true, + "type": "string", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history. When specified for list: - if unset, then the result is returned from remote storage based on quorum-read flag; - if it's 0, then we simply return what we currently have in cache, no guarantee; - if set to non zero, then the result is at least as fresh as given rv.", + "name": "resourceVersion", + "in": "query" + }, + { + "uniqueItems": true, + "type": "integer", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "name": "timeoutSeconds", + "in": "query" + }, + { + "uniqueItems": true, + "type": "boolean", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "name": "watch", + "in": "query" } ] }, @@ -11146,6 +12085,172 @@ } } }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.DNS": { + "description": "DNS contains values interesting for DNS resolvers", + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "nameservers": { + "type": "array", + "items": { + "type": "string" + } + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + }, + "search": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool": { + "type": "object", + "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" + }, + "spec": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolSpec" + }, + "status": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "network.kubesphere.io", + "kind": "IPPool", + "version": "v1alpha1" + } + ] + }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolList": { + "type": "object", + "required": [ + "items" + ], + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPool" + } + }, + "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" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "network.kubesphere.io", + "kind": "IPPoolList", + "version": "v1alpha1" + } + ] + }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolSpec": { + "type": "object", + "required": [ + "type", + "cidr" + ], + "properties": { + "blockSize": { + "description": "The block size to use for IP address assignments from this pool. Defaults to 26 for IPv4 and 112 for IPv6.", + "type": "integer", + "format": "int32" + }, + "cidr": { + "description": "The pool CIDR.", + "type": "string" + }, + "disabled": { + "description": "When disabled is true, IPAM will not assign addresses from this pool.", + "type": "boolean" + }, + "dns": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.DNS" + }, + "gateway": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "rangeEnd": { + "description": "The last ip, inclusive", + "type": "string" + }, + "rangeStart": { + "description": "The first ip, inclusive", + "type": "string" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.Route" + } + }, + "type": { + "type": "string" + }, + "vlanConfig": { + "$ref": "#/definitions/io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.VLANConfig" + }, + "workspace": { + "type": "string" + } + } + }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.IPPoolStatus": { + "type": "object", + "properties": { + "allocations": { + "type": "integer", + "format": "int32" + }, + "capacity": { + "type": "integer", + "format": "int32" + }, + "reserved": { + "type": "integer", + "format": "int32" + }, + "synced": { + "type": "boolean" + }, + "unallocated": { + "type": "integer", + "format": "int32" + } + } + }, "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.NamespaceNetworkPolicy": { "description": "NamespaceNetworkPolicy is the Schema for the namespacenetworkpolicies API", "type": "object", @@ -11300,6 +12405,17 @@ } } }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.Route": { + "type": "object", + "properties": { + "dst": { + "type": "string" + }, + "gateway": { + "type": "string" + } + } + }, "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.ServiceSelector": { "type": "object", "required": [ @@ -11315,6 +12431,22 @@ } } }, + "io.kubesphere.kubesphere.pkg.apis.network.v1alpha1.VLANConfig": { + "type": "object", + "required": [ + "vlanId", + "master" + ], + "properties": { + "master": { + "type": "string" + }, + "vlanId": { + "type": "integer", + "format": "int64" + } + } + }, "io.kubesphere.kubesphere.pkg.apis.tenant.v1alpha1.Workspace": { "description": "Workspace is the Schema for the workspaces API", "type": "object", diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index 0c7f5b6b4ba1f207462cb4beb36e8ebfc7bffd07..c07aabbf01f5aff258784a9056f0bb070ee07f11 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -51,7 +51,6 @@ import ( ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap" "kubesphere.io/kubesphere/pkg/simple/client/network" ippoolclient "kubesphere.io/kubesphere/pkg/simple/client/network/ippool" - calicoclient "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/s3" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -261,17 +260,12 @@ func addControllers( } var ippoolController manager.Runnable - if networkOptions.EnableIPPool { - var ippoolProvider ippoolclient.Provider - ippoolProvider = ippoolclient.NewProvider(client.KubeSphere(), networkOptions.IPPoolOptions) - if networkOptions.IPPoolOptions.Calico != nil { - ippoolProvider = calicoclient.NewProvider(client.KubeSphere(), *networkOptions.IPPoolOptions.Calico, options) - } + ippoolProvider := ippoolclient.NewProvider(kubernetesInformer.Core().V1().Pods(), client.KubeSphere(), client.Kubernetes(), networkOptions.IPPoolType, options) + if ippoolProvider != nil { ippoolController = ippool.NewIPPoolController(kubesphereInformer.Network().V1alpha1().IPPools(), kubesphereInformer.Network().V1alpha1().IPAMBlocks(), client.Kubernetes(), client.KubeSphere(), - networkOptions.IPPoolOptions, ippoolProvider) } diff --git a/cmd/controller-manager/app/options/options.go b/cmd/controller-manager/app/options/options.go index 3b8591f4cd9e9af923996c8a0fd05f1f02e1d480..2d98ef8b48090f9f96b1984809cca630860d5c27 100644 --- a/cmd/controller-manager/app/options/options.go +++ b/cmd/controller-manager/app/options/options.go @@ -18,6 +18,9 @@ package options import ( "flag" + "strings" + "time" + "github.com/spf13/pflag" "k8s.io/client-go/tools/leaderelection" cliflag "k8s.io/component-base/cli/flag" @@ -31,8 +34,6 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" "kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/servicemesh" - "strings" - "time" ) type KubeSphereControllerManagerOptions struct { diff --git a/cmd/controller-manager/app/server.go b/cmd/controller-manager/app/server.go index f5ca1db718cf91af43b70ed2c5c3532f17be9598..cde390a77c9e49d9ec669476b28b8c0902b5447a 100644 --- a/cmd/controller-manager/app/server.go +++ b/cmd/controller-manager/app/server.go @@ -27,7 +27,7 @@ import ( "kubesphere.io/kubesphere/pkg/apis" controllerconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/controller/namespace" - "kubesphere.io/kubesphere/pkg/controller/network/nsnetworkpolicy" + "kubesphere.io/kubesphere/pkg/controller/network/webhooks" "kubesphere.io/kubesphere/pkg/controller/user" "kubesphere.io/kubesphere/pkg/controller/workspace" "kubesphere.io/kubesphere/pkg/controller/workspacerole" @@ -252,7 +252,8 @@ func run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) klog.V(2).Info("registering webhooks to the webhook server") hookServer.Register("/validate-email-iam-kubesphere-io-v1alpha2-user", &webhook.Admission{Handler: &user.EmailValidator{Client: mgr.GetClient()}}) - hookServer.Register("/validate-nsnp-kubesphere-io-v1alpha1-network", &webhook.Admission{Handler: &nsnetworkpolicy.NSNPValidator{Client: mgr.GetClient()}}) + hookServer.Register("/validate-network-kubesphere-io-v1alpha1", &webhook.Admission{Handler: &webhooks.ValidatingHandler{C: mgr.GetClient()}}) + hookServer.Register("/mutate-network-kubesphere-io-v1alpha1", &webhook.Admission{Handler: &webhooks.MutatingHandler{C: mgr.GetClient()}}) klog.V(0).Info("Starting the controllers.") if err = mgr.Start(stopCh); err != nil { diff --git a/config/crds/network.kubesphere.io_ippools.yaml b/config/crds/network.kubesphere.io_ippools.yaml index 26bedfa0ce7719e2e6206f30aff873fdc33cafff..23489c6df2287ce9b72b4690ccd1449852c5d955 100644 --- a/config/crds/network.kubesphere.io_ippools.yaml +++ b/config/crds/network.kubesphere.io_ippools.yaml @@ -3,14 +3,20 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (devel) creationTimestamp: null name: ippools.network.kubesphere.io spec: group: network.kubesphere.io names: kind: IPPool + listKind: IPPoolList plural: ippools + singular: ippool scope: Cluster + subresources: + status: {} validation: openAPIV3Schema: properties: @@ -36,8 +42,8 @@ spec: description: The pool CIDR. type: string disabled: - description: When disabled is true, IPAM will not assign addresses - from this pool. + description: When disabled is true, IPAM will not assign addresses from + this pool. type: boolean dns: description: DNS contains values interesting for DNS resolvers @@ -59,11 +65,11 @@ spec: type: object gateway: type: string - namespace: - type: string rangeEnd: + description: The last ip, inclusive type: string rangeStart: + description: The first ip, inclusive type: string routes: items: @@ -87,8 +93,6 @@ spec: - master - vlanId type: object - workspace: - type: string required: - cidr - type @@ -96,7 +100,6 @@ spec: status: properties: allocations: - description: Allocations should equal to (Total - Reserved - Unallocated) type: integer capacity: type: integer @@ -106,6 +109,19 @@ spec: type: boolean unallocated: type: integer + workspaces: + additionalProperties: + properties: + allocations: + type: integer + required: + - allocations + type: object + type: object + required: + - allocations + - capacity + - unallocated type: object type: object version: v1alpha1 diff --git a/pkg/apis/network/v1alpha1/ipamblock_types.go b/pkg/apis/network/v1alpha1/ipamblock_types.go index c5272e65cdaa959853c97bed3b582625c126e17c..9e9085b656ff8a7b53ea9870d5c7aad66438b2dc 100644 --- a/pkg/apis/network/v1alpha1/ipamblock_types.go +++ b/pkg/apis/network/v1alpha1/ipamblock_types.go @@ -18,11 +18,11 @@ package v1alpha1 import ( "fmt" - "github.com/projectcalico/libcalico-go/lib/names" "math/big" "reflect" "strings" + "github.com/projectcalico/libcalico-go/lib/names" cnet "github.com/projectcalico/libcalico-go/lib/net" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/apis/network/v1alpha1/ippool_types.go b/pkg/apis/network/v1alpha1/ippool_types.go index ce71cf53e9b178f4dc9baf4e78df6b6bfb79ca7b..bb2871cce3936e6b7a2d2850782849e50821e39e 100644 --- a/pkg/apis/network/v1alpha1/ippool_types.go +++ b/pkg/apis/network/v1alpha1/ippool_types.go @@ -31,15 +31,21 @@ const ( // scope type > id > name // id used to detect cidr overlap - IPPoolTypeLabel = "ippool.network.kubesphere.io/type" - IPPoolNameLabel = "ippool.network.kubesphere.io/name" - IPPoolIDLabel = "ippool.network.kubesphere.io/id" + IPPoolTypeLabel = "ippool.network.kubesphere.io/type" + IPPoolNameLabel = "ippool.network.kubesphere.io/name" + IPPoolIDLabel = "ippool.network.kubesphere.io/id" + IPPoolDefaultLabel = "ippool.network.kubesphere.io/default" + + IPPoolTypeNone = "none" + IPPoolTypeLocal = "local" + IPPoolTypeCalico = "calico" ) // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:openapi-gen=true +// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster type IPPool struct { metav1.TypeMeta `json:",inline"` @@ -69,12 +75,17 @@ type DNS struct { Options []string `json:"options,omitempty"` } +type WorkspaceStatus struct { + Allocations int `json:"allocations"` +} + type IPPoolStatus struct { - Unallocated int `json:"unallocated,omitempty"` - Allocations int `json:"allocations,omitempty"` - Capacity int `json:"capacity,omitempty"` - Reserved int `json:"reserved,omitempty"` - Synced bool `json:"synced,omitempty"` + Unallocated int `json:"unallocated"` + Allocations int `json:"allocations"` + Capacity int `json:"capacity"` + Reserved int `json:"reserved,omitempty"` + Synced bool `json:"synced,omitempty"` + Workspaces map[string]WorkspaceStatus `json:"workspaces,omitempty"` } type IPPoolSpec struct { @@ -100,9 +111,6 @@ type IPPoolSpec struct { Gateway string `json:"gateway,omitempty"` Routes []Route `json:"routes,omitempty"` DNS DNS `json:"dns,omitempty"` - - Workspace string `json:"workspace,omitempty"` - Namespace string `json:"namespace,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -127,9 +135,9 @@ const ( // Find the ordinal (i.e. how far into the block) a given IP lies. Returns an error if the IP is outside the block. func (b IPPool) IPToOrdinal(ip cnet.IP) (int, error) { - netIP, _, _ := cnet.ParseCIDR(b.Spec.CIDR) + _, cidr, _ := cnet.ParseCIDR(b.Spec.CIDR) ipAsInt := cnet.IPToBigInt(ip) - baseInt := cnet.IPToBigInt(*netIP) + baseInt := cnet.IPToBigInt(cnet.IP{IP: cidr.IP}) ord := big.NewInt(0).Sub(ipAsInt, baseInt).Int64() if ord < 0 || ord >= int64(b.NumAddresses()) { return 0, fmt.Errorf("IP %s not in pool %s", ip, b.Spec.CIDR) @@ -145,6 +153,14 @@ func (b IPPool) NumAddresses() int { return numAddresses } +func (b IPPool) Type() string { + if b.Spec.Type == VLAN { + return IPPoolTypeLocal + } + + return b.Spec.Type +} + func (b IPPool) NumReservedAddresses() int { return b.StartReservedAddressed() + b.EndReservedAddressed() } @@ -166,6 +182,17 @@ func (b IPPool) EndReservedAddressed() int { return total - end - 1 } +func (b IPPool) Overlapped(dst IPPool) bool { + if b.ID() != dst.ID() { + return false + } + + _, cidr, _ := cnet.ParseCIDR(b.Spec.CIDR) + _, cidrDst, _ := cnet.ParseCIDR(dst.Spec.CIDR) + + return cidr.IsNetOverlap(cidrDst.IPNet) +} + func (pool IPPool) ID() uint32 { switch pool.Spec.Type { case VLAN: diff --git a/pkg/apis/network/v1alpha1/ippool_types_test.go b/pkg/apis/network/v1alpha1/ippool_types_test.go index b5831d392826e50e13ebdd81b5a421ebbcb41d52..534cbd0ffa27a3ede5130ff5ae7e62ab951bcd0f 100644 --- a/pkg/apis/network/v1alpha1/ippool_types_test.go +++ b/pkg/apis/network/v1alpha1/ippool_types_test.go @@ -30,7 +30,7 @@ func TestIPPool(t *testing.T) { }, Spec: IPPoolSpec{ Type: VLAN, - CIDR: "192.168.0.0/24", + CIDR: "192.168.0.1/24", RangeEnd: "192.168.0.250", RangeStart: "192.168.0.10", }, diff --git a/pkg/apis/network/v1alpha1/namespacenetworkpolicy_types.go b/pkg/apis/network/v1alpha1/namespacenetworkpolicy_types.go index fa1cb572facae41e4a73ca07a18c1de62cfcd00f..202116e4cd074d128a9478de0af5433f86774044 100644 --- a/pkg/apis/network/v1alpha1/namespacenetworkpolicy_types.go +++ b/pkg/apis/network/v1alpha1/namespacenetworkpolicy_types.go @@ -148,3 +148,7 @@ type NamespaceNetworkPolicyList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []NamespaceNetworkPolicy `json:"items"` } + +const ( + NSNPPrefix = "nsnp-" +) diff --git a/pkg/apis/network/v1alpha1/openapi_generated.go b/pkg/apis/network/v1alpha1/openapi_generated.go index 52447e1a69543cf3505246321a6e16abac182707..57db60b8243e59c620250f19c9ed6e466f3d5b0f 100644 --- a/pkg/apis/network/v1alpha1/openapi_generated.go +++ b/pkg/apis/network/v1alpha1/openapi_generated.go @@ -87,6 +87,18 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/apimachinery/pkg/apis/meta/v1.UpdateOptions": schema_pkg_apis_meta_v1_UpdateOptions(ref), "k8s.io/apimachinery/pkg/apis/meta/v1.WatchEvent": schema_pkg_apis_meta_v1_WatchEvent(ref), "k8s.io/apimachinery/pkg/util/intstr.IntOrString": schema_apimachinery_pkg_util_intstr_IntOrString(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.AllocationAttribute": schema_pkg_apis_network_v1alpha1_AllocationAttribute(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.DNS": schema_pkg_apis_network_v1alpha1_DNS(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlock": schema_pkg_apis_network_v1alpha1_IPAMBlock(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlockList": schema_pkg_apis_network_v1alpha1_IPAMBlockList(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlockSpec": schema_pkg_apis_network_v1alpha1_IPAMBlockSpec(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandle": schema_pkg_apis_network_v1alpha1_IPAMHandle(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandleList": schema_pkg_apis_network_v1alpha1_IPAMHandleList(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandleSpec": schema_pkg_apis_network_v1alpha1_IPAMHandleSpec(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPool": schema_pkg_apis_network_v1alpha1_IPPool(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolList": schema_pkg_apis_network_v1alpha1_IPPoolList(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolSpec": schema_pkg_apis_network_v1alpha1_IPPoolSpec(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolStatus": schema_pkg_apis_network_v1alpha1_IPPoolStatus(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NamespaceNetworkPolicy": schema_pkg_apis_network_v1alpha1_NamespaceNetworkPolicy(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NamespaceNetworkPolicyList": schema_pkg_apis_network_v1alpha1_NamespaceNetworkPolicyList(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NamespaceNetworkPolicySpec": schema_pkg_apis_network_v1alpha1_NamespaceNetworkPolicySpec(ref), @@ -94,7 +106,11 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NetworkPolicyEgressRule": schema_pkg_apis_network_v1alpha1_NetworkPolicyEgressRule(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NetworkPolicyIngressRule": schema_pkg_apis_network_v1alpha1_NetworkPolicyIngressRule(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.NetworkPolicyPeer": schema_pkg_apis_network_v1alpha1_NetworkPolicyPeer(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.ReservedAttr": schema_pkg_apis_network_v1alpha1_ReservedAttr(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.Route": schema_pkg_apis_network_v1alpha1_Route(ref), "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.ServiceSelector": schema_pkg_apis_network_v1alpha1_ServiceSelector(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.VLANConfig": schema_pkg_apis_network_v1alpha1_VLANConfig(ref), + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.WorkspaceStatus": schema_pkg_apis_network_v1alpha1_WorkspaceStatus(ref), } } @@ -2515,6 +2531,611 @@ func schema_apimachinery_pkg_util_intstr_IntOrString(ref common.ReferenceCallbac } } +func schema_pkg_apis_network_v1alpha1_AllocationAttribute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "handle_id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "secondary": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_network_v1alpha1_DNS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DNS contains values interesting for DNS resolvers", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nameservers": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "domain": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "search": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "options": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMBlock(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the IPAMBlock.", + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlockSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlockSpec"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMBlockList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlock"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMBlock"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMBlockSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMBlockSpec contains the specification for an IPAMBlock resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "cidr": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "allocations": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "unallocated": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "attributes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.AllocationAttribute"), + }, + }, + }, + }, + }, + "deleted": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"id", "cidr", "allocations", "unallocated", "attributes", "deleted"}, + }, + }, + Dependencies: []string{ + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.AllocationAttribute"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMHandle(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the IPAMHandle.", + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandleSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandleSpec"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMHandleList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandle"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPAMHandle"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPAMHandleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMHandleSpec contains the specification for an IPAMHandle resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "handleID": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "block": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "deleted": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"handleID", "block", "deleted"}, + }, + }, + } +} + +func schema_pkg_apis_network_v1alpha1_IPPool(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolSpec", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPoolStatus"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPPoolList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPool"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.IPPool"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPPoolSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "cidr": { + SchemaProps: spec.SchemaProps{ + Description: "The pool CIDR.", + Type: []string{"string"}, + Format: "", + }, + }, + "rangeStart": { + SchemaProps: spec.SchemaProps{ + Description: "The first ip, inclusive", + Type: []string{"string"}, + Format: "", + }, + }, + "rangeEnd": { + SchemaProps: spec.SchemaProps{ + Description: "The last ip, inclusive", + Type: []string{"string"}, + Format: "", + }, + }, + "disabled": { + SchemaProps: spec.SchemaProps{ + Description: "When disabled is true, IPAM will not assign addresses from this pool.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "blockSize": { + SchemaProps: spec.SchemaProps{ + Description: "The block size to use for IP address assignments from this pool. Defaults to 26 for IPv4 and 112 for IPv6.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "vlanConfig": { + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.VLANConfig"), + }, + }, + "gateway": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "routes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.Route"), + }, + }, + }, + }, + }, + "dns": { + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.DNS"), + }, + }, + }, + Required: []string{"type", "cidr"}, + }, + }, + Dependencies: []string{ + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.DNS", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.Route", "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.VLANConfig"}, + } +} + +func schema_pkg_apis_network_v1alpha1_IPPoolStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "unallocated": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "allocations": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "capacity": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "reserved": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "synced": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "workspaces": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.WorkspaceStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"unallocated", "allocations", "capacity"}, + }, + }, + Dependencies: []string{ + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1.WorkspaceStatus"}, + } +} + func schema_pkg_apis_network_v1alpha1_NamespaceNetworkPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2788,6 +3409,71 @@ func schema_pkg_apis_network_v1alpha1_NetworkPolicyPeer(ref common.ReferenceCall } } +func schema_pkg_apis_network_v1alpha1_ReservedAttr(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "StartOfBlock": { + SchemaProps: spec.SchemaProps{ + Description: "Number of addresses reserved from start of the block.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "EndOfBlock": { + SchemaProps: spec.SchemaProps{ + Description: "Number of addresses reserved from end of the block.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "Handle": { + SchemaProps: spec.SchemaProps{ + Description: "Handle for reserved addresses.", + Type: []string{"string"}, + Format: "", + }, + }, + "Note": { + SchemaProps: spec.SchemaProps{ + Description: "A description about the reserves.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"StartOfBlock", "EndOfBlock", "Handle", "Note"}, + }, + }, + } +} + +func schema_pkg_apis_network_v1alpha1_Route(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "dst": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "gateway": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_network_v1alpha1_ServiceSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2812,3 +3498,47 @@ func schema_pkg_apis_network_v1alpha1_ServiceSelector(ref common.ReferenceCallba }, } } + +func schema_pkg_apis_network_v1alpha1_VLANConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "vlanId": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "master": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"vlanId", "master"}, + }, + }, + } +} + +func schema_pkg_apis_network_v1alpha1_WorkspaceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "allocations": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"allocations"}, + }, + }, + } +} diff --git a/pkg/apis/network/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/network/v1alpha1/zz_generated.deepcopy.go index c39c5329dc2aeb71177afcddf24869d41c8fd6be..8e508884b1816789bf9916ee22edcf28e41df019 100644 --- a/pkg/apis/network/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/network/v1alpha1/zz_generated.deepcopy.go @@ -259,7 +259,7 @@ func (in *IPPool) DeepCopyInto(out *IPPool) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPool. @@ -337,6 +337,13 @@ func (in *IPPoolSpec) DeepCopy() *IPPoolSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { *out = *in + if in.Workspaces != nil { + in, out := &in.Workspaces, &out.Workspaces + *out = make(map[string]WorkspaceStatus, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolStatus. @@ -603,3 +610,18 @@ func (in *VLANConfig) DeepCopy() *VLANConfig { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceStatus) DeepCopyInto(out *WorkspaceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceStatus. +func (in *WorkspaceStatus) DeepCopy() *WorkspaceStatus { + if in == nil { + return nil + } + out := new(WorkspaceStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index e056f3ba65b63273e4e89b82a65a26367dd46c9b..d0843cb7c620eba794ee462b57d8c9c8b1b88a3f 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -409,6 +409,7 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error { {Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "groupbindings"}, {Group: "cluster.kubesphere.io", Version: "v1alpha1", Resource: "clusters"}, {Group: "devops.kubesphere.io", Version: "v1alpha3", Resource: "devopsprojects"}, + {Group: "network.kubesphere.io", Version: "v1alpha1", Resource: "ippools"}, } devopsGVRs := []schema.GroupVersionResource{ diff --git a/pkg/apiserver/config/config_test.go b/pkg/apiserver/config/config_test.go index 42445b544007aa04200dd1903a5de6684424081b..4d6c7e1d52892ab022c5d8386575465326fb9302 100644 --- a/pkg/apiserver/config/config_test.go +++ b/pkg/apiserver/config/config_test.go @@ -21,6 +21,7 @@ import ( "github.com/google/go-cmp/cmp" "gopkg.in/yaml.v2" "io/ioutil" + networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authorizationoptions "kubesphere.io/kubesphere/pkg/apiserver/authorization/options" @@ -106,6 +107,7 @@ func newTestConfig() (*Config, error) { AllowedIngressNamespaces: []string{}, }, WeaveScopeHost: "weave-scope-app.weave", + IPPoolType: networkv1alpha1.IPPoolTypeNone, }, MonitoringOptions: &prometheus.Options{ Endpoint: "http://prometheus.kubesphere-monitoring-system.svc", diff --git a/pkg/controller/network/ippool/ippool_controller.go b/pkg/controller/network/ippool/ippool_controller.go index 892e434eebae8d6bb2e3e6c37a8ea21897935441..eecc2fe3abc796094157178a23b3f52a84f7d629 100644 --- a/pkg/controller/network/ippool/ippool_controller.go +++ b/pkg/controller/network/ippool/ippool_controller.go @@ -22,10 +22,12 @@ import ( "time" cnet "github.com/projectcalico/libcalico-go/lib/net" + podv1 "k8s.io/api/core/v1" 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/labels" + "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" @@ -39,6 +41,7 @@ import ( kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned" networkInformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/network/v1alpha1" "kubesphere.io/kubesphere/pkg/controller/network/utils" + "kubesphere.io/kubesphere/pkg/controller/network/webhooks" "kubesphere.io/kubesphere/pkg/simple/client/network/ippool" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -62,8 +65,6 @@ type IPPoolController struct { client clientset.Interface kubesphereClient kubesphereclient.Interface - - options ippool.Options } func (c *IPPoolController) ippoolHandle(obj interface{}) { @@ -112,35 +113,86 @@ func (c *IPPoolController) removeFinalizer(pool *networkv1alpha1.IPPool) error { return nil } -// check cidr overlap -func (c *IPPoolController) checkIPPool(pool *networkv1alpha1.IPPool) (bool, error) { - _, poolCIDR, err := cnet.ParseCIDR(pool.Spec.CIDR) +func (c *IPPoolController) ValidateCreate(obj runtime.Object) error { + b := obj.(*networkv1alpha1.IPPool) + _, cidr, err := cnet.ParseCIDR(b.Spec.CIDR) if err != nil { - return false, err + return fmt.Errorf("invalid cidr") + } + + size, _ := cidr.Mask.Size() + if b.Spec.BlockSize > 0 && b.Spec.BlockSize < size { + return fmt.Errorf("the blocksize should be larger than the cidr mask") + } + + if b.Spec.RangeStart != "" || b.Spec.RangeEnd != "" { + iStart := cnet.ParseIP(b.Spec.RangeStart) + iEnd := cnet.ParseIP(b.Spec.RangeEnd) + if iStart == nil || iEnd == nil { + return fmt.Errorf("invalid rangeStart or rangeEnd") + } + offsetStart, err := b.IPToOrdinal(*iStart) + if err != nil { + return err + } + offsetEnd, err := b.IPToOrdinal(*iEnd) + if err != nil { + return err + } + if offsetEnd < offsetStart { + return fmt.Errorf("rangeStart should not big than rangeEnd") + } } pools, err := c.kubesphereClient.NetworkV1alpha1().IPPools().List(metav1.ListOptions{ LabelSelector: labels.SelectorFromSet(labels.Set{ - networkv1alpha1.IPPoolIDLabel: fmt.Sprintf("%d", pool.ID()), + networkv1alpha1.IPPoolIDLabel: fmt.Sprintf("%d", b.ID()), }).String(), }) - if err != nil { - return false, err + return err } for _, p := range pools.Items { - _, cidr, err := cnet.ParseCIDR(p.Spec.CIDR) - if err != nil { - return false, err + if b.Overlapped(p) { + return fmt.Errorf("ippool cidr is overlapped with %s", p.Name) } + } - if cidr.IsNetOverlap(poolCIDR.IPNet) { - return false, ErrCIDROverlap - } + return nil +} + +func (c *IPPoolController) ValidateUpdate(old runtime.Object, new runtime.Object) error { + oldP := old.(*networkv1alpha1.IPPool) + newP := new.(*networkv1alpha1.IPPool) + + if newP.Spec.CIDR != oldP.Spec.CIDR { + return fmt.Errorf("cidr cannot be modified") } - return true, nil + if newP.Spec.Type != oldP.Spec.Type { + return fmt.Errorf("ippool type cannot be modified") + } + + if newP.Spec.BlockSize != oldP.Spec.BlockSize { + return fmt.Errorf("ippool blockSize cannot be modified") + } + + if newP.Spec.RangeEnd != oldP.Spec.RangeEnd || newP.Spec.RangeStart != oldP.Spec.RangeStart { + return fmt.Errorf("ippool rangeEnd/rangeStart cannot be modified") + } + + return nil +} + +func (c *IPPoolController) ValidateDelete(obj runtime.Object) error { + p := obj.(*networkv1alpha1.IPPool) + + if p.Status.Allocations > 0 { + return fmt.Errorf("ippool is in use, please remove the workload before deleting") + } + + return nil } func (c *IPPoolController) disableIPPool(old *networkv1alpha1.IPPool) error { @@ -159,18 +211,19 @@ func (c *IPPoolController) disableIPPool(old *networkv1alpha1.IPPool) error { func (c *IPPoolController) updateIPPoolStatus(old *networkv1alpha1.IPPool) error { new, err := c.provider.GetIPPoolStats(old) if err != nil { - return err + return fmt.Errorf("failed to get ippool %s status %v", old.Name, err) } if reflect.DeepEqual(old.Status, new.Status) { return nil } - clone := old.DeepCopy() - clone.Status = new.Status - old, err = c.kubesphereClient.NetworkV1alpha1().IPPools().Update(clone) + _, err = c.kubesphereClient.NetworkV1alpha1().IPPools().UpdateStatus(new) + if err != nil { + return fmt.Errorf("failed to update ippool %s status %v", old.Name, err) + } - return err + return nil } func (c *IPPoolController) processIPPool(name string) (*time.Duration, error) { @@ -181,10 +234,16 @@ func (c *IPPoolController) processIPPool(name string) (*time.Duration, error) { }() pool, err := c.ippoolInformer.Lister().Get(name) - if apierrors.IsNotFound(err) { + if err != nil { + if apierrors.IsNotFound(err) { + return nil, nil + } + return nil, fmt.Errorf("failed to get ippool %s: %v", name, err) + } + + if pool.Type() != c.provider.Type() { + klog.V(4).Infof("pool %s type not match, ignored", pool.Name) return nil, nil - } else if err != nil { - return nil, err } if utils.IsDeletionCandidate(pool, networkv1alpha1.IPPoolFinalizer) { @@ -199,6 +258,7 @@ func (c *IPPoolController) processIPPool(name string) (*time.Duration, error) { if err != nil { return nil, err } + if canDelete { return nil, c.removeFinalizer(pool) } @@ -209,14 +269,6 @@ func (c *IPPoolController) processIPPool(name string) (*time.Duration, error) { } if utils.NeedToAddFinalizer(pool, networkv1alpha1.IPPoolFinalizer) { - valid, err := c.checkIPPool(pool) - if err != nil { - return nil, err - } - if !valid { - return nil, nil - } - err = c.addFinalizer(pool) if err != nil { return nil, err @@ -310,7 +362,6 @@ func NewIPPoolController( ipamblockInformer networkInformer.IPAMBlockInformer, client clientset.Interface, kubesphereClient kubesphereclient.Interface, - options ippool.Options, provider ippool.Provider) *IPPoolController { broadcaster := record.NewBroadcaster() @@ -318,7 +369,7 @@ func NewIPPoolController( klog.Info(fmt.Sprintf(format, args)) }) broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.CoreV1().Events("")}) - recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "cluster-controller"}) + recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ippool-controller"}) c := &IPPoolController{ eventBroadcaster: broadcaster, @@ -330,7 +381,6 @@ func NewIPPoolController( ipamblockSynced: ipamblockInformer.Informer().HasSynced, client: client, kubesphereClient: kubesphereClient, - options: options, provider: provider, } @@ -350,5 +400,11 @@ func NewIPPoolController( DeleteFunc: c.ipamblockHandle, }) + //register ippool webhook + webhooks.RegisterValidator(networkv1alpha1.SchemeGroupVersion.WithKind(networkv1alpha1.ResourceKindIPPool).String(), + &webhooks.ValidatorWrap{Obj: &networkv1alpha1.IPPool{}, Helper: c}) + webhooks.RegisterDefaulter(podv1.SchemeGroupVersion.WithKind("Pod").String(), + &webhooks.DefaulterWrap{Obj: &podv1.Pod{}, Helper: provider}) + return c } diff --git a/pkg/controller/network/ippool/ippool_controller_test.go b/pkg/controller/network/ippool/ippool_controller_test.go index b04c7e931032d2c09a4a76667e6514fda22dec85..8522bf7fc697767946b6b82836b26a80124cb797 100644 --- a/pkg/controller/network/ippool/ippool_controller_test.go +++ b/pkg/controller/network/ippool/ippool_controller_test.go @@ -18,6 +18,9 @@ package ippool import ( "flag" + "testing" + "time" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,7 +32,6 @@ import ( "kubesphere.io/kubesphere/pkg/controller/network/utils" "kubesphere.io/kubesphere/pkg/simple/client/network/ippool" "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/ipam" - "testing" ) func TestIPPoolSuit(t *testing.T) { @@ -49,31 +51,52 @@ var _ = Describe("test ippool", func() { Name: "testippool", }, Spec: v1alpha1.IPPoolSpec{ - Type: v1alpha1.VLAN, - CIDR: "192.168.0.0/24", - BlockSize: 24, + Type: v1alpha1.VLAN, + CIDR: "192.168.0.0/24", }, Status: v1alpha1.IPPoolStatus{}, } ksclient := ksfake.NewSimpleClientset() k8sclinet := k8sfake.NewSimpleClientset() - options := ippool.Options{} - p := ippool.NewProvider(ksclient, options) + p := ippool.NewProvider(nil, ksclient, k8sclinet, v1alpha1.IPPoolTypeLocal, nil) ipamClient := ipam.NewIPAMClient(ksclient, v1alpha1.VLAN) ksInformer := ksinformers.NewSharedInformerFactory(ksclient, 0) ippoolInformer := ksInformer.Network().V1alpha1().IPPools() ipamblockInformer := ksInformer.Network().V1alpha1().IPAMBlocks() - c := NewIPPoolController(ippoolInformer, ipamblockInformer, k8sclinet, ksclient, options, p) + c := NewIPPoolController(ippoolInformer, ipamblockInformer, k8sclinet, ksclient, p) stopCh := make(chan struct{}) go ksInformer.Start(stopCh) go c.Start(stopCh) It("test create ippool", func() { - _, err := ksclient.NetworkV1alpha1().IPPools().Create(pool) + clone := pool.DeepCopy() + clone.Spec.CIDR = "testxxx" + Expect(c.ValidateCreate(clone)).Should(HaveOccurred()) + + clone = pool.DeepCopy() + clone.Spec.CIDR = "192.168.0.0/24" + clone.Spec.RangeStart = "192.168.0.100" + clone.Spec.RangeEnd = "192.168.0.99" + Expect(c.ValidateCreate(clone)).Should(HaveOccurred()) + + clone = pool.DeepCopy() + clone.Spec.CIDR = "192.168.0.0/24" + clone.Spec.RangeStart = "192.168.3.100" + clone.Spec.RangeEnd = "192.168.3.111" + Expect(c.ValidateCreate(clone)).Should(HaveOccurred()) + + clone = pool.DeepCopy() + clone.Spec.CIDR = "192.168.0.0/24" + clone.Spec.BlockSize = 23 + Expect(c.ValidateCreate(clone)).Should(HaveOccurred()) + + clone = pool.DeepCopy() + _, err := ksclient.NetworkV1alpha1().IPPools().Create(clone) Expect(err).ShouldNot(HaveOccurred()) + Eventually(func() bool { result, _ := ksclient.NetworkV1alpha1().IPPools().Get(pool.Name, v1.GetOptions{}) if len(result.Labels) != 3 { @@ -85,7 +108,17 @@ var _ = Describe("test ippool", func() { } return true - }).Should(Equal(true)) + }, 3*time.Second).Should(Equal(true)) + + clone = pool.DeepCopy() + Expect(c.ValidateCreate(clone)).Should(HaveOccurred()) + }) + + It("test update ippool", func() { + old, _ := ksclient.NetworkV1alpha1().IPPools().Get(pool.Name, v1.GetOptions{}) + new := old.DeepCopy() + new.Spec.CIDR = "192.168.1.0/24" + Expect(c.ValidateUpdate(old, new)).Should(HaveOccurred()) }) It("test ippool stats", func() { @@ -102,10 +135,13 @@ var _ = Describe("test ippool", func() { } return true - }).Should(Equal(true)) + }, 3*time.Second).Should(Equal(true)) }) It("test delete pool", func() { + result, _ := ksclient.NetworkV1alpha1().IPPools().Get(pool.Name, v1.GetOptions{}) + Expect(c.ValidateDelete(result)).Should(HaveOccurred()) + ipamClient.ReleaseByHandle("testhandle") Eventually(func() bool { result, _ := ksclient.NetworkV1alpha1().IPPools().Get(pool.Name, v1.GetOptions{}) @@ -114,7 +150,7 @@ var _ = Describe("test ippool", func() { } return true - }).Should(Equal(true)) + }, 3*time.Second).Should(Equal(true)) err := ksclient.NetworkV1alpha1().IPPools().Delete(pool.Name, &v1.DeleteOptions{}) Expect(err).ShouldNot(HaveOccurred()) diff --git a/pkg/controller/network/nsnetworkpolicy/nsnetworkpolicy_controller.go b/pkg/controller/network/nsnetworkpolicy/nsnetworkpolicy_controller.go index 4308a2b5aeea32354e541c53e324e3250436af8b..f32261d04a6f7fae002be0d7d84b9a6a3a657920 100644 --- a/pkg/controller/network/nsnetworkpolicy/nsnetworkpolicy_controller.go +++ b/pkg/controller/network/nsnetworkpolicy/nsnetworkpolicy_controller.go @@ -18,7 +18,6 @@ package nsnetworkpolicy import ( "fmt" - "kubesphere.io/kubesphere/pkg/controller/network/types" "net" "sort" "strings" @@ -62,7 +61,7 @@ const ( NodeNSNPAnnotationKey = "kubesphere.io/snat-node-ips" - AnnotationNPNAME = types.NSNPPrefix + "network-isolate" + AnnotationNPNAME = v1alpha1.NSNPPrefix + "network-isolate" //TODO: configure it DNSLocalIP = "169.254.25.10" @@ -222,7 +221,7 @@ func (c *NSNetworkPolicyController) convertPeer(peer v1alpha1.NetworkPolicyPeer, func (c *NSNetworkPolicyController) convertToK8sNP(n *v1alpha1.NamespaceNetworkPolicy) (*netv1.NetworkPolicy, error) { np := &netv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ - Name: types.NSNPPrefix + n.Name, + Name: v1alpha1.NSNPPrefix + n.Name, Namespace: n.Namespace, }, Spec: netv1.NetworkPolicySpec{ @@ -564,7 +563,7 @@ func (c *NSNetworkPolicyController) syncNSNP(key string) error { if err != nil { if errors.IsNotFound(err) { klog.V(4).Infof("NSNP %v has been deleted", key) - c.provider.Delete(c.provider.GetKey(types.NSNPPrefix+name, namespace)) + c.provider.Delete(c.provider.GetKey(v1alpha1.NSNPPrefix+name, namespace)) return nil } diff --git a/pkg/controller/network/nsnetworkpolicy/provider/ns_k8s.go b/pkg/controller/network/nsnetworkpolicy/provider/ns_k8s.go index a22b00cd4eb34e276fda467374dc159a91d404a1..f9c1294f58fe242ab37e2eff7bdff4cc8ec95e30 100644 --- a/pkg/controller/network/nsnetworkpolicy/provider/ns_k8s.go +++ b/pkg/controller/network/nsnetworkpolicy/provider/ns_k8s.go @@ -19,7 +19,6 @@ package provider import ( "context" "fmt" - "kubesphere.io/kubesphere/pkg/controller/network/types" "reflect" "strings" "sync" @@ -36,6 +35,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/klog" + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" ) const ( @@ -246,7 +246,7 @@ func NewNsNetworkPolicyProvider(client kubernetes.Interface, npInformer informer // Filter in only objects that are written by policy controller. m := make(map[string]interface{}) for _, policy := range policies { - if strings.HasPrefix(policy.Name, types.NSNPPrefix) { + if strings.HasPrefix(policy.Name, v1alpha1.NSNPPrefix) { policy.ObjectMeta = metav1.ObjectMeta{Name: policy.Name, Namespace: policy.Namespace} k := c.GetKey(policy.Name, policy.Namespace) m[k] = *policy diff --git a/pkg/controller/network/nsnetworkpolicy/webhook.go b/pkg/controller/network/nsnetworkpolicy/webhook.go deleted file mode 100644 index b5c8788df54dfd6a6f4fcc1004e6dbbdcd844ea4..0000000000000000000000000000000000000000 --- a/pkg/controller/network/nsnetworkpolicy/webhook.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright 2020 KubeSphere 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 nsnetworkpolicy - -import ( - "context" - corev1 "k8s.io/api/core/v1" - k8snet "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" - "net" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -type NSNPValidator struct { - Client client.Client - decoder *admission.Decoder -} - -func (v *NSNPValidator) Handle(ctx context.Context, req admission.Request) admission.Response { - nsnp := &networkv1alpha1.NamespaceNetworkPolicy{} - - err := v.decoder.Decode(req, nsnp) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - allErrs := field.ErrorList{} - allErrs = append(allErrs, v.ValidateNSNPSpec(&nsnp.Spec, field.NewPath("spec"))...) - - if len(allErrs) != 0 { - return admission.Denied(allErrs.ToAggregate().Error()) - } - - return admission.Allowed("") -} - -func (v *NSNPValidator) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} - -// ValidateNetworkPolicyPort validates a NetworkPolicyPort -func (v *NSNPValidator) ValidateNetworkPolicyPort(port *k8snet.NetworkPolicyPort, portPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if port.Protocol != nil && *port.Protocol != corev1.ProtocolTCP && *port.Protocol != corev1.ProtocolUDP && *port.Protocol != corev1.ProtocolSCTP { - allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(corev1.ProtocolTCP), string(corev1.ProtocolUDP), string(corev1.ProtocolSCTP)})) - } - if port.Port != nil { - if port.Port.Type == intstr.Int { - for _, msg := range validation.IsValidPortNum(int(port.Port.IntVal)) { - allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.IntVal, msg)) - } - } else { - for _, msg := range validation.IsValidPortName(port.Port.StrVal) { - allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.StrVal, msg)) - } - } - } - - return allErrs -} - -func (v *NSNPValidator) ValidateServiceSelector(serviceSelector *networkv1alpha1.ServiceSelector, fldPath *field.Path) field.ErrorList { - service := &corev1.Service{} - allErrs := field.ErrorList{} - - err := v.Client.Get(context.TODO(), client.ObjectKey{Namespace: serviceSelector.Namespace, Name: serviceSelector.Name}, service) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath, serviceSelector, "cannot get service")) - return allErrs - } - - if len(service.Spec.Selector) <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath, serviceSelector, "service should have selector")) - } - - return allErrs -} - -// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR -func ValidateCIDR(cidr string) (*net.IPNet, error) { - _, net, err := net.ParseCIDR(cidr) - if err != nil { - return nil, err - } - return net, nil -} - -// ValidateIPBlock validates a cidr and the except fields of an IpBlock NetworkPolicyPeer -func (v *NSNPValidator) ValidateIPBlock(ipb *k8snet.IPBlock, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if len(ipb.CIDR) == 0 || ipb.CIDR == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "")) - return allErrs - } - cidrIPNet, err := ValidateCIDR(ipb.CIDR) - if err != nil { - allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), ipb.CIDR, "not a valid CIDR")) - return allErrs - } - exceptCIDR := ipb.Except - for i, exceptIP := range exceptCIDR { - exceptPath := fldPath.Child("except").Index(i) - exceptCIDR, err := ValidateCIDR(exceptIP) - if err != nil { - allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "not a valid CIDR")) - return allErrs - } - if !cidrIPNet.Contains(exceptCIDR.IP) { - allErrs = append(allErrs, field.Invalid(exceptPath, exceptCIDR.IP, "not within CIDR range")) - } - } - return allErrs -} - -// ValidateNSNPPeer validates a NetworkPolicyPeer -func (v *NSNPValidator) ValidateNSNPPeer(peer *networkv1alpha1.NetworkPolicyPeer, peerPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - numPeers := 0 - - if peer.ServiceSelector != nil { - numPeers++ - allErrs = append(allErrs, v.ValidateServiceSelector(peer.ServiceSelector, peerPath.Child("service"))...) - } - if peer.NamespaceSelector != nil { - numPeers++ - } - if peer.IPBlock != nil { - numPeers++ - allErrs = append(allErrs, v.ValidateIPBlock(peer.IPBlock, peerPath.Child("ipBlock"))...) - } - - if numPeers == 0 { - allErrs = append(allErrs, field.Required(peerPath, "must specify a peer")) - } else if numPeers > 1 && peer.IPBlock != nil { - allErrs = append(allErrs, field.Forbidden(peerPath, "may not specify both ipBlock and another peer")) - } - - return allErrs -} - -func (v *NSNPValidator) ValidateNSNPSpec(spec *networkv1alpha1.NamespaceNetworkPolicySpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - // Validate ingress rules. - for i, ingress := range spec.Ingress { - ingressPath := fldPath.Child("ingress").Index(i) - for i, port := range ingress.Ports { - portPath := ingressPath.Child("ports").Index(i) - allErrs = append(allErrs, v.ValidateNetworkPolicyPort(&port, portPath)...) - } - for i, from := range ingress.From { - fromPath := ingressPath.Child("from").Index(i) - allErrs = append(allErrs, v.ValidateNSNPPeer(&from, fromPath)...) - } - } - // Validate egress rules - for i, egress := range spec.Egress { - egressPath := fldPath.Child("egress").Index(i) - for i, port := range egress.Ports { - portPath := egressPath.Child("ports").Index(i) - allErrs = append(allErrs, v.ValidateNetworkPolicyPort(&port, portPath)...) - } - for i, to := range egress.To { - toPath := egressPath.Child("to").Index(i) - allErrs = append(allErrs, v.ValidateNSNPPeer(&to, toPath)...) - } - } - - return allErrs -} diff --git a/pkg/controller/network/types/types.go b/pkg/controller/network/types/types.go deleted file mode 100644 index d2e1506935720a7a58164c7d95b1ee4299a0bae1..0000000000000000000000000000000000000000 --- a/pkg/controller/network/types/types.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2020 KubeSphere 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 types - -const ( - NSNPPrefix = "nsnp-" -) diff --git a/pkg/controller/network/webhooks/default.go b/pkg/controller/network/webhooks/default.go new file mode 100644 index 0000000000000000000000000000000000000000..57abba1556d1243560718775c6c11c1ea458985b --- /dev/null +++ b/pkg/controller/network/webhooks/default.go @@ -0,0 +1,97 @@ +package webhooks + +import ( + "context" + "encoding/json" + "net/http" + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// Defaulter defines functions for setting defaults on resources +type Defaulter interface { + Default(obj runtime.Object) error +} + +type DefaulterWrap struct { + Obj runtime.Object + Helper Defaulter +} + +type MutatingHandler struct { + C client.Client + decoder *admission.Decoder +} + +var _ admission.DecoderInjector = &MutatingHandler{} + +// InjectDecoder injects the decoder into a MutatingHandler. +func (h *MutatingHandler) InjectDecoder(d *admission.Decoder) error { + h.decoder = d + return nil +} + +type defaulters struct { + ds map[string]*DefaulterWrap + lock sync.RWMutex +} + +var ( + ds defaulters +) + +func init() { + ds = defaulters{ + ds: make(map[string]*DefaulterWrap), + lock: sync.RWMutex{}, + } +} + +func RegisterDefaulter(name string, d *DefaulterWrap) { + ds.lock.Lock() + defer ds.lock.Unlock() + + ds.ds[name] = d +} + +func UnRegisterDefaulter(name string) { + ds.lock.Lock() + defer ds.lock.Unlock() + + delete(ds.ds, name) +} + +func GetDefaulter(name string) *DefaulterWrap { + ds.lock.Lock() + defer ds.lock.Unlock() + + return ds.ds[name] +} + +// Handle handles admission requests. +func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { + defaulter := GetDefaulter(req.Kind.String()) + if defaulter == nil { + return admission.Denied("crd has webhook configured, but the controller does not register the corresponding processing logic and refuses the operation by default.") + } + + // Get the object in the request + obj := defaulter.Obj.DeepCopyObject() + err := h.decoder.Decode(req, obj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + // Default the object + defaulter.Helper.Default(obj) + marshalled, err := json.Marshal(obj) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + // Create the patch + return admission.PatchResponseFromRaw(req.Object.Raw, marshalled) +} diff --git a/pkg/controller/network/webhooks/validator.go b/pkg/controller/network/webhooks/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..4c5df2e8b5a1e80ea9f3f31bce14cc55e448ad50 --- /dev/null +++ b/pkg/controller/network/webhooks/validator.go @@ -0,0 +1,146 @@ +/* +Copyright 2020 KubeSphere 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 webhooks + +import ( + "context" + "net/http" + "sync" + + "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// Validator defines functions for validating an operation +type Validator interface { + ValidateCreate(obj runtime.Object) error + ValidateUpdate(old runtime.Object, new runtime.Object) error + ValidateDelete(obj runtime.Object) error +} + +type ValidatorWrap struct { + Obj runtime.Object + Helper Validator +} + +type validators struct { + vs map[string]*ValidatorWrap + lock sync.RWMutex +} + +var ( + vs validators +) + +func init() { + vs = validators{ + vs: make(map[string]*ValidatorWrap), + lock: sync.RWMutex{}, + } +} + +func RegisterValidator(name string, v *ValidatorWrap) { + vs.lock.Lock() + defer vs.lock.Unlock() + + vs.vs[name] = v +} + +func UnRegisterValidator(name string) { + vs.lock.Lock() + defer vs.lock.Unlock() + + delete(vs.vs, name) +} + +func GetValidator(name string) *ValidatorWrap { + vs.lock.Lock() + defer vs.lock.Unlock() + + return vs.vs[name] +} + +type ValidatingHandler struct { + C client.Client + decoder *admission.Decoder +} + +var _ admission.DecoderInjector = &ValidatingHandler{} + +// InjectDecoder injects the decoder into a ValidatingHandler. +func (h *ValidatingHandler) InjectDecoder(d *admission.Decoder) error { + h.decoder = d + return nil +} + +// Handle handles admission requests. +func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response { + validator := GetValidator(req.Kind.String()) + if validator == nil { + return admission.Denied("crd has webhook configured, but the controller does not register the corresponding processing logic and refuses the operation by default.") + } + + // Get the object in the request + obj := validator.Obj.DeepCopyObject() + if req.Operation == v1beta1.Create { + err := h.decoder.Decode(req, obj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + err = validator.Helper.ValidateCreate(obj) + if err != nil { + return admission.Denied(err.Error()) + } + } + + if req.Operation == v1beta1.Update { + oldObj := obj.DeepCopyObject() + + err := h.decoder.DecodeRaw(req.Object, obj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + err = h.decoder.DecodeRaw(req.OldObject, oldObj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + err = validator.Helper.ValidateUpdate(oldObj, obj) + if err != nil { + return admission.Denied(err.Error()) + } + } + + if req.Operation == v1beta1.Delete { + // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346 + // OldObject contains the object being deleted + err := h.decoder.DecodeRaw(req.OldObject, obj) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + err = validator.Helper.ValidateDelete(obj) + if err != nil { + return admission.Denied(err.Error()) + } + } + + return admission.Allowed("") +} diff --git a/pkg/models/resources/v1alpha3/ippool/ippools.go b/pkg/models/resources/v1alpha3/ippool/ippools.go new file mode 100644 index 0000000000000000000000000000000000000000..138ac14f43bed8f931b1f26873acc28343b18aa7 --- /dev/null +++ b/pkg/models/resources/v1alpha3/ippool/ippools.go @@ -0,0 +1,115 @@ +/* +Copyright 2020 The KubeSphere 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 ippool + +import ( + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + k8sinformers "k8s.io/client-go/informers" + "kubesphere.io/kubesphere/pkg/api" + networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" + "kubesphere.io/kubesphere/pkg/apiserver/query" + informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3" +) + +type ippoolGetter struct { + informers informers.SharedInformerFactory + k8sInformers k8sinformers.SharedInformerFactory +} + +func New(informers informers.SharedInformerFactory, k8sInformers k8sinformers.SharedInformerFactory) v1alpha3.Interface { + return &ippoolGetter{ + informers: informers, + k8sInformers: k8sInformers, + } +} + +func (n ippoolGetter) Get(namespace, name string) (runtime.Object, error) { + return n.informers.Network().V1alpha1().IPPools().Lister().Get(name) +} + +func (n ippoolGetter) List(namespace string, query *query.Query) (*api.ListResult, error) { + var result []runtime.Object + + if namespace != "" { + workspace := "" + ns, err := n.k8sInformers.Core().V1().Namespaces().Lister().Get(namespace) + if err != nil { + return nil, err + } + if ns.Labels != nil { + workspace = ns.Labels[constants.WorkspaceLabelKey] + } + ps, err := n.informers.Network().V1alpha1().IPPools().Lister().List(labels.SelectorFromSet( + map[string]string{ + networkv1alpha1.IPPoolDefaultLabel: "", + })) + if err != nil { + return nil, err + } + for _, p := range ps { + result = append(result, p) + } + if workspace != "" { + query.LabelSelector = labels.SelectorFromSet( + map[string]string{ + constants.WorkspaceLabelKey: workspace, + }).String() + ps, err := n.informers.Network().V1alpha1().IPPools().Lister().List(query.Selector()) + if err != nil { + return nil, err + } + for _, p := range ps { + result = append(result, p) + } + } + } else { + ps, err := n.informers.Network().V1alpha1().IPPools().Lister().List(labels.Everything()) + if err != nil { + return nil, err + } + for _, p := range ps { + result = append(result, p) + } + } + + return v1alpha3.DefaultList(result, query, n.compare, n.filter), nil +} + +func (n ippoolGetter) filter(item runtime.Object, filter query.Filter) bool { + p, ok := item.(*networkv1alpha1.IPPool) + if !ok { + return false + } + + return v1alpha3.DefaultObjectMetaFilter(p.ObjectMeta, filter) +} + +func (n ippoolGetter) compare(left runtime.Object, right runtime.Object, field query.Field) bool { + leftP, ok := left.(*networkv1alpha1.IPPool) + if !ok { + return false + } + + rightP, ok := right.(*networkv1alpha1.IPPool) + if !ok { + return true + } + return v1alpha3.DefaultObjectMetaCompare(leftP.ObjectMeta, rightP.ObjectMeta, field) +} diff --git a/pkg/models/resources/v1alpha3/ippool/ippools_test.go b/pkg/models/resources/v1alpha3/ippool/ippools_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4ae689ce2c31150c87e99b1ce6b1c8f2325fad91 --- /dev/null +++ b/pkg/models/resources/v1alpha3/ippool/ippools_test.go @@ -0,0 +1,149 @@ +/* +Copyright 2019 The KubeSphere 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 ippool + +import ( + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sinformers "k8s.io/client-go/informers" + k8sfake "k8s.io/client-go/kubernetes/fake" + "kubesphere.io/kubesphere/pkg/api" + "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" + tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" + "kubesphere.io/kubesphere/pkg/apiserver/query" + "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" + informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3" + "testing" +) + +func TestListIPPools(t *testing.T) { + tests := []struct { + description string + namespace string + query *query.Query + expected *api.ListResult + expectedErr error + }{ + { + "test name filter", + "", + &query.Query{ + Pagination: &query.Pagination{ + Limit: 10, + Offset: 0, + }, + SortBy: query.FieldName, + Ascending: false, + Filters: map[query.Field]query.Value{ + query.FieldName: query.Value("foo2"), + }, + }, + &api.ListResult{ + Items: []interface{}{foo2}, + TotalItems: 1, + }, + nil, + }, + { + "test namespace filter", + "ns1", + &query.Query{ + Pagination: &query.Pagination{ + Limit: 10, + Offset: 0, + }, + SortBy: query.FieldName, + Ascending: false, + }, + &api.ListResult{ + Items: []interface{}{foo1}, + TotalItems: 1, + }, + nil, + }, + } + + getter := prepare() + + for _, test := range tests { + got, err := getter.List(test.namespace, test.query) + if test.expectedErr != nil && err != test.expectedErr { + t.Errorf("expected error, got nothing") + } else if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(got, test.expected); diff != "" { + t.Errorf("%T differ (-got, +want): %s", test.expected, diff) + } + } +} + +var ( + foo1 = &v1alpha1.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo1", + Labels: map[string]string{ + constants.WorkspaceLabelKey: "wk1", + }, + }, + } + foo2 = &v1alpha1.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo2", + }, + } + foo3 = &v1alpha1.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo3", + }, + } + ns = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns1", + Labels: map[string]string{ + constants.WorkspaceLabelKey: "wk1", + }, + }, + } + wk = &tenantv1alpha1.Workspace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "wk1", + }, + } + ps = []interface{}{foo1, foo2, foo3} +) + +func prepare() v1alpha3.Interface { + + client := fake.NewSimpleClientset() + k8sClient := k8sfake.NewSimpleClientset() + informer := informers.NewSharedInformerFactory(client, 0) + k8sInformer := k8sinformers.NewSharedInformerFactory(k8sClient, 0) + + for _, p := range ps { + informer.Network().V1alpha1().IPPools().Informer().GetIndexer().Add(p) + } + + informer.Tenant().V1alpha1().Workspaces().Informer().GetIndexer().Add(wk) + k8sInformer.Core().V1().Namespaces().Informer().GetIndexer().Add(ns) + + return New(informer, k8sInformer) +} diff --git a/pkg/models/resources/v1alpha3/resource/resource.go b/pkg/models/resources/v1alpha3/resource/resource.go index b3d5e25c8e9c6ba32138904521c8be8b54bec71d..284119da8e5301ea67cc3c5adccd19cc6954a398 100644 --- a/pkg/models/resources/v1alpha3/resource/resource.go +++ b/pkg/models/resources/v1alpha3/resource/resource.go @@ -26,6 +26,7 @@ import ( "kubesphere.io/kubesphere/pkg/api" devopsv1alpha3 "kubesphere.io/kubesphere/pkg/apis/devops/v1alpha3" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" + networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" tenantv1alpha2 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha2" typesv1beta1 "kubesphere.io/kubesphere/pkg/apis/types/v1beta1" @@ -55,6 +56,7 @@ import ( "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/group" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/groupbinding" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/ingress" + "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/ippool" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/job" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/loginrecord" "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/namespace" @@ -99,6 +101,7 @@ func NewResourceGetter(factory informers.InformerFactory) *ResourceGetter { // kubesphere resources getters[devopsv1alpha3.SchemeGroupVersion.WithResource(devopsv1alpha3.ResourcePluralDevOpsProject)] = devops.New(factory.KubeSphereSharedInformerFactory()) getters[tenantv1alpha1.SchemeGroupVersion.WithResource(tenantv1alpha1.ResourcePluralWorkspace)] = workspace.New(factory.KubeSphereSharedInformerFactory()) + getters[networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourcePluralIPPool)] = ippool.New(factory.KubeSphereSharedInformerFactory(), factory.KubernetesSharedInformerFactory()) getters[tenantv1alpha1.SchemeGroupVersion.WithResource(tenantv1alpha2.ResourcePluralWorkspaceTemplate)] = workspacetemplate.New(factory.KubeSphereSharedInformerFactory()) getters[iamv1alpha2.SchemeGroupVersion.WithResource(iamv1alpha2.ResourcesPluralGlobalRole)] = globalrole.New(factory.KubeSphereSharedInformerFactory()) getters[iamv1alpha2.SchemeGroupVersion.WithResource(iamv1alpha2.ResourcesPluralWorkspaceRole)] = workspacerole.New(factory.KubeSphereSharedInformerFactory()) diff --git a/pkg/simple/client/network/ippool/calico/provider.go b/pkg/simple/client/network/ippool/calico/provider.go index 38d5dfc7307fbb9b742050977e77f58f73b0f887..2d41e8a415c6dba25a4dc37ab4bc9953a9299b7a 100644 --- a/pkg/simple/client/network/ippool/calico/provider.go +++ b/pkg/simple/client/network/ippool/calico/provider.go @@ -17,31 +17,64 @@ limitations under the License. package calico import ( + "encoding/json" "errors" "fmt" + "net" + "time" v3 "github.com/projectcalico/libcalico-go/lib/apis/v3" "github.com/projectcalico/libcalico-go/lib/backend/model" cnet "github.com/projectcalico/libcalico-go/lib/net" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/watch" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + informercorev1 "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/retry" "k8s.io/client-go/util/workqueue" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/apis/network/calicov3" "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme" + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/simple/client/k8s" calicoset "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico/client/clientset/versioned" + calicoInformer "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico/client/informers/externalversions" + blockInformer "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico/client/informers/externalversions/network/calicov3" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) const ( - CalicoNamespaceAnnotationIPPoolV4 = "cni.projectcalico.org/ipv4pools" - CalicoNamespaceAnnotationIPPoolV6 = "cni.projectcalico.org/ipv6pools" - CalicoPodAnnotationIPAddr = "cni.projectcalico.org/ipAddrs" + CalicoAnnotationIPPoolV4 = "cni.projectcalico.org/ipv4pools" + CalicoAnnotationIPPoolV6 = "cni.projectcalico.org/ipv6pools" + CalicoPodAnnotationIPAddr = "cni.projectcalico.org/ipAddrs" + CalicoPodAnnotationPodIP = "cni.projectcalico.org/podIP" + + // Common attributes which may be set on allocations by clients. + IPAMBlockAttributePod = "pod" + IPAMBlockAttributeNamespace = "namespace" + IPAMBlockAttributeNode = "node" + IPAMBlockAttributeType = "type" + IPAMBlockAttributeTypeIPIP = "ipipTunnelAddress" + IPAMBlockAttributeTypeVXLAN = "vxlanTunnelAddress" + + CALICO_IPV4POOL_IPIP = "CALICO_IPV4POOL_IPIP" + CALICO_IPV4POOL_VXLAN = "CALICO_IPV4POOL_VXLAN" + CALICO_IPV4POOL_NAT_OUTGOING = "CALICO_IPV4POOL_NAT_OUTGOING" + CalicoNodeDaemonset = "calico-node" + CalicoNodeNamespace = "kube-system" + + DefaultBlockSize = 25 + // default re-sync period for all informer factories + defaultResync = 600 * time.Second ) var ( @@ -49,9 +82,15 @@ var ( ) type provider struct { - client calicoset.Interface - ksclient kubesphereclient.Interface - options Options + client calicoset.Interface + ksclient kubesphereclient.Interface + k8sclient clientset.Interface + pods informercorev1.PodInformer + block blockInformer.IPAMBlockInformer + queue workqueue.RateLimitingInterface + poolQueue workqueue.RateLimitingInterface + + options Options } func (c provider) CreateIPPool(pool *v1alpha1.IPPool) error { @@ -70,6 +109,12 @@ func (c provider) CreateIPPool(pool *v1alpha1.IPPool) error { }, } + _, cidr, _ := net.ParseCIDR(pool.Spec.CIDR) + size, _ := cidr.Mask.Size() + if size > DefaultBlockSize { + calicoPool.Spec.BlockSize = size + } + err := controllerutil.SetControllerReference(pool, calicoPool, scheme.Scheme) if err != nil { klog.Warningf("cannot set reference for calico ippool %s, err=%v", pool.Name, err) @@ -88,7 +133,7 @@ func (c provider) UpdateIPPool(pool *v1alpha1.IPPool) error { } func (c provider) GetIPPoolStats(pool *v1alpha1.IPPool) (*v1alpha1.IPPool, error) { - stats := &v1alpha1.IPPool{} + stats := pool.DeepCopy() calicoPool, err := c.client.CrdCalicov3().IPPools().Get(pool.Name, v1.GetOptions{}) if err != nil { @@ -100,24 +145,46 @@ func (c provider) GetIPPoolStats(pool *v1alpha1.IPPool) (*v1alpha1.IPPool, error return nil, err } - stats.Status.Capacity = pool.NumAddresses() - stats.Status.Reserved = 0 - stats.Status.Unallocated = 0 + if stats.Status.Capacity == 0 { + stats.Status.Capacity = pool.NumAddresses() + } stats.Status.Synced = true stats.Status.Allocations = 0 + stats.Status.Reserved = 0 + if stats.Status.Workspaces == nil { + stats.Status.Workspaces = make(map[string]v1alpha1.WorkspaceStatus) + } if len(blocks) <= 0 { stats.Status.Unallocated = pool.NumAddresses() stats.Status.Allocations = 0 - return stats, nil + } else { + for _, block := range blocks { + stats.Status.Allocations += block.NumAddresses() - block.NumFreeAddresses() - block.NumReservedAddresses() + stats.Status.Reserved += block.NumReservedAddresses() + } + + stats.Status.Unallocated = stats.Status.Capacity - stats.Status.Allocations - stats.Status.Reserved } - for _, block := range blocks { - stats.Status.Allocations += block.NumAddresses() - block.NumFreeAddresses() - block.NumReservedAddresses() - stats.Status.Reserved += block.NumReservedAddresses() + wks, err := c.getAssociatedWorkspaces(pool) + if err != nil { + return nil, err + } + + for _, wk := range wks { + status, err := c.getWorkspaceStatus(wk, pool.GetName()) + if err != nil { + return nil, err + } + stats.Status.Workspaces[wk] = *status } - stats.Status.Unallocated = stats.Status.Capacity - stats.Status.Allocations - stats.Status.Reserved + for name, wk := range stats.Status.Workspaces { + if wk.Allocations == 0 { + delete(stats.Status.Workspaces, name) + } + } return stats, nil } @@ -240,6 +307,9 @@ func (c provider) DeleteIPPool(pool *v1alpha1.IPPool) (bool, error) { // Get the pool so that we can find the CIDR associated with it. calicoPool, err := c.client.CrdCalicov3().IPPools().Get(pool.Name, v1.GetOptions{}) if err != nil { + if k8serrors.IsNotFound(err) { + return true, nil + } return false, err } @@ -318,6 +388,9 @@ func (c provider) syncIPPools() error { TypeMeta: v1.TypeMeta{}, ObjectMeta: v1.ObjectMeta{ Name: calicoPool.Name, + Labels: map[string]string{ + v1alpha1.IPPoolDefaultLabel: "", + }, }, Spec: v1alpha1.IPPoolSpec{ Type: v1alpha1.Calico, @@ -339,57 +412,233 @@ func (c provider) syncIPPools() error { return nil } -func (c provider) SyncStatus(stopCh <-chan struct{}, q workqueue.RateLimitingInterface) error { - blockWatch, err := c.client.CrdCalicov3().IPAMBlocks().Watch(v1.ListOptions{}) +func (p provider) getAssociatedWorkspaces(pool *v1alpha1.IPPool) ([]string, error) { + var result []string + + poolLabel := constants.WorkspaceLabelKey + if pool.GetLabels() == nil || pool.GetLabels()[poolLabel] == "" { + wks, err := p.ksclient.TenantV1alpha1().Workspaces().List(v1.ListOptions{}) + if err != nil { + return nil, err + } + + for _, wk := range wks.Items { + result = append(result, wk.GetName()) + } + + return result, nil + } + + return append(result, pool.GetLabels()[poolLabel]), nil +} + +func (p provider) getWorkspaceStatus(name string, poolName string) (*v1alpha1.WorkspaceStatus, error) { + var result v1alpha1.WorkspaceStatus + + namespaces, err := p.k8sclient.CoreV1().Namespaces().List(v1.ListOptions{ + LabelSelector: labels.SelectorFromSet( + map[string]string{ + constants.WorkspaceLabelKey: name, + }, + ).String(), + }) if err != nil { - return err + return nil, err } - ch := blockWatch.ResultChan() - defer blockWatch.Stop() + for _, ns := range namespaces.Items { + pods, err := p.k8sclient.CoreV1().Pods(ns.GetName()).List(v1.ListOptions{}) + if err != nil { + return nil, err + } + for _, pod := range pods.Items { + if pod.GetLabels() != nil && pod.GetLabels()[v1alpha1.IPPoolNameLabel] == poolName { + result.Allocations++ + } + } + } + + return &result, nil +} + +func (p provider) Type() string { + return v1alpha1.IPPoolTypeCalico +} + +func (p provider) SyncStatus(stopCh <-chan struct{}, q workqueue.RateLimitingInterface) error { + defer utilruntime.HandleCrash() + defer p.queue.ShutDown() + + klog.Info("starting calico block controller") + defer klog.Info("shutting down calico block controller") + + p.poolQueue = q + go p.block.Informer().Run(stopCh) + + if !cache.WaitForCacheSync(stopCh, p.pods.Informer().HasSynced, p.block.Informer().HasSynced) { + klog.Fatal("failed to wait for caches to sync") + } + + for i := 0; i < 5; i++ { + go wait.Until(p.runWorker, time.Second, stopCh) + } - for { - select { - case <-stopCh: + <-stopCh + return nil +} + +func (p provider) processBlock(name string) error { + block, err := p.block.Lister().Get(name) + if err != nil { + if k8serrors.IsNotFound(err) { return nil - case event, ok := <-ch: - if !ok { - // End of results. - return fmt.Errorf("calico ipamblock watch closed") - } + } + return err + } + _, blockCIDR, _ := cnet.ParseCIDR(block.Spec.CIDR) - if event.Type == watch.Added || event.Type == watch.Deleted || event.Type == watch.Modified { - block := event.Object.(*calicov3.IPAMBlock) - _, blockCIDR, _ := cnet.ParseCIDR(block.Spec.CIDR) + poolName := block.Labels[v1alpha1.IPPoolNameLabel] + if poolName == "" { + pools, err := p.ksclient.NetworkV1alpha1().IPPools().List(v1.ListOptions{}) + if err != nil { + return err + } - if block.Labels[v1alpha1.IPPoolNameLabel] != "" { - q.Add(block.Labels[v1alpha1.IPPoolNameLabel]) - continue - } + for _, pool := range pools.Items { + _, poolCIDR, _ := cnet.ParseCIDR(pool.Spec.CIDR) + if poolCIDR.IsNetOverlap(blockCIDR.IPNet) { + poolName = pool.Name - pools, err := c.ksclient.NetworkV1alpha1().IPPools().List(v1.ListOptions{}) - if err != nil { - continue + block.Labels = map[string]string{ + v1alpha1.IPPoolNameLabel: pool.Name, } + p.client.CrdCalicov3().IPAMBlocks().Update(block) + break + } + } + } - for _, pool := range pools.Items { - _, poolCIDR, _ := cnet.ParseCIDR(pool.Spec.CIDR) - if poolCIDR.IsNetOverlap(blockCIDR.IPNet) { - q.Add(pool.Name) - - block.Labels = map[string]string{ - v1alpha1.IPPoolNameLabel: pool.Name, - } - c.client.CrdCalicov3().IPAMBlocks().Update(block) - break - } + for _, podAttr := range block.Spec.Attributes { + name := podAttr.AttrSecondary[IPAMBlockAttributePod] + namespace := podAttr.AttrSecondary[IPAMBlockAttributeNamespace] + + if name == "" || namespace == "" { + continue + } + + pod, err := p.pods.Lister().Pods(namespace).Get(name) + if err != nil { + continue + } + + labels := pod.GetLabels() + if labels != nil { + poolLabel := labels[v1alpha1.IPPoolNameLabel] + if poolLabel != "" { + continue + } + } + + retry.RetryOnConflict(retry.DefaultBackoff, func() error { + pod, err = p.k8sclient.CoreV1().Pods(namespace).Get(name, v1.GetOptions{}) + if err != nil { + return err + } + + labels := pod.GetLabels() + if labels != nil { + poolLabel := labels[v1alpha1.IPPoolNameLabel] + if poolLabel != "" { + return nil } + } else { + pod.Labels = make(map[string]string) + } + + if pod.GetAnnotations() == nil { + pod.Annotations = make(map[string]string) } + + annostrs, _ := json.Marshal([]string{poolName}) + pod.GetAnnotations()[CalicoAnnotationIPPoolV4] = string(annostrs) + pod.Labels[v1alpha1.IPPoolNameLabel] = poolName + + _, err = p.k8sclient.CoreV1().Pods(namespace).Update(pod) + + return err + }) + } + + p.poolQueue.Add(poolName) + return nil +} + +func (p provider) processBlockItem() bool { + key, quit := p.queue.Get() + if quit { + return false + } + defer p.queue.Done(key) + + err := p.processBlock(key.(string)) + if err == nil { + p.queue.Forget(key) + return true + } + + utilruntime.HandleError(fmt.Errorf("error processing calico block %v (will retry): %v", key, err)) + p.queue.AddRateLimited(key) + return true +} + +func (p provider) runWorker() { + for p.processBlockItem() { + } +} + +func (p provider) addBlock(obj interface{}) { + block, ok := obj.(*calicov3.IPAMBlock) + if !ok { + return + } + + p.queue.Add(block.Name) +} + +func (p provider) Default(obj runtime.Object) error { + pod, ok := obj.(*corev1.Pod) + if !ok { + return nil + } + + annos := pod.GetAnnotations() + if annos == nil { + pod.Annotations = make(map[string]string) + } + + if annos[CalicoAnnotationIPPoolV4] == "" { + pools, err := p.ksclient.NetworkV1alpha1().IPPools().List(v1.ListOptions{ + LabelSelector: labels.SelectorFromSet(map[string]string{ + v1alpha1.IPPoolDefaultLabel: "", + }).String(), + }) + if err != nil { + return err + } + var poolNames []string + for _, pool := range pools.Items { + poolNames = append(poolNames, pool.Name) + } + if len(poolNames) > 0 { + annostrs, _ := json.Marshal(poolNames) + pod.Annotations[CalicoAnnotationIPPoolV4] = string(annostrs) } } + + return nil } -func NewProvider(ksclient kubesphereclient.Interface, options Options, k8sOptions *k8s.KubernetesOptions) provider { +func NewProvider(podInformer informercorev1.PodInformer, ksclient kubesphereclient.Interface, k8sClient clientset.Interface, k8sOptions *k8s.KubernetesOptions) provider { config, err := clientcmd.BuildConfigFromFlags("", k8sOptions.KubeConfig) if err != nil { klog.Fatalf("failed to build k8s config , err=%v", err) @@ -401,11 +650,49 @@ func NewProvider(ksclient kubesphereclient.Interface, options Options, k8sOption klog.Fatalf("failed to new calico client , err=%v", err) } - p := provider{ - client: client, - ksclient: ksclient, - options: options, + ds, err := k8sClient.AppsV1().DaemonSets(CalicoNodeNamespace).Get(CalicoNodeDaemonset, v1.GetOptions{}) + if err != nil { + klog.Fatalf("failed to get calico-node deployment in kube-system, err=%v", err) } + opts := Options{ + IPIPMode: "Always", + VXLANMode: "Never", + NATOutgoing: true, + } + envs := ds.Spec.Template.Spec.Containers[0].Env + for _, env := range envs { + if env.Name == CALICO_IPV4POOL_IPIP { + opts.IPIPMode = env.Value + } + + if env.Name == CALICO_IPV4POOL_VXLAN { + opts.VXLANMode = env.Value + } + + if env.Name == CALICO_IPV4POOL_NAT_OUTGOING { + if env.Value != "true" { + opts.NATOutgoing = false + } + } + } + + p := provider{ + client: client, + ksclient: ksclient, + k8sclient: k8sClient, + pods: podInformer, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "calicoBlock"), + options: opts, + } + + blockI := calicoInformer.NewSharedInformerFactory(client, defaultResync).Crd().Calicov3().IPAMBlocks() + blockI.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: p.addBlock, + UpdateFunc: func(old, new interface{}) { + p.addBlock(new) + }, + }) + p.block = blockI if err := p.syncIPPools(); err != nil { klog.Fatalf("failed to sync calico ippool to kubesphere ippool, err=%v", err) diff --git a/pkg/simple/client/network/ippool/calico/provider_test.go b/pkg/simple/client/network/ippool/calico/provider_test.go index 2ad8a664a032bf7b5c8a07ced52db62c4cb05b0c..74983eddb95eec703a15e90f1b54c2a72dc4afbf 100644 --- a/pkg/simple/client/network/ippool/calico/provider_test.go +++ b/pkg/simple/client/network/ippool/calico/provider_test.go @@ -18,14 +18,17 @@ package calico import ( "flag" + tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" + "testing" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" ksfake "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" + "kubesphere.io/kubesphere/pkg/constants" calicofake "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico/client/clientset/versioned/fake" - "testing" ) func TestCalicoIPPoolSuit(t *testing.T) { @@ -43,6 +46,9 @@ var _ = Describe("test calico ippool", func() { TypeMeta: v1.TypeMeta{}, ObjectMeta: v1.ObjectMeta{ Name: "testippool", + Labels: map[string]string{ + constants.WorkspaceLabelKey: "wk1", + }, }, Spec: v1alpha1.IPPoolSpec{ Type: v1alpha1.Calico, @@ -52,7 +58,23 @@ var _ = Describe("test calico ippool", func() { Status: v1alpha1.IPPoolStatus{}, } - ksclient := ksfake.NewSimpleClientset(pool) + wk1 := &tenantv1alpha1.Workspace{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{ + Name: "wk1", + }, + Spec: tenantv1alpha1.WorkspaceSpec{}, + Status: tenantv1alpha1.WorkspaceStatus{}, + } + wk2 := &tenantv1alpha1.Workspace{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{ + Name: "wk2", + }, + Spec: tenantv1alpha1.WorkspaceSpec{}, + Status: tenantv1alpha1.WorkspaceStatus{}, + } + ksclient := ksfake.NewSimpleClientset(pool, wk1, wk2) client := calicofake.NewSimpleClientset() p := provider{ @@ -69,4 +91,15 @@ var _ = Describe("test calico ippool", func() { err := p.CreateIPPool(pool) Expect(err).ShouldNot(HaveOccurred()) }) + + It("test get workspace", func() { + result, err := p.getAssociatedWorkspaces(pool) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(result)).Should(Equal(1)) + + pool.Labels = nil + result, err = p.getAssociatedWorkspaces(pool) + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(result)).Should(Equal(2)) + }) }) diff --git a/pkg/simple/client/network/ippool/provider.go b/pkg/simple/client/network/ippool/provider.go index 1f5f47e595f67c3d49f5df193830c5695108e53e..20e60b5f6e67ddd0372096cf77ecf53ad56d9609 100644 --- a/pkg/simple/client/network/ippool/provider.go +++ b/pkg/simple/client/network/ippool/provider.go @@ -17,9 +17,14 @@ limitations under the License. package ippool import ( + "k8s.io/apimachinery/pkg/runtime" + v1 "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/util/workqueue" networkv1alpha1 "kubesphere.io/kubesphere/pkg/apis/network/v1alpha1" kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned" + "kubesphere.io/kubesphere/pkg/simple/client/k8s" + calicoclient "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/calico" "kubesphere.io/kubesphere/pkg/simple/client/network/ippool/ipam" ) @@ -30,6 +35,8 @@ type Provider interface { UpdateIPPool(pool *networkv1alpha1.IPPool) error GetIPPoolStats(pool *networkv1alpha1.IPPool) (*networkv1alpha1.IPPool, error) SyncStatus(stopCh <-chan struct{}, q workqueue.RateLimitingInterface) error + Type() string + Default(obj runtime.Object) error } type provider struct { @@ -37,6 +44,14 @@ type provider struct { ipamclient ipam.IPAMClient } +func (p provider) Type() string { + return networkv1alpha1.IPPoolTypeLocal +} + +func (p provider) Default(obj runtime.Object) error { + return nil +} + func (p provider) DeleteIPPool(pool *networkv1alpha1.IPPool) (bool, error) { blocks, err := p.ipamclient.ListBlocks(pool.Name) if err != nil { @@ -77,22 +92,36 @@ func (p provider) GetIPPoolStats(pool *networkv1alpha1.IPPool) (*networkv1alpha1 } stat := stats[0] - return &networkv1alpha1.IPPool{ - Status: networkv1alpha1.IPPoolStatus{ - Allocations: stat.Allocate, - Unallocated: stat.Unallocated, - Reserved: stat.Reserved, - Capacity: stat.Capacity, - Synced: true, - }, - }, nil + clone := pool.DeepCopy() + clone.Status = networkv1alpha1.IPPoolStatus{ + Allocations: stat.Allocate, + Unallocated: stat.Unallocated, + Reserved: stat.Reserved, + Capacity: stat.Capacity, + Synced: true, + } + return clone, nil } -func NewProvider(clientset kubesphereclient.Interface, options Options) provider { - vlanProvider := provider{ +func newProvider(clientset kubesphereclient.Interface) provider { + return provider{ kubesphereClient: clientset, ipamclient: ipam.NewIPAMClient(clientset, networkv1alpha1.VLAN), } +} + +func NewProvider(podInformer v1.PodInformer, clientset kubesphereclient.Interface, client clientset.Interface, pt string, k8sOptions *k8s.KubernetesOptions) Provider { + var p Provider + + switch pt { + case networkv1alpha1.IPPoolTypeLocal: + p = provider{ + kubesphereClient: clientset, + ipamclient: ipam.NewIPAMClient(clientset, networkv1alpha1.VLAN), + } + case networkv1alpha1.IPPoolTypeCalico: + p = calicoclient.NewProvider(podInformer, clientset, client, k8sOptions) + } - return vlanProvider + return p } diff --git a/pkg/simple/client/network/ippool/provider_test.go b/pkg/simple/client/network/ippool/provider_test.go index fa30254bd3bcc267109a39cc969ffb613cc050b3..f03434f8cf4632f53850c7911e325b2f24592008 100644 --- a/pkg/simple/client/network/ippool/provider_test.go +++ b/pkg/simple/client/network/ippool/provider_test.go @@ -26,7 +26,7 @@ import ( ) func testNewProvider() provider { - return NewProvider(fakeks.NewSimpleClientset(), Options{}) + return newProvider(fakeks.NewSimpleClientset()) } func TestProvider_GetIPPoolStats(t *testing.T) { diff --git a/pkg/simple/client/network/options.go b/pkg/simple/client/network/options.go index 834613d7a879d34cd3506031c2dd757628052ff5..65a7e2d7ef7f2555962acc9d42b1acb1c536cb9d 100644 --- a/pkg/simple/client/network/options.go +++ b/pkg/simple/client/network/options.go @@ -18,8 +18,6 @@ package network import ( "github.com/spf13/pflag" - - "kubesphere.io/kubesphere/pkg/simple/client/network/ippool" ) type NSNPOptions struct { @@ -27,24 +25,20 @@ type NSNPOptions struct { } type Options struct { - EnableNetworkPolicy bool `json:"enableNetworkPolicy,omitempty" yaml:"enableNetworkPolicy"` - NSNPOptions NSNPOptions `json:"nsnpOptions,omitempty" yaml:"nsnpOptions,omitempty"` - EnableIPPool bool `json:"enableIPPool,omitempty" yaml:"enableIPPool"` - IPPoolOptions ippool.Options `json:"ippoolOptions,omitempty" yaml:"ippoolOptions,omitempty"` - WeaveScopeHost string `json:"weaveScopeHost,omitempty" yaml:"weaveScopeHost,omitempty"` + EnableNetworkPolicy bool `json:"enableNetworkPolicy,omitempty" yaml:"enableNetworkPolicy"` + NSNPOptions NSNPOptions `json:"nsnpOptions,omitempty" yaml:"nsnpOptions,omitempty"` + WeaveScopeHost string `json:"weaveScopeHost,omitempty" yaml:"weaveScopeHost,omitempty"` + IPPoolType string `json:"ippoolType,omitempty" yaml:"ippoolType,omitempty"` } // NewNetworkOptions returns a `zero` instance func NewNetworkOptions() *Options { return &Options{ EnableNetworkPolicy: false, - EnableIPPool: false, + IPPoolType: "none", NSNPOptions: NSNPOptions{ AllowedIngressNamespaces: []string{}, }, - IPPoolOptions: ippool.Options{ - Calico: nil, - }, WeaveScopeHost: "", } } @@ -56,16 +50,15 @@ func (s *Options) Validate() []error { func (s *Options) ApplyTo(options *Options) { options.EnableNetworkPolicy = s.EnableNetworkPolicy - options.EnableIPPool = s.EnableIPPool + options.IPPoolType = s.IPPoolType options.NSNPOptions = s.NSNPOptions - options.IPPoolOptions = s.IPPoolOptions options.WeaveScopeHost = s.WeaveScopeHost } func (s *Options) AddFlags(fs *pflag.FlagSet, c *Options) { fs.BoolVar(&s.EnableNetworkPolicy, "enable-network-policy", c.EnableNetworkPolicy, "This field instructs KubeSphere to enable network policy or not.") - fs.BoolVar(&s.EnableIPPool, "enable-ippool", c.EnableIPPool, + fs.StringVar(&s.IPPoolType, "ippool-type", c.IPPoolType, "This field instructs KubeSphere to enable ippool or not.") fs.StringVar(&s.WeaveScopeHost, "weave-scope-host", c.WeaveScopeHost, "Weave Scope service endpoint which build a topology API of the applications and the containers running on the hosts") diff --git a/tools/cmd/crd-doc-gen/main.go b/tools/cmd/crd-doc-gen/main.go index c3b48ac8b6e72d2d87da0a992d682cfc28ae6a20..c0412053f2f95fb0b6b5e9b49b688c88463731d5 100644 --- a/tools/cmd/crd-doc-gen/main.go +++ b/tools/cmd/crd-doc-gen/main.go @@ -96,6 +96,10 @@ func main() { mapper.AddSpecific(networkv1alpha1.SchemeGroupVersion.WithKind(networkv1alpha1.ResourceKindNamespaceNetworkPolicy), networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourcePluralNamespaceNetworkPolicy), networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourceSingularNamespaceNetworkPolicy), meta.RESTScopeRoot) + mapper.AddSpecific(networkv1alpha1.SchemeGroupVersion.WithKind(networkv1alpha1.ResourceKindIPPool), + networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourcePluralIPPool), + networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourceSingularIPPool), meta.RESTScopeRoot) + mapper.AddSpecific(devopsv1alpha3.SchemeGroupVersion.WithKind(devopsv1alpha3.ResourceKindDevOpsProject), devopsv1alpha3.SchemeGroupVersion.WithResource(devopsv1alpha3.ResourcePluralDevOpsProject), devopsv1alpha3.SchemeGroupVersion.WithResource(devopsv1alpha3.ResourceSingularDevOpsProject), meta.RESTScopeRoot) @@ -147,6 +151,7 @@ func main() { devopsv1alpha1.SchemeGroupVersion.WithResource(devopsv1alpha1.ResourcePluralS2iBuilderTemplate), devopsv1alpha1.SchemeGroupVersion.WithResource(devopsv1alpha1.ResourcePluralS2iBuilder), networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourcePluralNamespaceNetworkPolicy), + networkv1alpha1.SchemeGroupVersion.WithResource(networkv1alpha1.ResourcePluralIPPool), devopsv1alpha3.SchemeGroupVersion.WithResource(devopsv1alpha3.ResourcePluralDevOpsProject), devopsv1alpha3.SchemeGroupVersion.WithResource(devopsv1alpha3.ResourcePluralPipeline), clusterv1alpha1.SchemeGroupVersion.WithResource(clusterv1alpha1.ResourcesPluralCluster),