1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

Add links to other operators in dashboard menu

This commit is contained in:
Ewout Prangsma 2018-07-11 12:28:34 +02:00
parent 65cc238006
commit feccd07e49
No known key found for this signature in database
GPG key ID: 4DBAD380D93D0698
14 changed files with 361 additions and 90 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
import { Container, Segment, Message } from 'semantic-ui-react';
import { Container, Segment, Menu, Message } from 'semantic-ui-react';
import React, { Component } from 'react';
import ReactTimeout from 'react-timeout';
@ -19,7 +19,14 @@ const PodInfoView = ({pod, namespace}) => (
</Segment>
);
const OperatorsView = ({error, deployment, deploymentReplication, storage, pod, namespace}) => {
const OperatorsView = ({error, deployment, deploymentReplication, storage, pod, namespace, otherOperators}) => {
let commonMenuItems = otherOperators.map((item) => <Menu.Item><a href={item.url}>{operatorType2Name(item.type)}</a></Menu.Item>);
if (commonMenuItems.length > 0) {
commonMenuItems = (<Menu.Item>
<Menu.Header>Other operators</Menu.Header>
<Menu.Menu>{commonMenuItems}</Menu.Menu>
</Menu.Item>);
}
let Operator = NoOperator;
if (deployment)
Operator = DeploymentOperator;
@ -30,11 +37,25 @@ const OperatorsView = ({error, deployment, deploymentReplication, storage, pod,
return (
<Operator
podInfoView={<PodInfoView pod={pod} namespace={namespace} />}
commonMenuItems={commonMenuItems}
error={error}
/>
);
}
const operatorType2Name = (oType) => {
switch (oType) {
case "deployment":
return "Deployments";
case "deployment_replication":
return "Deployment replications";
case "storage":
return "Storage";
default:
return "";
}
};
const LoadingView = () => (
<Container>
<Loading/>
@ -76,6 +97,7 @@ class App extends Component {
deployment={this.state.operators.deployment}
deploymentReplication={this.state.operators.deployment_replication}
storage={this.state.operators.storage}
otherOperators={this.state.operators.other}
pod={this.state.operators.pod}
namespace={this.state.operators.namespace}
/>;

View file

@ -34,10 +34,16 @@ class DeploymentOperator extends Component {
{doLogout =>
<StyledMenu fixed="left" vertical>
<Menu.Item>
<Link to="/">Deployments</Link>
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
<Menu.Header>Deployment Operator</Menu.Header>
<Menu.Menu>
<Menu.Item>
<Link to="/">Deployments</Link>
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
</Menu.Item>
</Menu.Menu>
{this.props.commonMenuItems}
</Menu.Item>
</StyledMenu>
}

View file

@ -34,11 +34,17 @@ class DeploymentReplicationOperator extends Component {
{doLogout =>
<StyledMenu fixed="left" vertical>
<Menu.Item>
<Link to="/">Deployment replications</Link>
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
<Menu.Header>Deployment Replication Operator</Menu.Header>
<Menu.Menu>
<Menu.Item>
<Link to="/">Deployment replications</Link>
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
</Menu.Item>
</Menu.Menu>
</Menu.Item>
{this.props.commonMenuItems}
</StyledMenu>
}
</LogoutContext.Consumer>

View file

@ -20,14 +20,20 @@ class StorageOperator extends Component {
<div>
<LogoutContext.Consumer>
{doLogout =>
<StyledMenu fixed="left" vertical>
<StyledMenu fixed="left" vertical>
<Menu.Item>
Local storages
<Menu.Header>Deployment Operator</Menu.Header>
<Menu.Menu>
<Menu.Item>
Local storages
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
</Menu.Item>
</Menu.Menu>
{this.props.commonMenuItems}
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
</Menu.Item>
</StyledMenu>
</StyledMenu>
}
</LogoutContext.Consumer>
<StyledContentBox>

View file

@ -30,8 +30,8 @@ rules:
resources: ["pods", "services", "endpoints", "persistentvolumeclaims", "events", "secrets"]
verbs: ["*"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get"]
resources: ["namespaces", "nodes"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get"]

View file

@ -27,8 +27,8 @@ rules:
resources: ["pods", "services", "endpoints", "persistentvolumeclaims", "events", "secrets"]
verbs: ["*"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get"]
resources: ["namespaces", "nodes"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get"]

View file

@ -33,6 +33,9 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""]
resources: ["namespaces", "nodes"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["daemonsets"]
verbs: ["*"]

View file

@ -23,10 +23,7 @@
package deployment
import (
"fmt"
"net"
"sort"
"strconv"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -197,28 +194,25 @@ func (d *Deployment) DatabaseURL() string {
if err != nil {
return ""
}
host := ""
switch svc.Spec.Type {
case v1.ServiceTypeLoadBalancer:
for _, i := range svc.Status.LoadBalancer.Ingress {
if i.Hostname != "" {
host = i.Hostname
} else {
host = i.IP
}
break
}
case v1.ServiceTypeNodePort:
// TODO
}
if host == "" {
return ""
}
scheme := "https"
if !d.GetSpec().IsSecure() {
scheme = "http"
}
return fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(host, strconv.Itoa(k8sutil.ArangoPort)))
nodeFetcher := func() (v1.NodeList, error) {
result, err := d.deps.KubeCli.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return v1.NodeList{}, maskAny(err)
}
return *result, nil
}
portPredicate := func(p v1.ServicePort) bool {
return p.TargetPort.IntValue() == k8sutil.ArangoPort
}
url, err := k8sutil.CreateServiceURL(*svc, scheme, portPredicate, nodeFetcher)
if err != nil {
return ""
}
return url
}
// DatabaseVersion returns the version used by the deployment

View file

@ -36,8 +36,8 @@ var (
// The defaultLevels list is used during development to increase the
// default level for components that we care a little less about.
defaultLevels = map[string]string{
"operator": "info",
//"something.status": "info",
//"operator": "info",
//"something.status": "info",
}
)

View file

@ -0,0 +1,146 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package operator
import (
"sort"
"github.com/rs/zerolog"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/server"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
)
const (
appKey = "app"
roleKey = "role"
appDeploymentOperator = "arango-deployment-operator"
appDeploymentReplicationOperator = "arango-deployment-replication-operator"
appStorageOperator = "arango-storage-operator"
roleLeader = "leader"
)
// FindOtherOperators looks up references to other operators in the same Kubernetes cluster.
func (o *Operator) FindOtherOperators() []server.OperatorReference {
log := o.log
var result []server.OperatorReference
namespaces, err := o.Dependencies.KubeCli.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
log.Warn().Err(err).Msg("Failed to list namespaces")
} else {
for _, ns := range namespaces.Items {
if ns.Name != o.Config.Namespace {
log.Debug().Str("namespace", ns.Name).Msg("inspecting namespace for operators")
refs := o.findOtherOperatorsInNamespace(log, ns.Name, func(server.OperatorType) bool { return true })
result = append(result, refs...)
} else {
log.Debug().Str("namespace", ns.Name).Msg("skip inspecting my own namespace for operators")
}
}
}
refs := o.findOtherOperatorsInNamespace(log, o.Config.Namespace, func(oType server.OperatorType) bool {
// Exclude those operators that I provide myself.
switch oType {
case server.OperatorTypeDeployment:
return !o.Dependencies.DeploymentProbe.IsReady()
case server.OperatorTypeDeploymentReplication:
return !o.Dependencies.DeploymentReplicationProbe.IsReady()
case server.OperatorTypeStorage:
return !o.Dependencies.StorageProbe.IsReady()
default:
return true
}
})
result = append(result, refs...)
sort.Slice(result, func(i, j int) bool {
if result[i].Namespace == result[j].Namespace {
return result[i].Type < result[j].Type
}
return result[i].Namespace < result[j].Namespace
})
return result
}
// findOtherOperatorsInNamespace looks up references to other operators in the given namespace.
func (o *Operator) findOtherOperatorsInNamespace(log zerolog.Logger, namespace string, typePred func(server.OperatorType) bool) []server.OperatorReference {
log = log.With().Str("namespace", namespace).Logger()
var result []server.OperatorReference
services, err := o.Dependencies.KubeCli.CoreV1().Services(namespace).List(metav1.ListOptions{})
if err != nil {
log.Debug().Err(err).Msg("Failed to list services")
return nil
}
nodeFetcher := func() (v1.NodeList, error) {
result, err := o.Dependencies.KubeCli.CoreV1().Nodes().List(metav1.ListOptions{})
if err != nil {
return v1.NodeList{}, maskAny(err)
}
return *result, nil
}
for _, svc := range services.Items {
// Filter out unwanted services
selector := svc.Spec.Selector
if selector[roleKey] != roleLeader {
log.Debug().Str("service", svc.Name).Msg("Service has no leader role selector")
continue
}
var oType server.OperatorType
switch selector[appKey] {
case appDeploymentOperator:
oType = server.OperatorTypeDeployment
case appDeploymentReplicationOperator:
oType = server.OperatorTypeDeploymentReplication
case appStorageOperator:
oType = server.OperatorTypeStorage
default:
log.Debug().Str("service", svc.Name).Msg("Service has no or invalid app selector")
continue
}
if !typePred(oType) {
continue
}
var url string
switch svc.Spec.Type {
case v1.ServiceTypeNodePort, v1.ServiceTypeLoadBalancer:
if x, err := k8sutil.CreateServiceURL(svc, "https", nil, nodeFetcher); err == nil {
url = x
} else {
log.Warn().Err(err).Str("service", svc.Name).Msg("Failed to create URL for service")
}
default:
// No suitable service type
continue
}
result = append(result, server.OperatorReference{
Namespace: svc.GetNamespace(),
URL: url,
Type: oType,
})
}
log.Debug().Msgf("Found %d operator services", len(result))
return result
}

View file

@ -29,20 +29,39 @@ import (
)
type operatorsResponse struct {
PodName string `json:"pod"`
Namespace string `json:"namespace"`
Deployment bool `json:"deployment"`
DeploymentReplication bool `json:"deployment_replication"`
Storage bool `json:"storage"`
PodName string `json:"pod"`
Namespace string `json:"namespace"`
Deployment bool `json:"deployment"`
DeploymentReplication bool `json:"deployment_replication"`
Storage bool `json:"storage"`
Other []OperatorReference `json:"other"`
}
type OperatorType string
const (
OperatorTypeDeployment OperatorType = "deployment"
OperatorTypeDeploymentReplication OperatorType = "deployment_replication"
OperatorTypeStorage OperatorType = "storage"
)
// OperatorReference contains a reference to another operator
type OperatorReference struct {
Namespace string `json:"namespace"`
Type OperatorType `json:"type"`
URL string `json:"url"`
}
// Handle a GET /api/operators request
func (s *Server) handleGetOperators(c *gin.Context) {
c.JSON(http.StatusOK, operatorsResponse{
result := operatorsResponse{
PodName: s.cfg.PodName,
Namespace: s.cfg.Namespace,
Deployment: s.deps.DeploymentProbe.IsReady(),
DeploymentReplication: s.deps.DeploymentReplicationProbe.IsReady(),
Storage: s.deps.StorageProbe.IsReady(),
})
Other: s.deps.Operators.FindOtherOperators(),
}
s.deps.Log.Info().Interface("result", result).Msg("handleGetOperators")
c.JSON(http.StatusOK, result)
}

View file

@ -73,6 +73,8 @@ type Operators interface {
DeploymentReplicationOperator() DeploymentReplicationOperator
// Return the local storage operator (if any)
StorageOperator() StorageOperator
// FindOtherOperators looks up references to other operators in the same Kubernetes cluster.
FindOtherOperators() []OperatorReference
}
// Server is the HTTPS server for the operator.

View file

@ -23,7 +23,11 @@
package k8sutil
import (
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -164,3 +168,66 @@ func createService(kubecli kubernetes.Interface, svcName, deploymentName, ns, cl
}
return true, nil
}
// CreateServiceURL creates a URL used to reach the given service.
func CreateServiceURL(svc v1.Service, scheme string, portPredicate func(v1.ServicePort) bool, nodeFetcher func() (v1.NodeList, error)) (string, error) {
var port int32
var nodePort int32
portFound := false
for _, p := range svc.Spec.Ports {
if portPredicate == nil || portPredicate(p) {
port = p.Port
nodePort = p.NodePort
portFound = true
break
}
}
if !portFound {
return "", maskAny(fmt.Errorf("Cannot find port in service '%s.%s'", svc.GetName(), svc.GetNamespace()))
}
var host string
switch svc.Spec.Type {
case v1.ServiceTypeLoadBalancer:
for _, x := range svc.Status.LoadBalancer.Ingress {
if x.IP != "" {
host = x.IP
break
} else if x.Hostname != "" {
host = x.Hostname
break
}
}
if host == "" {
host = svc.Spec.LoadBalancerIP
}
case v1.ServiceTypeNodePort:
if nodePort > 0 {
port = nodePort
}
nodeList, err := nodeFetcher()
if err != nil {
return "", maskAny(err)
}
if len(nodeList.Items) == 0 {
return "", maskAny(fmt.Errorf("No nodes found"))
}
node := nodeList.Items[rand.Intn(len(nodeList.Items))]
if len(node.Status.Addresses) > 0 {
host = node.Status.Addresses[0].Address
}
case v1.ServiceTypeClusterIP:
if svc.Spec.ClusterIP != "None" {
host = svc.Spec.ClusterIP
}
default:
return "", maskAny(fmt.Errorf("Unknown service type '%s' in service '%s.%s'", svc.Spec.Type, svc.GetName(), svc.GetNamespace()))
}
if host == "" {
return "", maskAny(fmt.Errorf("Cannot find host for service '%s.%s'", svc.GetName(), svc.GetNamespace()))
}
if !strings.HasSuffix(scheme, "://") {
scheme = scheme + "://"
}
return scheme + net.JoinHostPort(host, strconv.Itoa(int(port))), nil
}