Skip to content
Merged
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
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,71 @@ 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) => {
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 +122,6 @@ test("happy flow - installed & connected", async () => {
},
},
]);
case "management/request":
return Promise.resolve({
viewSnapshotResponse: {
components: [
{
state: {
healthState: "CRITICAL",
},
},
],
},
});
}
},
};
Expand Down Expand Up @@ -144,13 +197,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 +234,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 +273,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