diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 329ee286a..bd8654567 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -30,6 +30,7 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2" loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2" monitoringv1alpha3 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha3" + networkv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/network/v1alpha2" "kubesphere.io/kubesphere/pkg/kapis/oauth" openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1" operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2" @@ -139,6 +140,7 @@ func (s *APIServer) installKubeSphereAPIs() { urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient)) urlruntime.Must(monitoringv1alpha3.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient)) urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient)) + urlruntime.Must(networkv1alpha2.AddToContainer(s.container)) urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes())) urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory)) urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory)) diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 2f45dd696..cbe048064 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -59,6 +59,7 @@ const ( OpenpitrixTag = "Openpitrix Resources" VerificationTag = "Verification" RegistryTag = "Docker Registry" + NetworkTopologyTag = "Network Topology" UserResourcesTag = "User Resources" DevOpsProjectTag = "DevOps Project" DevOpsProjectCredentialTag = "DevOps Project Credential" diff --git a/pkg/kapis/network/group.go b/pkg/kapis/network/group.go new file mode 100644 index 000000000..71d348680 --- /dev/null +++ b/pkg/kapis/network/group.go @@ -0,0 +1,18 @@ +/* +Copyright 2020 The KubeSphere authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package network contains network API versions +package network diff --git a/pkg/kapis/network/v1alpha2/handler.go b/pkg/kapis/network/v1alpha2/handler.go new file mode 100644 index 000000000..e096acdc8 --- /dev/null +++ b/pkg/kapis/network/v1alpha2/handler.go @@ -0,0 +1,77 @@ +package v1alpha2 + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + + restful "github.com/emicklei/go-restful" +) + +const ScopeQueryUrl = "http://weave-scope-app.weave.svc/api/topology/services" + +func getNamespaceTopology(request *restful.Request, response *restful.Response) { + var query = url.Values{ + "namespace": []string{request.PathParameter("namespace")}, + "timestamp": request.QueryParameters("timestamp"), + } + var u = fmt.Sprintf("%s?%s", ScopeQueryUrl, query.Encode()) + + resp, err := http.Get(u) + + if err != nil { + log.Printf("query scope faile with err %v", err) + _ = response.WriteError(http.StatusInternalServerError, err) + return + } + + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + log.Printf("read response error : %v", err) + _ = response.WriteError(http.StatusInternalServerError, err) + return + } + + // need to set header for proper response + response.Header().Set("Content-Type", "application/json") + _, err = response.Write(body) + + if err != nil { + log.Printf("write response failed %v", err) + } +} + +func getNamespaceNodeTopology(request *restful.Request, response *restful.Response) { + var query = url.Values{ + "namespace": []string{request.PathParameter("namespace")}, + "timestamp": request.QueryParameters("timestamp"), + } + var u = fmt.Sprintf("%s/%s?%s", ScopeQueryUrl, request.PathParameter("node_id"), query.Encode()) + + resp, err := http.Get(u) + + if err != nil { + log.Printf("query scope faile with err %v", err) + _ = response.WriteError(http.StatusInternalServerError, err) + return + } + + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + log.Printf("read response error : %v", err) + _ = response.WriteError(http.StatusInternalServerError, err) + return + } + + // need to set header for proper response + response.Header().Set("Content-Type", "application/json") + _, err = response.Write(body) + + if err != nil { + log.Printf("write response failed %v", err) + } +} diff --git a/pkg/kapis/network/v1alpha2/register.go b/pkg/kapis/network/v1alpha2/register.go new file mode 100644 index 000000000..ac1c296c8 --- /dev/null +++ b/pkg/kapis/network/v1alpha2/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 The KubeSphere authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "net/http" + + restful "github.com/emicklei/go-restful" + restfulspec "github.com/emicklei/go-restful-openapi" + "k8s.io/apimachinery/pkg/runtime/schema" + "kubesphere.io/kubesphere/pkg/apiserver/runtime" + "kubesphere.io/kubesphere/pkg/constants" +) + +const GroupName = "network.kubesphere.io" + +var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"} + +func AddToContainer(c *restful.Container) error { + webservice := runtime.NewWebService(GroupVersion) + + webservice.Route(webservice.GET("/namespaces/{namespace}/topology"). + To(getNamespaceTopology). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.NetworkTopologyTag}). + Doc("Get the topology with specifying a namespace"). + Param(webservice.PathParameter("namespace", "name of the namespace").Required(true)). + Returns(http.StatusOK, "ok", TopologyResponse{}). + Writes(TopologyResponse{})).Produces(restful.MIME_JSON) + + webservice.Route(webservice.GET("/namespaces/{namespace}/topology/{node_id}"). + To(getNamespaceNodeTopology). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.NetworkTopologyTag}). + Doc("Get the topology with specifying a node id in the whole topology and specifying a namespace"). + Param(webservice.PathParameter("namespace", "name of the namespace").Required(true)). + Param(webservice.PathParameter("node_id", "id of the node in the whole topology").Required(true)). + Returns(http.StatusOK, "ok", NodeResponse{}). + Writes(NodeResponse{})).Produces(restful.MIME_JSON) + + c.Add(webservice) + + return nil +} diff --git a/pkg/kapis/network/v1alpha2/swagger-doc.go b/pkg/kapis/network/v1alpha2/swagger-doc.go new file mode 100644 index 000000000..d8cdd4c0a --- /dev/null +++ b/pkg/kapis/network/v1alpha2/swagger-doc.go @@ -0,0 +1,218 @@ +/* +Copyright 2020 The KubeSphere authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "time" +) + +///////////////////// +// SWAGGER RESPONSES +///////////////////// + +// NoContent: the response is empty +type NoContent struct { + Status int32 `json:"status"` + Reason error `json:"reason"` +} + +// BadRequestError: the client request is incorrect +type BadRequestError struct { + Status int32 `json:"status"` + Reason error `json:"reason"` +} + +// NotFoundError is the error message that is generated when server could not find +// what was requested +type NotFoundError struct { + Status int32 `json:"status"` + Reason error `json:"reason"` +} + +// copy from github.com/weaveworks/scope v1.12.0 + +// MetadataRow is a row for the metadata table. +type MetadataRow struct { + ID string `json:"id"` + Label string `json:"label"` + Value string `json:"value"` + Priority float64 `json:"priority,omitempty"` + Datatype string `json:"dataType,omitempty"` + Truncate int `json:"truncate,omitempty"` +} + +// BasicNodeSummary is basic summary information about a Node, +// sufficient for rendering links to the node. +type BasicNodeSummary struct { + ID string `json:"id"` + Label string `json:"label"` + LabelMinor string `json:"labelMinor"` + Rank string `json:"rank"` + Shape string `json:"shape,omitempty"` + Tag string `json:"tag,omitempty"` + Stack bool `json:"stack,omitempty"` + Pseudo bool `json:"pseudo,omitempty"` +} + +// Parent is the information needed to build a link to the parent of a Node. +type Parent struct { + ID string `json:"id"` + Label string `json:"label"` + TopologyID string `json:"topologyId"` +} + +// Metric is a list of timeseries data with some metadata. Clients must use the +// Add method to add values. Metrics are immutable. +type Metric struct { + Samples []Sample `json:"samples,omitempty"` + Min float64 `json:"min"` + Max float64 `json:"max"` +} + +func (m Metric) first() time.Time { return m.Samples[0].Timestamp } +func (m Metric) last() time.Time { return m.Samples[len(m.Samples)-1].Timestamp } + +// Sample is a single datapoint of a metric. +type Sample struct { + Timestamp time.Time `json:"date"` + Value float64 `json:"value"` +} + +// MetricRow is a tuple of data used to render a metric as a sparkline and +// accoutrements. +type MetricRow struct { + ID string + Label string + Format string + Group string + Value float64 + ValueEmpty bool + Priority float64 + URL string + Metric *Metric +} + +// NodeSummaryGroup is a topology-typed group of children for a Node. +type NodeSummaryGroup struct { + ID string `json:"id"` + Label string `json:"label"` + Nodes []NodeSummary `json:"nodes"` + TopologyID string `json:"topologyId"` + Columns []Column `json:"columns"` +} + +// Connection is a row in the connections table. +type Connection struct { + ID string `json:"id"` // ID of this element in the UI. Must be unique for a given ConnectionsSummary. + NodeID string `json:"nodeId"` // ID of a node in the topology. Optional, must be set if linkable is true. + Label string `json:"label"` + LabelMinor string `json:"labelMinor,omitempty"` + Metadata []MetadataRow `json:"metadata,omitempty"` +} + +// ConnectionsSummary is the table of connection to/form a node +type ConnectionsSummary struct { + ID string `json:"id"` + TopologyID string `json:"topologyId"` + Label string `json:"label"` + Columns []Column `json:"columns"` + Connections []Connection `json:"connections"` +} + +// Column is the type for multi-column tables in the UI. +type Column struct { + ID string `json:"id"` + Label string `json:"label"` + DataType string `json:"dataType"` +} + +// Row is the type that holds the table data for the UI. Entries map from column ID to cell value. +type Row struct { + ID string `json:"id"` + Entries map[string]string `json:"entries"` +} + +// Table is the type for a table in the UI. +type Table struct { + ID string `json:"id"` + Label string `json:"label"` + Type string `json:"type"` + Columns []Column `json:"columns"` + Rows []Row `json:"rows"` + TruncationCount int `json:"truncationCount,omitempty"` +} + +// StringSet is a sorted set of unique strings. Clients must use the Add +// method to add strings. +type StringSet []string + +// IDList is a list of string IDs, which are always sorted and unique. +type IDList StringSet + +// NodeSummary is summary information about a Node. +type NodeSummary struct { + BasicNodeSummary + Metadata []MetadataRow `json:"metadata,omitempty"` + Parents []Parent `json:"parents,omitempty"` + Metrics []MetricRow `json:"metrics,omitempty"` + Tables []Table `json:"tables,omitempty"` + Adjacency IDList `json:"adjacency,omitempty"` +} + +type NodeSummaries map[string]NodeSummary + +type APITopology struct { + Nodes NodeSummaries `json:"nodes"` +} + +// A Control basically describes an RPC +type Control struct { + ID string `json:"id"` + Human string `json:"human"` + Icon string `json:"icon"` // from https://fortawesome.github.io/Font-Awesome/cheatsheet/ please + Confirmation string `json:"confirmation,omitempty"` + Rank int `json:"rank"` +} + +// ControlInstance contains a control description, and all the info +// needed to execute it. +type ControlInstance struct { + ProbeID string + NodeID string + Control Control +} + +// Node is the data type that's yielded to the JavaScript layer when +// we want deep information about an individual node. +type Node struct { + NodeSummary + Controls []ControlInstance `json:"controls"` + Children []NodeSummaryGroup `json:"children,omitempty"` + Connections []ConnectionsSummary `json:"connections,omitempty"` +} + +type APINode struct { + Node Node `json:"node"` +} + +type TopologyResponse struct { + APITopology +} + +type NodeResponse struct { + APINode +} diff --git a/tools/cmd/doc-gen/main.go b/tools/cmd/doc-gen/main.go index 6035b014c..9eb32821d 100644 --- a/tools/cmd/doc-gen/main.go +++ b/tools/cmd/doc-gen/main.go @@ -40,6 +40,7 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2" loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2" monitoringv1alpha3 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha3" + networkv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/network/v1alpha2" openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1" operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2" resourcesv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/resources/v1alpha2" @@ -122,6 +123,7 @@ func generateSwaggerJson() []byte { urlruntime.Must(tenantv1alpha2.AddToContainer(container, clientsets, informerFactory)) urlruntime.Must(terminalv1alpha2.AddToContainer(container, clientsets.Kubernetes(), nil)) urlruntime.Must(metricsv1alpha2.AddToContainer(container)) + urlruntime.Must(networkv1alpha2.AddToContainer(container)) config := restfulspec.Config{ WebServices: container.RegisteredWebServices(),