未验证 提交 69c6d91f 编写于 作者: Z zryfish 提交者: GitHub

fix cluster controller (#1996)

上级 e174dcb3
...@@ -63,7 +63,7 @@ vet: generate ...@@ -63,7 +63,7 @@ vet: generate
# Generate manifests e.g. CRD, RBAC etc. # Generate manifests e.g. CRD, RBAC etc.
manifests: manifests:
go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/... go run ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/... rbac:roleName=controller-perms ${CRD_OPTIONS} output:crd:artifacts:config=config/crd/bases
deploy: manifests deploy: manifests
kubectl apply -f config/crds kubectl apply -f config/crds
......
...@@ -14,7 +14,7 @@ spec: ...@@ -14,7 +14,7 @@ spec:
listKind: ClusterList listKind: ClusterList
plural: clusters plural: clusters
singular: cluster singular: cluster
scope: Namespaced scope: Cluster
validation: validation:
openAPIV3Schema: openAPIV3Schema:
description: Cluster is the schema for the clusters API description: Cluster is the schema for the clusters API
...@@ -37,35 +37,52 @@ spec: ...@@ -37,35 +37,52 @@ spec:
description: Desired state of the cluster description: Desired state of the cluster
type: boolean type: boolean
federated: federated:
description: Join cluster as kubefed cluster description: Join cluster as a kubefed cluster
type: boolean type: boolean
provider:
description: Provider of the cluster, this field is just for description
type: string
type: object type: object
status: status:
properties: properties:
lastTransitionTime: conditions:
description: Last time the condition transitioned from one status to description: Represents the latest available observations of a cluster's
another. current state.
format: date-time items:
type: string properties:
lastUpdateTime: lastTransitionTime:
description: The last time this condition was updated. description: Last time the condition transitioned from one status
format: date-time to another.
type: string format: date-time
message: type: string
description: A human readable message indicating details about the transition. lastUpdateTime:
type: string description: The last time this condition was updated.
reason: format: date-time
description: The reason for the condition's last transition. type: string
type: string message:
status: description: A human readable message indicating details about
description: Status of the condition, one of True, False, Unknown. the transition.
type: string type: string
type: reason:
description: Type of the condition description: The reason for the condition's last transition.
type: string
status:
description: Status of the condition, one of True, False, Unknown.
type: string
type:
description: Type of the condition
type: string
required:
- status
- type
type: object
type: array
count:
type: integer
version:
description: GitVersion of the kubernetes cluster, this field is set
by cluster controller
type: string type: string
required:
- status
- type
type: object type: object
type: object type: object
version: v1alpha1 version: v1alpha1
......
...@@ -82,8 +82,6 @@ type AgentCondition struct { ...@@ -82,8 +82,6 @@ type AgentCondition struct {
// AgentStatus defines the observed state of Agent // AgentStatus defines the observed state of Agent
type AgentStatus struct { type AgentStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Represents the latest available observations of a agent's current state. // Represents the latest available observations of a agent's current state.
Conditions []AgentCondition `json:"conditions,omitempty"` Conditions []AgentCondition `json:"conditions,omitempty"`
...@@ -99,6 +97,8 @@ type AgentStatus struct { ...@@ -99,6 +97,8 @@ type AgentStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
// +genclient:nonNamespaced // +genclient:nonNamespaced
// +kubebuilder:printcolumn:name="Paused",type="bool",JSONPath=".spec.Paused"
// +kubebuilder:resource:scope=Cluster
// Agent is the Schema for the agents API // Agent is the Schema for the agents API
type Agent struct { type Agent struct {
......
...@@ -9,31 +9,38 @@ const ( ...@@ -9,31 +9,38 @@ const (
ResourceKindCluster = "Cluster" ResourceKindCluster = "Cluster"
ResourcesSingularCluster = "cluster" ResourcesSingularCluster = "cluster"
ResourcesPluralCluster = "clusters" ResourcesPluralCluster = "clusters"
IsHostCluster = "cluster.kubesphere.io/is-host-cluster"
) )
type ClusterSpec struct { type ClusterSpec struct {
// Join cluster as kubefed cluster // Join cluster as a kubefed cluster
// +optional
Federated bool `json:"federated,omitempty"` Federated bool `json:"federated,omitempty"`
// Desired state of the cluster // Desired state of the cluster
Active bool `json:"active,omitempty"` Active bool `json:"active,omitempty"`
// Provider of the cluster, this field is just for description
// +optional
Provider string `json:"provider,omitempty"`
} }
type ClusterConditionType string type ClusterConditionType string
const ( const (
// Cluster agent is initialized and waiting for connecting // Cluster agent is initialized and waiting for connecting
ClusterAgentInitialized ClusterConditionType = "AgentInitialized" ClusterInitialized ClusterConditionType = "Initialized"
// Cluster agent is available // Cluster agent is available
ClusterAgentAvailable ClusterConditionType = "AgentAvailable" ClusterAgentAvailable ClusterConditionType = "AgentAvailable"
// // Cluster has been one of federated clusters
ClusterFederated ClusterConditionType = "Federated"
) )
type ClusterStatus struct { type ClusterCondition struct {
// Type of the condition // Type of the condition
Type ClusterConditionType `json:"type"` Type ClusterConditionType `json:"type"`
// Status of the condition, one of True, False, Unknown. // Status of the condition, one of True, False, Unknown.
...@@ -48,10 +55,28 @@ type ClusterStatus struct { ...@@ -48,10 +55,28 @@ type ClusterStatus struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
} }
type ClusterStatus struct {
// Represents the latest available observations of a cluster's current state.
Conditions []ClusterCondition `json:"conditions,omitempty"`
// GitVersion of the kubernetes cluster, this field is set by cluster controller
// +optional
KubernetesVersion string `json:"version,omitempty"`
//
NodeCount int `json:"count,omitempty"`
}
// +genclient // +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
// +genclient:nonNamespaced // +genclient:nonNamespaced
// +groupName
// +kubebuilder:printcolumn:name="Federated",type="bool",JSONPath=".spec.Federated"
// +kubebuilder:printcolumn:name="Provider",type="string",JSONPath=".spec.Provider"
// +kubebuilder:printcolumn:name="Active",type="bool",JSONPath=".spec.Active"
// +kubebuilder:resource:scope=Cluster
// Cluster is the schema for the clusters API // Cluster is the schema for the clusters API
type Cluster struct { type Cluster struct {
......
...@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and ...@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Code generated by deepcopy-gen. DO NOT EDIT. // Code generated by controller-gen. DO NOT EDIT.
package v1alpha1 package v1alpha1
...@@ -31,7 +31,6 @@ func (in *Agent) DeepCopyInto(out *Agent) { ...@@ -31,7 +31,6 @@ func (in *Agent) DeepCopyInto(out *Agent) {
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Agent. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Agent.
...@@ -57,7 +56,6 @@ func (in *AgentCondition) DeepCopyInto(out *AgentCondition) { ...@@ -57,7 +56,6 @@ func (in *AgentCondition) DeepCopyInto(out *AgentCondition) {
*out = *in *out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentCondition. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentCondition.
...@@ -82,7 +80,6 @@ func (in *AgentList) DeepCopyInto(out *AgentList) { ...@@ -82,7 +80,6 @@ func (in *AgentList) DeepCopyInto(out *AgentList) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentList. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentList.
...@@ -106,7 +103,6 @@ func (in *AgentList) DeepCopyObject() runtime.Object { ...@@ -106,7 +103,6 @@ func (in *AgentList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AgentSpec) DeepCopyInto(out *AgentSpec) { func (in *AgentSpec) DeepCopyInto(out *AgentSpec) {
*out = *in *out = *in
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentSpec.
...@@ -134,7 +130,6 @@ func (in *AgentStatus) DeepCopyInto(out *AgentStatus) { ...@@ -134,7 +130,6 @@ func (in *AgentStatus) DeepCopyInto(out *AgentStatus) {
*out = make([]byte, len(*in)) *out = make([]byte, len(*in))
copy(*out, *in) copy(*out, *in)
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentStatus.
...@@ -154,7 +149,6 @@ func (in *Cluster) DeepCopyInto(out *Cluster) { ...@@ -154,7 +149,6 @@ func (in *Cluster) DeepCopyInto(out *Cluster) {
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
...@@ -175,6 +169,23 @@ func (in *Cluster) DeepCopyObject() runtime.Object { ...@@ -175,6 +169,23 @@ func (in *Cluster) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterCondition) DeepCopyInto(out *ClusterCondition) {
*out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterCondition.
func (in *ClusterCondition) DeepCopy() *ClusterCondition {
if in == nil {
return nil
}
out := new(ClusterCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterList) DeepCopyInto(out *ClusterList) { func (in *ClusterList) DeepCopyInto(out *ClusterList) {
*out = *in *out = *in
...@@ -187,7 +198,6 @@ func (in *ClusterList) DeepCopyInto(out *ClusterList) { ...@@ -187,7 +198,6 @@ func (in *ClusterList) DeepCopyInto(out *ClusterList) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterList. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterList.
...@@ -211,7 +221,6 @@ func (in *ClusterList) DeepCopyObject() runtime.Object { ...@@ -211,7 +221,6 @@ func (in *ClusterList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
*out = *in *out = *in
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.
...@@ -227,9 +236,13 @@ func (in *ClusterSpec) DeepCopy() *ClusterSpec { ...@@ -227,9 +236,13 @@ func (in *ClusterSpec) DeepCopy() *ClusterSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
*out = *in *out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) if in.Conditions != nil {
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) in, out := &in.Conditions, &out.Conditions
return *out = make([]ClusterCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus.
......
...@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and ...@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Code generated by deepcopy-gen. DO NOT EDIT. // Code generated by controller-gen. DO NOT EDIT.
package v1alpha2 package v1alpha2
...@@ -31,7 +31,6 @@ func (in *User) DeepCopyInto(out *User) { ...@@ -31,7 +31,6 @@ func (in *User) DeepCopyInto(out *User) {
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec) in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User.
...@@ -56,7 +55,6 @@ func (in *User) DeepCopyObject() runtime.Object { ...@@ -56,7 +55,6 @@ func (in *User) DeepCopyObject() runtime.Object {
func (in *UserCondition) DeepCopyInto(out *UserCondition) { func (in *UserCondition) DeepCopyInto(out *UserCondition) {
*out = *in *out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserCondition. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserCondition.
...@@ -81,7 +79,6 @@ func (in *UserList) DeepCopyInto(out *UserList) { ...@@ -81,7 +79,6 @@ func (in *UserList) DeepCopyInto(out *UserList) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserList.
...@@ -115,7 +112,6 @@ func (in *UserSpec) DeepCopyInto(out *UserSpec) { ...@@ -115,7 +112,6 @@ func (in *UserSpec) DeepCopyInto(out *UserSpec) {
*out = make([]FinalizerName, len(*in)) *out = make([]FinalizerName, len(*in))
copy(*out, *in) copy(*out, *in)
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec.
...@@ -138,7 +134,6 @@ func (in *UserStatus) DeepCopyInto(out *UserStatus) { ...@@ -138,7 +134,6 @@ func (in *UserStatus) DeepCopyInto(out *UserStatus) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus.
......
...@@ -182,7 +182,9 @@ func (s *APIServer) buildHandlerChain() { ...@@ -182,7 +182,9 @@ func (s *APIServer) buildHandlerChain() {
handler := s.Server.Handler handler := s.Server.Handler
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{}) handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Agents().Lister()))
clusterDispatcher := dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Agents().Lister(), s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Lister())
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"} excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
......
...@@ -2,11 +2,8 @@ package config ...@@ -2,11 +2,8 @@ package config
import ( import (
"fmt" "fmt"
"github.com/emicklei/go-restful"
"github.com/spf13/viper" "github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime/schema"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/simple/client/alerting" "kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins" "kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
...@@ -20,7 +17,6 @@ import ( ...@@ -20,7 +17,6 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/servicemesh" "kubesphere.io/kubesphere/pkg/simple/client/servicemesh"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube" "kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
"net/http"
"reflect" "reflect"
"strings" "strings"
) )
...@@ -126,26 +122,6 @@ func TryLoadFromDisk() (*Config, error) { ...@@ -126,26 +122,6 @@ func TryLoadFromDisk() (*Config, error) {
return conf, nil return conf, nil
} }
// InstallAPI installs api for config
func (conf *Config) InstallAPI(c *restful.Container) {
ws := runtime.NewWebService(schema.GroupVersion{
Group: "",
Version: "v1alpha1",
})
ws.Route(ws.GET("/configz").
To(func(request *restful.Request, response *restful.Response) {
conf.stripEmptyOptions()
response.WriteAsJson(conf.ToMap())
}).
Doc("Get system components configuration").
Produces(restful.MIME_JSON).
Writes(Config{}).
Returns(http.StatusOK, "ok", Config{}))
c.Add(ws)
}
// convertToMap simply converts config to map[string]bool // convertToMap simply converts config to map[string]bool
// to hide sensitive information // to hide sensitive information
func (conf *Config) ToMap() map[string]bool { func (conf *Config) ToMap() map[string]bool {
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1" clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/request" "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1" "kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
...@@ -13,27 +14,46 @@ import ( ...@@ -13,27 +14,46 @@ import (
"strings" "strings"
) )
const defaultMultipleClusterAgentNamespace = "kubesphere-system"
// Dispatcher defines how to forward request to designated cluster based on cluster name // Dispatcher defines how to forward request to designated cluster based on cluster name
type Dispatcher interface { type Dispatcher interface {
Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler)
} }
type clusterDispatch struct { type clusterDispatch struct {
agentLister v1alpha1.AgentLister agentLister v1alpha1.AgentLister
clusterLister v1alpha1.ClusterLister
} }
func NewClusterDispatch(agentLister v1alpha1.AgentLister) Dispatcher { func NewClusterDispatch(agentLister v1alpha1.AgentLister, clusterLister v1alpha1.ClusterLister) Dispatcher {
return &clusterDispatch{ return &clusterDispatch{
agentLister: agentLister, agentLister: agentLister,
clusterLister: clusterLister,
} }
} }
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) { func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) {
info, _ := request.RequestInfoFrom(req.Context()) info, _ := request.RequestInfoFrom(req.Context())
if info.Cluster == "" { // fallback to host cluster if cluster name if empty
if len(info.Cluster) == 0 {
klog.Warningf("Request with empty cluster, %v", req.URL)
http.Error(w, fmt.Sprintf("Bad request, empty cluster"), http.StatusBadRequest)
return
}
cluster, err := c.clusterLister.Get(info.Cluster)
if err != nil {
if errors.IsNotFound(err) {
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// request cluster is host cluster, no need go through agent
if isClusterHostCluster(cluster) {
req.URL.Path = strings.Replace(req.URL.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
return return
} }
...@@ -74,3 +94,14 @@ func isAgentReady(agent *clusterv1alpha1.Agent) bool { ...@@ -74,3 +94,14 @@ func isAgentReady(agent *clusterv1alpha1.Agent) bool {
return false return false
} }
//
func isClusterHostCluster(cluster *clusterv1alpha1.Cluster) bool {
for key, value := range cluster.Annotations {
if key == clusterv1alpha1.IsHostCluster && value == "true" {
return true
}
}
return false
}
...@@ -78,8 +78,8 @@ type RequestInfoFactory struct { ...@@ -78,8 +78,8 @@ type RequestInfoFactory struct {
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource} // /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName} // /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
// With workspaces: // With workspaces:
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource} // /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}/{resourceName} // /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
// //
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) { func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
...@@ -111,6 +111,16 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er ...@@ -111,6 +111,16 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
requestInfo.APIPrefix = currentParts[0] requestInfo.APIPrefix = currentParts[0]
currentParts = currentParts[1:] currentParts = currentParts[1:]
// URL forms: /clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) { if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?" // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
if len(currentParts) < 3 { if len(currentParts) < 3 {
...@@ -150,16 +160,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er ...@@ -150,16 +160,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
} }
} }
// URL forms: /clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
// URL forms: /workspaces/{workspace}/* // URL forms: /workspaces/{workspace}/*
if currentParts[0] == "workspaces" { if currentParts[0] == "workspaces" {
if len(currentParts) > 1 { if len(currentParts) > 1 {
......
...@@ -59,59 +59,48 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) { ...@@ -59,59 +59,48 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
expectedKubernetesRequest: false, expectedKubernetesRequest: false,
}, },
{ {
name: "list cluster roles", name: "list clusterRoles of cluster gondor",
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/clusterroles", url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/clusterroles",
method: http.MethodGet, method: http.MethodGet,
expectedErr: nil, expectedErr: nil,
expectedVerb: "list", expectedVerb: "list",
expectedResource: "clusterroles", expectedResource: "clusterroles",
expectedIsResourceRequest: true, expectedIsResourceRequest: true,
expectedCluster: "cluster1", expectedCluster: "gondor",
expectedKubernetesRequest: true, expectedKubernetesRequest: true,
}, },
{ {
name: "list cluster nodes", name: "list nodes",
url: "/api/v1/clusters/cluster1/nodes", url: "/api/v1/nodes",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "nodes",
expectedIsResourceRequest: true,
expectedCluster: "cluster1",
expectedKubernetesRequest: true,
},
{
name: "list cluster nodes",
url: "/api/v1/clusters/cluster1/nodes",
method: http.MethodGet, method: http.MethodGet,
expectedErr: nil, expectedErr: nil,
expectedVerb: "list", expectedVerb: "list",
expectedResource: "nodes", expectedResource: "nodes",
expectedIsResourceRequest: true, expectedIsResourceRequest: true,
expectedCluster: "cluster1", expectedCluster: "",
expectedKubernetesRequest: true, expectedKubernetesRequest: true,
}, },
{ {
name: "list cluster nodes", name: "list nodes of cluster gondor",
url: "/api/v1/nodes", url: "/api/clusters/gondor/v1/nodes",
method: http.MethodGet, method: http.MethodGet,
expectedErr: nil, expectedErr: nil,
expectedVerb: "list", expectedVerb: "list",
expectedResource: "nodes", expectedResource: "nodes",
expectedIsResourceRequest: true, expectedIsResourceRequest: true,
expectedCluster: "", expectedCluster: "gondor",
expectedKubernetesRequest: true, expectedKubernetesRequest: true,
}, },
{ {
name: "list roles", name: "list roles of cluster gondor",
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/namespaces/namespace1/roles", url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
method: http.MethodGet, method: http.MethodGet,
expectedErr: nil, expectedErr: nil,
expectedVerb: "list", expectedVerb: "list",
expectedResource: "roles", expectedResource: "roles",
expectedIsResourceRequest: true, expectedIsResourceRequest: true,
expectedNamespace: "namespace1", expectedNamespace: "namespace1",
expectedCluster: "cluster1", expectedCluster: "gondor",
expectedKubernetesRequest: true, expectedKubernetesRequest: true,
}, },
{ {
...@@ -139,17 +128,41 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) { ...@@ -139,17 +128,41 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
expectedKubernetesRequest: false, expectedKubernetesRequest: false,
}, },
{ {
name: "list namespaces", name: "list namespaces of cluster gondor",
url: "/kapis/resources.kubesphere.io/v1alpha3/clusters/cluster1/workspaces/workspace1/namespaces", url: "/kapis/clusters/gondor/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
method: http.MethodGet, method: http.MethodGet,
expectedErr: nil, expectedErr: nil,
expectedVerb: "list", expectedVerb: "list",
expectedResource: "namespaces", expectedResource: "namespaces",
expectedIsResourceRequest: true, expectedIsResourceRequest: true,
expectedWorkspace: "workspace1", expectedWorkspace: "workspace1",
expectedCluster: "cluster1", expectedCluster: "gondor",
expectedKubernetesRequest: false, expectedKubernetesRequest: false,
}, },
{
name: "list clusters",
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "clusters",
expectedIsResourceRequest: true,
expectedWorkspace: "",
expectedCluster: "",
expectedKubernetesRequest: true,
},
{
name: "get cluster gondor",
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters/gondor",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "get",
expectedResource: "clusters",
expectedIsResourceRequest: true,
expectedWorkspace: "",
expectedCluster: "",
expectedKubernetesRequest: true,
},
{ {
name: "random query", name: "random query",
url: "/foo/bar", url: "/foo/bar",
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
...@@ -87,6 +88,12 @@ func NewClusterController( ...@@ -87,6 +88,12 @@ func NewClusterController(
DeleteFunc: c.addCluster, DeleteFunc: c.addCluster,
}) })
agentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: nil,
UpdateFunc: nil,
DeleteFunc: nil,
})
return c return c
} }
...@@ -141,7 +148,7 @@ func (c *ClusterController) syncCluster(key string) error { ...@@ -141,7 +148,7 @@ func (c *ClusterController) syncCluster(key string) error {
} }
defer func() { defer func() {
klog.V(4).Info("Finished syncing cluster.", "name", name, "duration", time.Since(startTime)) klog.V(4).Infof("Finished syncing cluster %s in %s", name, time.Since(startTime))
}() }()
cluster, err := c.clusterLister.Get(name) cluster, err := c.clusterLister.Get(name)
...@@ -209,6 +216,66 @@ func (c *ClusterController) syncCluster(key string) error { ...@@ -209,6 +216,66 @@ func (c *ClusterController) syncCluster(key string) error {
}) })
} }
// agent connection is ready, update cluster status
// set
if len(agent.Status.KubeConfig) != 0 && c.isAgentReady(agent) {
clientConfig, err := clientcmd.NewClientConfigFromBytes(agent.Status.KubeConfig)
if err != nil {
klog.Errorf("Unable to create client config from kubeconfig bytes, %#v", err)
return err
}
config, err := clientConfig.ClientConfig()
if err != nil {
klog.Errorf("Failed to get client config, %#v", err)
return err
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
klog.Errorf("Failed to create ClientSet from config, %#v", err)
return nil
}
version, err := clientSet.Discovery().ServerVersion()
if err != nil {
klog.Errorf("Failed to get kubernetes version, %#v", err)
return err
}
cluster.Status.KubernetesVersion = version.GitVersion
nodes, err := clientSet.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
klog.Errorf("Failed to get cluster nodes, %#v", err)
return err
}
cluster.Status.NodeCount = len(nodes.Items)
}
agentReadyCondition := clusterv1alpha1.ClusterCondition{
Type: clusterv1alpha1.ClusterAgentAvailable,
LastUpdateTime: metav1.NewTime(time.Now()),
LastTransitionTime: metav1.NewTime(time.Now()),
Reason: "",
Message: "Cluster agent is available now.",
}
if c.isAgentReady(agent) {
agentReadyCondition.Status = v1.ConditionTrue
} else {
agentReadyCondition.Status = v1.ConditionFalse
}
c.addClusterCondition(cluster, agentReadyCondition)
_, err = c.clusterClient.Update(cluster)
if err != nil {
klog.Errorf("Failed to update cluster status, %#v", err)
return err
}
return nil return nil
} }
...@@ -217,14 +284,64 @@ func (c *ClusterController) addCluster(obj interface{}) { ...@@ -217,14 +284,64 @@ func (c *ClusterController) addCluster(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj) key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil { if err != nil {
utilruntime.HandleError(fmt.Errorf("get cluster key %s/%s failed", cluster.Namespace, cluster.Name)) utilruntime.HandleError(fmt.Errorf("get cluster key %s failed", cluster.Name))
return return
} }
c.queue.Add(key) c.queue.Add(key)
} }
func (c *ClusterController) handleErr(err error, key interface{}) { func (c *ClusterController) handleErr(err error, key interface{}) {
if err == nil {
c.queue.Forget(key)
return
}
if c.queue.NumRequeues(key) < maxRetries {
klog.V(2).Infof("Error syncing virtualservice %s for service retrying, %#v", key, err)
c.queue.AddRateLimited(key)
return
}
klog.V(4).Infof("Dropping service %s out of the queue.", key)
c.queue.Forget(key)
utilruntime.HandleError(err)
}
func (c *ClusterController) addAgent(obj interface{}) {
agent := obj.(*clusterv1alpha1.Agent)
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
utilruntime.HandleError(fmt.Errorf("get agent key %s failed", agent.Name))
return
}
c.queue.Add(key)
}
func (c *ClusterController) isAgentReady(agent *clusterv1alpha1.Agent) bool {
for _, condition := range agent.Status.Conditions {
if condition.Type == clusterv1alpha1.AgentConnected && condition.Status == v1.ConditionTrue {
return true
}
}
return false
}
// addClusterCondition add condition
func (c *ClusterController) addClusterCondition(cluster *clusterv1alpha1.Cluster, condition clusterv1alpha1.ClusterCondition) {
if cluster.Status.Conditions == nil {
cluster.Status.Conditions = make([]clusterv1alpha1.ClusterCondition, 0)
}
newConditions := make([]clusterv1alpha1.ClusterCondition, 0)
for _, cond := range cluster.Status.Conditions {
if cond.Type == condition.Type {
continue
}
newConditions = append(newConditions, cond)
}
newConditions = append(newConditions, condition)
cluster.Status.Conditions = newConditions
} }
...@@ -199,7 +199,7 @@ func (v *DestinationRuleController) processNextWorkItem() bool { ...@@ -199,7 +199,7 @@ func (v *DestinationRuleController) processNextWorkItem() bool {
func (v *DestinationRuleController) syncService(key string) error { func (v *DestinationRuleController) syncService(key string) error {
startTime := time.Now() startTime := time.Now()
defer func() { defer func() {
log.V(4).Info("Finished syncing service destinationrule.", "key", key, "duration", time.Since(startTime)) log.V(4).Infof("Finished syncing service destinationrule %s in %s.", key, time.Since(startTime))
}() }()
namespace, name, err := cache.SplitMetaNamespaceKey(key) namespace, name, err := cache.SplitMetaNamespaceKey(key)
...@@ -212,14 +212,14 @@ func (v *DestinationRuleController) syncService(key string) error { ...@@ -212,14 +212,14 @@ func (v *DestinationRuleController) syncService(key string) error {
// delete the corresponding destinationrule if there is any, as the service has been deleted. // delete the corresponding destinationrule if there is any, as the service has been deleted.
err = v.destinationRuleClient.NetworkingV1alpha3().DestinationRules(namespace).Delete(name, nil) err = v.destinationRuleClient.NetworkingV1alpha3().DestinationRules(namespace).Delete(name, nil)
if err != nil && !errors.IsNotFound(err) { if err != nil && !errors.IsNotFound(err) {
log.Error(err, "delete destination rule failed", "namespace", namespace, "name", name) log.Errorf("delete destination rule failed %s/%s, error %v.", namespace, name, err)
return err return err
} }
// delete orphan service policy if there is any // delete orphan service policy if there is any
err = v.servicemeshClient.ServicemeshV1alpha2().ServicePolicies(namespace).Delete(name, nil) err = v.servicemeshClient.ServicemeshV1alpha2().ServicePolicies(namespace).Delete(name, nil)
if err != nil && !errors.IsNotFound(err) { if err != nil && !errors.IsNotFound(err) {
log.Error(err, "delete orphan service policy failed", "namespace", namespace, "name", name) log.Errorf("delete orphan service policy %s/%s failed, %#v", namespace, name, err)
return err return err
} }
...@@ -259,7 +259,7 @@ func (v *DestinationRuleController) syncService(key string) error { ...@@ -259,7 +259,7 @@ func (v *DestinationRuleController) syncService(key string) error {
version := util.GetComponentVersion(&deployment.ObjectMeta) version := util.GetComponentVersion(&deployment.ObjectMeta)
if len(version) == 0 { if len(version) == 0 {
log.V(4).Info("Deployment doesn't have a version label", "key", types.NamespacedName{Namespace: deployment.Namespace, Name: deployment.Name}.String()) log.V(4).Infof("Deployment %s doesn't have a version label", types.NamespacedName{Namespace: deployment.Namespace, Name: deployment.Name}.String())
continue continue
} }
......
...@@ -213,7 +213,7 @@ func (v *VirtualServiceController) syncService(key string) error { ...@@ -213,7 +213,7 @@ func (v *VirtualServiceController) syncService(key string) error {
appName := name appName := name
defer func() { defer func() {
log.V(4).Info("Finished syncing service virtualservice.", "namespace", namespace, "name", name, "duration", time.Since(startTime)) log.V(4).Infof("Finished syncing service virtualservice %s/%s in %s.", namespace, name, time.Since(startTime))
}() }()
service, err := v.serviceLister.Services(namespace).Get(name) service, err := v.serviceLister.Services(namespace).Get(name)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册