diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 4620051eb..5e4f3ba8c 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -216,8 +216,8 @@ func (s *APIServer) installKubeSphereAPIs() { urlruntime.Must(configv1alpha2.AddToContainer(s.container, s.Config)) urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory, s.RuntimeCache)) - urlruntime.Must(monitoringv1alpha3.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient, s.MetricsClient, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.Config.OpenPitrixOptions)) - urlruntime.Must(meteringv1alpha1.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.RuntimeCache, s.Config.MeteringOptions, nil)) + urlruntime.Must(monitoringv1alpha3.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient, s.MetricsClient, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.Config.OpenPitrixOptions, s.RuntimeClient)) + urlruntime.Must(meteringv1alpha1.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.RuntimeCache, s.Config.MeteringOptions, nil, s.RuntimeClient)) urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.Config.OpenPitrixOptions)) urlruntime.Must(openpitrixv2alpha1.AddToContainer(s.container, s.InformerFactory, s.KubernetesClient.KubeSphere(), s.Config.OpenPitrixOptions)) urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes())) diff --git a/pkg/kapis/metering/v1alpha1/handler.go b/pkg/kapis/metering/v1alpha1/handler.go index 499bb3c53..bc19ef670 100644 --- a/pkg/kapis/metering/v1alpha1/handler.go +++ b/pkg/kapis/metering/v1alpha1/handler.go @@ -24,6 +24,8 @@ import ( openpitrixoptions "kubesphere.io/kubesphere/pkg/simple/client/openpitrix" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "kubesphere.io/kubesphere/pkg/client/clientset/versioned" "kubesphere.io/kubesphere/pkg/informers" monitorhle "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha3" @@ -45,6 +47,6 @@ type meterHandler interface { HandlePVCMeterQuery(req *restful.Request, resp *restful.Response) } -func newHandler(k kubernetes.Interface, m monitoring.Interface, f informers.InformerFactory, ksClient versioned.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options) meterHandler { - return monitorhle.NewHandler(k, m, nil, f, ksClient, resourceGetter, meteringOptions, opOptions) +func newHandler(k kubernetes.Interface, m monitoring.Interface, f informers.InformerFactory, ksClient versioned.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options, rtClient runtimeclient.Client) meterHandler { + return monitorhle.NewHandler(k, m, nil, f, ksClient, resourceGetter, meteringOptions, opOptions, rtClient) } diff --git a/pkg/kapis/metering/v1alpha1/register.go b/pkg/kapis/metering/v1alpha1/register.go index c7bbce0ab..145bf7b67 100644 --- a/pkg/kapis/metering/v1alpha1/register.go +++ b/pkg/kapis/metering/v1alpha1/register.go @@ -30,6 +30,8 @@ import ( "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/cache" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" @@ -47,10 +49,10 @@ const ( var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha1"} -func AddToContainer(c *restful.Container, k8sClient kubernetes.Interface, meteringClient monitoring.Interface, factory informers.InformerFactory, ksClient versioned.Interface, cache cache.Cache, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options) error { +func AddToContainer(c *restful.Container, k8sClient kubernetes.Interface, meteringClient monitoring.Interface, factory informers.InformerFactory, ksClient versioned.Interface, cache cache.Cache, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options, rtClient runtimeclient.Client) error { ws := runtime.NewWebService(GroupVersion) - h := newHandler(k8sClient, meteringClient, factory, ksClient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opOptions) + h := newHandler(k8sClient, meteringClient, factory, ksClient, resourcev1alpha3.NewResourceGetter(factory, cache), meteringOptions, opOptions, rtClient) ws.Route(ws.GET("/cluster"). To(h.HandleClusterMeterQuery). diff --git a/pkg/kapis/monitoring/v1alpha3/dashboard_template_test.sh b/pkg/kapis/monitoring/v1alpha3/dashboard_template_test.sh deleted file mode 100644 index a144c21ab..000000000 --- a/pkg/kapis/monitoring/v1alpha3/dashboard_template_test.sh +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/bash - -curl -d '{"grafanaDashboardUrl":"https://grafana.com/api/dashboards/7362/revisions/5/download", "description":"this is a test dashboard."}' -H "Content-Type: application/json" localhost:9090/kapis/monitoring.kubesphere.io/v1alpha3/clusterdashboards/test1/template \ No newline at end of file diff --git a/pkg/kapis/monitoring/v1alpha3/handler.go b/pkg/kapis/monitoring/v1alpha3/handler.go index 8da172dc0..5e468be6a 100644 --- a/pkg/kapis/monitoring/v1alpha3/handler.go +++ b/pkg/kapis/monitoring/v1alpha3/handler.go @@ -20,7 +20,6 @@ package v1alpha3 import ( "context" - "encoding/json" "errors" "io/ioutil" "net/http" @@ -41,6 +40,7 @@ import ( "github.com/emicklei/go-restful" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" monitoringdashboardv1alpha2 "kubesphere.io/monitoring-dashboard/api/v1alpha2" @@ -57,9 +57,10 @@ type handler struct { mo model.MonitoringOperator opRelease openpitrix.ReleaseInterface meteringOptions *meteringclient.Options + rtClient runtimeclient.Client } -func NewHandler(k kubernetes.Interface, monitoringClient monitoring.Interface, metricsClient monitoring.Interface, f informers.InformerFactory, ksClient versioned.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options) *handler { +func NewHandler(k kubernetes.Interface, monitoringClient monitoring.Interface, metricsClient monitoring.Interface, f informers.InformerFactory, ksClient versioned.Interface, resourceGetter *resourcev1alpha3.ResourceGetter, meteringOptions *meteringclient.Options, opOptions *openpitrixoptions.Options, rtClient runtimeclient.Client) *handler { var opRelease openpitrix.Interface var s3Client s3.Interface if opOptions != nil && opOptions.S3Options != nil && len(opOptions.S3Options.Endpoint) != 0 { @@ -81,6 +82,7 @@ func NewHandler(k kubernetes.Interface, monitoringClient monitoring.Interface, m mo: model.NewMonitoringOperator(monitoringClient, metricsClient, k, f, resourceGetter, opRelease), opRelease: opRelease, meteringOptions: meteringOptions, + rtClient: rtClient, } } @@ -345,6 +347,7 @@ func (h handler) handleGrafanaDashboardImport(req *restful.Request, resp *restfu } grafanaDashboardName := req.PathParameter("grafanaDashboardName") + namespace := req.PathParameter("namespace") if grafanaDashboardName == "" { err := errors.New("the requested parameter grafanaDashboardName cannot be empty") @@ -357,6 +360,7 @@ func (h handler) handleGrafanaDashboardImport(req *restful.Request, resp *restfu return } + // download the Grafana dashboard grafanaDashboardContent := []byte(entity.GrafanaDashboardContent) if entity.GrafanaDashboardUrl != "" { c, err := func(u string) ([]byte, error) { @@ -392,57 +396,89 @@ func (h handler) handleGrafanaDashboardImport(req *restful.Request, resp *restfu grafanaDashboardContent = []byte(c) } + isClusterCrd := namespace == "" c := converter.NewConverter() - convertedDashboard, err := c.ConvertToDashboard(grafanaDashboardContent, true, "", grafanaDashboardName) + convertedDashboard, err := c.ConvertToDashboard(grafanaDashboardContent, isClusterCrd, namespace, grafanaDashboardName) if err != nil { api.HandleBadRequest(resp, nil, err) return } + ctx := context.TODO() annotation := map[string]string{"kubesphere.io/description": entity.Description} - dashboard := monitoringdashboardv1alpha2.ClusterDashboard{ - TypeMeta: v1.TypeMeta{ - APIVersion: convertedDashboard.APIVersion, - Kind: convertedDashboard.Kind, - }, - ObjectMeta: v1.ObjectMeta{ - Name: convertedDashboard.Metadata["name"], - Annotations: annotation, - }, - Spec: *convertedDashboard.Spec, + // a cluster scope dashboard or a namespaced dashboard with the same name cannot post. + if isClusterCrd { + clusterdashboard := monitoringdashboardv1alpha2.ClusterDashboard{ + TypeMeta: v1.TypeMeta{ + APIVersion: convertedDashboard.APIVersion, + Kind: convertedDashboard.Kind, + }, + ObjectMeta: v1.ObjectMeta{ + Name: convertedDashboard.Metadata["name"], + Annotations: annotation, + }, + Spec: *convertedDashboard.Spec, + } + + objKey := runtimeclient.ObjectKey{ + Namespace: "", + Name: clusterdashboard.Name, + } + + err = h.rtClient.Get(ctx, objKey, &clusterdashboard) + + if err == nil { + api.HandleBadRequest(resp, nil, errors.New("dashboards with the same name already exists.")) + return + } + + // create this dashboard + err = h.rtClient.Create(ctx, &clusterdashboard) + + if err != nil { + api.HandleBadRequest(resp, nil, err) + return + } + + resp.WriteAsJson(clusterdashboard) + + } else { + dashboard := monitoringdashboardv1alpha2.Dashboard{ + TypeMeta: v1.TypeMeta{ + APIVersion: convertedDashboard.APIVersion, + Kind: convertedDashboard.Kind, + }, + ObjectMeta: v1.ObjectMeta{ + Name: convertedDashboard.Metadata["name"], + Namespace: namespace, + Annotations: annotation, + }, + Spec: *convertedDashboard.Spec, + } + + objKey := runtimeclient.ObjectKey{ + Namespace: namespace, + Name: dashboard.Name, + } + + err = h.rtClient.Get(ctx, objKey, &dashboard) + + if err == nil { + api.HandleBadRequest(resp, nil, errors.New("dashboards with the same name already exists.")) + return + } + + // create this dashboard + err = h.rtClient.Create(ctx, &dashboard) + + if err != nil { + api.HandleBadRequest(resp, nil, err) + return + } + + resp.WriteAsJson(dashboard) + } - jsonDashbaord, err := json.Marshal(dashboard) - if err != nil { - api.HandleBadRequest(resp, nil, err) - return - } - - // a dashboard with the same name cannot post. - ctx := context.TODO() - _, err = h.k.Discovery().RESTClient(). - Get(). - AbsPath("/apis/monitoring.kubesphere.io/v1alpha2/clusterdashboards/" + dashboard.Name). - DoRaw(ctx) - - if err == nil { - api.HandleBadRequest(resp, nil, errors.New("a dashboard with the same name already exists.")) - return - } - - // create this dashboard - _, err = h.k.Discovery().RESTClient(). - Post(). - AbsPath("/apis/monitoring.kubesphere.io/v1alpha2/clusterdashboards"). - Body(jsonDashbaord). - DoRaw(ctx) - - if err != nil { - api.HandleBadRequest(resp, nil, err) - return - } - - resp.WriteAsJson(dashboard) - } diff --git a/pkg/kapis/monitoring/v1alpha3/helper_test.go b/pkg/kapis/monitoring/v1alpha3/helper_test.go index 6c6922778..8ed5dd8d6 100644 --- a/pkg/kapis/monitoring/v1alpha3/helper_test.go +++ b/pkg/kapis/monitoring/v1alpha3/helper_test.go @@ -373,7 +373,7 @@ func TestParseRequestParams(t *testing.T) { fakeInformerFactory.KubeSphereSharedInformerFactory() - handler := NewHandler(client, nil, nil, fakeInformerFactory, ksClient, nil, nil, nil) + handler := NewHandler(client, nil, nil, fakeInformerFactory, ksClient, nil, nil, nil, nil) result, err := handler.makeQueryOptions(tt.params, tt.lvl) if err != nil { diff --git a/pkg/kapis/monitoring/v1alpha3/register.go b/pkg/kapis/monitoring/v1alpha3/register.go index 45253899f..9394203d8 100644 --- a/pkg/kapis/monitoring/v1alpha3/register.go +++ b/pkg/kapis/monitoring/v1alpha3/register.go @@ -36,6 +36,8 @@ import ( "kubesphere.io/kubesphere/pkg/informers" model "kubesphere.io/kubesphere/pkg/models/monitoring" "kubesphere.io/kubesphere/pkg/simple/client/monitoring" + + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -45,10 +47,10 @@ const ( var GroupVersion = schema.GroupVersion{Group: groupName, Version: "v1alpha3"} -func AddToContainer(c *restful.Container, k8sClient kubernetes.Interface, monitoringClient monitoring.Interface, metricsClient monitoring.Interface, factory informers.InformerFactory, ksClient versioned.Interface, opOptions *openpitrixoptions.Options) error { +func AddToContainer(c *restful.Container, k8sClient kubernetes.Interface, monitoringClient monitoring.Interface, metricsClient monitoring.Interface, factory informers.InformerFactory, ksClient versioned.Interface, opOptions *openpitrixoptions.Options, rtClient runtimeclient.Client) error { ws := runtime.NewWebService(GroupVersion) - h := NewHandler(k8sClient, monitoringClient, metricsClient, factory, ksClient, nil, nil, opOptions) + h := NewHandler(k8sClient, monitoringClient, metricsClient, factory, ksClient, nil, nil, opOptions, rtClient) ws.Route(ws.GET("/kubesphere"). To(h.handleKubeSphereMetricsQuery). @@ -555,6 +557,16 @@ func AddToContainer(c *restful.Container, k8sClient kubernetes.Interface, monito Returns(http.StatusOK, respOK, monitoringdashboardv1alpha2.ClusterDashboard{})). Produces(restful.MIME_JSON) + ws.Route(ws.POST("/namespaces/{namespace}/dashboards/{grafanaDashboardName}/template"). + To(h.handleGrafanaDashboardImport). + Doc("Convert Grafana templates to KubeSphere dashboards."). + Param(ws.PathParameter("grafanaDashboardName", "The name of the Grafana template to be converted").DataType("string").Required(true)). + Param(ws.PathParameter("namespace", "The name of the project").DataType("string").Required(true)). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.DashboardTag}). + Writes(monitoringdashboardv1alpha2.Dashboard{}). + Returns(http.StatusOK, respOK, monitoringdashboardv1alpha2.Dashboard{})). + Produces(restful.MIME_JSON) + c.Add(ws) return nil } diff --git a/pkg/kapis/monitoring/v1alpha3/scripts/dashboard_template_debug.sh b/pkg/kapis/monitoring/v1alpha3/scripts/dashboard_template_debug.sh new file mode 100644 index 000000000..50ee1e279 --- /dev/null +++ b/pkg/kapis/monitoring/v1alpha3/scripts/dashboard_template_debug.sh @@ -0,0 +1,4 @@ +#! /bin/bash + +curl -d '{"grafanaDashboardUrl":"https://grafana.com/api/dashboards/7362/revisions/5/download", "description":"this is a test clusterdashboard."}' -H "Content-Type: application/json" localhost:9090/kapis/monitoring.kubesphere.io/v1alpha3/clusterdashboards/test1/template +curl -d '{"grafanaDashboardUrl":"https://grafana.com/api/dashboards/7362/revisions/5/download", "description":"this is a test dashboard."}' -H "Content-Type: application/json" localhost:9090/kapis/monitoring.kubesphere.io/v1alpha3/namespaces/default/dashboards/test2/template \ No newline at end of file diff --git a/pkg/simple/client/monitoring/types.go b/pkg/simple/client/monitoring/types.go index 255c2a273..39276b86b 100644 --- a/pkg/simple/client/monitoring/types.go +++ b/pkg/simple/client/monitoring/types.go @@ -53,6 +53,7 @@ type DashboardEntity struct { GrafanaDashboardUrl string `json:"grafanaDashboardUrl,omitempty"` GrafanaDashboardContent string `json:"grafanaDashboardContent,omitempty"` Description string `json:"description,omitempty"` + Namespace string `json:"namespace,omitempty"` } // The first element is the timestamp, the second is the metric value. diff --git a/tools/cmd/doc-gen/main.go b/tools/cmd/doc-gen/main.go index 041ae516a..3d1744bf8 100644 --- a/tools/cmd/doc-gen/main.go +++ b/tools/cmd/doc-gen/main.go @@ -123,7 +123,7 @@ func generateSwaggerJson() []byte { urlruntime.Must(devopsv1alpha2.AddToContainer(container, "")) urlruntime.Must(devopsv1alpha3.AddToContainer(container, "")) urlruntime.Must(iamv1alpha2.AddToContainer(container, nil, nil, group.New(informerFactory, clientsets.KubeSphere(), clientsets.Kubernetes()), nil)) - urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, nil, informerFactory, nil, nil)) + urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, nil, informerFactory, nil, nil, nil)) urlruntime.Must(openpitrixv1.AddToContainer(container, informerFactory, fake.NewSimpleClientset(), nil)) urlruntime.Must(openpitrixv2.AddToContainer(container, informerFactory, fake.NewSimpleClientset(), nil)) urlruntime.Must(operationsv1alpha2.AddToContainer(container, clientsets.Kubernetes()))