Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions vulcan/lib/client/jsx/api_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,10 @@ export const defaultVulcanStorage: VulcanStorage = {

export type LatencyReturn = {
latency: string // of form "#{median_latency}ms"
};

export type ClusterStatusReturn = {
connection_success: boolean;
expected_down: boolean;
message: string; // of form "#{median_latency}ms"
};
55 changes: 55 additions & 0 deletions vulcan/lib/client/jsx/components/cluster_check.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {useCallback, useState, useContext, useEffect} from 'react';

import {VulcanContext} from '../contexts/vulcan_context';
import Alert from '@material-ui/lab/Alert';
import AlertTitle from '@material-ui/lab/AlertTitle';

export default function ClusterStatusReport({projectName}: {
projectName: string;
}) {
const [connected, setConnected] = useState(true);
const [message, setMessage] = useState('');
const [expectedDown, setExpectedDown] = useState(false);

let {showErrors,
showError,
getClusterStatus
} = useContext(VulcanContext);

const handleCheck = useCallback( () => {
showErrors(getClusterStatus(projectName))
.then( (StatusReturn) => {
setConnected(StatusReturn.connection_success)
setExpectedDown(StatusReturn.expected_down)
setMessage(StatusReturn.message)
})
}, [projectName, getClusterStatus]);

// Run once, automatically on page load
useEffect(() => {
handleCheck()
}, [])

console.log({
connected,
expectedDown,
message
})

// Connected with no message to show
if (message==='' && connected) return null

// Connected, but message to show (likely: planned downage on the horizon)
let messageUse: string = message;
// Disconnected, but expectedly
if (!connected && expectedDown) {
messageUse = `Expected Vulcan Connection Failure. Workspaces are inaccessible.\n\n${message}`;
}
// Disconnected and unexpectedly so
if (!connected && !expectedDown) {
messageUse = 'Unexpected Vulcan Connection Failure. Workspaces are inaccessible.\n\n' +
`Try again in a few minutes, but let the Data Library team know if the issue persists.\n\nAlso note: ${message}`
}
showError(messageUse);
return null
}
10 changes: 5 additions & 5 deletions vulcan/lib/client/jsx/components/latency_check/latency_check.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useCallback, useState, useContext} from 'react';
import React, {useCallback, useState, useContext, useEffect} from 'react';

import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
Expand All @@ -13,18 +13,18 @@ export default function LatencyCheckButton({projectName}: {
const [latency, setLatency] = useState<number | null>(null);

let {showErrors,
getConnectionLatency
getClusterLatency
} = useContext(VulcanContext);

const handleCheck = useCallback( () => {
setChecking(true);
showErrors(getConnectionLatency(projectName), () => {setChecking(false)})
showErrors(getClusterLatency(projectName), () => {setChecking(false)})
.then( (latencyReturn) => {
setChecking(false);
const latencyString = latencyReturn.latency;
setLatency(parseFloat(latencyString))
})
}, [projectName, getConnectionLatency]);
}, [projectName, getClusterLatency]);

