Fix Arc Postgres refresh (#11074)

This commit is contained in:
Brian Bergeron
2020-06-24 11:14:45 -07:00
committed by GitHub
parent 00836e1890
commit 6f6bf3f3b3
6 changed files with 192 additions and 82 deletions

View File

@@ -172,10 +172,10 @@ export class PostgresModel extends ResourceModel {
}
/** Given a V1Pod returns its status */
public static getPodStatus(pod: V1Pod) {
public static getPodStatus(pod: V1Pod): string {
const phase = pod.status?.phase;
if (phase !== 'Running') {
return phase;
return phase ?? '';
}
// Pods can be in the running phase while some

View File

@@ -7,18 +7,19 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles } from '../../../constants';
import { KeyValueContainer, InputKeyValue } from '../../components/keyValueContainer';
import { KeyValueContainer, InputKeyValue, KeyValue } from '../../components/keyValueContainer';
import { DashboardPage } from '../../components/dashboardPage';
import { PostgresModel } from '../../../models/postgresModel';
export class PostgresConnectionStringsPage extends DashboardPage {
private loading?: azdata.LoadingComponent;
private keyValueContainer?: KeyValueContainer;
constructor(protected modelView: azdata.ModelView, private _postgresModel: PostgresModel) {
super(modelView);
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
}
protected get title(): string {
@@ -58,8 +59,14 @@ export class PostgresConnectionStringsPage extends DashboardPage {
infoAndLink.addItem(link);
content.addItem(infoAndLink, { CSSStyles: { 'margin-bottom': '25px' } });
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
content.addItem(this.keyValueContainer.container);
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
this.loading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.keyValueContainer.container)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.serviceLastUpdated
}).component();
content.addItem(this.loading);
this.initialized = true;
return root;
}
@@ -74,6 +81,7 @@ export class PostgresConnectionStringsPage extends DashboardPage {
refreshButton.onDidClick(async () => {
refreshButton.enabled = false;
try {
this.loading!.loading = true;
await this._postgresModel.refresh();
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));
@@ -87,10 +95,10 @@ export class PostgresConnectionStringsPage extends DashboardPage {
]).component();
}
private refresh() {
private getConnectionStrings(): KeyValue[] {
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
this.keyValueContainer?.refresh([
return [
new InputKeyValue('ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password={your_password_here};Ssl Mode=Require;`),
new InputKeyValue('C++ (libpq)', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
new InputKeyValue('JDBC', `jdbc:postgresql://${endpoint.ip}:${endpoint.port}/postgres?user=postgres&password={your_password_here}&sslmode=require`),
@@ -100,6 +108,11 @@ export class PostgresConnectionStringsPage extends DashboardPage {
new InputKeyValue('Python', `dbname='postgres' user='postgres' host='${endpoint.ip}' password='{your_password_here}' port='${endpoint.port}' sslmode='true'`),
new InputKeyValue('Ruby', `host=${endpoint.ip}; dbname=postgres user=postgres password={your_password_here} port=${endpoint.port} sslmode=require`),
new InputKeyValue('Web App', `Database=postgres; Data Source=${endpoint.ip}; User Id=postgres; Password={your_password_here}`)
]);
];
}
private handleServiceUpdated() {
this.keyValueContainer?.refresh(this.getConnectionStrings());
this.loading!.loading = false;
}
}

View File

@@ -23,8 +23,10 @@ export class PostgresDashboard extends Dashboard {
public async showDashboard(): Promise<void> {
await super.showDashboard();
// Kick off the model refresh but don't wait on it since that's all handled with callbacks anyways
this._postgresModel.refresh().catch(err => console.log(`Error refreshing Postgres dashboard ${err}`));
this._controllerModel.refresh().catch(err => console.log(`Error refreshing controller model for Postgres dashboard ${err}`));
this._postgresModel.refresh().catch(err => console.log(`Error refreshing Postgres model for Postgres dashboard ${err}`));
}
protected async registerTabs(modelView: azdata.ModelView): Promise<(azdata.DashboardTab | azdata.DashboardTabGroup)[]> {

View File

@@ -29,26 +29,10 @@ export class PostgresOverviewPage extends DashboardPage {
super(modelView);
this.disposables.push(
this._controllerModel.onEndpointsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refreshEndpoints())));
this.disposables.push(
this._controllerModel.onRegistrationsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refreshProperties())));
this.disposables.push(
this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
})));
this.disposables.push(
this._postgresModel.onPodsUpdated(
() => this.eventuallyRunOnInitialized(() => {
this.refreshProperties();
this.refreshNodes();
})));
this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdated())),
this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
this._postgresModel.onServiceUpdated(() => this.eventuallyRunOnInitialized(() => this.hadleServiceUpdated())),
this._postgresModel.onPodsUpdated(() => this.eventuallyRunOnInitialized(() => this.handlePodsUpdated())));
}
protected get title(): string {
@@ -69,8 +53,17 @@ export class PostgresOverviewPage extends DashboardPage {
root.addItem(content, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
// Properties
this.properties = this.modelView.modelBuilder.propertiesContainer().component();
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.properties).component();
this.properties = this.modelView.modelBuilder.propertiesContainer()
.withProperties<azdata.PropertiesContainerComponentProperties>({
propertyItems: this.getProperties()
}).component();
this.propertiesLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.properties)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._controllerModel.registrationsLastUpdated && !this._postgresModel.serviceLastUpdated && !this._postgresModel.podsLastUpdated
}).component();
content.addItem(this.propertiesLoading, { CSSStyles: cssStyles.text });
// Service endpoints
@@ -80,10 +73,29 @@ export class PostgresOverviewPage extends DashboardPage {
CSSStyles: titleCSS
}).component());
this.kibanaLink = this.modelView.modelBuilder.hyperlink().component();
this.grafanaLink = this.modelView.modelBuilder.hyperlink().component();
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.kibanaLink).component();
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.grafanaLink).component();
this.kibanaLink = this.modelView.modelBuilder.hyperlink()
.withProperties<azdata.HyperlinkComponentProperties>({
label: this.getKibanaLink(),
url: this.getKibanaLink()
}).component();
this.grafanaLink = this.modelView.modelBuilder.hyperlink()
.withProperties<azdata.HyperlinkComponentProperties>({
label: this.getGrafanaLink(),
url: this.getGrafanaLink()
}).component();
this.kibanaLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.kibanaLink)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._controllerModel.endpointsLastUpdated
}).component();
this.grafanaLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.grafanaLink)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._controllerModel.endpointsLastUpdated
}).component();
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%',
@@ -167,10 +179,15 @@ export class PostgresOverviewPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: []
data: this.getNodes()
}).component();
this.nodesTableLoading = this.modelView.modelBuilder.loadingComponent().withItem(this.nodesTable).component();
this.nodesTableLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.nodesTable)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.serviceLastUpdated && !this._postgresModel.podsLastUpdated
}).component();
content.addItem(this.nodesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
this.initialized = true;
return root;
@@ -300,11 +317,11 @@ export class PostgresOverviewPage extends DashboardPage {
]).component();
}
private refreshProperties() {
private getProperties(): azdata.PropertiesContainerItem[] {
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
this.properties!.propertyItems = [
return [
{ displayName: loc.name, value: this._postgresModel.name },
{ displayName: loc.coordinatorEndpoint, value: `postgresql://postgres@${endpoint.ip}:${endpoint.port}` },
{ displayName: loc.status, value: this._postgresModel.service?.status?.state ?? '' },
@@ -314,29 +331,24 @@ export class PostgresOverviewPage extends DashboardPage {
{ displayName: loc.subscriptionId, value: registration?.subscriptionId ?? '' },
{ displayName: loc.postgresVersion, value: this._postgresModel.service?.spec?.engine?.version?.toString() ?? '' }
];
this.propertiesLoading!.loading = false;
}
private refreshEndpoints() {
private getKibanaLink(): string {
const kibanaQuery = `kubernetes_namespace:"${this._postgresModel.namespace}" and cluster_name:"${this._postgresModel.name}"`;
const kibanaUrl = `${this._controllerModel.getEndpoint('logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
this.kibanaLink!.label = kibanaUrl;
this.kibanaLink!.url = kibanaUrl;
return `${this._controllerModel.getEndpoint('logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
const grafanaUrl = `${this._controllerModel.getEndpoint('metricsui')?.endpoint}/d/postgres-metrics?var-Namespace=${this._postgresModel.namespace}&var-Name=${this._postgresModel.name}`;
this.grafanaLink!.label = grafanaUrl;
this.grafanaLink!.url = grafanaUrl;
this.kibanaLoading!.loading = false;
this.grafanaLoading!.loading = false;
}
private refreshNodes() {
private getGrafanaLink(): string {
const grafanaQuery = `var-Namespace=${this._postgresModel.namespace}&var-Name=${this._postgresModel.name}`;
return `${this._controllerModel.getEndpoint('metricsui')?.endpoint}/d/postgres-metrics?${grafanaQuery}`;
}
private getNodes(): string[][] {
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
this.nodesTable!.data = this._postgresModel.pods?.map((pod: V1Pod) => {
const name = pod.metadata?.name;
return this._postgresModel.pods?.map((pod: V1Pod) => {
const name = pod.metadata?.name ?? '';
const role: PodRole | undefined = PostgresModel.getPodRole(pod);
const service = pod.metadata?.annotations?.['arcdata.microsoft.com/serviceHost'];
const internalDns = service ? `${name}.${service}` : '';
@@ -348,7 +360,37 @@ export class PostgresOverviewPage extends DashboardPage {
role === PodRole.Router ? `${endpoint.ip}:${endpoint.port}` : internalDns
];
}) ?? [];
}
private handleEndpointsUpdated() {
this.kibanaLink!.label = this.getKibanaLink();
this.kibanaLink!.url = this.getKibanaLink();
this.kibanaLoading!.loading = false;
this.grafanaLink!.label = this.getGrafanaLink();
this.grafanaLink!.url = this.getGrafanaLink();
this.grafanaLoading!.loading = false;
}
private handleRegistrationsUpdated() {
this.properties!.propertyItems = this.getProperties();
this.propertiesLoading!.loading = false;
}
private hadleServiceUpdated() {
this.properties!.propertyItems = this.getProperties();
this.propertiesLoading!.loading = false;
this.nodesTable!.data = this.getNodes();
this.nodesTableLoading!.loading = false;
}
private handlePodsUpdated() {
this.properties!.propertyItems = this.getProperties();
this.propertiesLoading!.loading = false;
this.nodesTable!.data = this.getNodes();
this.nodesTableLoading!.loading = false;
}
}

View File

@@ -7,22 +7,23 @@ import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles, ResourceType } from '../../../constants';
import { KeyValueContainer, InputKeyValue, LinkKeyValue, TextKeyValue } from '../../components/keyValueContainer';
import { KeyValueContainer, InputKeyValue, TextKeyValue, KeyValue } from '../../components/keyValueContainer';
import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel } from '../../../models/controllerModel';
import { PostgresModel } from '../../../models/postgresModel';
export class PostgresPropertiesPage extends DashboardPage {
private loading?: azdata.LoadingComponent;
private keyValueContainer?: KeyValueContainer;
constructor(protected modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _postgresModel: PostgresModel) {
super(modelView);
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
this.disposables.push(this._controllerModel.onRegistrationsUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())));
}
protected get title(): string {
@@ -47,8 +48,14 @@ export class PostgresPropertiesPage extends DashboardPage {
CSSStyles: { ...cssStyles.title, 'margin-bottom': '25px' }
}).component());
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
content.addItem(this.keyValueContainer.container);
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getProperties());
this.loading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.keyValueContainer.container)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.serviceLastUpdated && !this._controllerModel.registrationsLastUpdated
}).component();
content.addItem(this.loading);
this.initialized = true;
return root;
}
@@ -63,6 +70,7 @@ export class PostgresPropertiesPage extends DashboardPage {
refreshButton.onDidClick(async () => {
refreshButton.enabled = false;
try {
this.loading!.loading = true;
await Promise.all([
this._postgresModel.refresh(),
this._controllerModel.refresh()
@@ -80,20 +88,31 @@ export class PostgresPropertiesPage extends DashboardPage {
]).component();
}
private refresh() {
private getProperties(): KeyValue[] {
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
const connectionString = `postgresql://postgres@${endpoint.ip}:${endpoint.port}`;
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
this.keyValueContainer?.refresh([
return [
new InputKeyValue(loc.coordinatorEndpoint, connectionString),
new InputKeyValue(loc.postgresAdminUsername, 'postgres'),
new TextKeyValue(loc.status, this._postgresModel.service?.status?.state ?? 'Unknown'),
new LinkKeyValue(loc.dataController, this._controllerModel.namespace ?? '', _ => vscode.window.showInformationMessage('TODO: Go to data controller')),
// TODO: Make this a LinkKeyValue that opens the controller dashboard
new TextKeyValue(loc.dataController, this._controllerModel.namespace ?? ''),
new TextKeyValue(loc.nodeConfiguration, this._postgresModel.configuration),
new TextKeyValue(loc.postgresVersion, this._postgresModel.service?.spec?.engine?.version?.toString() ?? ''),
new TextKeyValue(loc.resourceGroup, registration?.resourceGroupName ?? ''),
new TextKeyValue(loc.subscriptionId, registration?.subscriptionId ?? '')
]);
];
}
private handleRegistrationsUpdated() {
this.keyValueContainer?.refresh(this.getProperties());
this.loading!.loading = false;
}
private handleServiceUpdated() {
this.keyValueContainer?.refresh(this.getProperties());
this.loading!.loading = false;
}
}

View File

@@ -14,6 +14,8 @@ import { fromNow } from '../../../common/date';
export class PostgresResourceHealthPage extends DashboardPage {
private interval: NodeJS.Timeout;
private podsUpdated?: azdata.TextComponent;
private podsLoading?: azdata.LoadingComponent;
private conditionsLoading?: azdata.LoadingComponent;
private podsTable?: azdata.DeclarativeTableComponent;
private conditionsTable?: azdata.DeclarativeTableComponent;
@@ -27,10 +29,10 @@ export class PostgresResourceHealthPage extends DashboardPage {
}));
this.disposables.push(this._postgresModel.onServiceUpdated(
() => this.eventuallyRunOnInitialized(() => this.refresh())));
() => this.eventuallyRunOnInitialized(() => this.handleServiceUpdated())));
// Keep the last updated timestamps up to date with the current time
this.interval = setInterval(() => this.refresh(), 60 * 1000);
this.interval = setInterval(() => this.handleServiceUpdated(), 60 * 1000);
}
protected get title(): string {
@@ -61,7 +63,10 @@ export class PostgresResourceHealthPage extends DashboardPage {
CSSStyles: titleCSS
}).component());
this.podsUpdated = this.modelView.modelBuilder.text().component();
this.podsUpdated = this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: this.getPodsLastUpdated()
}).component();
content.addItem(this.podsUpdated);
// Pod overview
@@ -84,9 +89,16 @@ export class PostgresResourceHealthPage extends DashboardPage {
rowCssStyles: cssStyles.tableRow
}
],
data: []
data: this.getPodsTable()
}).component();
content.addItem(this.podsTable, { CSSStyles: { 'margin-bottom': '30px' } });
this.podsLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.podsTable)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.serviceLastUpdated
}).component();
content.addItem(this.podsLoading, { CSSStyles: { 'margin-bottom': '30px' } });
// Conditions table
this.conditionsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
@@ -125,10 +137,16 @@ export class PostgresResourceHealthPage extends DashboardPage {
rowCssStyles: { ...cssStyles.tableRow, 'white-space': 'nowrap' }
}
],
data: []
data: this.getConditionsTable()
}).component();
content.addItem(this.conditionsTable);
this.conditionsLoading = this.modelView.modelBuilder.loadingComponent()
.withItem(this.conditionsTable)
.withProperties<azdata.LoadingComponentProperties>({
loading: !this._postgresModel.serviceLastUpdated
}).component();
content.addItem(this.conditionsLoading);
this.initialized = true;
return root;
}
@@ -143,6 +161,8 @@ export class PostgresResourceHealthPage extends DashboardPage {
refreshButton.onDidClick(async () => {
refreshButton.enabled = false;
try {
this.podsLoading!.loading = true;
this.conditionsLoading!.loading = true;
await this._postgresModel.refresh();
} catch (error) {
vscode.window.showErrorMessage(loc.refreshFailed(error));
@@ -156,17 +176,22 @@ export class PostgresResourceHealthPage extends DashboardPage {
]).component();
}
private refresh() {
this.podsUpdated!.value = loc.updated(fromNow(this._postgresModel.serviceLastUpdated!, true));
private getPodsLastUpdated(): string {
return this._postgresModel.serviceLastUpdated
? loc.updated(fromNow(this._postgresModel.serviceLastUpdated!, true)) : '';
}
this.podsTable!.data = [
[this._postgresModel.service?.status?.podsRunning, loc.running],
[this._postgresModel.service?.status?.podsPending, loc.pending],
[this._postgresModel.service?.status?.podsFailed, loc.failed],
[this._postgresModel.service?.status?.podsUnknown, loc.unknown]
private getPodsTable(): (string | number)[][] {
return [
[this._postgresModel.service?.status?.podsRunning ?? 0, loc.running],
[this._postgresModel.service?.status?.podsPending ?? 0, loc.pending],
[this._postgresModel.service?.status?.podsFailed ?? 0, loc.failed],
[this._postgresModel.service?.status?.podsUnknown ?? 0, loc.unknown]
];
}
this.conditionsTable!.data = this._postgresModel.service?.status?.conditions?.map(c => {
private getConditionsTable(): (string | azdata.ImageComponent)[][] {
return this._postgresModel.service?.status?.conditions?.map(c => {
const healthy = c.type === 'Ready' ? c.status === 'True' : c.status === 'False';
const image = this.modelView.modelBuilder.image().withProperties<azdata.ImageComponentProperties>({
@@ -178,11 +203,20 @@ export class PostgresResourceHealthPage extends DashboardPage {
}).component();
return [
c.type,
c.type ?? '',
image,
c.message,
fromNow(c.lastTransitionTime!, true)
c.message ?? '',
c.lastTransitionTime ? fromNow(c.lastTransitionTime!, true) : ''
];
}) ?? [];
}
private handleServiceUpdated() {
this.podsUpdated!.value = this.getPodsLastUpdated();
this.podsTable!.data = this.getPodsTable();
this.podsLoading!.loading = false;
this.conditionsTable!.data = this.getConditionsTable();
this.conditionsLoading!.loading = false;
}
}