mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
Extensing deployment list
This commit is contained in:
parent
7c7849ac46
commit
81b98028b3
10 changed files with 357 additions and 77 deletions
File diff suppressed because one or more lines are too long
67
dashboard/package-lock.json
generated
67
dashboard/package-lock.json
generated
|
@ -4,6 +4,22 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.0.0-beta.51",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.51.tgz",
|
||||||
|
"integrity": "sha1-SLjtGDBwNMZiD2Q1FGUMoszAFlo=",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^2.5.7",
|
||||||
|
"regenerator-runtime": "^0.11.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
||||||
|
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"abab": {
|
"abab": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
|
||||||
|
@ -1334,6 +1350,7 @@
|
||||||
"version": "6.26.0",
|
"version": "6.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||||
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
|
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-js": "^2.4.0",
|
"core-js": "^2.4.0",
|
||||||
"regenerator-runtime": "^0.11.0"
|
"regenerator-runtime": "^0.11.0"
|
||||||
|
@ -1342,7 +1359,8 @@
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "2.5.7",
|
"version": "2.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
||||||
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
|
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2396,6 +2414,14 @@
|
||||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"copy-to-clipboard": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==",
|
||||||
|
"requires": {
|
||||||
|
"toggle-selection": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
||||||
|
@ -7037,6 +7063,11 @@
|
||||||
"integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
|
"integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"keyboard-key": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-OAfjaSI917BOonwfH6LQHMZJRv5035jjZvgElouB/DM4I7l5zEjrA15RD80YwIjhN69xqEfWCZIbhBcGpb85Ig=="
|
||||||
|
},
|
||||||
"killable": {
|
"killable": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz",
|
||||||
|
@ -9847,6 +9878,15 @@
|
||||||
"prop-types": "^15.6.0"
|
"prop-types": "^15.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-copy-to-clipboard": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==",
|
||||||
|
"requires": {
|
||||||
|
"copy-to-clipboard": "^3",
|
||||||
|
"prop-types": "^15.5.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dev-utils": {
|
"react-dev-utils": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.1.tgz",
|
||||||
|
@ -10532,15 +10572,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semantic-ui-react": {
|
"semantic-ui-react": {
|
||||||
"version": "0.78.3",
|
"version": "0.81.3",
|
||||||
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.78.3.tgz",
|
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-0.81.3.tgz",
|
||||||
"integrity": "sha512-JRmuqjyigCehHfzrS2ir5nGoytZWCifU8G2T++G/CMdahUJBME7S6E9rU7WW9Qg2Fqn2aJIxfn6Ry/rlOTJDOw==",
|
"integrity": "sha512-AGLKjtWT0HnOyduMJn+6T4JmV5DfEuOfN2iSpBmQ5ZNJxrmdD4hoiskP89MUT7JD4Mno7aH8KkzdIKxavxpJUw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"babel-runtime": "^6.25.0",
|
"@babel/runtime": "^7.0.0-beta.49",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"fbjs": "^0.8.16",
|
"keyboard-key": "^1.0.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.10",
|
||||||
"prop-types": "^15.5.10"
|
"prop-types": "^15.6.1",
|
||||||
|
"shallowequal": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
|
@ -10676,6 +10717,11 @@
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
@ -11446,6 +11492,11 @@
|
||||||
"repeat-string": "^1.6.1"
|
"repeat-string": "^1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toggle-selection": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
||||||
|
},
|
||||||
"toposort": {
|
"toposort": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.4.1",
|
"react": "^16.4.1",
|
||||||
|
"react-copy-to-clipboard": "^5.0.1",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.4.1",
|
||||||
"semantic-ui-less": "^2.2.12",
|
"semantic-ui-less": "^2.2.12",
|
||||||
"semantic-ui-react": "^0.78.2"
|
"semantic-ui-react": "^0.81.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"react-scripts": "1.1.4"
|
"react-scripts": "1.1.4"
|
||||||
},
|
},
|
||||||
"proxy": "https://kube-arangodb-operator:8528",
|
"proxy": "https://192.168.140.208:8528",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<!-- meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" -->
|
||||||
<meta name="theme-color" content="#000000">
|
<meta name="theme-color" content="#000000">
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is added to the
|
manifest.json provides metadata used when your web app is added to the
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { apiGet } from '../api/api.js';
|
import { apiGet } from '../api/api.js';
|
||||||
import { Icon, Table } from 'semantic-ui-react';
|
import { Icon, Popup, Table } from 'semantic-ui-react';
|
||||||
import Loading from '../util/Loading.js';
|
import Loading from '../util/Loading.js';
|
||||||
|
import CommandInstruction from '../util/CommandInstruction.js';
|
||||||
|
|
||||||
const HeaderView = () => (
|
const HeaderView = () => (
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
|
@ -9,17 +10,47 @@ const HeaderView = () => (
|
||||||
<Table.HeaderCell>State</Table.HeaderCell>
|
<Table.HeaderCell>State</Table.HeaderCell>
|
||||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||||
<Table.HeaderCell>Mode</Table.HeaderCell>
|
<Table.HeaderCell>Mode</Table.HeaderCell>
|
||||||
<Table.HeaderCell>Pods</Table.HeaderCell>
|
<Table.HeaderCell>Version</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell><Popup trigger={<span>Pods</span>}>Ready / Total</Popup></Table.HeaderCell>
|
||||||
|
<Table.HeaderCell><Popup trigger={<span>Volumes</span>}>Bound / Total</Popup></Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>StorageClass</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell></Table.HeaderCell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RowView = ({name, mode, ready_pod_count, pod_count}) => (
|
const DatabaseLinkView = ({name, url}) => (
|
||||||
|
<a href={url} target={name}>
|
||||||
|
<Popup trigger={<Icon link name="database"/>}>
|
||||||
|
Go the the web-UI of the database.
|
||||||
|
</Popup>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NoDatabaseLinkView = () => (
|
||||||
|
<Popup trigger={<Icon link name="database"/>}>
|
||||||
|
This deployment is not reachable outside the Kubernetes cluster.
|
||||||
|
</Popup>
|
||||||
|
);
|
||||||
|
|
||||||
|
const RowView = ({name, mode, version, license, ready_pod_count, pod_count, ready_volume_count, volume_count, storage_classes, database_url, delete_command}) => (
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.Cell><Icon name="bell" color="red"/></Table.Cell>
|
<Table.Cell><Icon name="bell" color="red"/></Table.Cell>
|
||||||
<Table.Cell>{name}</Table.Cell>
|
<Table.Cell>{name}</Table.Cell>
|
||||||
<Table.Cell>{mode}</Table.Cell>
|
<Table.Cell>{mode}</Table.Cell>
|
||||||
|
<Table.Cell>{version} {(license) ? `(${license})` : "" }</Table.Cell>
|
||||||
<Table.Cell>{ready_pod_count} / {pod_count}</Table.Cell>
|
<Table.Cell>{ready_pod_count} / {pod_count}</Table.Cell>
|
||||||
|
<Table.Cell>{ready_volume_count} / {volume_count}</Table.Cell>
|
||||||
|
<Table.Cell>{storage_classes.map((item) => (item === "") ? "<default>" : item)}</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{ database_url ? <DatabaseLinkView name={name} url={database_url}/> : <NoDatabaseLinkView/>}
|
||||||
|
<CommandInstruction
|
||||||
|
trigger={<Icon floated="right" name="trash alternate"/>}
|
||||||
|
command={delete_command}
|
||||||
|
title="Delete deployment"
|
||||||
|
description="To delete this deployment, run:"
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -32,9 +63,17 @@ const ListView = ({items}) => (
|
||||||
<RowView
|
<RowView
|
||||||
key={item.name}
|
key={item.name}
|
||||||
name={item.name}
|
name={item.name}
|
||||||
|
namespace={item.namespace}
|
||||||
mode={item.mode}
|
mode={item.mode}
|
||||||
|
version={item.database_version}
|
||||||
|
license={item.database_license}
|
||||||
ready_pod_count={item.ready_pod_count}
|
ready_pod_count={item.ready_pod_count}
|
||||||
pod_count={item.pod_count}
|
pod_count={item.pod_count}
|
||||||
|
ready_volume_count={item.ready_volume_count}
|
||||||
|
volume_count={item.volume_count}
|
||||||
|
storage_classes={item.storage_classes}
|
||||||
|
database_url={item.database_url}
|
||||||
|
delete_command={createDeleteCommand(item.name, item.namespace)}
|
||||||
/>) : <p>No items</p>
|
/>) : <p>No items</p>
|
||||||
}
|
}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
|
@ -43,6 +82,10 @@ const ListView = ({items}) => (
|
||||||
|
|
||||||
const EmptyView = () => (<div>No deployments</div>);
|
const EmptyView = () => (<div>No deployments</div>);
|
||||||
|
|
||||||
|
function createDeleteCommand(name, namespace) {
|
||||||
|
return `kubectl delete ArangoDeployment -n ${namespace} ${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
class DeploymentList extends Component {
|
class DeploymentList extends Component {
|
||||||
state = {};
|
state = {};
|
||||||
|
|
||||||
|
|
40
dashboard/src/util/CommandInstruction.js
Normal file
40
dashboard/src/util/CommandInstruction.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Button, Modal, Segment } from 'semantic-ui-react';
|
||||||
|
import {CopyToClipboard} from 'react-copy-to-clipboard';
|
||||||
|
|
||||||
|
class CommandInstruction extends Component {
|
||||||
|
state = {};
|
||||||
|
|
||||||
|
close = () => { this.setState({open:false}); }
|
||||||
|
open = () => { this.setState({open:true}); }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal trigger={this.props.trigger} onClose={this.close} onOpen={this.open} open={this.state.open}>
|
||||||
|
<Modal.Header>{this.props.title}</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<Modal.Description>
|
||||||
|
<p>
|
||||||
|
{this.props.description}
|
||||||
|
</p>
|
||||||
|
<Segment clearing>
|
||||||
|
<code>{this.props.command}</code>
|
||||||
|
</Segment>
|
||||||
|
</Modal.Description>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Actions>
|
||||||
|
<CopyToClipboard text={this.props.command} onCopy={this.close}>
|
||||||
|
<Button
|
||||||
|
positive
|
||||||
|
icon='copy'
|
||||||
|
labelPosition='right'
|
||||||
|
content="Copy"
|
||||||
|
/>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</Modal.Actions>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandInstruction;
|
|
@ -6,7 +6,7 @@ const Loading = ({message}) => (
|
||||||
<Dimmer inverted active>
|
<Dimmer inverted active>
|
||||||
<Loader inverted>{message || "Loading..."}</Loader>
|
<Loader inverted>{message || "Loading..."}</Loader>
|
||||||
</Dimmer>
|
</Dimmer>
|
||||||
<div style={{"min-height":"3em"}}/>
|
<div style={{minHeight:"3em"}}/>
|
||||||
</Segment>
|
</Segment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ rules:
|
||||||
verbs: ["*"]
|
verbs: ["*"]
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["pods"]
|
resources: ["pods"]
|
||||||
verbs: ["get"]
|
verbs: ["get", "update"]
|
||||||
- apiGroups: ["apps"]
|
- apiGroups: ["apps"]
|
||||||
resources: ["daemonsets"]
|
resources: ["daemonsets"]
|
||||||
verbs: ["*"]
|
verbs: ["*"]
|
||||||
|
|
|
@ -23,7 +23,16 @@
|
||||||
package deployment
|
package deployment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha"
|
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha"
|
||||||
|
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name returns the name of the deployment.
|
// Name returns the name of the deployment.
|
||||||
|
@ -31,6 +40,11 @@ func (d *Deployment) Name() string {
|
||||||
return d.apiObject.Name
|
return d.apiObject.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Namespace returns the namespace that contains the deployment.
|
||||||
|
func (d *Deployment) Namespace() string {
|
||||||
|
return d.apiObject.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
// Mode returns the mode of the deployment.
|
// Mode returns the mode of the deployment.
|
||||||
func (d *Deployment) Mode() api.DeploymentMode {
|
func (d *Deployment) Mode() api.DeploymentMode {
|
||||||
return d.GetSpec().GetMode()
|
return d.GetSpec().GetMode()
|
||||||
|
@ -68,3 +82,113 @@ func (d *Deployment) ReadyPodCount() int {
|
||||||
})
|
})
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VolumeCount returns the number of volumes for the deployment
|
||||||
|
func (d *Deployment) VolumeCount() int {
|
||||||
|
count := 0
|
||||||
|
status, _ := d.GetStatus()
|
||||||
|
status.Members.ForeachServerGroup(func(group api.ServerGroup, list api.MemberStatusList) error {
|
||||||
|
for _, m := range list {
|
||||||
|
if m.PersistentVolumeClaimName != "" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadyVolumeCount returns the number of volumes for the deployment that are in ready state
|
||||||
|
func (d *Deployment) ReadyVolumeCount() int {
|
||||||
|
count := 0
|
||||||
|
status, _ := d.GetStatus()
|
||||||
|
pvcs, _ := d.GetOwnedPVCs() // Ignore errors on purpose
|
||||||
|
status.Members.ForeachServerGroup(func(group api.ServerGroup, list api.MemberStatusList) error {
|
||||||
|
for _, m := range list {
|
||||||
|
if m.PersistentVolumeClaimName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Find status
|
||||||
|
for _, pvc := range pvcs {
|
||||||
|
if pvc.Name == m.PersistentVolumeClaimName {
|
||||||
|
if pvc.Status.Phase == v1.ClaimBound {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageClasses returns the names of the StorageClasses used by this deployment.
|
||||||
|
func (d *Deployment) StorageClasses() []string {
|
||||||
|
scNames := make(map[string]struct{})
|
||||||
|
spec := d.GetSpec()
|
||||||
|
mode := spec.GetMode()
|
||||||
|
if mode.HasAgents() {
|
||||||
|
scNames[spec.Agents.GetStorageClassName()] = struct{}{}
|
||||||
|
}
|
||||||
|
if mode.HasDBServers() {
|
||||||
|
scNames[spec.DBServers.GetStorageClassName()] = struct{}{}
|
||||||
|
}
|
||||||
|
if mode.HasSingleServers() {
|
||||||
|
scNames[spec.Single.GetStorageClassName()] = struct{}{}
|
||||||
|
}
|
||||||
|
result := make([]string, 0, len(scNames))
|
||||||
|
for k := range scNames {
|
||||||
|
result = append(result, k)
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseURL returns an URL to reach the database from outside the Kubernetes cluster
|
||||||
|
// Empty string means that the database is not reachable outside the Kubernetes cluster.
|
||||||
|
func (d *Deployment) DatabaseURL() string {
|
||||||
|
eaSvcName := k8sutil.CreateDatabaseExternalAccessServiceName(d.Name())
|
||||||
|
ns := d.apiObject.Namespace
|
||||||
|
svc, err := d.deps.KubeCli.CoreV1().Services(ns).Get(eaSvcName, metav1.GetOptions{})
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseVersion returns the version used by the deployment
|
||||||
|
// Returns versionNumber, licenseType
|
||||||
|
func (d *Deployment) DatabaseVersion() (string, string) {
|
||||||
|
image := d.GetSpec().GetImage()
|
||||||
|
status, _ := d.GetStatus()
|
||||||
|
info, found := status.Images.GetByImage(image)
|
||||||
|
if !found {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
license := "community"
|
||||||
|
if info.Enterprise {
|
||||||
|
license = "enterprise"
|
||||||
|
}
|
||||||
|
return string(info.ArangoDBVersion), license
|
||||||
|
}
|
||||||
|
|
|
@ -33,9 +33,15 @@ import (
|
||||||
// Deployment is the API implemented by an ArangoDeployment.
|
// Deployment is the API implemented by an ArangoDeployment.
|
||||||
type Deployment interface {
|
type Deployment interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
Namespace() string
|
||||||
Mode() api.DeploymentMode
|
Mode() api.DeploymentMode
|
||||||
PodCount() int
|
PodCount() int
|
||||||
ReadyPodCount() int
|
ReadyPodCount() int
|
||||||
|
VolumeCount() int
|
||||||
|
ReadyVolumeCount() int
|
||||||
|
StorageClasses() []string
|
||||||
|
DatabaseURL() string
|
||||||
|
DatabaseVersion() (string, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeploymentOperator is the API implemented by the deployment operator.
|
// DeploymentOperator is the API implemented by the deployment operator.
|
||||||
|
@ -46,9 +52,16 @@ type DeploymentOperator interface {
|
||||||
// DeploymentInfo is the information returned per deployment.
|
// DeploymentInfo is the information returned per deployment.
|
||||||
type DeploymentInfo struct {
|
type DeploymentInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
Mode api.DeploymentMode `json:"mode"`
|
Mode api.DeploymentMode `json:"mode"`
|
||||||
PodCount int `json:"pod_count"`
|
PodCount int `json:"pod_count"`
|
||||||
ReadyPodCount int `json:"ready_pod_count"`
|
ReadyPodCount int `json:"ready_pod_count"`
|
||||||
|
VolumeCount int `json:"volume_count"`
|
||||||
|
ReadyVolumeCount int `json:"ready_volume_count"`
|
||||||
|
StorageClasses []string `json:"storage_classes"`
|
||||||
|
DatabaseURL string `json:"database_url"`
|
||||||
|
DatabaseVersion string `json:"database_version"`
|
||||||
|
DatabaseLicense string `json:"database_license"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle a GET /api/deployment request
|
// Handle a GET /api/deployment request
|
||||||
|
@ -61,11 +74,19 @@ func (s *Server) handleGetDeployments(c *gin.Context) {
|
||||||
} else {
|
} else {
|
||||||
result := make([]DeploymentInfo, len(depls))
|
result := make([]DeploymentInfo, len(depls))
|
||||||
for i, d := range depls {
|
for i, d := range depls {
|
||||||
|
version, license := d.DatabaseVersion()
|
||||||
result[i] = DeploymentInfo{
|
result[i] = DeploymentInfo{
|
||||||
Name: d.Name(),
|
Name: d.Name(),
|
||||||
|
Namespace: d.Namespace(),
|
||||||
Mode: d.Mode(),
|
Mode: d.Mode(),
|
||||||
PodCount: d.PodCount(),
|
PodCount: d.PodCount(),
|
||||||
ReadyPodCount: d.ReadyPodCount(),
|
ReadyPodCount: d.ReadyPodCount(),
|
||||||
|
VolumeCount: d.VolumeCount(),
|
||||||
|
ReadyVolumeCount: d.ReadyVolumeCount(),
|
||||||
|
StorageClasses: d.StorageClasses(),
|
||||||
|
DatabaseURL: d.DatabaseURL(),
|
||||||
|
DatabaseVersion: version,
|
||||||
|
DatabaseLicense: license,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|
Loading…
Reference in a new issue