Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Mar 22, 2023
1 parent ec7c5bd commit 1c8583d
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 84 deletions.
8 changes: 8 additions & 0 deletions service/lib/dinstaller/dbus/storage/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ def register_iscsi_callbacks

backend.iscsi.on_probe do
refresh_iscsi_nodes
deprecate_proposal
end
end

Expand All @@ -259,6 +260,13 @@ def export_proposal
@service.export(@dbus_proposal)
end

def deprecate_proposal
return unless dbus_proposal

dbus_proposal.deprecate
update_validation
end

def refresh_iscsi_nodes
nodes = backend.iscsi.nodes
iscsi_nodes_tree.update(nodes)
Expand Down
13 changes: 13 additions & 0 deletions service/lib/dinstaller/dbus/storage/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ def initialize(backend, logger)
dbus_reader :volumes, "aa{sv}"

dbus_reader :actions, "aa{sv}"

dbus_reader :deprecated, "b"
end

def deprecate
return if backend.deprecated?

backend.deprecate
dbus_properties_changed(STORAGE_PROPOSAL_INTERFACE, { "Deprecated" => true }, [])
end

def deprecated
backend.deprecated?
end

# Devices used by the storage proposal
Expand Down
12 changes: 8 additions & 4 deletions service/lib/dinstaller/storage/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,14 @@ def probe_devices

# Calculates the default proposal
def calculate_proposal
settings = ProposalSettings.new
# FIXME: by now, the UI only allows to select one disk
device = proposal.available_devices.first&.name
settings.candidate_devices << device if device
settings = proposal.settings

if !settings
settings = ProposalSettings.new
# FIXME: by now, the UI only allows to select one disk
device = proposal.available_devices.first&.name
settings.candidate_devices << device if device
end

proposal.calculate(settings)
end
Expand Down
56 changes: 43 additions & 13 deletions service/lib/dinstaller/storage/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ module Storage
# proposal.calculate(settings) #=> true
# proposal.calculated_volumes #=> [Volume, Volume]
class Proposal
# Settings used for calculating the proposal
#
# @return [ProposalSettings]
attr_reader :settings

# Constructor
#
# @param logger [Logger]
Expand All @@ -56,6 +61,14 @@ def on_calculate(&block)
@on_calculate_callbacks << block
end

def deprecate
@deprecated = true
end

def deprecated?
!!@deprecated
end

# Available devices for installation
#
# @return [Array<Y2Storage::Device>]
Expand Down Expand Up @@ -117,11 +130,12 @@ def calculated_settings
# @param settings [ProposalSettings] settings to calculate the proposal
# @return [Boolean] whether the proposal was correctly calculated
def calculate(settings = nil)
settings ||= ProposalSettings.new
settings.freeze
proposal_settings = to_y2storage_settings(settings)
@deprecated = false
@settings = settings || ProposalSettings.new
@settings.freeze
y2storage_settings = to_y2storage_settings(@settings)

@proposal = new_proposal(proposal_settings)
@proposal = new_proposal(y2storage_settings)
storage_manager.proposal = proposal

@on_calculate_callbacks.each(&:call)
Expand All @@ -145,9 +159,11 @@ def validate
return [] if proposal.nil?

[
validate_proposal,
validate_available_devices,
validate_candidate_devices
deprecated_proposal_error,
empty_available_devices_error,
empty_candidate_devices_error,
missing_candidate_devices_error,
proposal_error,
].compact
end

Expand Down Expand Up @@ -248,24 +264,38 @@ def storage_manager
Y2Storage::StorageManager.instance
end

def validate_proposal
return if candidate_devices.empty? || !proposal.failed?
def deprecated_proposal_error
return unless deprecated?

ValidationError.new("Cannot accommodate the required file systems for installation")
ValidationError.new("The proposal is deprecated")
end

def validate_available_devices
def empty_available_devices_error
return if available_devices.any?

ValidationError.new("There is no suitable device for installation")
end

def validate_candidate_devices
return if available_devices.empty? || candidate_devices.any?
def empty_candidate_devices_error
return if candidate_devices.any?

ValidationError.new("No devices are selected for installation")
end

