Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions .changeset/nice-pigs-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"observability": patch
---

use direct calls to observability
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20
22
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"license": "Apache-2.0",
"private": true,
"engines": {
"node": ">=20"
"node": ">=22"
},
"workspaces": [
"pkg/*"
Expand Down
2 changes: 1 addition & 1 deletion pkg/observability/components/ComponentHealth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default {
return;
}

const component = await loadComponent(this.$store, settings, this.urn);
const component = await loadComponent(settings, this.urn);
this.health = component.state.healthState;
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default {
},

async save(btnCb) {
const conn = await checkConnection(this.$store, {
const conn = await checkConnection({
apiURL: this.suseObservabilityURL,
serviceToken: this.suseObservabilityServiceToken,
});
Expand Down
3 changes: 1 addition & 2 deletions pkg/observability/components/MonitorTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,9 @@ export default {

this.urn = this.componentIdentifier;

const component = await loadComponent(this.$store, settings, this.urn);
const component = await loadComponent(settings, this.urn);
if (!component) {
this.observationStatus = await loadObservationStatus(
this.$store,
this.clusterId,
settings,
);
Expand Down
2 changes: 0 additions & 2 deletions pkg/observability/components/ObservabilityClusterCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export default {
this.installUrl = `${settings.url}/#/stackpacks/kubernetes-v2`;

this.observationStatus = await loadObservationStatus(
this.$store,
this.resource.spec.displayName,
settings,
);
Expand All @@ -80,7 +79,6 @@ export default {

try {
this.snapshot = await getSnapshot(
this.$store,
`not healthstate in ("CLEAR", "UNKNOWN") AND label = "cluster-name:${this.resource.spec.displayName}"`,
settings,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "vitest";
import { beforeAll, afterAll, afterEach, test, expect } from "vitest";
import { mount } from "@vue/test-utils";

import ObservabilityClusterCard from "../ObservabilityClusterCard.vue";
Expand All @@ -23,6 +23,72 @@ const mountComponent = (mockStore: any) => {
});
};

const setupServer = () => {
const restHandlers = [
{
url: "https://ye-observability.invalid.com/api/components",
resp: () => new Response("Unauthorized", { status: 401 }),
},
{
url: "https://ye-observability.invalid.com/api/snapshot",
resp: () => new Response("Unauthorized", { status: 401 }),
},
{
url: "https://ye-observability.example.com/api/components",
resp: () => new Response(JSON.stringify({}), { status: 200 }),
},
{
url: "https://ye-observability.example.com/api/snapshot",
resp: () =>
new Response(
JSON.stringify({
viewSnapshotResponse: {
components: [
{
state: {
healthState: "CRITICAL",
},
},
],
},
}),
{ status: 200 },
),
},
{
url: "https://no-observability.example.com/api/components",
resp: () => new Response("Not found", { status: 404 }),
},
{
url: "https://no-observability.example.com/api/snapshot",
resp: () =>
new Response(
JSON.stringify({
viewSnapshotResponse: { components: [] },
}),
{ status: 200 },
),
},
];
global.fetch = (url: RequestInfo | URL, options?: RequestInit) => {
console.log(url.toString());
const handler = restHandlers.find((handler) =>
url.toString().startsWith(handler.url),
);
return Promise.resolve(handler!.resp());
};
};

const fetch = global.fetch;

// Start server before all tests
beforeAll(() => setupServer());

// Close server after all tests
afterAll(() => {
global.fetch = fetch;
});

test("initial state", () => {
const mockStore = {};
const wrapper = mountComponent(mockStore);
Expand Down Expand Up @@ -57,18 +123,6 @@ test("happy flow - installed & connected", async () => {
},
},
]);
case "management/request":
return Promise.resolve({
viewSnapshotResponse: {
components: [
{
state: {
healthState: "CRITICAL",
},
},
],
},
});
}
},
};
Expand Down Expand Up @@ -144,13 +198,11 @@ test("configured, but cannot connect", async () => {
},
apiVersion: "observability.rancher.io/v1",
spec: {
url: "https://ye-observability.example.com",
url: "https://ye-observability.invalid.com",
serviceToken: "ye-token",
},
},
]);
case "management/request":
return Promise.reject(Error("Cannot connect to SUSE Observability"));
}
},
};
Expand Down Expand Up @@ -183,14 +235,11 @@ test("no component for cluster, agent is not deployed", async () => {
},
apiVersion: "observability.rancher.io/v1",
spec: {
url: "https://ye-observability.example.com",
url: "https://no-observability.example.com",
serviceToken: "ye-token",
},
},
]);
case "management/request":
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject("no such cluster component");
case "cluster/request":
return Promise.resolve({ data: [] });
}
Expand Down Expand Up @@ -225,14 +274,11 @@ test("no component for cluster, though agent is deployed", async () => {
},
apiVersion: "observability.rancher.io/v1",
spec: {
url: "https://ye-observability.example.com",
url: "https://no-observability.example.com",
serviceToken: "ye-token",
},
},
]);
case "management/request":
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject("no such cluster component");
case "cluster/request":
return Promise.resolve({
data: [
Expand Down
6 changes: 1 addition & 5 deletions pkg/observability/formatters/ComponentLinkedHealthState.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,7 @@ export default {
try {
const settings = await loadSuseObservabilitySettings(this.$store);

const component = await loadComponent(
this.$store,
settings,
componentIdentifier,
);
const component = await loadComponent(settings, componentIdentifier);

this.data = {
health: component.state.healthState,
Expand Down
1 change: 0 additions & 1 deletion pkg/observability/modules/rancher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ export async function loadAgentStatus(
"stackstate-k8s-agent"),
);

console.log(deployments);
return deployments.length > 0
? AgentStatus.Installed
: AgentStatus.NotInstalled;
Expand Down
83 changes: 51 additions & 32 deletions pkg/observability/modules/suseObservability.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { ConnectionInfo } from "types/component";
import { ObservabilitySettings } from "./settings";

class FetchError extends Error {
status: number;

constructor(message: string, status: number) {
super(message);
this.status = status;
}
}

/**
* Check whether the connection credentials are valid.
* @param store The Vue Store
Expand All @@ -13,32 +23,29 @@ export enum ConnectionStatus {
}

export async function checkConnection(
store: any,
credentials: ConnectionInfo,
): Promise<ConnectionStatus> {
const creds = token(credentials.serviceToken);

try {
const resp = await store.dispatch("management/request", {
url: `${credentials.apiURL}/api/server/info`,
method: "GET",
const resp = await fetch(`${credentials.apiURL}/api/server/info`, {
credentials: "omit",
mode: "cors",
headers: {
"Content-Type": "application/json",
Authorization: creds,
},
redirectUnauthorized: false,
});

if (resp._status !== 200) {
if (resp.ok) {
return ConnectionStatus.Connected;
} else {
return ConnectionStatus.InvalidToken;
}

return ConnectionStatus.Connected;
} catch (e) {
if (e instanceof Error) {
return ConnectionStatus.CrossOriginError;
} else {
if (e instanceof FetchError) {
return ConnectionStatus.InvalidToken;
} else {
return ConnectionStatus.CrossOriginError;
}
}
}
Expand All @@ -50,25 +57,25 @@ export enum ObservationStatus {
}

export async function loadObservationStatus(
store: any,
clusterName: string,
settings: ObservabilitySettings,
): Promise<ObservationStatus> {
try {
const clusterUrn = `urn:cluster:/kubernetes:${clusterName}`;
await loadComponent(store, settings, clusterUrn);
await loadComponent(settings, clusterUrn);
return ObservationStatus.Observed;
} catch (e) {
if (e instanceof Error) {
return ObservationStatus.ConnectionError;
} else {
return ObservationStatus.NotDeployed;
if (e instanceof FetchError) {
const err = e as FetchError;
if (err.status === 404) {
return ObservationStatus.NotDeployed;
}
}
return ObservationStatus.ConnectionError;
}
}

export async function getSnapshot(
store: any,
stql: string,
settings: ObservabilitySettings,
): Promise<any | void> {
Expand All @@ -81,15 +88,15 @@ export async function getSnapshot(

const httpToken = token(serviceToken);

return await store.dispatch("management/request", {
url: `${suseObservabilityURL}/api/snapshot`,
const resp = await fetch(`${suseObservabilityURL}/api/snapshot`, {
method: "POST",
credentials: "omit",
mode: "cors",
headers: {
"Content-Type": "application/json",
Authorization: httpToken,
},
withCredentials: true,
data: {
body: JSON.stringify({
query: stql,
queryVersion: "1.0",
metadata: {
Expand All @@ -104,22 +111,34 @@ export async function getSnapshot(
neighboringComponents: false,
showFullComponent: false,
},
},
}),
});
if (resp.ok) {
return await resp.json();
} else {
throw new FetchError(await resp.text(), resp.status);
}
}

export function loadComponent(
store: any,
export async function loadComponent(
spec: ObservabilitySettings,
identifier: string,
) {
const creds = token(spec.serviceToken);

return store.dispatch("management/request", {
url: `${spec.url}/api/components?identifier=${encodeURIComponent(identifier)}`,
method: "GET",
headers: { "Content-Type": "application/json", Authorization: creds },
});
const resp = await fetch(
`${spec.url}/api/components?identifier=${encodeURIComponent(identifier)}`,
{
method: "GET",
mode: "cors",
credentials: "omit",
headers: { "Content-Type": "application/json", Authorization: creds },
},
);
if (resp.ok) {
return await resp.json();
} else {
throw new FetchError(await resp.text(), resp.status);
}
}

function token(serviceToken: string): string {
Expand Down
Loading
Loading