const valShow = 'Latency: ' + (latency!=null ? latency + 'ms' : '???ms');
const actionIcon = checking ? <LoadingIcon/> : <TimerIcon/>;
Expand All @@ -36,8 +36,8 @@ export default function LatencyCheckButton({projectName}: {
color='primary'
variant='contained'
>
{valShow}
{actionIcon}
{valShow}
</Button>
</Tooltip>
}
Expand Down
29 changes: 18 additions & 11 deletions vulcan/lib/client/jsx/components/vulcan_nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import Nav from 'etna-js/components/Nav';
import Link from 'etna-js/components/link';
import {selectUser} from 'etna-js/selectors/user-selector';
import LatencyCheckButton from './latency_check/latency_check';
import ClusterStatusReport from './cluster_check'
import Grid from '@material-ui/core/Grid';

const {sin, cos, PI, random, max, min, pow, abs, sqrt} = Math;

Expand Down Expand Up @@ -145,17 +147,22 @@ const getTabs = (workspace) => ({
});

const ModeBar = ({mode, workspace}) => (
<div id='nav'>
{Object.entries(getTabs(workspace)).map(([tab_name, route]) => (
<div
key={tab_name}
className={`nav_tab ${mode == tab_name ? 'selected' : ''}`}
>
<Link link={route}>{tab_name}</Link>
</div>
))}
<LatencyCheckButton/>
</div>
<Grid id='nav' container alignItems='center' spacing={2} style={{paddingLeft: '5px', paddingRight: '5px'}}>
<Grid item>
{Object.entries(getTabs(workspace)).map(([tab_name, route]) => (
<div
key={tab_name}
className={`nav_tab ${mode == tab_name ? 'selected' : ''}`}
>
<Link link={route}>{tab_name}</Link>
</div>
))}
</Grid>
<Grid item>
<LatencyCheckButton/>
<ClusterStatusReport/>
</Grid>
</Grid>
);

const VulcanNav = ({mode, user}) => {
Expand Down
19 changes: 14 additions & 5 deletions vulcan/lib/client/jsx/contexts/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
CreateWorkspaceResponse,
isRunningReturn,
WorkspacesResponseRaw,
LatencyReturn
LatencyReturn,
ClusterStatusReturn
} from '../api_types';
import { paramValuesToRaw, workspacesFromResponse } from '../selectors/workflow_selectors';
import { isSome } from '../selectors/maybe';
Expand Down Expand Up @@ -103,7 +104,10 @@ export const defaultApiHelpers = {
pullRunStatus(projectName: string, workspaceId: number, runId: number): Promise<RunStatus> {
return new Promise(() => null);
},
getConnectionLatency(projectName: string): Promise<LatencyReturn> {
getClusterLatency(projectName: string): Promise<LatencyReturn> {
return new Promise(() => null);
},
getClusterStatus(projectName: string): Promise<ClusterStatusReturn> {
return new Promise(() => null);
}
};
Expand Down Expand Up @@ -163,7 +167,7 @@ export function useApi(
const showError = useCallback((e: any, dismissOld: boolean = false) => {
if (dismissOld) invoke(dismissMessages());
console.error(e);
invoke(showMessages(e));
invoke(showMessages([e]));
}, [invoke]);
const showErrors = useCallback(
<T>(work: Promise<T>, additional: (e: any) => void = (e) => {}): Promise<T> => {
Expand Down Expand Up @@ -351,10 +355,14 @@ export function useApi(
return vulcanGet(vulcanPath(`/api/v2/${projectName}/workspace/${workspaceId}/run/${runId}`))
}, [vulcanGet, vulcanPath]);

const getConnectionLatency = useCallback((projectName: string): Promise<LatencyReturn> => {
const getClusterLatency = useCallback((projectName: string): Promise<LatencyReturn> => {
return vulcanGet(vulcanPath(`/api/v2/${projectName}/cluster-latency`))
}, [vulcanGet, vulcanPath]);

const getClusterStatus = useCallback((projectName: string): Promise<ClusterStatusReturn> => {
return vulcanGet(vulcanPath(`/api/v2/${projectName}/cluster-status`))
}, [vulcanGet, vulcanPath]);

return {
vulcanPath,
showError,
Expand All @@ -375,6 +383,7 @@ export function useApi(
getIsRunning,
pullRunStatus,
getImage,
getConnectionLatency
getClusterLatency,
getClusterStatus,
};
}
10 changes: 10 additions & 0 deletions vulcan/lib/remote_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@ def invoke_and_close(command)
end
end

def check_connection()
command = build_command.add('echo', 'connection_check')
begin
invoke_ssh_command(command.to_s)
return true
rescue => e
return false
end
end

def measure_latency(runs: 15)
# Measure SSH latency by running a simple command multiple times and taking the median
latencies = []
Expand Down
1 change: 1 addition & 0 deletions vulcan/lib/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Server < Etna::Server

# Cluster latency endpoint
get 'api/v2/:project_name/cluster-latency', action: 'vulcan_v2#cluster_latency', auth: { user: { can_view?: :project_name }}
get 'api/v2/:project_name/cluster-status', action: 'vulcan_v2#cluster_status', auth: { user: { can_view?: :project_name }}

# root path
get '/', as: :root do erb_view(:client) end
Expand Down
8 changes: 8 additions & 0 deletions vulcan/lib/server/controllers/vulcan_v2_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ def download_file
retrieve_file(@params[:file_name], "application/octet-stream", disposition: "attachment; filename=#{@params[:file_name]}")
end

def cluster_status
success_json({
connection_success: @remote_manager.check_connection,
expected_down: Vulcan.instance.config(:ssh)[:downage_expected],
message: Vulcan.instance.config(:ssh)[:downage_message],
})
end

def cluster_latency
begin
# Measure SSH latency using the remote_manager's measure_latency method
Expand Down