def missing_candidate_devices_error
available_names = available_devices.map(&:name)
missing = candidate_devices - available_names
return if missing.none?

ValidationError.new("Some selected devices are not found in the system")
end

def proposal_error
return unless proposal.failed?

ValidationError.new("Cannot accommodate the required file systems for installation")
end

# Adjusts the encryption-related settings of the given Y2Storage::ProposalSettings object
#
# @param settings [Y2Storage::ProposalSettings]
Expand Down
38 changes: 24 additions & 14 deletions web/src/client/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
import DBusClient from "./dbus";
import { WithStatus, WithProgress, WithValidation } from "./mixins";

const STORAGE_IFACE = "org.opensuse.DInstaller.Storage1";
const PROPOSAL_CALCULATOR_IFACE = "org.opensuse.DInstaller.Storage1.Proposal.Calculator";
const ISCSI_NODE_IFACE = "org.opensuse.DInstaller.Storage1.ISCSI.Node";
const ISCSI_NODES_NAMESPACE = "/org/opensuse/DInstaller/Storage1/iscsi_nodes";
const ISCSI_INITIATOR_IFACE = "org.opensuse.DInstaller.Storage1.ISCSI.Initiator";
const PROPOSAL_IFACE = "org.opensuse.DInstaller.Storage1.Proposal";
const PROPOSAL_OBJECT = "/org/opensuse/DInstaller/Storage1/Proposal";
const STORAGE_OBJECT = "/org/opensuse/DInstaller/Storage1";

/**
Expand Down Expand Up @@ -59,7 +61,14 @@ class ProposalManager {
*/
constructor(client) {
this.client = client;
this.proxies = {};
this.proxies = {
proposalCalculator: this.client.proxy(PROPOSAL_CALCULATOR_IFACE, STORAGE_OBJECT)
};
}

async isDeprecated() {
const proxy = await this.proposalProxy();
return proxy?.Deprecated;
}

