Skip to content

Commit

Permalink
web: Handle HTTP failures in storage (#1203)
Browse files Browse the repository at this point in the history
## Problem

The UI is broken in a diskless system. It started failing after
migrating storage to the new architecture (see
#1175).

## Solution

Return correct default values if a HTTP call fails, fix the issues
dialog and fix storage events.

## Testing

* Added new unit tests
* Tested manually

## Screenshots

<details>
<summary>Toggle</summary>


![localhost_8080_](https://github.com/openSUSE/agama/assets/1112304/6d671cd3-9d12-481f-b27d-8ab4afe63e3f)

![localhost_8080_
(1)](https://github.com/openSUSE/agama/assets/1112304/58a3f0b3-3425-4657-aa0b-7a7c938fef40)

![localhost_8080_
(2)](https://github.com/openSUSE/agama/assets/1112304/3d0d9831-c345-462d-86d3-87f2b7a7fd6c)

</details>
  • Loading branch information
joseivanlopez committed May 14, 2024
2 parents 09f8c0a + fa9d239 commit 2ecbab8
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 126 deletions.
27 changes: 27 additions & 0 deletions rust/agama-server/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,33 @@ async fn run_events_monitor(dbus: zbus::Connection, events: EventsSender) -> Res
for (id, software_stream) in software_streams(dbus.clone()).await? {
stream.insert(id, software_stream);
}
stream.insert(
"storage-status",
service_status_stream(
dbus.clone(),
"org.opensuse.Agama.Storage1",
"/org/opensuse/Agama/Storage1",
)
.await?,
);
stream.insert(
"storage-progress",
progress_stream(
dbus.clone(),
"org.opensuse.Agama.Storage1",
"/org/opensuse/Agama/Storage1",
)
.await?,
);
stream.insert(
"storage-issues",
issues_stream(
dbus.clone(),
"org.opensuse.Agama.Storage1",
"/org/opensuse/Agama/Storage1",
)
.await?,
);
stream.insert(
"software-status",
service_status_stream(
Expand Down
6 changes: 6 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Tue May 14 12:24:23 UTC 2024 - José Iván López González <jlopez@suse.com>

- Make storage UI work again when there are no devices
(gh#openSUSE/agama#1203).

-------------------------------------------------------------------
Tue May 14 11:17:45 UTC 2024 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down
8 changes: 4 additions & 4 deletions web/src/client/mixins.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ const buildIssue = ({ description, details, source, severity }) => {
* @template {!WithHTTPClient} T
* @param {T} superclass - superclass to extend
* @param {string} issues_path - validation resource path (e.g., "/manager/issues").
* @param {string} dbus_path - service name (e.g., "/org/opensuse/Agama/Software1/product").
* @param {string} service_name - service name (e.g., "org.opensuse.Agama.Manager1").
*/
const WithIssues = (superclass, issues_path, dbus_path) =>
const WithIssues = (superclass, issues_path, service_name) =>
class extends superclass {
/**
* Returns the issues
Expand Down Expand Up @@ -119,8 +119,8 @@ const WithIssues = (superclass, issues_path, dbus_path) =>
* @return {import ("./http").RemoveFn} function to disable the callback
*/
onIssuesChange(handler) {
return this.client.onEvent("IssuesChanged", ({ path, issues }) => {
if (path === dbus_path) {
return this.client.onEvent("IssuesChanged", ({ service, issues }) => {
if (service === service_name) {
handler(issues.map(buildIssue));
}
});
Expand Down
38 changes: 13 additions & 25 deletions web/src/client/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import { compact, hex, uniq } from "~/utils";
import { WithIssues, WithProgress, WithStatus } from "./mixins";
import { HTTPClient } from "./http";

const SERVICE_NAME = "org.opensuse.Agama.Storage1";
const STORAGE_OBJECT = "/org/opensuse/Agama/Storage1";
const STORAGE_JOBS_NAMESPACE = "/org/opensuse/Agama/Storage1/jobs";
const STORAGE_JOB_IFACE = "org.opensuse.Agama.Storage1.Job";
const ISCSI_INITIATOR_IFACE = "org.opensuse.Agama.Storage1.ISCSI.Initiator";
const ISCSI_NODES_NAMESPACE = "/storage/iscsi/nodes";
const ISCSI_NODE_IFACE = "org.opensuse.Agama.Storage1.ISCSI.Node";
const DASD_MANAGER_IFACE = "org.opensuse.Agama.Storage1.DASD.Manager";
const DASD_DEVICES_NAMESPACE = "/org/opensuse/Agama/Storage1/dasds";
const DASD_DEVICE_IFACE = "org.opensuse.Agama.Storage1.DASD.Device";
Expand Down Expand Up @@ -197,25 +196,6 @@ const EncryptionMethods = Object.freeze({
TPM: "tpm_fde"
});

/**
* Removes properties with undefined value
*
* @example
* removeUndefinedCockpitProperties({
* property1: { t: "s", v: "foo" },
* property2: { t: b, v: false },
* property3: { t: "s", v: undefined }
* });
* //returns { property1: { t: "s", v: "foo" }, property2: { t: "b", v: false } }
*
* @param {object} cockpitObject
* @returns {object}
*/
const removeUndefinedCockpitProperties = (cockpitObject) => {
const filtered = Object.entries(cockpitObject).filter(([, { v }]) => v !== undefined);
return Object.fromEntries(filtered);
};

/**
* Gets the basename of a D-Bus path
*
Expand Down Expand Up @@ -373,6 +353,7 @@ class DevicesManager {
const response = await this.client.get(`/storage/devices/${this.rootPath}`);
if (!response.ok) {
console.warn("Failed to get storage devices: ", response);
return [];
}
const jsonDevices = await response.json();
return jsonDevices.map(d => buildDevice(d, jsonDevices));
Expand Down Expand Up @@ -411,6 +392,7 @@ class ProposalManager {
const response = await this.client.get("/storage/proposal/usable_devices");
if (!response.ok) {
console.warn("Failed to get usable devices: ", response);
return [];
}
const usable_devices = await response.json();
return usable_devices.map(name => findDevice(systemDevices, name)).filter(d => d);
Expand Down Expand Up @@ -459,6 +441,7 @@ class ProposalManager {
const response = await this.client.get("/storage/product/params");
if (!response.ok) {
console.warn("Failed to get product params: ", response);
return [];
}

return response.json().then(params => params.mountPoints);
Expand All @@ -473,6 +456,7 @@ class ProposalManager {
const response = await this.client.get("/storage/product/params");
if (!response.ok) {
console.warn("Failed to get product params: ", response);
return [];
}

return response.json().then(params => params.encryptionMethods);
Expand All @@ -482,13 +466,14 @@ class ProposalManager {
* Obtains the default volume for the given mount path
*
* @param {string} mountPath
* @returns {Promise<Volume>}
* @returns {Promise<Volume|undefined>}
*/
async defaultVolume(mountPath) {
const param = encodeURIComponent(mountPath);
const response = await this.client.get(`/storage/product/volume_for?mount_path=${param}`);
if (!response.ok) {
console.warn("Failed to get product volume: ", response);
return undefined;
}

const systemDevices = await this.system.getDevices();
Expand Down Expand Up @@ -1314,7 +1299,7 @@ class ISCSIManager {
/**
* Gets the iSCSI initiator
*
* @return {Promise<ISCSIInitiator>}
* @return {Promise<ISCSIInitiator|undefined>}
*
* @typedef {object} ISCSIInitiator
* @property {string} name
Expand All @@ -1324,6 +1309,7 @@ class ISCSIManager {
const response = await this.client.get("/storage/iscsi/initiator");
if (!response.ok) {
console.error("Failed to get the iSCSI initiator", response);
return undefined;
}

return response.json();
Expand Down Expand Up @@ -1357,6 +1343,7 @@ class ISCSIManager {
const response = await this.client.get("/storage/iscsi/nodes");
if (!response.ok) {
console.error("Failed to get the list of iSCSI nodes", response);
return [];
}

return response.json();
Expand Down Expand Up @@ -1574,6 +1561,7 @@ class StorageBaseClient {
const response = await this.client.get("/storage/devices/dirty");
if (!response.ok) {
console.warn("Failed to get storage devices dirty: ", response);
return false;
}
return response.json();
}
Expand All @@ -1600,8 +1588,8 @@ class StorageBaseClient {
*/
class StorageClient extends WithIssues(
WithProgress(
WithStatus(StorageBaseClient, "/storage/status", STORAGE_OBJECT), "/storage/progress", STORAGE_OBJECT
), "/storage/issues", STORAGE_OBJECT
WithStatus(StorageBaseClient, "/storage/status", SERVICE_NAME), "/storage/progress", SERVICE_NAME
), "/storage/issues", SERVICE_NAME
) { }

export { StorageClient, EncryptionMethods };
Loading

0 comments on commit 2ecbab8

Please sign in to comment.