mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
Adding local storage dashboard
This commit is contained in:
parent
bf29d7a6c0
commit
44cec706b0
16 changed files with 787 additions and 107 deletions
File diff suppressed because one or more lines are too long
|
@ -16,7 +16,7 @@
|
|||
"devDependencies": {
|
||||
"react-scripts": "1.1.4"
|
||||
},
|
||||
"proxy": "https://192.168.140.208:8528",
|
||||
"proxy": "https://192.168.140.211:8528",
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #222;
|
||||
height: 150px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import React, { Component } from 'react';
|
||||
import ReactTimeout from 'react-timeout';
|
||||
import DeploymentOperator from './deployment/DeploymentOperator.js';
|
||||
import StorageOperator from './storage/StorageOperator.js';
|
||||
import NoOperator from './NoOperator.js';
|
||||
import Loading from './util/Loading.js';
|
||||
import api from './api/api.js';
|
||||
import { Container, Segment, Message } from 'semantic-ui-react';
|
||||
import './App.css';
|
||||
|
||||
const PodInfoView = ({pod, namespace}) => (
|
||||
<Segment basic>
|
||||
|
@ -16,11 +16,14 @@ const PodInfoView = ({pod, namespace}) => (
|
|||
</Segment>
|
||||
);
|
||||
|
||||
const OperatorsView = ({error, deployment, pod, namespace}) => {
|
||||
const OperatorsView = ({error, deployment, storage, pod, namespace}) => {
|
||||
const podInfoView = (<PodInfoView pod={pod} namespace={namespace}/>);
|
||||
if (deployment) {
|
||||
return (<DeploymentOperator podInfoView={podInfoView} error={error}/>);
|
||||
}
|
||||
if (storage) {
|
||||
return (<StorageOperator podInfoView={podInfoView} error={error}/>);
|
||||
}
|
||||
return (<NoOperator podInfoView={podInfoView} error={error}/>);
|
||||
}
|
||||
|
||||
|
@ -55,6 +58,7 @@ class App extends Component {
|
|||
return <OperatorsView
|
||||
error={this.state.error}
|
||||
deployment={this.state.operators.deployment}
|
||||
storage={this.state.operators.storage}
|
||||
pod={this.state.operators.pod}
|
||||
namespace={this.state.operators.namespace}
|
||||
/>;
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import React, { Component } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import { Message } from 'semantic-ui-react';
|
||||
import { Container, Message, Modal, Segment } from 'semantic-ui-react';
|
||||
|
||||
class NoOperator extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<h1 className="App-title">Welcome to Kube-ArangoDB</h1>
|
||||
</header>
|
||||
<p className="App-intro">
|
||||
There are no operators available yet.
|
||||
</p>
|
||||
{this.props.podInfoView}
|
||||
{(this.props.error) ? <Message error content={this.props.error}/> : null}
|
||||
</div>
|
||||
<Container>
|
||||
<Modal open>
|
||||
<Modal.Header>Welcome to Kube-ArangoDB</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Segment basic>
|
||||
<Message color="orange">
|
||||
There are no operators available yet.
|
||||
</Message>
|
||||
</Segment>
|
||||
{this.props.podInfoView}
|
||||
{(this.props.error) ? <Message error content={this.props.error}/> : null}
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
198
dashboard/src/storage/StorageList.js
Normal file
198
dashboard/src/storage/StorageList.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
import { Accordion, Header, Icon, Loader, Popup, Table } from 'semantic-ui-react';
|
||||
import api from '../api/api.js';
|
||||
import CommandInstruction from '../util/CommandInstruction.js';
|
||||
import VolumeList from './VolumeList.js';
|
||||
import Loading from '../util/Loading.js';
|
||||
import React, { Component } from 'react';
|
||||
import ReactTimeout from 'react-timeout';
|
||||
import styled from 'react-emotion';
|
||||
|
||||
const LoaderBox = styled('span')`
|
||||
float: right;
|
||||
width: 0;
|
||||
padding-right: 1em;
|
||||
max-width: 0;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const HeaderView = ({loading}) => (
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>State</Table.HeaderCell>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Local path(s)</Table.HeaderCell>
|
||||
<Table.HeaderCell>StorageClass</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
Actions
|
||||
<LoaderBox><Loader size="mini" active={loading} inline/></LoaderBox>
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
);
|
||||
|
||||
const RowView = ({name, stateColor,localPaths, storageClass, storageClassIsDefault, deleteCommand, describeCommand, expanded, toggleExpand}) => (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Popup trigger={<Icon name={(stateColor==="green") ? "check" : "bell"} color={stateColor}/>}>
|
||||
{getStateColorDescription(stateColor)}
|
||||
</Popup>
|
||||
</Table.Cell>
|
||||
<Table.Cell onClick={toggleExpand}>
|
||||
<Accordion>
|
||||
<Accordion.Title active={expanded}>
|
||||
<Icon name='dropdown' />
|
||||
{name}
|
||||
</Accordion.Title>
|
||||
</Accordion>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{localPaths.map((item) => <code>{item}</code>)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{storageClass}
|
||||
<span style={{"float":"right"}}>
|
||||
{storageClassIsDefault && <Popup trigger={<Icon name="exclamation"/>} content="Default storage class"/>}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<CommandInstruction
|
||||
trigger={<Icon link name="zoom"/>}
|
||||
command={describeCommand}
|
||||
title="Describe local storage"
|
||||
description="To get more information on the state of this local storage, run:"
|
||||
/>
|
||||
<span style={{"float":"right"}}>
|
||||
<CommandInstruction
|
||||
trigger={<Icon link name="trash"/>}
|
||||
command={deleteCommand}
|
||||
title="Delete local storage"
|
||||
description="To delete this local storage, run:"
|
||||
/>
|
||||
</span>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
|
||||
const VolumesRowView = ({name}) => (
|
||||
<Table.Row>
|
||||
<Table.Cell colspan="5">
|
||||
<Header sub>Volumes</Header>
|
||||
<VolumeList storageName={name}/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
|
||||
const ListView = ({items, loading}) => (
|
||||
<Table celled>
|
||||
<HeaderView loading={loading}/>
|
||||
<Table.Body>
|
||||
{
|
||||
(items) ? items.map((item) =>
|
||||
<RowComponent
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
localPaths={item.local_paths}
|
||||
stateColor={item.state_color}
|
||||
storageClass={item.storage_class}
|
||||
storageClassIsDefault={item.storage_class_is_default}
|
||||
deleteCommand={createDeleteCommand(item.name)}
|
||||
describeCommand={createDescribeCommand(item.name)}
|
||||
/>
|
||||
) : <p>No items</p>
|
||||
}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
);
|
||||
|
||||
class RowComponent extends Component {
|
||||
state = {expanded: true};
|
||||
|
||||
onToggleExpand = () => { this.setState({expanded: !this.state.expanded});}
|
||||
|
||||
render() {
|
||||
return [<RowView
|
||||
key={this.props.name}
|
||||
name={this.props.name}
|
||||
localPaths={this.props.localPaths}
|
||||
stateColor={this.props.stateColor}
|
||||
storageClass={this.props.storageClass}
|
||||
storageClassIsDefault={this.props.storageClassIsDefault}
|
||||
deleteCommand={this.props.deleteCommand}
|
||||
describeCommand={this.props.describeCommand}
|
||||
toggleExpand={this.onToggleExpand}
|
||||
expanded={this.state.expanded}
|
||||
/>,
|
||||
this.state.expanded && <VolumesRowView
|
||||
key={`${this.props.name}-vol`}
|
||||
name={this.props.name}
|
||||
expanded={this.state.expanded}
|
||||
toggleExpand={this.onToggleExpand}
|
||||
/>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const EmptyView = () => (<div>No local storage resources</div>);
|
||||
|
||||
function createDeleteCommand(name) {
|
||||
return `kubectl delete ArangoLocalStorage ${name}`;
|
||||
}
|
||||
|
||||
function createDescribeCommand(name) {
|
||||
return `kubectl describe ArangoLocalStorage ${name}`;
|
||||
}
|
||||
|
||||
function getStateColorDescription(stateColor) {
|
||||
switch (stateColor) {
|
||||
case "green":
|
||||
return "Everything is running smooth.";
|
||||
case "yellow":
|
||||
return "There is some activity going on, but local storage is available.";
|
||||
case "orange":
|
||||
return "There is some activity going on, local storage may be/become unavailable. You should pay attention now!";
|
||||
case "red":
|
||||
return "The local storage is in a bad state and manual intervention is likely needed.";
|
||||
default:
|
||||
return "State is not known.";
|
||||
}
|
||||
}
|
||||
|
||||
class StorageList extends Component {
|
||||
state = {
|
||||
items: undefined,
|
||||
error: undefined,
|
||||
loading: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.reloadStorages();
|
||||
}
|
||||
|
||||
reloadStorages = async() => {
|
||||
try {
|
||||
this.setState({loading: true});
|
||||
const result = await api.get('/api/storage');
|
||||
this.setState({
|
||||
items: result.storages,
|
||||
loading: false,
|
||||
error: undefined
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({error: e.message, loading: false});
|
||||
}
|
||||
this.props.setTimeout(this.reloadStorages, 5000);
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = this.state.items;
|
||||
if (!items) {
|
||||
return (<Loading/>);
|
||||
}
|
||||
if (items.length === 0) {
|
||||
return (<EmptyView/>);
|
||||
}
|
||||
return (<ListView items={items} loading={this.state.loading}/>);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactTimeout(StorageList);
|
58
dashboard/src/storage/StorageOperator.js
Normal file
58
dashboard/src/storage/StorageOperator.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { Component } from 'react';
|
||||
import LogoutContext from '../auth/LogoutContext.js';
|
||||
import StorageList from './StorageList.js';
|
||||
import { Header, Menu, Message, Segment } from 'semantic-ui-react';
|
||||
import styled from 'react-emotion';
|
||||
|
||||
const StyledMenu = styled(Menu)`
|
||||
width: 15rem !important;
|
||||
@media (max-width: 768px) {
|
||||
width: 10rem !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContentBox = styled('div')`
|
||||
margin-left: 15rem;
|
||||
@media (max-width: 768px) {
|
||||
margin-left: 10rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const ListView = () => (
|
||||
<div>
|
||||
<Header dividing>
|
||||
ArangoLocalStorages
|
||||
</Header>
|
||||
<StorageList/>
|
||||
</div>
|
||||
);
|
||||
|
||||
class StorageOperator extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<LogoutContext.Consumer>
|
||||
{doLogout =>
|
||||
<StyledMenu fixed="left" vertical>
|
||||
<Menu.Item>
|
||||
Local storages
|
||||
</Menu.Item>
|
||||
<Menu.Item position="right" onClick={() => doLogout()}>
|
||||
Logout
|
||||
</Menu.Item>
|
||||
</StyledMenu>
|
||||
}
|
||||
</LogoutContext.Consumer>
|
||||
<StyledContentBox>
|
||||
<Segment basic clearing>
|
||||
<ListView/>
|
||||
</Segment>
|
||||
{this.props.podInfoView}
|
||||
{(this.props.error) ? <Segment basic><Message error content={this.props.error}/></Segment> : null}
|
||||
</StyledContentBox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StorageOperator;
|
141
dashboard/src/storage/VolumeList.js
Normal file
141
dashboard/src/storage/VolumeList.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
import { Icon, Loader, Popup, Table } from 'semantic-ui-react';
|
||||
import api from '../api/api.js';
|
||||
import CommandInstruction from '../util/CommandInstruction.js';
|
||||
import Loading from '../util/Loading.js';
|
||||
import React, { Component } from 'react';
|
||||
import ReactTimeout from 'react-timeout';
|
||||
import styled from 'react-emotion';
|
||||
|
||||
const LoaderBox = styled('span')`
|
||||
float: right;
|
||||
width: 0;
|
||||
padding-right: 1em;
|
||||
max-width: 0;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const HeaderView = ({loading}) => (
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>State</Table.HeaderCell>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
Actions
|
||||
<LoaderBox><Loader size="mini" active={loading} inline/></LoaderBox>
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
);
|
||||
|
||||
const RowView = ({name, stateColor, describeCommand, deleteCommand}) => (
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Popup trigger={<Icon name={(stateColor==="green") ? "check" : "bell"} color={stateColor}/>}>
|
||||
{getStateColorDescription(stateColor)}
|
||||
</Popup>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{name}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<CommandInstruction
|
||||
trigger={<Icon link name="zoom"/>}
|
||||
command={describeCommand}
|
||||
title="Describe PersistentVolume"
|
||||
description="To get more information on the state of this PersistentVolume, run:"
|
||||
/>
|
||||
<span style={{"float":"right"}}>
|
||||
<CommandInstruction
|
||||
trigger={<Icon link name="trash"/>}
|
||||
command={deleteCommand}
|
||||
title="Delete PersistentVolume"
|
||||
description="To delete this PersistentVolume, run:"
|
||||
/>
|
||||
</span>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
|
||||
const ListView = ({items, loading}) => (
|
||||
<Table celled>
|
||||
<HeaderView loading={loading}/>
|
||||
<Table.Body>
|
||||
{
|
||||
(items) ? items.map((item) =>
|
||||
<RowView
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
stateColor={item.state_color}
|
||||
deleteCommand={createDeleteCommand(item.name)}
|
||||
describeCommand={createDescribeCommand(item.name)}
|
||||
/>
|
||||
) : <p>No items</p>
|
||||
}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
);
|
||||
|
||||
const EmptyView = () => (<div>No PersistentVolumes</div>);
|
||||
|
||||
function createDeleteCommand(name) {
|
||||
return `kubectl delete PersistentVolume ${name}`;
|
||||
}
|
||||
|
||||
function createDescribeCommand(name) {
|
||||
return `kubectl describe PersistentVolume ${name}`;
|
||||
}
|
||||
|
||||
function getStateColorDescription(stateColor) {
|
||||
switch (stateColor) {
|
||||
case "green":
|
||||
return "Everything is running smooth.";
|
||||
case "yellow":
|
||||
return "There is some activity going on, but PersistentVolume is available.";
|
||||
case "orange":
|
||||
return "There is some activity going on, PersistentVolume may be/become unavailable. You should pay attention now!";
|
||||
case "red":
|
||||
return "The PersistentVolume is in a bad state and manual intervention is likely needed.";
|
||||
default:
|
||||
return "State is not known.";
|
||||
}
|
||||
}
|
||||
|
||||
class VolumeList extends Component {
|
||||
state = {
|
||||
items: undefined,
|
||||
error: undefined,
|
||||
loading: true
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.reloadVolumes();
|
||||
}
|
||||
|
||||
reloadVolumes = async() => {
|
||||
try {
|
||||
this.setState({loading: true});
|
||||
const result = await api.get(`/api/storage/${this.props.storageName}`);
|
||||
this.setState({
|
||||
items: result.volumes,
|
||||
loading: false,
|
||||
error: undefined
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({error: e.message, loading: false});
|
||||
}
|
||||
this.props.setTimeout(this.reloadVolumes, 5000);
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = this.state.items;
|
||||
if (!items) {
|
||||
return (<Loading/>);
|
||||
}
|
||||
if (items.length === 0) {
|
||||
return (<EmptyView/>);
|
||||
}
|
||||
return (<ListView items={items} loading={this.state.loading}/>);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactTimeout(VolumeList);
|
|
@ -30,6 +30,9 @@ rules:
|
|||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["daemonsets"]
|
||||
verbs: ["*"]
|
||||
|
|
19
manifests/templates/storage/service.yaml
Normal file
19
manifests/templates/storage/service.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
apiVersion: ""
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Storage.OperatorDeploymentName }}
|
||||
namespace: {{ .Storage.Operator.Namespace }}
|
||||
labels:
|
||||
name: {{ .Storage.OperatorDeploymentName }}
|
||||
app: arango-storage-operator
|
||||
spec:
|
||||
ports:
|
||||
- name: server
|
||||
port: 8528
|
||||
protocol: TCP
|
||||
targetPort: 8528
|
||||
selector:
|
||||
name: {{ .Storage.OperatorDeploymentName }}
|
||||
app: arango-storage-operator
|
||||
role: leader
|
||||
type: {{ .Storage.Operator.ServiceType }}
|
|
@ -60,3 +60,36 @@ func (o *Operator) GetDeployment(name string) (server.Deployment, error) {
|
|||
}
|
||||
return nil, maskAny(server.NotFoundError)
|
||||
}
|
||||
|
||||
// StorageOperator provides the local storage operator (if any)
|
||||
func (o *Operator) StorageOperator() server.StorageOperator {
|
||||
return o
|
||||
}
|
||||
|
||||
// GetLocalStorages returns basic information for all local storages managed by the operator
|
||||
func (o *Operator) GetLocalStorages() ([]server.LocalStorage, error) {
|
||||
o.Dependencies.LivenessProbe.Lock()
|
||||
defer o.Dependencies.LivenessProbe.Unlock()
|
||||
|
||||
result := make([]server.LocalStorage, 0, len(o.localStorages))
|
||||
for _, ls := range o.localStorages {
|
||||
result = append(result, ls)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Name() < result[j].Name()
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetLocalStorage returns detailed information for a local, managed by the operator, with given name
|
||||
func (o *Operator) GetLocalStorage(name string) (server.LocalStorage, error) {
|
||||
o.Dependencies.LivenessProbe.Lock()
|
||||
defer o.Dependencies.LivenessProbe.Unlock()
|
||||
|
||||
for _, ls := range o.localStorages {
|
||||
if ls.Name() == name {
|
||||
return ls, nil
|
||||
}
|
||||
}
|
||||
return nil, maskAny(server.NotFoundError)
|
||||
}
|
||||
|
|
139
pkg/server/handlers_storage.go
Normal file
139
pkg/server/handlers_storage.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
//
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// LocalStorage is the API implemented by an ArangoLocalStorage.
|
||||
type LocalStorage interface {
|
||||
Name() string
|
||||
LocalPaths() []string
|
||||
StateColor() StateColor
|
||||
StorageClass() string
|
||||
StorageClassIsDefault() bool
|
||||
Volumes() []Volume
|
||||
}
|
||||
|
||||
// StorageOperator is the API implemented by the storage operator.
|
||||
type StorageOperator interface {
|
||||
// GetLocalStorages returns basic information for all local storages managed by the operator
|
||||
GetLocalStorages() ([]LocalStorage, error)
|
||||
// GetLocalStorage returns detailed information for a local, managed by the operator, with given name
|
||||
GetLocalStorage(name string) (LocalStorage, error)
|
||||
}
|
||||
|
||||
// LocalStorageInfo is the information returned per local storage.
|
||||
type LocalStorageInfo struct {
|
||||
Name string `json:"name"`
|
||||
LocalPaths []string `json:"local_paths"`
|
||||
StateColor StateColor `json:"state_color"`
|
||||
StorageClass string `json:"storage_class"`
|
||||
StorageClassIsDefault bool `json:"storage_class_is_default"`
|
||||
}
|
||||
|
||||
// newLocalStorageInfo initializes a LocalStorageInfo for the given LocalStorage.
|
||||
func newLocalStorageInfo(ls LocalStorage) LocalStorageInfo {
|
||||
return LocalStorageInfo{
|
||||
Name: ls.Name(),
|
||||
LocalPaths: ls.LocalPaths(),
|
||||
StateColor: ls.StateColor(),
|
||||
StorageClass: ls.StorageClass(),
|
||||
StorageClassIsDefault: ls.StorageClassIsDefault(),
|
||||
}
|
||||
}
|
||||
|
||||
// LocalStorageInfoDetails contains detailed info a local storage
|
||||
type LocalStorageInfoDetails struct {
|
||||
LocalStorageInfo
|
||||
Volumes []VolumeInfo `json:"volumes"`
|
||||
}
|
||||
|
||||
// newLocalStorageInfoDetails creates a LocalStorageInfoDetails for the given local storage
|
||||
func newLocalStorageInfoDetails(ls LocalStorage) LocalStorageInfoDetails {
|
||||
vols := ls.Volumes()
|
||||
result := LocalStorageInfoDetails{
|
||||
LocalStorageInfo: newLocalStorageInfo(ls),
|
||||
Volumes: make([]VolumeInfo, 0, len(vols)),
|
||||
}
|
||||
for _, v := range vols {
|
||||
result.Volumes = append(result.Volumes, newVolumeInfo(v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Volume is the API implemented by a volume created in a ArangoLocalStorage.
|
||||
type Volume interface {
|
||||
Name() string
|
||||
StateColor() StateColor
|
||||
}
|
||||
|
||||
// VolumeInfo contained the information returned per volume that is created on behalf of a local storage.
|
||||
type VolumeInfo struct {
|
||||
Name string `json:"name"`
|
||||
StateColor StateColor `json:"state_color"`
|
||||
}
|
||||
|
||||
// newVolumeInfo creates a VolumeInfo for the given volume
|
||||
func newVolumeInfo(v Volume) VolumeInfo {
|
||||
return VolumeInfo{
|
||||
Name: v.Name(),
|
||||
StateColor: v.StateColor(),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a GET /api/storage request
|
||||
func (s *Server) handleGetLocalStorages(c *gin.Context) {
|
||||
if o := s.deps.Operators.StorageOperator(); o != nil {
|
||||
// Fetch local storages
|
||||
stgs, err := o.GetLocalStorages()
|
||||
if err != nil {
|
||||
sendError(c, err)
|
||||
} else {
|
||||
result := make([]LocalStorageInfo, len(stgs))
|
||||
for i, ls := range stgs {
|
||||
result[i] = newLocalStorageInfo(ls)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"storages": result,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a GET /api/storage/:name request
|
||||
func (s *Server) handleGetLocalStorageDetails(c *gin.Context) {
|
||||
if o := s.deps.Operators.StorageOperator(); o != nil {
|
||||
// Fetch deployments
|
||||
ls, err := o.GetLocalStorage(c.Params.ByName("name"))
|
||||
if err != nil {
|
||||
sendError(c, err)
|
||||
} else {
|
||||
result := newLocalStorageInfoDetails(ls)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,6 +69,8 @@ type Dependencies struct {
|
|||
type Operators interface {
|
||||
// Return the deployment operator (if any)
|
||||
DeploymentOperator() DeploymentOperator
|
||||
// Return the local storage operator (if any)
|
||||
StorageOperator() StorageOperator
|
||||
}
|
||||
|
||||
// Server is the HTTPS server for the operator.
|
||||
|
@ -153,6 +155,10 @@ func NewServer(cli corev1.CoreV1Interface, cfg Config, deps Dependencies) (*Serv
|
|||
// Deployment operator
|
||||
api.GET("/deployment", s.handleGetDeployments)
|
||||
api.GET("/deployment/:name", s.handleGetDeploymentDetails)
|
||||
|
||||
// Local storage operator
|
||||
api.GET("/storage", s.handleGetLocalStorages)
|
||||
api.GET("/storage/:name", s.handleGetLocalStorageDetails)
|
||||
}
|
||||
// Dashboard
|
||||
r.GET("/", createAssetFileHandler(dashboard.Assets.Files["index.html"]))
|
||||
|
|
70
pkg/storage/server_api.go
Normal file
70
pkg/storage/server_api.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"github.com/arangodb/kube-arangodb/pkg/server"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Name returns the name of the local storage resource
|
||||
func (ls *LocalStorage) Name() string {
|
||||
return ls.apiObject.Name
|
||||
}
|
||||
|
||||
// LocalPaths returns the local paths (on nodes) of the local storage resource
|
||||
func (ls *LocalStorage) LocalPaths() []string {
|
||||
return ls.apiObject.Spec.LocalPath
|
||||
}
|
||||
|
||||
// StateColor returns a color describing the state of the local storage resource
|
||||
func (ls *LocalStorage) StateColor() server.StateColor {
|
||||
// TODO
|
||||
return server.StateYellow
|
||||
}
|
||||
|
||||
// StorageClass returns the name of the StorageClass specified in the local storage resource
|
||||
func (ls *LocalStorage) StorageClass() string {
|
||||
return ls.apiObject.Spec.StorageClass.Name
|
||||
}
|
||||
|
||||
// StorageClassIsDefault returns true if the StorageClass used by this local storage resource is supposed to be default
|
||||
func (ls *LocalStorage) StorageClassIsDefault() bool {
|
||||
return ls.apiObject.Spec.StorageClass.IsDefault
|
||||
}
|
||||
|
||||
// Volumes returns all volumes created by the local storage resource
|
||||
func (ls *LocalStorage) Volumes() []server.Volume {
|
||||
list, err := ls.deps.KubeCli.CoreV1().PersistentVolumes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
ls.deps.Log.Error().Err(err).Msg("Failed to list persistent volumes")
|
||||
return nil
|
||||
}
|
||||
result := make([]server.Volume, 0, len(list.Items))
|
||||
for _, pv := range list.Items {
|
||||
if ls.isOwnerOf(&pv) {
|
||||
result = append(result, serverVolume(pv))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
46
pkg/storage/server_volume_api.go
Normal file
46
pkg/storage/server_volume_api.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"github.com/arangodb/kube-arangodb/pkg/server"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type serverVolume v1.PersistentVolume
|
||||
|
||||
// Name returns the name of the volume
|
||||
func (v serverVolume) Name() string {
|
||||
return v.ObjectMeta.GetName()
|
||||
}
|
||||
|
||||
func (v serverVolume) StateColor() server.StateColor {
|
||||
switch v.Status.Phase {
|
||||
default:
|
||||
return server.StateYellow
|
||||
case v1.VolumeBound:
|
||||
return server.StateGreen
|
||||
case v1.VolumeFailed:
|
||||
return server.StateRed
|
||||
}
|
||||
}
|
|
@ -63,6 +63,7 @@ var (
|
|||
storageTemplateNames = []string{
|
||||
"rbac.yaml",
|
||||
"deployment.yaml",
|
||||
"service.yaml",
|
||||
}
|
||||
testTemplateNames = []string{
|
||||
"rbac.yaml",
|
||||
|
|
Loading…
Reference in a new issue