/**
Expand Down Expand Up @@ -95,7 +104,7 @@ class ProposalManager {
};
};

const proxy = await this.proposalCalculatorProxy();
const proxy = await this.proxies.proposalCalculator;
return proxy.AvailableDevices.map(buildDevice);
}

Expand Down Expand Up @@ -214,21 +223,14 @@ class ProposalManager {
Volumes: { t: "aa{sv}", v: volumes?.map(dbusVolume) }
});

const proxy = await this.proposalCalculatorProxy();
const proxy = await this.proxies.proposalCalculator;
return proxy.Calculate(settings);
}

/**
* @private
* Proxy for org.opensuse.DInstaller.Storage1.Proposal.Calculator iface
*
* @returns {Promise<object>}
*/
async proposalCalculatorProxy() {
if (!this.proxies.proposalCalculator)
this.proxies.proposalCalculator = await this.client.proxy(PROPOSAL_CALCULATOR_IFACE, STORAGE_OBJECT);

return this.proxies.proposalCalculator;
onDeprecate(handler) {
return this.client.onObjectChanged(PROPOSAL_OBJECT, PROPOSAL_IFACE, (changes) => {
if (changes.Deprecated?.v) return handler();
});
}

/**
Expand Down Expand Up @@ -516,6 +518,14 @@ class StorageBaseClient {
this.client = new DBusClient(StorageBaseClient.SERVICE, address);
this.proposal = new ProposalManager(this.client);
this.iscsi = new ISCSIManager(StorageBaseClient.SERVICE, address);
this.proxies = {
storage: this.client.proxy(STORAGE_IFACE)
};
}

async probe() {
const proxy = await this.proxies.storage;
return proxy.Probe();
}
}

Expand Down
9 changes: 8 additions & 1 deletion web/src/components/overview/StorageSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ export default function StorageSection({ showErrors }) {

useEffect(() => {
const updateProposal = async () => {
const isDeprecated = await cancellablePromise(client.proposal.isDeprecated());
if (isDeprecated) await cancellablePromise(client.probe());

const proposal = await cancellablePromise(client.proposal.getData());
const errors = await cancellablePromise(client.getValidationErrors());

dispatch({ type: "UPDATE_PROPOSAL", payload: { proposal, errors } });
};

updateProposal();
if (!state.busy) updateProposal();
}, [client, cancellablePromise, state.busy]);

useEffect(() => {
Expand All @@ -102,6 +105,10 @@ export default function StorageSection({ showErrors }) {
});
}, [client, cancellablePromise]);

useEffect(() => {
return client.proposal.onDeprecate(() => client.probe());
}, [client]);

const errors = showErrors ? state.errors : [];

const busy = state.busy || !state.proposal;
Expand Down
61 changes: 35 additions & 26 deletions web/src/components/storage/ProposalPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* find current contact information at www.suse.com.
*/

import React, { useReducer, useEffect } from "react";
import React, { useCallback, useReducer, useEffect } from "react";
import { Alert } from "@patternfly/react-core";
import { Link } from "react-router-dom";

Expand All @@ -34,24 +34,21 @@ import {
} from "~/components/storage";

const initialState = {
busy: false,
loading: false,
proposal: undefined,
errors: []
};

const reducer = (state, action) => {
switch (action.type) {
case "SET_BUSY" : {
return { ...state, busy: true };
case "UPDATE_LOADING" : {
const { loading } = action.payload;
return { ...state, loading };
}

case "LOAD": {
case "UPDATE_PROPOSAL": {
const { proposal, errors } = action.payload;
return { ...state, proposal, errors, busy: false };
}

case "CALCULATE": {
return initialState;
return { ...state, proposal, errors };
}

default: {
Expand All @@ -61,34 +58,46 @@ const reducer = (state, action) => {
};

export default function ProposalPage() {
const client = useInstallerClient();
const { storage: client } = useInstallerClient();
const { cancellablePromise } = useCancellablePromise();
const [state, dispatch] = useReducer(reducer, initialState);

useEffect(() => {
const loadProposal = async () => {
dispatch({ type: "SET_BUSY" });
const loadProposal = useCallback(async (hooks = {}) => {
dispatch({ type: "UPDATE_LOADING", payload: { loading: true } });

if (hooks.before !== undefined) await cancellablePromise(hooks.before());
const proposal = await cancellablePromise(client.proposal.getData());
const errors = await cancellablePromise(client.getValidationErrors());

dispatch({ type: "UPDATE_PROPOSAL", payload: { proposal, errors } });
dispatch({ type: "UPDATE_LOADING", payload: { loading: false } });
}, [client, cancellablePromise]);

const proposal = await cancellablePromise(client.storage.proposal.getData());
const errors = await cancellablePromise(client.storage.getValidationErrors());
useEffect(() => {
const probeAndLoad = async () => {
await loadProposal({ before: () => client.probe() });
};

dispatch({
type: "LOAD",
payload: { proposal, errors }
});
const load = async () => {
const isDeprecated = await cancellablePromise(client.proposal.isDeprecated());
isDeprecated ? probeAndLoad() : loadProposal();
};

if (!state.proposal) loadProposal().catch(console.error);
}, [client.storage, cancellablePromise, state.proposal]);
load().catch(console.error);

return client.proposal.onDeprecate(() => probeAndLoad());
}, [client, cancellablePromise, loadProposal]);

const calculateProposal = async (settings) => {
dispatch({ type: "SET_BUSY" });
await client.storage.proposal.calculate({ ...state.proposal.result, ...settings });
dispatch({ type: "CALCULATE" });
const calculate = async () => {
await client.proposal.calculate({ ...state.proposal.result, ...settings });
};

loadProposal({ before: calculate }).catch(console.error);
};

const PageContent = () => {
if (state.busy || state.proposal?.result === undefined) return <SectionSkeleton lines={3} />;
if (state.loading || state.proposal?.result === undefined) return <SectionSkeleton lines={3} />;

return (
<>
Expand Down
4 changes: 3 additions & 1 deletion web/src/components/storage/ProposalSummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ export default function ProposalSummary({ proposal }) {
);
}

const deviceLabel = device?.label || candidateDevice;

return (
<Text>
Install using device <Em>{device.label}</Em> and deleting all its content
Install using device <Em>{deviceLabel}</Em> and deleting all its content
</Text>
);
}
Loading

0 comments on commit 1c8583d

Please sign in to comment.