diff --git a/Gopkg.lock b/Gopkg.lock index 2d21b4817..c6c5017a0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -650,6 +650,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9f1ac4fe878dadee802b8971f67ba69171326c81e2c379247015003f2e0ba134" + inputs-digest = "d92e4ea931c670e5ebb04661788db5386ee647b8b6bac4ec6b519a1544b2c400" solver-name = "gps-cdcl" solver-version = 1 diff --git a/pkg/apis/v1alpha/resources/resources.go b/pkg/apis/v1alpha/resources/resources.go index f158cf6e8..240bdebef 100644 --- a/pkg/apis/v1alpha/resources/resources.go +++ b/pkg/apis/v1alpha/resources/resources.go @@ -23,6 +23,9 @@ import ( "github.com/emicklei/go-restful-openapi" + "fmt" + "strings" + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models" ) @@ -31,15 +34,33 @@ func Register(ws *restful.WebService, subPath string) { tags := []string{"resources"} - ws.Route(ws.GET(subPath+"/{resource}").To(handleResource).Produces(restful.MIME_JSON).Metadata(restfulspec.KeyOpenAPITags, tags).Doc("Get resource" + - " list").Param(ws.PathParameter("resource", "resource name").DataType( - "string")).Param(ws.QueryParameter("conditions", "search "+ - "conditions").DataType("string")).Param(ws.QueryParameter("paging", + ws.Route(ws.GET(subPath+"/{resource}").To(listResource).Produces(restful.MIME_JSON).Metadata(restfulspec.KeyOpenAPITags, tags).Doc("Get resource" + + " list").Param(ws.PathParameter("resource", "resource name").DataType("string")).Param(ws.QueryParameter("conditions", + "search conditions").DataType("string")).Param(ws.QueryParameter("reverse", + "support reverse ordering").DataType("bool").DefaultValue("false")).Param(ws.QueryParameter("order", + "the field for sorting").DataType("string")).Param(ws.QueryParameter("paging", "support paging function").DataType("string")).Writes(models.ResourceList{})) - } -func handleResource(req *restful.Request, resp *restful.Response) { +func isInvalid(str string) bool { + invalidList := []string{"exec", "insert", "select", "delete", "update", "count", "*", "%", "truncate", "drop"} + str = strings.Replace(str, "=", " ", -1) + str = strings.Replace(str, ",", " ", -1) + str = strings.Replace(str, "~", " ", -1) + items := strings.Split(str, " ") + + for _, invalid := range invalidList { + for _, item := range items { + if item == invalid || strings.ToLower(item) == invalid { + return true + } + } + } + + return false +} + +func listResource(req *restful.Request, resp *restful.Response) { resource := req.PathParameter("resource") if resource == "applications" { @@ -48,7 +69,23 @@ func handleResource(req *restful.Request, resp *restful.Response) { } conditions := req.QueryParameter("conditions") paging := req.QueryParameter("paging") - res, err := models.ListResource(resource, conditions, paging) + orderField := req.QueryParameter("order") + reverse := req.QueryParameter("reverse") + + if len(orderField) > 0 { + if reverse == "true" { + orderField = fmt.Sprintf("%s %s", orderField, "desc") + } else { + orderField = fmt.Sprintf("%s %s", orderField, "asc") + } + } + + if isInvalid(conditions) || isInvalid(paging) || isInvalid(orderField) { + resp.WriteHeaderAndEntity(http.StatusBadRequest, constants.MessageResponse{Message: "invalid input"}) + return + } + + res, err := models.ListResource(resource, conditions, paging, orderField) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) return @@ -58,7 +95,6 @@ func handleResource(req *restful.Request, resp *restful.Response) { } func handleApplication(req *restful.Request, resp *restful.Response) { - //searchWord := req.QueryParameter("search-word") paging := req.QueryParameter("paging") clusterId := req.QueryParameter("cluster_id") runtimeId := req.QueryParameter("runtime_id") diff --git a/pkg/models/controllers/applications.go b/pkg/models/controllers/applications.go index 69c994251..9a927ab8a 100644 --- a/pkg/models/controllers/applications.go +++ b/pkg/models/controllers/applications.go @@ -240,7 +240,7 @@ func (ctl *ApplicationCtl) GetWorkLoads(namespace string, clusterRoles []cluster if strings.HasSuffix(workLoadName, deploySurffix) { name := strings.Split(workLoadName, deploySurffix)[0] ctl := ResourceControllers.Controllers[Deployments] - _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil) + _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil, "") works.Deployments = append(works.Deployments, items.([]Deployment)...) continue } @@ -248,7 +248,7 @@ func (ctl *ApplicationCtl) GetWorkLoads(namespace string, clusterRoles []cluster if strings.HasSuffix(workLoadName, daemonSurffix) { name := strings.Split(workLoadName, daemonSurffix)[0] ctl := ResourceControllers.Controllers[Daemonsets] - _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil) + _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil, "") works.Daemonsets = append(works.Daemonsets, items.([]Daemonset)...) continue } @@ -256,7 +256,7 @@ func (ctl *ApplicationCtl) GetWorkLoads(namespace string, clusterRoles []cluster if strings.HasSuffix(workLoadName, stateSurffix) { name := strings.Split(workLoadName, stateSurffix)[0] ctl := ResourceControllers.Controllers[Statefulsets] - _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil) + _, items, _ := ctl.ListWithConditions(fmt.Sprintf("namespace='%s' and name = '%s'", namespace, name), nil, "") works.Statefulsets = append(works.Statefulsets, items.([]Statefulset)...) continue } @@ -265,16 +265,6 @@ func (ctl *ApplicationCtl) GetWorkLoads(namespace string, clusterRoles []cluster return &works } -// -//func (ctl *ApplicationCtl) getCreator(desc string) string { -// var dc description -// err := json.Unmarshal([]byte(desc), &dc) -// if err != nil { -// return unknown -// } -// return dc.Creator -//} - func (ctl *ApplicationCtl) getLabels(namespace string, workloads *workLoads) *[]map[string]string { k8sClient := client.NewK8sClient() @@ -349,7 +339,7 @@ func (ctl *ApplicationCtl) getIng(namespace string, services *[]Service) *[]ing ingCtl := ResourceControllers.Controllers[Ingresses] var ings []ing for _, svc := range *services { - _, items, err := ingCtl.ListWithConditions(fmt.Sprintf("namespace = '%s' and rules like '%%%s%%' ", namespace, svc.Name), nil) + _, items, err := ingCtl.ListWithConditions(fmt.Sprintf("namespace = '%s' and rules like '%%%s%%' ", namespace, svc.Name), nil, "") if err != nil { glog.Error(err) return nil diff --git a/pkg/models/controllers/clusterroles.go b/pkg/models/controllers/clusterroles.go index 3aab38c2a..bd74540ef 100644 --- a/pkg/models/controllers/clusterroles.go +++ b/pkg/models/controllers/clusterroles.go @@ -30,6 +30,11 @@ import ( const systemPrefix = "system:" func (ctl *ClusterRoleCtl) generateObject(item v1.ClusterRole) *ClusterRole { + var displayName string + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + name := item.Name if strings.HasPrefix(name, systemPrefix) { return nil @@ -40,7 +45,7 @@ func (ctl *ClusterRoleCtl) generateObject(item v1.ClusterRole) *ClusterRole { createTime = time.Now() } - object := &ClusterRole{Name: name, CreateTime: createTime, Annotation: Annotation{item.Annotations}} + object := &ClusterRole{Name: name, CreateTime: createTime, Annotation: MapString{item.Annotations}, DisplayName: displayName} return object } @@ -136,15 +141,23 @@ func (ctl *ClusterRoleCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *ClusterRoleCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *ClusterRoleCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var object ClusterRole var list []ClusterRole var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } + db := ctl.DB listWithConditions(db, &total, &object, &list, conditions, paging, order) return total, list, nil } + +func (ctl *ClusterRoleCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/common.go b/pkg/models/controllers/common.go index bc2ac4bf2..3bd269305 100644 --- a/pkg/models/controllers/common.go +++ b/pkg/models/controllers/common.go @@ -133,7 +133,7 @@ func checkAndResync(ctl Controller, stopChan chan struct{}) { func listAndWatch(ctl Controller) { defer handleCrash(ctl) - + defer ctl.CloseDB() stopChan := make(chan struct{}) go ctl.sync(stopChan) diff --git a/pkg/models/controllers/controllerrevisions.go b/pkg/models/controllers/controllerrevisions.go new file mode 100644 index 000000000..95b54612f --- /dev/null +++ b/pkg/models/controllers/controllerrevisions.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "k8s.io/client-go/informers" +) + +func (ctl *ControllerRevisionCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *ControllerRevisionCtl) sync(stopChan chan struct{}) { + + ctl.initListerAndInformer() + ctl.informer.Run(stopChan) +} + +func (ctl *ControllerRevisionCtl) total() int { + + return 0 +} + +func (ctl *ControllerRevisionCtl) initListerAndInformer() { + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + + ctl.lister = informerFactory.Apps().V1().ControllerRevisions().Lister() + + informer := informerFactory.Apps().V1().ControllerRevisions().Informer() + + ctl.informer = informer +} + +func (ctl *ControllerRevisionCtl) CountWithConditions(conditions string) int { + + return 0 +} + +func (ctl *ControllerRevisionCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + + return 0, nil, nil +} + +func (ctl *ControllerRevisionCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/cronjobs.go b/pkg/models/controllers/cronjobs.go new file mode 100644 index 000000000..6076282ac --- /dev/null +++ b/pkg/models/controllers/cronjobs.go @@ -0,0 +1,158 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "github.com/golang/glog" + "k8s.io/api/batch/v1beta1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +func (ctl *CronJobCtl) generateObject(item v1beta1.CronJob) *CronJob { + var status, displayName string + var lastScheduleTime *time.Time + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + + name := item.Name + namespace := item.Namespace + + status = Running + if *item.Spec.Suspend { + status = Pause + } + + schedule := item.Spec.Schedule + if item.Status.LastScheduleTime != nil { + lastScheduleTime = &item.Status.LastScheduleTime.Time + } + + active := len(item.Status.Active) + + object := &CronJob{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + LastScheduleTime: lastScheduleTime, + Active: active, + Schedule: schedule, + Status: status, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.ObjectMeta.Labels}, + } + + return object +} + +func (ctl *CronJobCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *CronJobCtl) sync(stopChan chan struct{}) { + db := ctl.DB + + if db.HasTable(&CronJob{}) { + db.DropTable(&CronJob{}) + } + + db = db.CreateTable(&CronJob{}) + + ctl.initListerAndInformer() + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Error(err) + return + } + + for _, item := range list { + obj := ctl.generateObject(*item) + db.Create(obj) + } + + ctl.informer.Run(stopChan) +} + +func (ctl *CronJobCtl) total() int { + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Errorf("count %s falied, reason:%s", err, ctl.Name()) + return 0 + } + return len(list) +} + +func (ctl *CronJobCtl) initListerAndInformer() { + db := ctl.DB + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + ctl.lister = informerFactory.Batch().V1beta1().CronJobs().Lister() + + informer := informerFactory.Batch().V1beta1().CronJobs().Informer() + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + + object := obj.(*v1beta1.CronJob) + mysqlObject := ctl.generateObject(*object) + db.Create(mysqlObject) + }, + UpdateFunc: func(old, new interface{}) { + object := new.(*v1beta1.CronJob) + mysqlObject := ctl.generateObject(*object) + db.Save(mysqlObject) + }, + DeleteFunc: func(obj interface{}) { + var item CronJob + object := obj.(*v1beta1.CronJob) + db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&item) + db.Delete(item) + + }, + }) + + ctl.informer = informer +} + +func (ctl *CronJobCtl) CountWithConditions(conditions string) int { + var object CronJob + + return countWithConditions(ctl.DB, conditions, &object) +} + +func (ctl *CronJobCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + var list []CronJob + var object CronJob + var total int + + if len(order) == 0 { + order = "lastScheduleTime desc" + } + + listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) + + return total, list, nil +} + +func (ctl *CronJobCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/daemonsets.go b/pkg/models/controllers/daemonsets.go index e96e22fd9..beabc3880 100644 --- a/pkg/models/controllers/daemonsets.go +++ b/pkg/models/controllers/daemonsets.go @@ -28,8 +28,11 @@ import ( ) func (ctl *DaemonsetCtl) generateObject(item v1.DaemonSet) *Daemonset { - var app string - var status string + var app, status, displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } name := item.Name namespace := item.Namespace availablePodNum := item.Status.NumberAvailable @@ -55,8 +58,19 @@ func (ctl *DaemonsetCtl) generateObject(item v1.DaemonSet) *Daemonset { status = Updating } - object := &Daemonset{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum, - App: app, CreateTime: createTime, Status: status, NodeSelector: string(nodeSelectorStr), Annotation: Annotation{item.Annotations}} + object := &Daemonset{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + Available: availablePodNum, + Desire: desirePodNum, + App: app, + CreateTime: createTime, + Status: status, + NodeSelector: string(nodeSelectorStr), + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Spec.Selector.MatchLabels}, + } return object } @@ -135,25 +149,21 @@ func (ctl *DaemonsetCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *DaemonsetCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *DaemonsetCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Daemonset var object Daemonset var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } -//func (ctl *DaemonsetCtl) Count(namespace string) int { -// var count int -// db := ctl.DB -// if len(namespace) == 0 { -// db.Model(&Daemonset{}).Count(&count) -// } else { -// db.Model(&Daemonset{}).Where("namespace = ?", namespace).Count(&count) -// } -// return count -//} +func (ctl *DaemonsetCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/deployments.go b/pkg/models/controllers/deployments.go index 4ae7f097d..44156ea3d 100644 --- a/pkg/models/controllers/deployments.go +++ b/pkg/models/controllers/deployments.go @@ -27,9 +27,13 @@ import ( ) func (ctl *DeploymentCtl) generateObject(item v1.Deployment) *Deployment { - var app string - var status string + var app, status, displayName string var updateTime time.Time + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + name := item.Name namespace := item.Namespace availablePodNum := item.Status.AvailableReplicas @@ -41,9 +45,13 @@ func (ctl *DeploymentCtl) generateObject(item v1.Deployment) *Deployment { app = release + "/" + chart } - for _, conditon := range item.Status.Conditions { - if conditon.Type == "Available" { - updateTime = conditon.LastUpdateTime.Time + for _, condition := range item.Status.Conditions { + if updateTime.IsZero() { + updateTime = condition.LastUpdateTime.Time + } else { + if updateTime.Before(condition.LastUpdateTime.Time) { + updateTime = condition.LastUpdateTime.Time + } } } if updateTime.IsZero() { @@ -60,8 +68,18 @@ func (ctl *DeploymentCtl) generateObject(item v1.Deployment) *Deployment { } } - return &Deployment{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum, - App: app, UpdateTime: updateTime, Status: status, Annotation: Annotation{item.Annotations}} + return &Deployment{ + Namespace: namespace, + Name: name, + Available: availablePodNum, + Desire: desirePodNum, + App: app, + UpdateTime: updateTime, + Status: status, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Spec.Selector.MatchLabels}, + DisplayName: displayName, + } } func (ctl *DeploymentCtl) Name() string { @@ -138,14 +156,21 @@ func (ctl *DeploymentCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *DeploymentCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *DeploymentCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Deployment var object Deployment var total int - order := "updateTime desc" + if len(order) == 0 { + order = "updateTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } + +func (ctl *DeploymentCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/ingresses.go b/pkg/models/controllers/ingresses.go index 74b6704c0..981c4af43 100644 --- a/pkg/models/controllers/ingresses.go +++ b/pkg/models/controllers/ingresses.go @@ -30,9 +30,16 @@ import ( ) func (ctl *IngressCtl) generateObject(item v1beta1.Ingress) *Ingress { + + var ip, tls, displayName string + name := item.Name namespace := item.Namespace - var ip, tls string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + createTime := item.CreationTimestamp.Time if createTime.IsZero() { createTime = time.Now() @@ -63,7 +70,17 @@ func (ctl *IngressCtl) generateObject(item v1beta1.Ingress) *Ingress { ruleStr, _ := json.Marshal(ingRules) - object := &Ingress{Namespace: namespace, Name: name, TlsTermination: tls, Ip: ip, CreateTime: createTime, Annotation: Annotation{item.Annotations}, Rules: string(ruleStr)} + object := &Ingress{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + TlsTermination: tls, + Ip: ip, + CreateTime: createTime, + Annotation: MapString{item.Annotations}, + Rules: string(ruleStr), + Labels: MapString{item.Labels}, + } return object } @@ -143,25 +160,21 @@ func (ctl *IngressCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *IngressCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *IngressCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Ingress var object Ingress var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } -//func (ctl *IngressCtl) Count(namespace string) int { -// var count int -// db := ctl.DB -// if len(namespace) == 0 { -// db.Model(&Ingress{}).Count(&count) -// } else { -// db.Model(&Ingress{}).Where("namespace = ?", namespace).Count(&count) -// } -// return count -//} +func (ctl *IngressCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/jobs.go b/pkg/models/controllers/jobs.go new file mode 100644 index 000000000..9e93a1e2a --- /dev/null +++ b/pkg/models/controllers/jobs.go @@ -0,0 +1,276 @@ +/* +Copyright 2018 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 controllers + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + "kubesphere.io/kubesphere/pkg/client" +) + +var k8sClient *kubernetes.Clientset + +func (ctl *JobCtl) generateObject(item v1.Job) *Job { + var status, displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + + name := item.Name + namespace := item.Namespace + succeedPodNum := item.Status.Succeeded + desirePodNum := *item.Spec.Completions + createTime := item.CreationTimestamp.Time + updteTime := createTime + for _, condition := range item.Status.Conditions { + if condition.Type == "Failed" && condition.Status == "True" { + status = Failed + } + + if condition.Type == "Complete" && condition.Status == "True" { + status = Completed + } + + if updteTime.Before(condition.LastProbeTime.Time) { + updteTime = condition.LastProbeTime.Time + } + + if updteTime.Before(condition.LastTransitionTime.Time) { + updteTime = condition.LastTransitionTime.Time + } + } + + if desirePodNum > succeedPodNum && len(status) == 0 { + status = Running + } + + object := &Job{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + Desire: desirePodNum, + Completed: succeedPodNum, + UpdateTime: updteTime, + CreateTime: createTime, + Status: status, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Labels}, + } + + return object +} + +func (ctl *JobCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *JobCtl) sync(stopChan chan struct{}) { + db := ctl.DB + + if db.HasTable(&Job{}) { + db.DropTable(&Job{}) + } + + db = db.CreateTable(&Job{}) + + ctl.initListerAndInformer() + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Error(err) + return + } + + for _, item := range list { + obj := ctl.generateObject(*item) + db.Create(obj) + } + + ctl.informer.Run(stopChan) +} + +func (ctl *JobCtl) total() int { + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Errorf("count %s falied, reason:%s", err, ctl.Name()) + return 0 + } + return len(list) +} + +func (ctl *JobCtl) initListerAndInformer() { + db := ctl.DB + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + ctl.lister = informerFactory.Batch().V1().Jobs().Lister() + + informer := informerFactory.Batch().V1().Jobs().Informer() + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + + object := obj.(*v1.Job) + mysqlObject := ctl.generateObject(*object) + db.Create(mysqlObject) + }, + UpdateFunc: func(old, new interface{}) { + object := new.(*v1.Job) + mysqlObject := ctl.generateObject(*object) + db.Save(mysqlObject) + }, + DeleteFunc: func(obj interface{}) { + var item Job + object := obj.(*v1.Job) + db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&item) + db.Delete(item) + + }, + }) + + ctl.informer = informer +} + +func (ctl *JobCtl) CountWithConditions(conditions string) int { + var object Job + + return countWithConditions(ctl.DB, conditions, &object) +} + +func (ctl *JobCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + var list []Job + var object Job + var total int + + if len(order) == 0 { + order = "updateTime desc" + } + + listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) + + return total, list, nil +} + +func (ctl *JobCtl) Lister() interface{} { + + return ctl.lister +} + +func getRevisions(job v1.Job) (JobRevisions, error) { + revisions := make(JobRevisions) + + if _, exist := job.Annotations["revisions"]; exist { + revisionsStr := job.Annotations["revisions"] + + err := json.Unmarshal([]byte(revisionsStr), &revisions) + if err != nil { + glog.Errorf("failed to rerun job %s, reason: %s", err, err) + return nil, fmt.Errorf("failed to rerun job %s", job.Name) + } + } + + return revisions, nil +} + +func getStatus(item *v1.Job) JobStatus { + var status JobStatus + for _, condition := range item.Status.Conditions { + if condition.Type == "Failed" && condition.Status == "True" { + status.Status = Failed + status.Reasons = append(status.Reasons, condition.Reason) + status.Messages = append(status.Messages, condition.Message) + } + + if condition.Type == "Complete" && condition.Status == "True" { + status.Status = Completed + } + } + + if len(status.Status) == 0 { + status.Status = Unfinished + } + + status.DesirePodNum = *item.Spec.Completions + status.Succeed = item.Status.Succeeded + status.Failed = item.Status.Failed + status.StartTime = item.Status.StartTime.Time + if item.Status.CompletionTime != nil { + status.CompletionTime = item.Status.CompletionTime.Time + } + + return status +} + +func deleteJob(namespace, job string) error { + deletePolicy := metav1.DeletePropagationBackground + err := k8sClient.BatchV1().Jobs(namespace).Delete(job, &metav1.DeleteOptions{PropagationPolicy: &deletePolicy}) + return err +} + +func JobReRun(namespace, jobName string) (string, error) { + k8sClient = client.NewK8sClient() + job, err := k8sClient.BatchV1().Jobs(namespace).Get(jobName, metav1.GetOptions{}) + if err != nil { + return "", err + } + newJob := *job + newJob.ResourceVersion = "" + newJob.Status = v1.JobStatus{} + newJob.ObjectMeta.UID = "" + delete(newJob.Spec.Selector.MatchLabels, "controller-uid") + delete(newJob.Spec.Template.ObjectMeta.Labels, "controller-uid") + + revisions, err := getRevisions(*job) + + if err != nil { + return "", err + } + + index := len(revisions) + 1 + value := getStatus(job) + revisions[index] = value + + revisionsByte, err := json.Marshal(revisions) + if err != nil { + glog.Errorf("failed to rerun job %s, reason: %s", err, err) + return "", fmt.Errorf("failed to rerun job %s", jobName) + } + + newJob.Annotations["revisions"] = string(revisionsByte) + + err = deleteJob(job.Namespace, job.Name) + if err != nil { + glog.Errorf("failed to rerun job %s, reason: %s", err, err) + return "", fmt.Errorf("failed to rerun job %s", jobName) + } + + _, err = k8sClient.BatchV1().Jobs(namespace).Create(&newJob) + if err != nil { + glog.Errorf("failed to rerun job %s, reason: %s", err, err) + return "", fmt.Errorf("failed to rerun job %s", jobName) + } + + return "succeed", nil +} diff --git a/pkg/models/controllers/namespaces.go b/pkg/models/controllers/namespaces.go index 1a2622305..dbb6ec5b3 100644 --- a/pkg/models/controllers/namespaces.go +++ b/pkg/models/controllers/namespaces.go @@ -227,6 +227,11 @@ func (ctl *NamespaceCtl) createRoleAndRuntime(item v1.Namespace) { } func (ctl *NamespaceCtl) generateObject(item v1.Namespace) *Namespace { + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } name := item.Name createTime := item.CreationTimestamp.Time @@ -236,7 +241,13 @@ func (ctl *NamespaceCtl) generateObject(item v1.Namespace) *Namespace { createTime = time.Now() } - object := &Namespace{Name: name, CreateTime: createTime, Status: status, Annotation: Annotation{item.Annotations}} + object := &Namespace{ + Name: name, + DisplayName: displayName, + CreateTime: createTime, + Status: status, + Annotation: MapString{item.Annotations}, + } return object } @@ -320,12 +331,14 @@ func (ctl *NamespaceCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *NamespaceCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *NamespaceCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Namespace var object Namespace var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) @@ -333,7 +346,7 @@ func (ctl *NamespaceCtl) ListWithConditions(conditions string, paging *Paging) ( for index := range list { usage, err := ctl.GetNamespaceQuota(list[index].Name) if err == nil { - list[index].Usaeg = usage + list[index].Usage = usage } } } @@ -350,7 +363,7 @@ func (ctl *NamespaceCtl) GetNamespaceQuota(namespace string) (v1.ResourceList, e usage := make(v1.ResourceList) - resourceList := []string{Daemonsets, Deployments, Ingresses, Roles, Services, Statefulsets, PersistentVolumeClaim, Pods} + resourceList := []string{Daemonsets, Deployments, Ingresses, Roles, Services, Statefulsets, PersistentVolumeClaim, Pods, Jobs, Cronjobs} for _, resourceName := range resourceList { used := getUsage(namespace, resourceName) var quantity resource.Quantity @@ -365,3 +378,8 @@ func (ctl *NamespaceCtl) GetNamespaceQuota(namespace string) (v1.ResourceList, e usage["runningPods"] = quantity return usage, nil } + +func (ctl *NamespaceCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/nodes.go b/pkg/models/controllers/nodes.go new file mode 100644 index 000000000..5da5d6c8d --- /dev/null +++ b/pkg/models/controllers/nodes.go @@ -0,0 +1,183 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "strings" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" +) + +const nodeRole = "role" + +func (ctl *NodeCtl) generateObject(item v1.Node) *Node { + var status, ip, role, displayName, msgStr string + var msg []string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + + name := item.Name + createTime := item.ObjectMeta.CreationTimestamp.Time + annotation := item.Annotations + + if _, exist := item.Labels[nodeRole]; exist { + role = item.Labels[nodeRole] + } + + for _, condition := range item.Status.Conditions { + if condition.Type == "Ready" { + if condition.Status == "True" { + status = Running + } else { + status = Error + } + + } else { + if condition.Status == "True" { + msg = append(msg, condition.Reason) + } + } + } + + if len(msg) > 0 { + msgStr = strings.Join(msg, ",") + if status == Running { + status = Warning + } + } + + for _, address := range item.Status.Addresses { + if address.Type == "InternalIP" { + ip = address.Address + } + } + + object := &Node{ + Name: name, + DisplayName: displayName, + Ip: ip, + Status: status, + CreateTime: createTime, + Annotation: MapString{annotation}, + Taints: Taints{item.Spec.Taints}, + Msg: msgStr, + Role: role, + Labels: MapString{item.Labels}} + + return object +} + +func (ctl *NodeCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *NodeCtl) sync(stopChan chan struct{}) { + db := ctl.DB + + if db.HasTable(&Node{}) { + db.DropTable(&Node{}) + } + + db = db.CreateTable(&Node{}) + + ctl.initListerAndInformer() + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Error(err) + return + } + + for _, item := range list { + obj := ctl.generateObject(*item) + db.Create(obj) + } + + ctl.informer.Run(stopChan) +} + +func (ctl *NodeCtl) total() int { + list, err := ctl.lister.List(labels.Everything()) + if err != nil { + glog.Errorf("count %s falied, reason:%s", err, ctl.Name()) + return 0 + } + return len(list) +} + +func (ctl *NodeCtl) initListerAndInformer() { + db := ctl.DB + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + + ctl.lister = informerFactory.Core().V1().Nodes().Lister() + + informer := informerFactory.Core().V1().Nodes().Informer() + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + object := obj.(*v1.Node) + mysqlObject := ctl.generateObject(*object) + db.Create(mysqlObject) + }, + UpdateFunc: func(old, new interface{}) { + object := new.(*v1.Node) + mysqlObject := ctl.generateObject(*object) + db.Save(mysqlObject) + + }, + DeleteFunc: func(obj interface{}) { + var item Node + object := obj.(*v1.Node) + db.Where("name=? ", object.Name, object.Namespace).Find(&item) + db.Delete(item) + }, + }) + + ctl.informer = informer +} + +func (ctl *NodeCtl) CountWithConditions(conditions string) int { + var object Node + + return countWithConditions(ctl.DB, conditions, &object) +} + +func (ctl *NodeCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + var list []Node + var object Node + var total int + + if len(order) == 0 { + order = "createTime desc" + } + + listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) + + return total, list, nil +} + +func (ctl *NodeCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/pods.go b/pkg/models/controllers/pods.go index 62515ecad..dc3caa290 100644 --- a/pkg/models/controllers/pods.go +++ b/pkg/models/controllers/pods.go @@ -193,8 +193,19 @@ func (ctl *PodCtl) generateObject(item v1.Pod) *Pod { containers = append(containers, container) } - object := &Pod{Namespace: namespace, Name: name, Node: nodeName, PodIp: podIp, Status: status, NodeIp: nodeIp, - CreateTime: createTime, Annotation: Annotation{item.Annotations}, Containers: containers, RestartCount: restartCount} + object := &Pod{ + Namespace: namespace, + Name: name, + Node: nodeName, + PodIp: podIp, + Status: status, + NodeIp: nodeIp, + CreateTime: createTime, + Annotation: MapString{item.Annotations}, + Containers: containers, + RestartCount: restartCount, + Labels: MapString{item.Labels}, + } return object } @@ -277,25 +288,21 @@ func (ctl *PodCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *PodCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *PodCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Pod var object Pod var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } -//func (ctl *PodCtl) Count(namespace string) int { -// var count int -// db := ctl.DB -// if len(namespace) == 0 { -// db.Model(&Pod{}).Count(&count) -// } else { -// db.Model(&Pod{}).Where("namespace = ?", namespace).Count(&count) -// } -// return count -//} +func (ctl *PodCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/pvcs.go b/pkg/models/controllers/pvcs.go index 6295cb9f4..38a20dfc8 100644 --- a/pkg/models/controllers/pvcs.go +++ b/pkg/models/controllers/pvcs.go @@ -30,6 +30,12 @@ import ( ) func (ctl *PvcCtl) generateObject(item *v1.PersistentVolumeClaim) *Pvc { + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + name := item.Name namespace := item.Namespace status := fmt.Sprintf("%s", item.Status.Phase) @@ -58,8 +64,18 @@ func (ctl *PvcCtl) generateObject(item *v1.PersistentVolumeClaim) *Pvc { accessModeStr = strings.Join(accessModeList, ",") - object := &Pvc{Namespace: namespace, Name: name, Status: status, Capacity: capacity, - AccessMode: accessModeStr, StorageClassName: storageClass, CreateTime: createTime, Annotation: Annotation{item.Annotations}} + object := &Pvc{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + Status: status, + Capacity: capacity, + AccessMode: accessModeStr, + StorageClassName: storageClass, + CreateTime: createTime, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Labels}, + } return object } @@ -138,12 +154,14 @@ func (ctl *PvcCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *PvcCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *PvcCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Pvc var object Pvc var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) @@ -163,13 +181,7 @@ func (ctl *PvcCtl) ListWithConditions(conditions string, paging *Paging) (int, i return total, list, nil } -//func (ctl *PvcCtl) Count(namespace string) int { -// var count int -// db := ctl.DB -// if len(namespace) == 0 { -// db.Model(&Pvc{}).Count(&count) -// } else { -// db.Model(&Pvc{}).Where("namespace = ?", namespace).Count(&count) -// } -// return count -//} +func (ctl *PvcCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/replicasets.go b/pkg/models/controllers/replicasets.go new file mode 100644 index 000000000..3c85761c2 --- /dev/null +++ b/pkg/models/controllers/replicasets.go @@ -0,0 +1,64 @@ +/* +Copyright 2018 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 controllers + +import ( + "time" + + "k8s.io/client-go/informers" +) + +func (ctl *ReplicaSetCtl) Name() string { + return ctl.CommonAttribute.Name +} + +func (ctl *ReplicaSetCtl) sync(stopChan chan struct{}) { + + ctl.initListerAndInformer() + ctl.informer.Run(stopChan) +} + +func (ctl *ReplicaSetCtl) total() int { + + return 0 +} + +func (ctl *ReplicaSetCtl) initListerAndInformer() { + + informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) + + ctl.lister = informerFactory.Apps().V1().ReplicaSets().Lister() + + informer := informerFactory.Apps().V1().ReplicaSets().Informer() + + ctl.informer = informer +} + +func (ctl *ReplicaSetCtl) CountWithConditions(conditions string) int { + + return 0 +} + +func (ctl *ReplicaSetCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { + + return 0, nil, nil +} + +func (ctl *ReplicaSetCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/roles.go b/pkg/models/controllers/roles.go index 5f6020b2b..8116ae5f4 100644 --- a/pkg/models/controllers/roles.go +++ b/pkg/models/controllers/roles.go @@ -28,6 +28,12 @@ import ( ) func (ctl *RoleCtl) generateObject(item v1.Role) *Role { + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + name := item.Name if strings.HasPrefix(name, systemPrefix) { return nil @@ -38,7 +44,13 @@ func (ctl *RoleCtl) generateObject(item v1.Role) *Role { createTime = time.Now() } - object := &Role{Namespace: namespace, Name: name, CreateTime: createTime, Annotation: Annotation{item.Annotations}} + object := &Role{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + CreateTime: createTime, + Annotation: MapString{item.Annotations}, + } return object } @@ -132,21 +144,21 @@ func (ctl *RoleCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *RoleCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *RoleCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Role var object Role var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } -//func (ctl *RoleCtl) Count(namespace string) int { -// var count int -// db := ctl.DB -// db.Model(&Role{}).Where("namespace = ?", namespace).Count(&count) -// return count -//} +func (ctl *RoleCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/run.go b/pkg/models/controllers/run.go index 4388ab2d3..36eff2001 100644 --- a/pkg/models/controllers/run.go +++ b/pkg/models/controllers/run.go @@ -31,10 +31,9 @@ type resourceControllers struct { k8sClient *kubernetes.Clientset } -var stopChan chan struct{} var ResourceControllers resourceControllers -func (rec *resourceControllers) runContoller(name string) { +func (rec *resourceControllers) runContoller(name string, stopChan chan struct{}) { var ctl Controller attr := CommonAttribute{DB: client.NewDBClient(), K8sClient: rec.k8sClient, stopChan: stopChan, aliveChan: make(chan struct{}), Name: name} @@ -61,6 +60,16 @@ func (rec *resourceControllers) runContoller(name string) { ctl = &NamespaceCtl{CommonAttribute: attr} case StorageClasses: ctl = &StorageClassCtl{CommonAttribute: attr} + case Jobs: + ctl = &JobCtl{CommonAttribute: attr} + case Cronjobs: + ctl = &CronJobCtl{CommonAttribute: attr} + case Nodes: + ctl = &NodeCtl{CommonAttribute: attr} + case Replicasets: + ctl = &ReplicaSetCtl{CommonAttribute: attr} + case ControllerRevisions: + ctl = &ControllerRevisionCtl{CommonAttribute: attr} default: return } @@ -71,6 +80,8 @@ func (rec *resourceControllers) runContoller(name string) { } func dbHealthCheck(db *gorm.DB) { + defer db.Close() + for { count := 0 var err error @@ -79,7 +90,7 @@ func dbHealthCheck(db *gorm.DB) { if err != nil { count++ } - time.Sleep(1 * time.Second) + time.Sleep(5 * time.Second) } if count > 3 { @@ -98,8 +109,8 @@ func Run() { ResourceControllers = resourceControllers{k8sClient: k8sClient, Controllers: make(map[string]Controller)} for _, item := range []string{Deployments, Statefulsets, Daemonsets, PersistentVolumeClaim, Pods, Services, - Ingresses, Roles, ClusterRoles, Namespaces, StorageClasses} { - ResourceControllers.runContoller(item) + Ingresses, Roles, ClusterRoles, Namespaces, StorageClasses, Jobs, Cronjobs, Nodes, Replicasets, ControllerRevisions} { + ResourceControllers.runContoller(item, stopChan) } go dbHealthCheck(client.NewDBClient()) @@ -110,7 +121,7 @@ func Run() { case _, isClose := <-controller.chanAlive(): if !isClose { glog.Errorf("controller %s have stopped, restart it", ctlName) - ResourceControllers.runContoller(ctlName) + ResourceControllers.runContoller(ctlName, stopChan) } default: time.Sleep(5 * time.Second) diff --git a/pkg/models/controllers/services.go b/pkg/models/controllers/services.go index 026278265..c07755f4d 100644 --- a/pkg/models/controllers/services.go +++ b/pkg/models/controllers/services.go @@ -69,12 +69,25 @@ func getExternalIp(item v1.Service) string { } func generateSvcObject(item v1.Service) *Service { + var app string + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } name := item.Name namespace := item.Namespace createTime := item.CreationTimestamp.Time externalIp := getExternalIp(item) serviceType := item.Spec.Type vip := item.Spec.ClusterIP + + release := item.ObjectMeta.Labels["release"] + chart := item.ObjectMeta.Labels["chart"] + + if len(release) > 0 && len(chart) > 0 { + app = release + "/" + chart + } ports := "" var nodePorts []string @@ -118,13 +131,16 @@ func generateSvcObject(item v1.Service) *Service { object := &Service{ Namespace: namespace, Name: name, + DisplayName: displayName, ServiceType: string(serviceType), ExternalIp: externalIp, VirtualIp: vip, CreateTime: createTime, Ports: ports, NodePorts: strings.Join(nodePorts, ","), - Annotation: Annotation{item.Annotations}, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Labels}, + App: app, } return object @@ -209,14 +225,21 @@ func (ctl *ServiceCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *ServiceCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *ServiceCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Service var object Service var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } + +func (ctl *ServiceCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/statefulsets.go b/pkg/models/controllers/statefulsets.go index 372b25466..56f40f897 100644 --- a/pkg/models/controllers/statefulsets.go +++ b/pkg/models/controllers/statefulsets.go @@ -30,6 +30,11 @@ import ( func (ctl *StatefulsetCtl) generateObject(item v1.StatefulSet) *Statefulset { var app string var status string + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } name := item.Name namespace := item.Namespace availablePodNum := item.Status.ReadyReplicas @@ -56,8 +61,18 @@ func (ctl *StatefulsetCtl) generateObject(item v1.StatefulSet) *Statefulset { } } - statefulSetObject := &Statefulset{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum, - App: app, CreateTime: createTime, Status: status, Annotation: Annotation{item.Annotations}} + statefulSetObject := &Statefulset{ + Namespace: namespace, + Name: name, + DisplayName: displayName, + Available: availablePodNum, + Desire: desirePodNum, + App: app, + CreateTime: createTime, + Status: status, + Annotation: MapString{item.Annotations}, + Labels: MapString{item.Spec.Selector.MatchLabels}, + } return statefulSetObject } @@ -137,14 +152,21 @@ func (ctl *StatefulsetCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *StatefulsetCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *StatefulsetCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []Statefulset var object Statefulset var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) return total, list, nil } + +func (ctl *StatefulsetCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/storageclasses.go b/pkg/models/controllers/storageclasses.go index 92bb0f732..705ca32b3 100644 --- a/pkg/models/controllers/storageclasses.go +++ b/pkg/models/controllers/storageclasses.go @@ -30,6 +30,12 @@ import ( func (ctl *StorageClassCtl) generateObject(item v1.StorageClass) *StorageClass { + var displayName string + + if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + displayName = item.Annotations[DisplayName] + } + name := item.Name createTime := item.CreationTimestamp.Time isDefault := false @@ -41,7 +47,13 @@ func (ctl *StorageClassCtl) generateObject(item v1.StorageClass) *StorageClass { createTime = time.Now() } - object := &StorageClass{Name: name, CreateTime: createTime, IsDefault: isDefault, Annotation: Annotation{item.Annotations}} + object := &StorageClass{ + Name: name, + DisplayName: displayName, + CreateTime: createTime, + IsDefault: isDefault, + Annotation: MapString{item.Annotations}, + } return object } @@ -120,12 +132,14 @@ func (ctl *StorageClassCtl) CountWithConditions(conditions string) int { return countWithConditions(ctl.DB, conditions, &object) } -func (ctl *StorageClassCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) { +func (ctl *StorageClassCtl) ListWithConditions(conditions string, paging *Paging, order string) (int, interface{}, error) { var list []StorageClass var object StorageClass var total int - order := "createTime desc" + if len(order) == 0 { + order = "createTime desc" + } listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order) @@ -138,3 +152,8 @@ func (ctl *StorageClassCtl) ListWithConditions(conditions string, paging *Paging return total, list, nil } + +func (ctl *StorageClassCtl) Lister() interface{} { + + return ctl.lister +} diff --git a/pkg/models/controllers/types.go b/pkg/models/controllers/types.go index 355dad600..34e9ab55b 100644 --- a/pkg/models/controllers/types.go +++ b/pkg/models/controllers/types.go @@ -27,6 +27,8 @@ import ( "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" appV1 "k8s.io/client-go/listers/apps/v1" + batchv1 "k8s.io/client-go/listers/batch/v1" + batchv1beta1 "k8s.io/client-go/listers/batch/v1beta1" coreV1 "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/listers/extensions/v1beta1" rbacV1 "k8s.io/client-go/listers/rbac/v1" @@ -35,22 +37,18 @@ import ( ) const ( - resyncCircle = 600 - Stopped = "stopped" - PvcPending = "Pending" - Running = "running" - Updating = "updating" - tablePods = "pods" - tableDeployments = "deployments" - tableDaemonsets = "daemonsets" - tableStatefulsets = "statefulsets" - tableNamespaces = "namespaces" - tableIngresses = "ingresses" - tablePersistentVolumeClaim = "pvcs" - tableRoles = "roles" - tableClusterRoles = "cluster_roles" - tableServices = "services" - tableStorageClasses = "storage_classes" + resyncCircle = 600 + Stopped = "stopped" + PvcPending = "pending" + Running = "running" + Updating = "updating" + Failed = "failed" + Unfinished = "unfinished" + Completed = "completed" + Pause = "pause" + Warning = "warning" + Error = "error" + DisplayName = "displayName" Pods = "pods" Deployments = "deployments" @@ -64,13 +62,18 @@ const ( Services = "services" StorageClasses = "storage-classes" Applications = "applications" + Jobs = "jobs" + Cronjobs = "cronjobs" + Nodes = "nodes" + Replicasets = "replicasets" + ControllerRevisions = "controllerrevisions" ) -type Annotation struct { - Values map[string]string `gorm:"type:TEXT"` +type MapString struct { + Values map[string]string `json:"values" gorm:"type:TEXT"` } -func (annotation *Annotation) Scan(val interface{}) error { +func (annotation *MapString) Scan(val interface{}) error { switch val := val.(type) { case string: return json.Unmarshal([]byte(val), annotation) @@ -82,92 +85,104 @@ func (annotation *Annotation) Scan(val interface{}) error { return nil } -func (annotation Annotation) Value() (driver.Value, error) { +func (annotation MapString) Value() (driver.Value, error) { bytes, err := json.Marshal(annotation) return string(bytes), err } -type Deployment struct { - Name string `gorm:"primary_key" json:"name"` - Namespace string `gorm:"primary_key" json:"namespace"` - App string `json:"app,omitempty"` - - Available int32 `json:"available"` - Desire int32 `json:"desire"` - Status string `json:"status"` - Annotation Annotation `json:"annotations"` - UpdateTime time.Time `gorm:"column:updateTime" json:"updateTime,omitempty"` +type Taints struct { + Values []v1.Taint `json:"values" gorm:"type:TEXT"` } -func (Deployment) TableName() string { - return tableDeployments +func (taints *Taints) Scan(val interface{}) error { + switch val := val.(type) { + case string: + return json.Unmarshal([]byte(val), taints) + case []byte: + return json.Unmarshal(val, taints) + default: + return errors.New("not support") + } + return nil +} + +func (taints Taints) Value() (driver.Value, error) { + bytes, err := json.Marshal(taints) + return string(bytes), err +} + +type Deployment struct { + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace"` + App string `json:"app,omitempty"` + + Available int32 `json:"available"` + Desire int32 `json:"desire"` + Status string `json:"status"` + Labels MapString `json:"labels"` + Annotation MapString `json:"annotations"` + UpdateTime time.Time `gorm:"column:updateTime" json:"updateTime,omitempty"` } type Statefulset struct { - Name string `gorm:"primary_key" json:"name,omitempty"` - Namespace string `gorm:"primary_key" json:"namespace,omitempty"` - App string `json:"app,omitempty"` + Name string `gorm:"primary_key" json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace,omitempty"` + App string `json:"app,omitempty"` - Available int32 `json:"available"` - Desire int32 `json:"desire"` - Status string `json:"status"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (Statefulset) TableName() string { - return tableStatefulsets + Available int32 `json:"available"` + Desire int32 `json:"desire"` + Status string `json:"status"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type Daemonset struct { - Name string `gorm:"primary_key" json:"name,omitempty"` - Namespace string `gorm:"primary_key" json:"namespace,omitempty"` - App string `json:"app,omitempty"` + Name string `gorm:"primary_key" json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace,omitempty"` + App string `json:"app,omitempty"` - Available int32 `json:"available"` - Desire int32 `json:"desire"` - Status string `json:"status"` - NodeSelector string `json:"nodeSelector, omitempty"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (Daemonset) TableName() string { - return tableDaemonsets + Available int32 `json:"available"` + Desire int32 `json:"desire"` + Status string `json:"status"` + NodeSelector string `json:"nodeSelector, omitempty"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type Service struct { Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` Namespace string `gorm:"primary_key" json:"namespace"` - ServiceType string `json:"type,omitempty"` + ServiceType string `gorm:"column:type" json:"type,omitempty"` - VirtualIp string `json:"virtualIp,omitempty"` - ExternalIp string `json:"externalIp,omitempty"` + App string `json:"app,omitempty"` + VirtualIp string `gorm:"column:virtualIp" json:"virtualIp,omitempty"` + ExternalIp string `gorm:"column:externalIp" json:"externalIp,omitempty"` - Ports string `json:"ports,omitempty"` - NodePorts string `json:"nodePorts,omitempty"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (Service) TableName() string { - return tableServices + Ports string `json:"ports,omitempty"` + NodePorts string `gorm:"column:nodePorts" json:"nodePorts,omitempty"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type Pvc struct { - Name string `gorm:"primary_key" json:"name"` - Namespace string `gorm:"primary_key" json:"namespace"` - Status string `json:"status,omitempty"` - Capacity string `json:"capacity,omitempty"` - AccessMode string `json:"accessMode,omitempty"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` - StorageClassName string `gorm:"column:storage_class" json:"storage_class,omitempty"` - InUse bool `gorm:"-" json:"inUse"` -} - -func (Pvc) TableName() string { - return tablePersistentVolumeClaim + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace"` + Status string `json:"status,omitempty"` + Capacity string `json:"capacity,omitempty"` + AccessMode string `gorm:"column:accessMode" json:"accessMode,omitempty"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` + StorageClassName string `gorm:"column:storage_class" json:"storage_class,omitempty"` + InUse bool `gorm:"column:inUse" json:"inUse"` } type ingressRule struct { @@ -178,17 +193,15 @@ type ingressRule struct { } type Ingress struct { - Name string `gorm:"primary_key" json:"name"` - Namespace string `gorm:"primary_key" json:"namespace"` - Ip string `json:"ip,omitempty"` - Rules string `gorm:"type:text" json:"rules, omitempty"` - TlsTermination string `json:"tlsTermination,omitempty"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (Ingress) TableName() string { - return tableIngresses + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace"` + Ip string `json:"ip,omitempty"` + Rules string `gorm:"type:text" json:"rules, omitempty"` + TlsTermination string `gorm:"column:tlsTermination" json:"tlsTermination,omitempty"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type Pod struct { @@ -196,10 +209,11 @@ type Pod struct { Namespace string `gorm:"primary_key" json:"namespace"` Status string `json:"status,omitempty"` Node string `json:"node,omitempty"` - NodeIp string `json:"nodeIp,omitempty"` - PodIp string `json:"podIp,omitempty"` + NodeIp string `gorm:"column:nodeIp" json:"nodeIp,omitempty"` + PodIp string `gorm:"column:podIp" json:"podIp,omitempty"` Containers Containers `gorm:"type:text" json:"containers,omitempty"` - Annotation Annotation `json:"annotations"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` RestartCount int `json:"restartCount"` CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } @@ -230,61 +244,97 @@ func (containers Containers) Value() (driver.Value, error) { return string(bytes), err } -func (Pod) TableName() string { - return tablePods -} - type Role struct { - Name string `gorm:"primary_key" json:"name"` - Namespace string `gorm:"primary_key" json:"namespace"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (Role) TableName() string { - return tableRoles + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace"` + Annotation MapString `json:"annotations"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type ClusterRole struct { - Name string `gorm:"primary_key" json:"name"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` -} - -func (ClusterRole) TableName() string { - return tableClusterRoles + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Annotation MapString `json:"annotations"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` } type Namespace struct { - Name string `gorm:"primary_key" json:"name"` - Creator string `json:"creator,omitempty"` - Status string `json:"status"` + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Creator string `json:"creator,omitempty"` + Status string `json:"status"` Descrition string `json:"description,omitempty"` - Annotation Annotation `json:"annotations"` + Annotation MapString `json:"annotations"` CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` - Usaeg v1.ResourceList `gorm:"-" json:"usage,omitempty"` -} - -func (Namespace) TableName() string { - return tableNamespaces + Usage v1.ResourceList `gorm:"-" json:"usage,omitempty"` } type StorageClass struct { - Name string `gorm:"primary_key" json:"name"` - Creator string `json:"creator,omitempty"` - Annotation Annotation `json:"annotations"` - CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` - IsDefault bool `json:"default"` - Count int `json:"count"` + Name string `gorm:"primary_key" json:"name"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Creator string `json:"creator,omitempty"` + Annotation MapString `json:"annotations"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` + IsDefault bool `json:"default"` + Count int `json:"count"` } -func (StorageClass) TableName() string { - return tableStorageClasses +type JobRevisions map[int]JobStatus + +type JobStatus struct { + Status string `json:"status"` + Reasons []string `json:"reasons"` + Messages []string `json:"messages"` + Succeed int32 `json:"succeed"` + DesirePodNum int32 `json:"desire"` + Failed int32 `json:"failed"` + StartTime time.Time `json:"start-time"` + CompletionTime time.Time `json:"completion-time"` } +type Job struct { + Name string `gorm:"primary_key" json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace,omitempty"` + + Completed int32 `json:"completed"` + Desire int32 `json:"desire"` + Status string `json:"status"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` + UpdateTime time.Time `gorm:"column:updateTime" json:"updateTime,omitempty"` +} + +type CronJob struct { + Name string `gorm:"primary_key" json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Namespace string `gorm:"primary_key" json:"namespace,omitempty"` + + Active int `json:"active"` + Schedule string `json:"schedule"` + Status string `json:"status"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + LastScheduleTime *time.Time `gorm:"column:lastScheduleTime" json:"lastScheduleTime,omitempty"` +} + +type Node struct { + Name string `gorm:"primary_key" json:"name,omitempty"` + DisplayName string `json:"displayName,omitempty" gorm:"column:displayName"` + Ip string `json:"ip"` + Status string `json:"status"` + Annotation MapString `json:"annotations"` + Labels MapString `json:"labels"` + Taints Taints `json:"taints"` + Msg string `json:"msg"` + Role string `json:"role"` + CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"` +} type Paging struct { - Limit, Offset int + Limit, Offset, Page int } type Controller interface { @@ -295,7 +345,9 @@ type Controller interface { initListerAndInformer() sync(stopChan chan struct{}) Name() string - ListWithConditions(condition string, paging *Paging) (int, interface{}, error) + CloseDB() + Lister() interface{} + ListWithConditions(condition string, paging *Paging, order string) (int, interface{}, error) } type CommonAttribute struct { @@ -316,6 +368,11 @@ func (ca *CommonAttribute) chanAlive() chan struct{} { return ca.aliveChan } +func (ca *CommonAttribute) CloseDB() { + + ca.DB.Close() +} + type DeploymentCtl struct { CommonAttribute lister appV1.DeploymentLister @@ -381,3 +438,33 @@ type ClusterRoleCtl struct { informer cache.SharedIndexInformer CommonAttribute } + +type JobCtl struct { + lister batchv1.JobLister + informer cache.SharedIndexInformer + CommonAttribute +} + +type CronJobCtl struct { + lister batchv1beta1.CronJobLister + informer cache.SharedIndexInformer + CommonAttribute +} + +type NodeCtl struct { + lister coreV1.NodeLister + informer cache.SharedIndexInformer + CommonAttribute +} + +type ReplicaSetCtl struct { + lister appV1.ReplicaSetLister + informer cache.SharedIndexInformer + CommonAttribute +} + +type ControllerRevisionCtl struct { + lister appV1.ControllerRevisionLister + informer cache.SharedIndexInformer + CommonAttribute +} diff --git a/pkg/models/quota.go b/pkg/models/quota.go index 5cc29ba70..92025c50a 100644 --- a/pkg/models/quota.go +++ b/pkg/models/quota.go @@ -40,12 +40,15 @@ const ( persistentvolumeclaimsKey = "persistentvolumeclaims" storageClassesKey = "count/storageClass" namespaceKey = "count/namespace" + jobsKey = "count/jobs.batch" + cronJobsKey = "count/cronjobs.batch" ) var resourceMap = map[string]string{daemonsetsKey: controllers.Daemonsets, deploymentsKey: controllers.Deployments, ingressKey: controllers.Ingresses, rolesKey: controllers.Roles, servicesKey: controllers.Services, statefulsetsKey: controllers.Statefulsets, persistentvolumeclaimsKey: controllers.PersistentVolumeClaim, podsKey: controllers.Pods, - namespaceKey: controllers.Namespaces, storageClassesKey: controllers.StorageClasses, clusterRolesKey: controllers.ClusterRoles} + namespaceKey: controllers.Namespaces, storageClassesKey: controllers.StorageClasses, clusterRolesKey: controllers.ClusterRoles, + jobsKey: controllers.Jobs, cronJobsKey: controllers.Cronjobs} type ResourceQuota struct { NameSpace string `json:"namespace"` diff --git a/pkg/models/resources.go b/pkg/models/resources.go index b44774e8d..b281bc907 100644 --- a/pkg/models/resources.go +++ b/pkg/models/resources.go @@ -1,3 +1,19 @@ +/* +Copyright 2018 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 models import ( @@ -24,11 +40,19 @@ type ResourceList struct { Items interface{} `json:"items,omitempty"` } +type searchConditions struct { + match map[string]string + fuzzy map[string]string + matchOr map[string]string + fuzzyOr map[string]string +} + func getController(resource string) (controllers.Controller, error) { switch resource { case controllers.Deployments, controllers.Statefulsets, controllers.Daemonsets, controllers.Ingresses, controllers.PersistentVolumeClaim, controllers.Roles, controllers.ClusterRoles, controllers.Services, - controllers.Pods, controllers.Namespaces, controllers.StorageClasses: + controllers.Pods, controllers.Namespaces, controllers.StorageClasses, controllers.Jobs, controllers.Cronjobs, + controllers.Nodes: return controllers.ResourceControllers.Controllers[resource], nil default: @@ -38,20 +62,21 @@ func getController(resource string) (controllers.Controller, error) { } -func getConditions(str string) (map[string]string, map[string]string, error) { +func getConditions(str string) (*searchConditions, map[string]string, error) { match := make(map[string]string) fuzzy := make(map[string]string) + matchOr := make(map[string]string) + fuzzyOr := make(map[string]string) + orderField := make(map[string]string) + if len(str) == 0 { return nil, nil, nil } - list := strings.Split(str, ",") - for _, item := range list { - if strings.Count(item, "=") >= 2 { - return nil, nil, errors.New("invalid condition input, invalid character \"=\"") - } - if strings.Count(item, "~") >= 2 { - return nil, nil, errors.New("invalid condition input, invalid character \"~\"") + conditions := strings.Split(str, ",") + for _, item := range conditions { + if strings.Count(item, "=") >= 2 || strings.Count(item, "~") >= 2 { + return nil, nil, errors.New("invalid condition input") } if strings.Count(item, "=") == 1 { @@ -59,7 +84,17 @@ func getConditions(str string) (map[string]string, map[string]string, error) { if len(kvs) < 2 || len(kvs[1]) == 0 { return nil, nil, errors.New("invalid condition input") } - match[kvs[0]] = kvs[1] + + if !strings.Contains(kvs[0], "|") { + match[kvs[0]] = kvs[1] + } else { + multiFields := strings.Split(kvs[0], "|") + for _, filed := range multiFields { + if len(filed) > 0 { + matchOr[filed] = kvs[1] + } + } + } continue } @@ -68,86 +103,130 @@ func getConditions(str string) (map[string]string, map[string]string, error) { if len(kvs) < 2 || len(kvs[1]) == 0 { return nil, nil, errors.New("invalid condition input") } - fuzzy[kvs[0]] = kvs[1] + if !strings.Contains(kvs[0], "|") { + fuzzy[kvs[0]] = kvs[1] + } else { + multiFields := strings.Split(kvs[0], "|") + if len(multiFields) > 1 && len(multiFields[1]) > 0 { + orderField[multiFields[0]] = kvs[1] + } + for _, filed := range multiFields { + if len(filed) > 0 { + fuzzyOr[filed] = kvs[1] + } + } + } continue } return nil, nil, errors.New("invalid condition input") - } - return match, fuzzy, nil + + return &searchConditions{match: match, fuzzyOr: fuzzyOr, matchOr: matchOr, fuzzy: fuzzy}, orderField, nil } -func getPaging(resourceName, pagingStr string) (*controllers.Paging, map[string]int, error) { - defaultPaging := &controllers.Paging{Limit: 10, Offset: 0} - defautlPagingMap := map[string]int{"page": 1, "limit": 10} +func getPaging(resourceName, pagingStr string) (*controllers.Paging, error) { + defaultPaging := &controllers.Paging{Limit: 10, Offset: 0, Page: 1} + paging := controllers.Paging{} + if resourceName == controllers.Namespaces { defaultPaging = nil - defautlPagingMap = map[string]int{"page": 0, "limit": 0} } - pagingMap := make(map[string]int) if len(pagingStr) == 0 { - return defaultPaging, defautlPagingMap, nil + return defaultPaging, nil } list := strings.Split(pagingStr, ",") for _, item := range list { kvs := strings.Split(item, "=") if len(kvs) < 2 { - return nil, nil, errors.New("invalid Paging input") + return nil, errors.New("invalid Paging input") } value, err := strconv.Atoi(kvs[1]) - if err != nil { - return nil, nil, errors.New("invalid Paging input") + if err != nil || value <= 0 { + return nil, errors.New("invalid Paging input") } - pagingMap[kvs[0]] = value + if kvs[0] == limit { + paging.Limit = value + } + + if kvs[0] == page { + paging.Page = value + } } - if pagingMap[limit] <= 0 || pagingMap[page] <= 0 { - return nil, nil, errors.New("invalid Paging input") + if paging.Limit > 0 && paging.Page > 0 { + paging.Offset = (paging.Page - 1) * paging.Limit + return &paging, nil } - if pagingMap[limit] > 0 && pagingMap[page] > 0 { - offset := (pagingMap[page] - 1) * pagingMap[limit] - return &controllers.Paging{Limit: pagingMap[limit], Offset: offset}, pagingMap, nil - } - - return defaultPaging, defautlPagingMap, nil + return defaultPaging, nil } -func ListResource(resourceName, conditonSrt, pagingStr string) (*ResourceList, error) { - match, fuzzy, err := getConditions(conditonSrt) +func generateOrder(orderField map[string]string, order string) string { + if len(orderField) == 0 { + return order + } + + var str string + for k, v := range orderField { + if len(str) > 0 { + str = fmt.Sprintf("%s, (%s like '%%%s%%')", str, k, v) + } else { + str = fmt.Sprintf("(%s like '%%%s%%')", k, v) + } + + } + + if len(order) == 0 { + return fmt.Sprintf("%s desc", str) + } else { + return fmt.Sprintf("%s, %s", str, order) + } +} + +func ListResource(resourceName, conditonSrt, pagingStr, order string) (*ResourceList, error) { + conditions, OrderFields, err := getConditions(conditonSrt) if err != nil { return nil, err } - paging, pagingMap, err := getPaging(resourceName, pagingStr) + order = generateOrder(OrderFields, order) + conditionStr := generateConditionStr(conditions) + + paging, err := getPaging(resourceName, pagingStr) if err != nil { return nil, err } - conditionStr := generateConditionStr(match, fuzzy) - ctl, err := getController(resourceName) if err != nil { return nil, err } - total, items, err := ctl.ListWithConditions(conditionStr, paging) + total, items, err := ctl.ListWithConditions(conditionStr, paging, order) if err != nil { return nil, err } - return &ResourceList{Total: total, Items: items, Page: pagingMap[page], Limit: pagingMap[limit]}, nil + if paging != nil { + return &ResourceList{Total: total, Items: items, Page: paging.Page, Limit: paging.Limit}, nil + } else { + return &ResourceList{Total: total, Items: items}, nil + } } -func generateConditionStr(match map[string]string, fuzzy map[string]string) string { +func generateConditionStr(conditions *searchConditions) string { conditionStr := "" - for k, v := range match { + if conditions == nil { + return conditionStr + } + + for k, v := range conditions.match { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s = \"%s\" ", k, v) } else { @@ -155,7 +234,7 @@ func generateConditionStr(match map[string]string, fuzzy map[string]string) stri } } - for k, v := range fuzzy { + for k, v := range conditions.fuzzy { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s like '%%%s%%' ", k, v) } else { @@ -163,6 +242,22 @@ func generateConditionStr(match map[string]string, fuzzy map[string]string) stri } } + for k, v := range conditions.matchOr { + if len(conditionStr) == 0 { + conditionStr = fmt.Sprintf("%s = \"%s\" ", k, v) + } else { + conditionStr = fmt.Sprintf("%s OR %s = \"%s\" ", conditionStr, k, v) + } + } + + for k, v := range conditions.fuzzyOr { + if len(conditionStr) == 0 { + conditionStr = fmt.Sprintf("%s like '%%%s%%' ", k, v) + } else { + conditionStr = fmt.Sprintf("%s OR %s like '%%%s%%' ", conditionStr, k, v) + } + } + return conditionStr } @@ -182,9 +277,9 @@ func GetNamespacesResourceStatus(namespace string) (*workLoadStatus, error) { notReadyStatus = controllers.PvcPending } if len(namespace) > 0 { - status, err = ListResource(resource, fmt.Sprintf("status=%s,namespace=%s", notReadyStatus, namespace), "") + status, err = ListResource(resource, fmt.Sprintf("status=%s,namespace=%s", notReadyStatus, namespace), "", "") } else { - status, err = ListResource(resource, fmt.Sprintf("status=%s", notReadyStatus), "") + status, err = ListResource(resource, fmt.Sprintf("status=%s", notReadyStatus), "", "") } if err != nil { @@ -208,25 +303,30 @@ func GetApplication(clusterId string) (interface{}, error) { return ctl.GetApp(clusterId) } -func ListApplication(runtimeId, conditions, pagingStr string) (*ResourceList, error) { - paging, pagingMap, err := getPaging(controllers.Applications, pagingStr) +func ListApplication(runtimeId, conditionStr, pagingStr string) (*ResourceList, error) { + paging, err := getPaging(controllers.Applications, pagingStr) if err != nil { return nil, err } - match, fuzzy, err := getConditions(conditions) + conditions, _, err := getConditions(conditionStr) if err != nil { glog.Error(err) return nil, err } + if conditions == nil { + conditions = &searchConditions{} + } + ctl := &controllers.ApplicationCtl{OpenpitrixAddr: options.ServerOptions.GetOpAddress()} - total, items, err := ctl.ListApplication(runtimeId, match, fuzzy, paging) + + total, items, err := ctl.ListApplication(runtimeId, conditions.match, conditions.fuzzy, paging) if err != nil { glog.Errorf("get application list failed, reason: %s", err) return nil, err } - return &ResourceList{Total: total, Items: items, Page: pagingMap[page], Limit: pagingMap[limit]}, nil + return &ResourceList{Total: total, Items: items, Page: paging.Page, Limit: paging.Limit}, nil } diff --git a/pkg/models/revisions.go b/pkg/models/revisions.go new file mode 100644 index 000000000..8199db738 --- /dev/null +++ b/pkg/models/revisions.go @@ -0,0 +1,112 @@ +/* +Copyright 2018 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 models + +import ( + "fmt" + "strconv" + + "github.com/golang/glog" + "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + appsV1 "k8s.io/client-go/listers/apps/v1" + + "kubesphere.io/kubesphere/pkg/models/controllers" +) + +func GetDeployRevision(namespace, name, revision string) (*v1.ReplicaSet, error) { + deployLister := controllers.ResourceControllers.Controllers[controllers.Deployments].Lister().(appsV1.DeploymentLister) + deploy, err := deployLister.Deployments(namespace).Get(name) + if err != nil { + glog.Errorf("get deployment %s failed, reason: %s", name, err) + return nil, err + } + + labelMap := deploy.Spec.Template.Labels + labelSelector := labels.Set(labelMap).AsSelector() + + rsLister := controllers.ResourceControllers.Controllers[controllers.Replicasets].Lister().(appsV1.ReplicaSetLister) + rsList, err := rsLister.ReplicaSets(namespace).List(labelSelector) + if err != nil { + return nil, err + } + + for _, rs := range rsList { + if rs.Annotations["deployment.kubernetes.io/revision"] == revision { + return rs, nil + } + } + + return nil, errors.NewNotFound(v1.Resource("deployment revision"), fmt.Sprintf("%s#%s", name, revision)) +} + +func GetDaemonSetRevision(namespace, name, revision string) (*v1.ControllerRevision, error) { + revisionInt, err := strconv.Atoi(revision) + if err != nil { + return nil, err + } + + dsLister := controllers.ResourceControllers.Controllers[controllers.Daemonsets].Lister().(appsV1.DaemonSetLister) + ds, err := dsLister.DaemonSets(namespace).Get(name) + if err != nil { + glog.Errorf("get Daemonset %s failed, reason: %s", name, err) + return nil, err + } + + labels := ds.Spec.Template.Labels + + return getControllerRevision(namespace, name, labels, revisionInt) +} + +func GetStatefulSetRevision(namespace, name, revision string) (*v1.ControllerRevision, error) { + revisionInt, err := strconv.Atoi(revision) + if err != nil { + return nil, err + } + + stLister := controllers.ResourceControllers.Controllers[controllers.Statefulsets].Lister().(appsV1.StatefulSetLister) + st, err := stLister.StatefulSets(namespace).Get(name) + if err != nil { + glog.Errorf("get Daemonset %s failed, reason: %s", name, err) + return nil, err + } + + labels := st.Spec.Template.Labels + + return getControllerRevision(namespace, name, labels, revisionInt) +} + +func getControllerRevision(namespace, name string, labelMap map[string]string, revision int) (*v1.ControllerRevision, error) { + + labelSelector := labels.Set(labelMap).AsSelector() + + revisionLister := controllers.ResourceControllers.Controllers[controllers.ControllerRevisions].Lister().(appsV1.ControllerRevisionLister) + revisions, err := revisionLister.ControllerRevisions(namespace).List(labelSelector) + if err != nil { + return nil, err + } + + for _, controllerRevision := range revisions { + if controllerRevision.Revision == int64(revision) { + return controllerRevision, nil + } + } + + return nil, errors.NewNotFound(v1.Resource("revision"), fmt.Sprintf("%s#%s", name, revision)) + +}