From 189e199cf75825e67f4b447b95076d64aff58b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 20 Oct 2022 10:59:32 +0100 Subject: [PATCH 01/12] [service] Replace DInstaller::Network class with a module * The old DInstaller::Network class is now DInstaller::Network::Manager. --- service/lib/dinstaller/manager.rb | 2 +- service/lib/dinstaller/network/manager.rb | 56 +++++++++++++++++++ .../dinstaller/{ => network}/network_test.rb | 4 +- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 service/lib/dinstaller/network/manager.rb rename service/test/dinstaller/{ => network}/network_test.rb (95%) diff --git a/service/lib/dinstaller/manager.rb b/service/lib/dinstaller/manager.rb index efed925201..217d7c391d 100644 --- a/service/lib/dinstaller/manager.rb +++ b/service/lib/dinstaller/manager.rb @@ -149,7 +149,7 @@ def users # # @return [Network] def network - @network ||= Network.new(logger) + @network ||= Network::Manager.new(logger) end # Storage manager diff --git a/service/lib/dinstaller/network/manager.rb b/service/lib/dinstaller/network/manager.rb new file mode 100644 index 0000000000..0fc0b2a882 --- /dev/null +++ b/service/lib/dinstaller/network/manager.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "singleton" +require "yast" +require "y2network/proposal_settings" +Yast.import "Lan" + +module DInstaller + module Network + # Backend class to handle network configuration + class Manager + def initialize(logger) + @logger = logger + end + + # Probes the network configuration + def probe + logger.info "Probing network" + Yast::Lan.read_config + settings = Y2Network::ProposalSettings.instance + settings.apply_defaults + # force NetworkManager as we are not supporting other backends + settings.enable_network_manager! + end + + # Writes the network configuration to the installed system + def install + Yast::WFM.CallFunction("save_network", []) + end + + private + + # @return [Logger] + attr_reader :logger + end + end +end diff --git a/service/test/dinstaller/network_test.rb b/service/test/dinstaller/network/network_test.rb similarity index 95% rename from service/test/dinstaller/network_test.rb rename to service/test/dinstaller/network/network_test.rb index e64bd79356..d118834b48 100644 --- a/service/test/dinstaller/network_test.rb +++ b/service/test/dinstaller/network/network_test.rb @@ -20,10 +20,10 @@ # find current contact information at www.suse.com. require_relative "../test_helper" -require "dinstaller/network" +require "dinstaller/network/manager" require "dinstaller/progress" -describe DInstaller::Network do +describe DInstaller::Network::Manager do subject(:network) { described_class.new(logger) } let(:logger) { Logger.new($stdout, level: :warn) } From 38caa07254335f5c183c80f5c405252b85ecdb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 20 Oct 2022 12:59:03 +0100 Subject: [PATCH 02/12] [service] Add a NetworkManager D-Bus client * It only implements the API required by D-Installer. --- .../dbus/clients/network_manager.rb | 102 ++++++++++++++++++ service/lib/dinstaller/network/connection.rb | 80 ++++++++++++++ service/lib/dinstaller/network/ip_config.rb | 57 ++++++++++ .../dbus/clients/network_manager_test.rb | 90 ++++++++++++++++ 4 files changed, 329 insertions(+) create mode 100644 service/lib/dinstaller/dbus/clients/network_manager.rb create mode 100644 service/lib/dinstaller/network/connection.rb create mode 100644 service/lib/dinstaller/network/ip_config.rb create mode 100644 service/test/dinstaller/dbus/clients/network_manager_test.rb diff --git a/service/lib/dinstaller/dbus/clients/network_manager.rb b/service/lib/dinstaller/dbus/clients/network_manager.rb new file mode 100644 index 0000000000..45f24c1ad3 --- /dev/null +++ b/service/lib/dinstaller/dbus/clients/network_manager.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "dbus" +require "dinstaller/network/connection" +require "dinstaller/network/ip_config" + +module DInstaller + module DBus + module Clients + # Implements a simple network manager which just exposes the part of the D-Bus + # interface that it is needed by D-Installer + class NetworkManager + NM_SERVICE = "org.freedesktop.NetworkManager".freeze + private_constant :NM_SERVICE + + NM_IFACE = "org.freedesktop.NetworkManager".freeze + private_constant :NM_IFACE + + NM_PATH = "/org/freedesktop/NetworkManager".freeze + private_constant :NM_PATH + + NM_ACTIVE_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Connection.Active".freeze + private_constant :NM_ACTIVE_CONNECTION_IFACE + + NM_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection" + private_constant :NM_SETTINGS_CONNECTION + + def initialize + @bus = ::DBus::SystemBus.instance + @service = @bus.service(NM_SERVICE) + end + + def active_connections + nm_object[NM_IFACE]["ActiveConnections"].map do |path| + find_active_connection(path) + end + end + + private + + # System D-Bus + # + # @return [::DBus::Connection] + attr_reader :bus + + attr_reader :service + + def nm_object + @nm_object ||= @service.object(NM_PATH) + end + + # Returns an active connection object from the given path + # + # @param path [String] Connection path + # @return [Connection] + def find_active_connection(path) + obj = service.object(path) + conn = obj[NM_ACTIVE_CONNECTION_IFACE] + ip4_config = find_ip4_config(conn["Connection"]) + + DInstaller::Network::Connection.new( + conn["Id"], path, conn["Type"], conn["State"], ip4_config + ) + end + + # Returns the IP configuration from a given path + # + # @param path [String] IP configuration path + # @return [IPConfig] + def find_ip4_config(path) + obj = service.object(path) + settings = obj[NM_SETTINGS_CONNECTION].GetSettings.first + + ip4_config = DInstaller::Network::IPConfig.new( + settings["ipv4"]["method"], + settings["ipv4"]["address-data"], + settings["ipv4"]["gateway"] + ) + end + end + end + end +end diff --git a/service/lib/dinstaller/network/connection.rb b/service/lib/dinstaller/network/connection.rb new file mode 100644 index 0000000000..c845a76a12 --- /dev/null +++ b/service/lib/dinstaller/network/connection.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module DInstaller + module Network + # Represents a network connection + # + # It contains just the relevant parts for D-Installer. + class Connection < Struct.new("Connection", :id, :path, :type, :state, :ipv4) + module TYPE + ETHERNET = "802-3-ethernet", + WIFI = "802-11-wireless" + end + + module STATE + UNKWOWN = 0 + ACTIVATING = 1 + ACTIVATED = 2 + DEACTIVATING = 3 + DEACTIVATED = 4 + end + + # @!attribute [r] id + # @return [String] Connection ID + # + # @!attribute [r] path + # @return [String] Connection path in D-Bus (FIXME: do not expose the path) + # + # @!attribute [r] type + # @return [String] Connection type + # @see TYPE + # + # @!attribute [r] state + # @return [String] Connection state + # @see STATE + # + # @!attribute [r] ipv4 + # @return [IPConfig] + + # Determines whether two connection objects are equivalent + # + # @param other [Connection] Object to compare with + def ==(other) + id == other.id && path == other.path && type == other.type && + state == other.state && ipv4 == other.ipv4 + end + + # Returns a hash representation to be used in D-Bus + # + # @return [Hash] + def to_dbus + { + "id" => id, + "path" => path, + "type" => type, + "state" => state, + "ipv4" => ipv4.to_dbus + } + end + end + end +end diff --git a/service/lib/dinstaller/network/ip_config.rb b/service/lib/dinstaller/network/ip_config.rb new file mode 100644 index 0000000000..45c0fabc29 --- /dev/null +++ b/service/lib/dinstaller/network/ip_config.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module DInstaller + module Network + # Represents the IP configuration + class IPConfig < Struct.new("IPConfig", :method, :addresses, :gateway) + module METHODS + AUTO = "auto" # DHCP + STATIC = "static" + end + + # @!attribute [r] method + # @return [String] Configuration method + # @see METHODS + # + # @!attribute [r] + # @return [String] IP Address + + # Determines whether two connection objects are equivalent + # + # @param other [IPConfig] Object to compare with + def ==(other) + method == other.method && addresses == other.addresses && gateway == other.gateway + end + + # Returns a hash representation to be used in D-Bus + # + # @return [Hash] + def to_dbus + { + "method" => method, + "addresses" => addresses, + "gateway" => gateway + } + end + end + end +end diff --git a/service/test/dinstaller/dbus/clients/network_manager_test.rb b/service/test/dinstaller/dbus/clients/network_manager_test.rb new file mode 100644 index 0000000000..8247070d87 --- /dev/null +++ b/service/test/dinstaller/dbus/clients/network_manager_test.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../../test_helper" +require "dinstaller/dbus/clients/network_manager" + +describe DInstaller::DBus::Clients::NetworkManager do + before do + allow(::DBus::SystemBus).to receive(:instance).and_return(bus) + allow(service).to receive(:object).with("/org/freedesktop/NetworkManager") + .and_return(dbus_object) + allow(service).to receive(:object).with(active_connection_path) + .and_return(connection_object) + allow(service).to receive(:object).with(settings_path) + .and_return(settings_object) + allow(dbus_object).to receive(:introspect) + + allow(dbus_object).to receive(:[]).with("org.freedesktop.NetworkManager") + .and_return(network_iface) + allow(connection_object).to receive(:[]).with("org.freedesktop.NetworkManager.Connection.Active") + .and_return(connection_iface) + allow(settings_object).to receive(:[]).with("org.freedesktop.NetworkManager.Settings.Connection") + .and_return(settings_iface) + + allow(network_iface).to receive(:[]).with("ActiveConnections").and_return(active_connections) + allow(connection_iface).to receive(:[]) { |k| connection_data[k] } + end + + + let(:bus) { instance_double(::DBus::SystemBus, service: service) } + let(:service) { instance_double(::DBus::Service) } + let(:dbus_object) { instance_double(::DBus::ProxyObject) } + let(:network_iface) { instance_double(::DBus::ProxyObjectInterface, ) } + let(:settings_iface) { instance_double(::DBus::ProxyObjectInterface) } + + let(:connection_object) { instance_double(::DBus::ProxyObject) } + let(:connection_iface) { instance_double(::DBus::ProxyObjectInterface) } + let(:connection_data) do + { "Id" => "enp1s0", "Type" => "802-3-ethernet", "State" => 2, "Connection" => settings_path } + end + + let(:settings_object) { instance_double(::DBus::ProxyObject) } + let(:settings_iface) { double(::DBus::ProxyObjectInterface, GetSettings: [settings_data]) } + let(:settings_data) do + { "ipv4" => { "method" => "auto", "address-data" => addresses, "gateway" => "192.168.122.1" } } + end + let(:addresses) do + [ { "address" => "192.168.122.10", "prefix" => 24 }] + end + + let(:active_connection_path) { "/org/freedesktop/NetworkManager/ActiveConnection/1" } + let(:active_connections) { [active_connection_path] } + let(:settings_path) { "/org/freedesktop/NetworkManager/Settings/1" } + + describe "#active_connections" do + it "returns an array of NetworkManager active connections" do + connections = subject.active_connections + expected_connection = DInstaller::Network::Connection.new( + connection_data["Id"], + active_connection_path, + connection_data["Type"], + connection_data["State"], + DInstaller::Network::IPConfig.new( + settings_data["ipv4"]["method"], + addresses, + settings_data["ipv4"]["gateway"] + ) + ) + expect(connections).to eq([expected_connection]) + end + end +end From 5d68859416fcecda5e1ffac841bd1f273d68482f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 20 Oct 2022 13:27:46 +0100 Subject: [PATCH 03/12] [service] Expose network active connections through D-Bus --- .../lib/dinstaller/dbus/manager_service.rb | 10 ++- service/lib/dinstaller/dbus/network.rb | 57 +++++++++++++++++ service/lib/dinstaller/network/manager.rb | 10 +++ service/test/dinstaller/dbus/network_test.rb | 61 +++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 service/lib/dinstaller/dbus/network.rb create mode 100644 service/test/dinstaller/dbus/network_test.rb diff --git a/service/lib/dinstaller/dbus/manager_service.rb b/service/lib/dinstaller/dbus/manager_service.rb index e722935170..ad3713d02a 100644 --- a/service/lib/dinstaller/dbus/manager_service.rb +++ b/service/lib/dinstaller/dbus/manager_service.rb @@ -23,6 +23,7 @@ require "dinstaller/manager" require "dinstaller/cockpit_manager" require "dinstaller/dbus/manager" +require "dinstaller/dbus/network" require "dinstaller/dbus/storage/proposal" module DInstaller @@ -101,13 +102,20 @@ def service # @return [Array<::DBus::Object>] def dbus_objects @dbus_objects ||= [ - manager_dbus + manager_dbus, + network_dbus ] end + # @return [Manager] def manager_dbus @manager_dbus ||= DInstaller::DBus::Manager.new(manager, logger) end + + # @return [Network] + def network_dbus + @network_dbus ||= DInstaller::DBus::Network.new(manager.network, logger) + end end end end diff --git a/service/lib/dinstaller/dbus/network.rb b/service/lib/dinstaller/dbus/network.rb new file mode 100644 index 0000000000..6cd321baa1 --- /dev/null +++ b/service/lib/dinstaller/dbus/network.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "dbus" +require "dinstaller/dbus/base_object" + +module DInstaller + module DBus + class Network < BaseObject + PATH = "/org/opensuse/DInstaller/Network1" + private_constant :PATH + + # Constructor + # + # @param backend [DInstaller::Network] + # @param logger [Logger] + def initialize(backend, logger) + super(PATH, logger: logger) + @backend = backend + end + + NETWORK_INTERFACE = "org.opensuse.DInstaller.Network1" + private_constant :NETWORK_INTERFACE + + dbus_interface NETWORK_INTERFACE do + dbus_reader :active_connections, "aa{sv}" + end + + # Returns the list of active connections + # + # @return [Array] + def active_connections + @backend.active_connections.map do |conn| + conn.to_dbus + end + end + end + end +end diff --git a/service/lib/dinstaller/network/manager.rb b/service/lib/dinstaller/network/manager.rb index 0fc0b2a882..04d2b89390 100644 --- a/service/lib/dinstaller/network/manager.rb +++ b/service/lib/dinstaller/network/manager.rb @@ -20,16 +20,26 @@ # find current contact information at www.suse.com. require "singleton" +require "forwardable" require "yast" require "y2network/proposal_settings" +require "dinstaller/dbus/clients/network_manager" Yast.import "Lan" module DInstaller module Network # Backend class to handle network configuration class Manager + extend Forwardable + + def_delegators :@nm_client, :active_connections + + # Constructor + # + # @param logger [Logger] def initialize(logger) @logger = logger + @nm_client = DInstaller::DBus::Clients::NetworkManager.new end # Probes the network configuration diff --git a/service/test/dinstaller/dbus/network_test.rb b/service/test/dinstaller/dbus/network_test.rb new file mode 100644 index 0000000000..4a6ad39465 --- /dev/null +++ b/service/test/dinstaller/dbus/network_test.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/dbus/network" +require "dinstaller/network/manager" + +describe DInstaller::DBus::Network do + subject { described_class.new(backend, logger) } + + let(:backend) do + instance_double(DInstaller::Network::Manager, active_connections: [connection]) + end + + let(:logger) { Logger.new($stdout) } + + let(:connection) do + DInstaller::Network::Connection.new( + "enp1s0", + "/org/freedesktop/NetworkManager/ActiveConnection/1", + "802-3-ethernet", + 2, + DInstaller::Network::IPConfig.new( + "auto", [], "192.168.122.1" + ) + ) + end + + describe "#active_connections" do + it "returns an array of a hash-based representation of the connections" do + expect(subject.active_connections).to eq( + [ + { "id" => "enp1s0", + "ipv4" => { "addresses" => [], "gateway" => "192.168.122.1", "method" => "auto" }, + "path" => "/org/freedesktop/NetworkManager/ActiveConnection/1", + "state" => 2, + "type" => "802-3-ethernet" + } + ] + ) + end + end +end From 2c7da0751d982127d330421b20cb09167f3f309b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 20 Oct 2022 13:37:35 +0100 Subject: [PATCH 04/12] [web] Use DInstaller API to get active connections --- web/src/client/network.js | 73 ++++++++++++++------------------------- 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/web/src/client/network.js b/web/src/client/network.js index 930c177a71..d271e4e60a 100644 --- a/web/src/client/network.js +++ b/web/src/client/network.js @@ -32,6 +32,8 @@ const NM_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Settings.Connection" const NM_ACTIVE_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Connection.Active"; const NM_IP4CONFIG_IFACE = "org.freedesktop.NetworkManager.IP4Config"; +const NETWORK_IFACE = "org.opensuse.DInstaller.Network1"; + const CONNECTION_ADDED = "CONNECTIONS_ADDED"; const CONNECTION_UPDATED = "CONNECTION_UPDATED"; const CONNECTION_REMOVED = "CONNECTION_REMOVED"; @@ -123,22 +125,6 @@ class NetworkManagerAdapter { this.client = dbusClient || new DBusClient(NM_SERVICE_NAME); } - /** - * Returns the active connections - * - * @returns { Promise. } - */ - async activeConnections() { - let connections = []; - const paths = await this.activeConnectionsPaths(); - - for (const path of paths) { - connections = [...connections, await this.connectionFromPath(path)]; - } - - return connections; - } - /** * Subscribes to network events * @@ -236,21 +222,6 @@ class NetworkManagerAdapter { return proxy.ActivateConnection(connection.settings_path, connection.device_path, "/"); } - /* - * Returns the list of active NM connections paths - * - * @private - * @returns {Promise} - * - * Private method. - * See NM API documentation for details. - * https://developer-old.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html - */ - async activeConnectionsPaths() { - const proxy = await this.client.proxy(NM_IFACE); - return proxy.ActiveConnections; - } - /** * Builds a connection object from a Cockpit's proxy object * @@ -282,17 +253,6 @@ class NetworkManagerAdapter { }; } - /** - * Builds a connection object from a D-Bus path. - * - * @param {string} path - Connection D-Bus path - * @returns {Promise.} - */ - async connectionFromPath(path) { - const proxy = await this.client.proxy(NM_ACTIVE_CONNECTION_IFACE, path); - return this.connectionFromProxy(proxy); - } - /** * * Returns connection settings for the given connection @@ -342,9 +302,9 @@ class NetworkClient { /** * @param {NetworkAdapter} [adapter] - Network adapter. By default, it is set to o * NetworkManagerAdapter. + * @param {DBusClient} [dbusClient] - D-Bus client */ - constructor(adapter) { - this.adapter = adapter || new NetworkManagerAdapter(); + constructor(adapter, dbusClient) { /** @type {!boolean} */ this.subscribed = false; /** @type {Handlers} */ @@ -353,6 +313,8 @@ o * NetworkManagerAdapter. connectionRemoved: [], connectionUpdated: [] }; + this.client = dbusClient || new DBusClient("org.opensuse.DInstaller"); + this.adapter = adapter || new NetworkManagerAdapter(this.client); } /** @@ -361,7 +323,7 @@ o * NetworkManagerAdapter. */ async config() { return { - connections: await this.adapter.activeConnections(), + connections: await this.activeConnections(), addresses: await this.addresses(), hostname: await this.adapter.hostname() }; @@ -420,10 +382,25 @@ o * NetworkManagerAdapter. /** * Returns the active connections * - * @returns { Promise. } + * @returns { Promise } */ async activeConnections() { - return this.adapter.activeConnections(); + const proxy = await this.client.proxy(NETWORK_IFACE); + return proxy.ActiveConnections.map(conn => { + const { id, path, ipv4, state, type } = conn; + const { method, gateway } = ipv4.v; + const addresses = ipv4.v.addresses.v.map(({ v: { address, prefix } }) => { + return { address: address.v, prefix: prefix.v }; + }); + return { + id: id.v, + path: path.v, + state: state.v, + type: type.v, + ipv4: { method: method.v, gateway: gateway.v }, + addresses + }; + }); } /** @@ -445,7 +422,7 @@ o * NetworkManagerAdapter. * @return {Promise.} */ async addresses() { - const conns = await this.adapter.activeConnections(); + const conns = await this.activeConnections(); return conns.flatMap(c => c.addresses); } } From 049767e3225582a1698d6e232e2bfef09bfe85c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 21 Oct 2022 12:20:37 +0100 Subject: [PATCH 05/12] [service] Fix Network module definition --- service/lib/dinstaller/network.rb | 36 ++++++------------------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/service/lib/dinstaller/network.rb b/service/lib/dinstaller/network.rb index 5ebfb22477..c01289f1c1 100644 --- a/service/lib/dinstaller/network.rb +++ b/service/lib/dinstaller/network.rb @@ -19,36 +19,12 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. -require "singleton" -require "yast" -require "y2network/proposal_settings" -Yast.import "Lan" - module DInstaller - # Backend class to handle network configuration - class Network - def initialize(logger) - @logger = logger - end - - # Probes the network configuration - def probe - logger.info "Probing network" - Yast::Lan.read_config - settings = Y2Network::ProposalSettings.instance - settings.apply_defaults - # force NetworkManager as we are not supporting other backends - settings.enable_network_manager! - end - - # Writes the network configuration to the installed system - def install - Yast::WFM.CallFunction("save_network", []) - end - - private - - # @return [Logger] - attr_reader :logger + # Namespace for network backend + module Network end end + +require "dinstaller/network/manager" +require "dinstaller/network/connection" +require "dinstaller/network/ip_config" From 2d6ff90a04d108bd912575ee910f9d5cc2f99b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 21 Oct 2022 12:21:03 +0100 Subject: [PATCH 06/12] [service] Signal connection changes through D-Bus --- .../dbus/clients/network_manager.rb | 87 +++++++++--- service/lib/dinstaller/dbus/network.rb | 18 +++ service/lib/dinstaller/network.rb | 2 +- .../{connection.rb => active_connection.rb} | 16 +-- service/lib/dinstaller/network/ip_config.rb | 2 +- service/lib/dinstaller/network/manager.rb | 3 +- web/src/Network.jsx | 6 +- web/src/NetworkWifiStatus.jsx | 2 +- web/src/NetworkWiredStatus.jsx | 2 +- web/src/client/network.js | 128 ++++++------------ 10 files changed, 146 insertions(+), 120 deletions(-) rename service/lib/dinstaller/network/{connection.rb => active_connection.rb} (80%) diff --git a/service/lib/dinstaller/dbus/clients/network_manager.rb b/service/lib/dinstaller/dbus/clients/network_manager.rb index 45f24c1ad3..086cfd371c 100644 --- a/service/lib/dinstaller/dbus/clients/network_manager.rb +++ b/service/lib/dinstaller/dbus/clients/network_manager.rb @@ -20,7 +20,7 @@ # find current contact information at www.suse.com. require "dbus" -require "dinstaller/network/connection" +require "dinstaller/network/active_connection" require "dinstaller/network/ip_config" module DInstaller @@ -44,15 +44,32 @@ class NetworkManager NM_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection" private_constant :NM_SETTINGS_CONNECTION + CONNECTION_ADDED = "connection_added".freeze + CONNECTION_UPDATED = "connection_updated".freeze + CONNECTION_REMOVED = "connection_removed".freeze + def initialize @bus = ::DBus::SystemBus.instance @service = @bus.service(NM_SERVICE) + @callbacks = {} + cache_active_connections + register_handlers end def active_connections - nm_object[NM_IFACE]["ActiveConnections"].map do |path| - find_active_connection(path) - end + @active_connections.values + end + + def on_connection_added(&block) + add_callback(CONNECTION_ADDED, &block) + end + + def on_connection_updated(&block) + add_callback(CONNECTION_UPDATED, &block) + end + + def on_connection_removed(&block) + add_callback(CONNECTION_REMOVED, &block) end private @@ -68,6 +85,10 @@ def nm_object @nm_object ||= @service.object(NM_PATH) end + def active_connections_paths + nm_object[NM_IFACE]["ActiveConnections"] + end + # Returns an active connection object from the given path # # @param path [String] Connection path @@ -75,26 +96,56 @@ def nm_object def find_active_connection(path) obj = service.object(path) conn = obj[NM_ACTIVE_CONNECTION_IFACE] - ip4_config = find_ip4_config(conn["Connection"]) + + ip4_obj = service.object(conn["Ip4Config"]) + ip4_config = ip4_obj["org.freedesktop.NetworkManager.IP4Config"] - DInstaller::Network::Connection.new( - conn["Id"], path, conn["Type"], conn["State"], ip4_config + DInstaller::Network::ActiveConnection.new( + conn["Id"], conn["Type"], conn["State"], ip4_config["AddressData"], ip4_config["Gateway"] ) + rescue ::DBus::Error + nil end - # Returns the IP configuration from a given path + def cache_active_connections + @active_connections ||= active_connections_paths.each_with_object({}) do |path, conns| + conns[path] = find_active_connection(path) + end + end + + # Registers D-Bus signal handlers + def register_handlers + mr = ::DBus::MatchRule.new.from_s "type='signal',interface='org.freedesktop.NetworkManager.Connection.Active',member='StateChanged'" + bus.add_match(mr) do |msg| + conn = find_active_connection(msg.path) + if conn.nil? + old_conn = @active_connections.delete(msg.path) + run_callbacks(CONNECTION_REMOVED, old_conn) unless old_conn.nil? + else + callback_type = @active_connections.key?(msg.path) ? CONNECTION_UPDATED : CONNECTION_ADDED + @active_connections[msg.path] = conn + run_callbacks(callback_type, conn) + end + end + end + + # Adds a callback for the given event type # - # @param path [String] IP configuration path - # @return [IPConfig] - def find_ip4_config(path) - obj = service.object(path) - settings = obj[NM_SETTINGS_CONNECTION].GetSettings.first + # @param type [String] Event type + # @param block [Proc] Callback to call in the given event + def add_callback(type, &block) + @callbacks[type] = [] + @callbacks[type] << block + end - ip4_config = DInstaller::Network::IPConfig.new( - settings["ipv4"]["method"], - settings["ipv4"]["address-data"], - settings["ipv4"]["gateway"] - ) + # Runs the callback for the given even type + # + # @param type [String] Event type + # @param *args [Array] Arguments for the callback + def run_callbacks(type, *args) + return unless @callbacks[type] + + @callbacks[type].each { |cb| cb.call(*args) } end end end diff --git a/service/lib/dinstaller/dbus/network.rb b/service/lib/dinstaller/dbus/network.rb index 6cd321baa1..66c2abd6e7 100644 --- a/service/lib/dinstaller/dbus/network.rb +++ b/service/lib/dinstaller/dbus/network.rb @@ -35,6 +35,7 @@ class Network < BaseObject def initialize(backend, logger) super(PATH, logger: logger) @backend = backend + register_callbacks end NETWORK_INTERFACE = "org.opensuse.DInstaller.Network1" @@ -42,6 +43,9 @@ def initialize(backend, logger) dbus_interface NETWORK_INTERFACE do dbus_reader :active_connections, "aa{sv}" + dbus_signal(:ConnectionAdded, "conn:a{sv}") + dbus_signal(:ConnectionUpdated, "conn:a{sv}") + dbus_signal(:ConnectionRemoved, "conn:a{sv}") end # Returns the list of active connections @@ -52,6 +56,20 @@ def active_connections conn.to_dbus end end + + def register_callbacks + @backend.on_connection_added do |conn| + ConnectionAdded(conn.to_dbus) + end + + @backend.on_connection_updated do |conn| + ConnectionUpdated(conn.to_dbus) + end + + @backend.on_connection_removed do |conn| + ConnectionRemoved(conn.to_dbus) + end + end end end end diff --git a/service/lib/dinstaller/network.rb b/service/lib/dinstaller/network.rb index c01289f1c1..51a0ac8ec8 100644 --- a/service/lib/dinstaller/network.rb +++ b/service/lib/dinstaller/network.rb @@ -26,5 +26,5 @@ module Network end require "dinstaller/network/manager" -require "dinstaller/network/connection" +require "dinstaller/network/active_connection" require "dinstaller/network/ip_config" diff --git a/service/lib/dinstaller/network/connection.rb b/service/lib/dinstaller/network/active_connection.rb similarity index 80% rename from service/lib/dinstaller/network/connection.rb rename to service/lib/dinstaller/network/active_connection.rb index c845a76a12..324fe836b1 100644 --- a/service/lib/dinstaller/network/connection.rb +++ b/service/lib/dinstaller/network/active_connection.rb @@ -24,7 +24,7 @@ module Network # Represents a network connection # # It contains just the relevant parts for D-Installer. - class Connection < Struct.new("Connection", :id, :path, :type, :state, :ipv4) + class ActiveConnection < Struct.new("Connection", :id, :type, :state, :addresses, :gateway) module TYPE ETHERNET = "802-3-ethernet", WIFI = "802-11-wireless" @@ -41,9 +41,6 @@ module STATE # @!attribute [r] id # @return [String] Connection ID # - # @!attribute [r] path - # @return [String] Connection path in D-Bus (FIXME: do not expose the path) - # # @!attribute [r] type # @return [String] Connection type # @see TYPE @@ -52,15 +49,14 @@ module STATE # @return [String] Connection state # @see STATE # - # @!attribute [r] ipv4 - # @return [IPConfig] + # @!attribute [r] addresses + # @return [Array] Assigned addresses # Determines whether two connection objects are equivalent # # @param other [Connection] Object to compare with def ==(other) - id == other.id && path == other.path && type == other.type && - state == other.state && ipv4 == other.ipv4 + id == other.id && type == other.type && state == other.state end # Returns a hash representation to be used in D-Bus @@ -69,10 +65,10 @@ def ==(other) def to_dbus { "id" => id, - "path" => path, "type" => type, "state" => state, - "ipv4" => ipv4.to_dbus + "addresses" => addresses, + "gateway" => gateway.to_s } end end diff --git a/service/lib/dinstaller/network/ip_config.rb b/service/lib/dinstaller/network/ip_config.rb index 45c0fabc29..4bc183f002 100644 --- a/service/lib/dinstaller/network/ip_config.rb +++ b/service/lib/dinstaller/network/ip_config.rb @@ -49,7 +49,7 @@ def to_dbus { "method" => method, "addresses" => addresses, - "gateway" => gateway + "gateway" => gateway.to_s } end end diff --git a/service/lib/dinstaller/network/manager.rb b/service/lib/dinstaller/network/manager.rb index 04d2b89390..0a4ed256f0 100644 --- a/service/lib/dinstaller/network/manager.rb +++ b/service/lib/dinstaller/network/manager.rb @@ -32,7 +32,8 @@ module Network class Manager extend Forwardable - def_delegators :@nm_client, :active_connections + def_delegators :@nm_client, :active_connections, :on_connection_added, + :on_connection_updated, :on_connection_removed # Constructor # diff --git a/web/src/Network.jsx b/web/src/Network.jsx index a5c8d26602..85455922c4 100644 --- a/web/src/Network.jsx +++ b/web/src/Network.jsx @@ -45,8 +45,8 @@ export default function Network() { }, [client.network]); useEffect(() => { - const onConnectionRemoved = connectionPath => { - setConnections(conns => conns.filter(c => c.path !== connectionPath)); + const onConnectionRemoved = ({ id }) => { + setConnections(conns => conns.filter(c => c.id !== id)); }; return client.network.listen("connectionRemoved", onConnectionRemoved); @@ -55,7 +55,7 @@ export default function Network() { useEffect(() => { const onConnectionUpdated = connection => { setConnections(conns => { - const newConnections = conns.filter(c => c.path !== connection.path); + const newConnections = conns.filter(c => c.id !== connection.id); return [...newConnections, connection]; }); }; diff --git a/web/src/NetworkWifiStatus.jsx b/web/src/NetworkWifiStatus.jsx index 8870d8a778..597a48e6ea 100644 --- a/web/src/NetworkWifiStatus.jsx +++ b/web/src/NetworkWifiStatus.jsx @@ -29,7 +29,7 @@ import ConnectionsDataList from "./ConnectionsDataList"; * * @todo evaluate if it should be "merged" into NetworkWiredStatus * @todo display link for setting up a WiFi connection when possible - * @param {import ("client/network").Connection[]} connections + * @param {import ("client/network").ActiveConnection[]} connections */ export default function NetworkWiFiStatus({ connections }) { const conns = connections.filter(c => c.state === CONNECTION_STATE.ACTIVATED); diff --git a/web/src/NetworkWiredStatus.jsx b/web/src/NetworkWiredStatus.jsx index 7cafd3fc58..62503a055c 100644 --- a/web/src/NetworkWiredStatus.jsx +++ b/web/src/NetworkWiredStatus.jsx @@ -29,7 +29,7 @@ import { CONNECTION_STATE } from "./client/network"; * D-Installer component to show status of wired network connections * * @todo evaluate if it should be "merged" into NetworkWifiStatus - * @param {import ("client/network").Connection[]} connections + * @param {import ("client/network").ActiveConnection[]} connections */ export default function NetworkWiredStatus({ connections }) { const [connection, setConnection] = useState(null); diff --git a/web/src/client/network.js b/web/src/client/network.js index d271e4e60a..51c39e2da5 100644 --- a/web/src/client/network.js +++ b/web/src/client/network.js @@ -34,10 +34,6 @@ const NM_IP4CONFIG_IFACE = "org.freedesktop.NetworkManager.IP4Config"; const NETWORK_IFACE = "org.opensuse.DInstaller.Network1"; -const CONNECTION_ADDED = "CONNECTIONS_ADDED"; -const CONNECTION_UPDATED = "CONNECTION_UPDATED"; -const CONNECTION_REMOVED = "CONNECTION_REMOVED"; - /** * Enum for the active connection state values * @@ -66,18 +62,15 @@ const CONNECTION_TYPES = { */ /** - * @typedef {object} Connection + * @typedef {object} ActiveConnection * @property {string} id - * @property {string} path - * @property {string} settings_path - * @property {string} device_path * @property {string} type * @property {number} state - * @property {object} ipv4 * @property {IPAddress[]} addresses + * @property {IPAddress} gateway */ -/** @typedef {(conns: Connection[]) => void} ConnectionFn */ +/** @typedef {(conns: ActiveConnection[]) => void} ConnectionFn */ /** @typedef {(conns: string[]) => void} ConnectionPathsFn */ /** @@ -91,7 +84,7 @@ const CONNECTION_TYPES = { * @typedef {object} NetworkAdapter * @property {function} activeConnections * @property {(handler: (event: NetworkEvent) => void) => void} subscribe - * @property {(connection: Connection) => Promise} updateConnection + * @property {(connection: ActiveConnection) => Promise} updateConnection * @property {() => Promise} hostname */ @@ -125,48 +118,12 @@ class NetworkManagerAdapter { this.client = dbusClient || new DBusClient(NM_SERVICE_NAME); } - /** - * Subscribes to network events - * - * Registers a handler for changes in /org/freedesktop/NetworkManager/ActiveConnection/*. - * The handler recevies a NetworkEvent object. - * - * @param {(event: NetworkEvent) => void} handler - Event handler function - */ - async subscribe(handler) { - const proxies = await this.client.proxies( - "org.freedesktop.NetworkManager.Connection.Active", - "/org/freedesktop/NetworkManager/ActiveConnection", - { watch: true } - ); - - proxies.addEventListener("added", (_event, proxy) => { - proxy.wait(() => { - this.connectionFromProxy(proxy).then(connection => { - handler({ type: CONNECTION_ADDED, payload: connection }); - }); - }); - }); - - proxies.addEventListener("changed", (_event, proxy) => { - proxy.wait(() => { - this.connectionFromProxy(proxy).then(connection => { - handler({ type: CONNECTION_UPDATED, payload: connection }); - }); - }); - }); - - proxies.addEventListener("removed", (_event, proxy) => { - handler({ type: CONNECTION_REMOVED, payload: proxy.path }); - }); - } - /** * Updates the connection * * @fixme improve it. * - * @param {Connection} connection - Connection to update + * @param {ActiveConnection} connection - Connection to update */ async updateConnection(connection) { const settingsObject = await this.client.proxy(NM_CONNECTION_IFACE, connection.settings_path); @@ -213,7 +170,7 @@ class NetworkManagerAdapter { * Reactivate the given connection * * @private - * @param {Connection} connection + * @param {ActiveConnection} connection * See NM API documentation for details. * https://developer-old.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html */ @@ -358,49 +315,32 @@ o * NetworkManagerAdapter. async subscribe() { // TODO: refactor this method this.subscribed = true; - - this.adapter.subscribe(({ type, payload }) => { - switch (type) { - case CONNECTION_ADDED: { - this.handlers.connectionAdded.forEach(handler => handler(payload)); - break; - } - - case CONNECTION_UPDATED: { - this.handlers.connectionUpdated.forEach(handler => handler(payload)); - break; - } - - case CONNECTION_REMOVED: { - this.handlers.connectionRemoved.forEach(handler => handler(payload.path)); - break; - } - } + const networkProxy = await this.client.proxy("org.opensuse.DInstaller.Network1"); + networkProxy.addEventListener("ConnectionAdded", (event, conn) => { + this.handlers.connectionAdded.forEach(handler => + handler(this.activeConnectionFromDBus(conn)) + ); + }); + networkProxy.addEventListener("ConnectionUpdated", (event, conn) => { + this.handlers.connectionUpdated.forEach(handler => + handler(this.activeConnectionFromDBus(conn)) + ); + }); + networkProxy.addEventListener("ConnectionRemoved", (event, conn) => { + this.handlers.connectionRemoved.forEach(handler => + handler(this.activeConnectionFromDBus(conn)) + ); }); } /** * Returns the active connections * - * @returns { Promise } + * @returns { Promise } */ async activeConnections() { const proxy = await this.client.proxy(NETWORK_IFACE); - return proxy.ActiveConnections.map(conn => { - const { id, path, ipv4, state, type } = conn; - const { method, gateway } = ipv4.v; - const addresses = ipv4.v.addresses.v.map(({ v: { address, prefix } }) => { - return { address: address.v, prefix: prefix.v }; - }); - return { - id: id.v, - path: path.v, - state: state.v, - type: type.v, - ipv4: { method: method.v, gateway: gateway.v }, - addresses - }; - }); + return proxy.ActiveConnections.map(this.activeConnectionFromDBus); } /** @@ -408,7 +348,7 @@ o * NetworkManagerAdapter. * * It uses the 'path' to match the connection in the backend. * - * @param {Connection} connection - Connection to update + * @param {ActiveConnection} connection - Connection to update */ async updateConnection(connection) { return this.adapter.updateConnection(connection); @@ -425,6 +365,26 @@ o * NetworkManagerAdapter. const conns = await this.activeConnections(); return conns.flatMap(c => c.addresses); } + + /** + * Converts an active connection from D-Bus to a proper ActiveConnection object + * + * @param {object} conn - Connection as it comes from Cockpit + * @return {ActiveConnection} + */ + activeConnectionFromDBus(conn) { + const { id, state, type, gateway } = conn; + const addresses = conn.addresses.v.map(({ v: { address, prefix } }) => { + return { address: address.v, prefix: prefix.v }; + }); + return { + id: id.v, + state: state.v, + type: type.v, + addresses, + gateway: gateway.v + }; + } } export { CONNECTION_STATE, CONNECTION_TYPES, formatIp, NetworkClient, NetworkManagerAdapter }; From e82f87edf81fee38761d1ea71265784c8a3eadc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 25 Oct 2022 11:00:24 +0100 Subject: [PATCH 07/12] [service] Improve network model --- service/lib/dinstaller/network.rb | 3 +- .../dinstaller/network/active_connection.rb | 63 +++++++------ .../network/{ip_config.rb => connection.rb} | 48 ++++++---- .../dinstaller/network/connection_method.rb | 70 ++++++++++++++ .../dinstaller/network/connection_state.rb | 73 +++++++++++++++ .../lib/dinstaller/network/connection_type.rb | 71 +++++++++++++++ service/lib/dinstaller/network/ipv4.rb | 91 +++++++++++++++++++ .../network/active_connection_test.rb | 45 +++++++++ .../network/connection_method_test.rb | 57 ++++++++++++ .../network/connection_state_test.rb | 57 ++++++++++++ .../dinstaller/network/connection_test.rb | 48 ++++++++++ .../network/connection_type_test.rb | 57 ++++++++++++ service/test/dinstaller/network/ipv4_test.rb | 45 +++++++++ .../test/dinstaller/network/network_test.rb | 2 +- 14 files changed, 676 insertions(+), 54 deletions(-) rename service/lib/dinstaller/network/{ip_config.rb => connection.rb} (51%) create mode 100644 service/lib/dinstaller/network/connection_method.rb create mode 100644 service/lib/dinstaller/network/connection_state.rb create mode 100644 service/lib/dinstaller/network/connection_type.rb create mode 100644 service/lib/dinstaller/network/ipv4.rb create mode 100644 service/test/dinstaller/network/active_connection_test.rb create mode 100644 service/test/dinstaller/network/connection_method_test.rb create mode 100644 service/test/dinstaller/network/connection_state_test.rb create mode 100644 service/test/dinstaller/network/connection_test.rb create mode 100644 service/test/dinstaller/network/connection_type_test.rb create mode 100644 service/test/dinstaller/network/ipv4_test.rb diff --git a/service/lib/dinstaller/network.rb b/service/lib/dinstaller/network.rb index 51a0ac8ec8..9e6ac37dc6 100644 --- a/service/lib/dinstaller/network.rb +++ b/service/lib/dinstaller/network.rb @@ -27,4 +27,5 @@ module Network require "dinstaller/network/manager" require "dinstaller/network/active_connection" -require "dinstaller/network/ip_config" +require "dinstaller/network/connection" +require "dinstaller/network/ipv4" diff --git a/service/lib/dinstaller/network/active_connection.rb b/service/lib/dinstaller/network/active_connection.rb index 324fe836b1..0a07fcecb9 100644 --- a/service/lib/dinstaller/network/active_connection.rb +++ b/service/lib/dinstaller/network/active_connection.rb @@ -19,44 +19,43 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. +require "dinstaller/network/connection_type" +require "dinstaller/network/connection_state" + module DInstaller module Network - # Represents a network connection + # Represents an active network connection # # It contains just the relevant parts for D-Installer. - class ActiveConnection < Struct.new("Connection", :id, :type, :state, :addresses, :gateway) - module TYPE - ETHERNET = "802-3-ethernet", - WIFI = "802-11-wireless" - end + class ActiveConnection + # @return [String] Connection ID (e.g., an UUID) + attr_reader :id - module STATE - UNKWOWN = 0 - ACTIVATING = 1 - ACTIVATED = 2 - DEACTIVATING = 3 - DEACTIVATED = 4 - end + # @return [String] Connection name (e.g., "Wired connection") + attr_reader :name - # @!attribute [r] id - # @return [String] Connection ID - # - # @!attribute [r] type - # @return [String] Connection type - # @see TYPE - # - # @!attribute [r] state - # @return [String] Connection state - # @see STATE - # - # @!attribute [r] addresses - # @return [Array] Assigned addresses + # @return [ConnectionType] Connection type + attr_reader :type + + # @return [ConnectionState] Connection state + attr_reader :state + + # @return [Array] Assigned addresses + attr_reader :addresses + + def initialize(id, name, type: nil, state: nil, addresses: []) + @id = id + @name = name + @type = type || ConnectionType::ETHERNET + @state = state || ConnectionState::UNKNOWN + @addresses = addresses + end # Determines whether two connection objects are equivalent # # @param other [Connection] Object to compare with def ==(other) - id == other.id && type == other.type && state == other.state + id == other.id && name == other.name && type == other.type && state == other.state end # Returns a hash representation to be used in D-Bus @@ -64,11 +63,11 @@ def ==(other) # @return [Hash] def to_dbus { - "id" => id, - "type" => type, - "state" => state, - "addresses" => addresses, - "gateway" => gateway.to_s + "id" => id, + "name" => name, + "type" => type.id, + "state" => state.id, + "addresses" => addresses } end end diff --git a/service/lib/dinstaller/network/ip_config.rb b/service/lib/dinstaller/network/connection.rb similarity index 51% rename from service/lib/dinstaller/network/ip_config.rb rename to service/lib/dinstaller/network/connection.rb index 4bc183f002..0c6dda4cd7 100644 --- a/service/lib/dinstaller/network/ip_config.rb +++ b/service/lib/dinstaller/network/connection.rb @@ -21,25 +21,33 @@ module DInstaller module Network - # Represents the IP configuration - class IPConfig < Struct.new("IPConfig", :method, :addresses, :gateway) - module METHODS - AUTO = "auto" # DHCP - STATIC = "static" - end + # Represents the configuration (profile) for a network connection + class Connection + # @return [String] Connection ID (e.g., an UUID) + attr_reader :id - # @!attribute [r] method - # @return [String] Configuration method - # @see METHODS - # - # @!attribute [r] - # @return [String] IP Address + # @return [String] Connection name (e.g., "Wired connection") + attr_reader :name - # Determines whether two connection objects are equivalent - # - # @param other [IPConfig] Object to compare with - def ==(other) - method == other.method && addresses == other.addresses && gateway == other.gateway + # @return [IPv4] IPv4 settings + attr_reader :ipv4 + + class << self + # @param id [String] Connection ID + # @param name [String] Connection name + # @param ipv4 [Hash] IPv4 configuration data + def from_dbus(data) + new(data["id"], data["name"], ipv4: IPv4.from_dbus(data["ipv4"])) + end + end + + # @param id [String] Connection ID + # @param name [String] Connection name + # @param ipv4 [IPv4] IPv4 settings + def initialize(id, name, ipv4: IPv4.new) + @id = id + @name = name + @ipv4 = ipv4 end # Returns a hash representation to be used in D-Bus @@ -47,9 +55,9 @@ def ==(other) # @return [Hash] def to_dbus { - "method" => method, - "addresses" => addresses, - "gateway" => gateway.to_s + "id" => id, + "name" => name, + "ipv4" => ipv4.to_dbus } end end diff --git a/service/lib/dinstaller/network/connection_method.rb b/service/lib/dinstaller/network/connection_method.rb new file mode 100644 index 0000000000..03ba3bd6a9 --- /dev/null +++ b/service/lib/dinstaller/network/connection_method.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module DInstaller + module Network + # Connection states + # + # The values are kind of coupled to NetworkManager. We can change them + # as soon as we support another backend. + class ConnectionMethod + # @return [String] Method ID (e.g., "auto") + attr_reader :id + # @return [String] Method label (e.g., "Automatic") + attr_reader :name + + class << self + # Returns all the known IDs + # + # @return [ConnectionState] + def all + [AUTO, MANUAL] + end + + # Returns the known IDs + # + # @param id [String] Method ID + # @return [ConnectionMethod,nil] + def by_id(id) + all.find { |s| s.id == id } + end + end + + # @param id [String] Method ID + # @param name [String] Method label + def initialize(id, name) + @id = id + @name = name + end + + # Determines whether two methods are the same + # + # @param [ConnectionState] Method to compare with + # @return [Boolean] + def ==(other) + id == other.id + end + + AUTO = new("auto", "Automatic") + MANUAL = new("manual", "Manual") + end + end +end diff --git a/service/lib/dinstaller/network/connection_state.rb b/service/lib/dinstaller/network/connection_state.rb new file mode 100644 index 0000000000..3167a6d217 --- /dev/null +++ b/service/lib/dinstaller/network/connection_state.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module DInstaller + module Network + # Connection states + # + # The values are kind of coupled to NetworkManager. We can change them + # as soon as we support another backend. + class ConnectionState + # @return [Integer] State ID (e.g., 1) + attr_reader :id + # @return [String] State label (e.g., "Activating") + attr_reader :name + + class << self + # Returns all the known IDs + # + # @return [ConnectionState] + def all + [UNKNOWN, ACTIVATING, ACTIVATED, DEACTIVATING, DEACTIVATED] + end + + # Returns the known IDs + # + # @param id [Integer] State ID + # @return [ConnectionState,nil] + def by_id(id) + all.find { |s| s.id == id } + end + end + + # @param id [Integer] State ID + # @param name [String] State label + def initialize(id, name) + @id = id + @name = name + end + + # Determines whether two states are the same + # + # @param [ConnectionState] State to compare with + # @return [Boolean] + def ==(other) + id == other.id + end + + UNKNOWN = new(0, "Unknown") + ACTIVATING = new(1, "Activating") + ACTIVATED = new(2, "Activated") + DEACTIVATING = new(3, "Deactivating") + DEACTIVATED = new(4, "Deactivated") + end + end +end diff --git a/service/lib/dinstaller/network/connection_type.rb b/service/lib/dinstaller/network/connection_type.rb new file mode 100644 index 0000000000..76d1af42c3 --- /dev/null +++ b/service/lib/dinstaller/network/connection_type.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +module DInstaller + module Network + # Connection type + # + # The values are kind of coupled to NetworkManager. We can change them + # as soon as we support another backend. + class ConnectionType + # @return [String] Type ID + attr_reader :id + + # @return [String] Type name + attr_reader :name + + class << self + # Returns all the known IDs + # + # @return [ConnectionType] + def all + [ETHERNET, WIRELESS] + end + + # Returns the known IDs + # + # @param id [String] State ID + # @return [ConnectionState,nil] + def by_id(id) + all.find { |s| s.id == id } + end + end + + # @param id [String] Type ID + # @param name [String] Type name + def initialize(id, name) + @id = id + @name = name + end + + # Determines whether two types are the same + # + # @param [ConnectionState] + # @return [Boolean] + def ==(other) + @id == other.id + end + + ETHERNET = new("802-3-ethernet", "Ethernet") + WIRELESS = new("802-11-wireless", "Wireless") + end + end +end diff --git a/service/lib/dinstaller/network/ipv4.rb b/service/lib/dinstaller/network/ipv4.rb new file mode 100644 index 0000000000..1524c48215 --- /dev/null +++ b/service/lib/dinstaller/network/ipv4.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "dinstaller/network/connection_method" + +module DInstaller + module Network + # Represents the IP configuration + class IPv4 + # @return [ConnectionMethod] Connection method + attr_reader :meth + + # @return [Array] Static addresses + attr_reader :addresses + + # @return [IPAddr,nil] Gateway address + attr_reader :gateway + + # @return [Array] Name servers addresses + attr_reader :nameservers + + class << self + # Builds an instance from a hash from D-Bus + # + # @param data [Hash] IP configuration from the D-Bus object + def from_dbus(data) + addresses = data.fetch("addresses", []).map { |a| a.transform_keys(&:to_sym) } + nameservers = data.fetch("nameServers", []).map { |a| IPAddr.new(a) } + gateway = data["gateway"].to_s.empty? ? nil : IPAddr.new(data["gateway"]) + new( + meth: ConnectionMethod.by_id(data["method"]), + addresses: addresses, + gateway: gateway, + nameservers: nameservers + ) + end + end + + # Constructor + # + # @param meth [ConnectionMethod] Connection method + # @param addresses [Array] Static addresses (each hash) + # contains an :address and a :prefix + # @param gateway [IPAddr,nil] Gateway address + # @param nameservers [Array] Name servers addresses + def initialize(meth: nil, addresses: [], nameservers: [], gateway: nil) + @meth = meth || ConnectionMethod::AUTO + @addresses = addresses + @nameservers = nameservers + @gateway = gateway + end + + # Determines whether two connection objects are equivalent + # + # @param other [IPv4] Object to compare with + def ==(other) + meth == other.meth && addresses == other.addresses && gateway == other.gateway + end + + # Returns a hash representation to be used in D-Bus + # + # @return [Hash] + def to_dbus + { + "method" => meth.id, + "addresses" => addresses, + "gateway" => gateway.to_s, + "nameservers" => nameservers.map(&:to_s) + } + end + end + end +end diff --git a/service/test/dinstaller/network/active_connection_test.rb b/service/test/dinstaller/network/active_connection_test.rb new file mode 100644 index 0000000000..c4e4bed935 --- /dev/null +++ b/service/test/dinstaller/network/active_connection_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/network/active_connection" + +describe DInstaller::Network::ActiveConnection do + describe "#==" do + subject { described_class.new("1234", "Wired") } + + context "when the given connection has the same values" do + let(:other) { described_class.new("1234", "Wired") } + + it "returns true" do + expect(subject).to eq(other) + end + end + + context "when the given connection has different values" do + let(:other) { described_class.new("1234", "Wireless") } + + it "returns false" do + expect(subject).to_not eq(other) + end + end + end +end diff --git a/service/test/dinstaller/network/connection_method_test.rb b/service/test/dinstaller/network/connection_method_test.rb new file mode 100644 index 0000000000..06810feb60 --- /dev/null +++ b/service/test/dinstaller/network/connection_method_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/network/connection_method" + +describe DInstaller::Network::ConnectionMethod do + subject { DInstaller::Network::ConnectionMethod::AUTO } + + describe "#by_id" do + it "returns the type with the given ID" do + expect(described_class.by_id("auto")).to eq(subject) + end + + context "when the type does not exist" do + it "returns nil" do + expect(described_class.by_id("unknown")).to be_nil + end + end + end + + describe "#==" do + context "when the given type has the same ID" do + let(:other) { DInstaller::Network::ConnectionMethod::AUTO } + + it "returns true" do + expect(subject).to eq(other) + end + end + + context "when the given type has different ID" do + let(:other) { DInstaller::Network::ConnectionMethod::MANUAL } + + it "returns false" do + expect(subject).to_not eq(other) + end + end + end +end diff --git a/service/test/dinstaller/network/connection_state_test.rb b/service/test/dinstaller/network/connection_state_test.rb new file mode 100644 index 0000000000..d9843a38b4 --- /dev/null +++ b/service/test/dinstaller/network/connection_state_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/network/connection_state" + +describe DInstaller::Network::ConnectionState do + subject { DInstaller::Network::ConnectionState::UNKNOWN } + + describe "#by_id" do + it "returns the type with the given ID" do + expect(described_class.by_id(0)).to eq(subject) + end + + context "when the type does not exist" do + it "returns nil" do + expect(described_class.by_id(50)).to be_nil + end + end + end + + describe "#==" do + context "when the given type has the same id" do + let(:other) { DInstaller::Network::ConnectionState::UNKNOWN } + + it "returns true" do + expect(subject).to eq(other) + end + end + + context "when the given type has different ids" do + let(:other) { DInstaller::Network::ConnectionState::ACTIVATED } + + it "returns false" do + expect(subject).to_not eq(other) + end + end + end +end diff --git a/service/test/dinstaller/network/connection_test.rb b/service/test/dinstaller/network/connection_test.rb new file mode 100644 index 0000000000..0f9dea4967 --- /dev/null +++ b/service/test/dinstaller/network/connection_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/network/connection" + +describe DInstaller::Network::Connection do + subject do + DInstaller::Network::Connection.new("1234", "Wired") + end + + describe ".from_dbus" do + it "returns a Connection instance" do + connection = described_class.from_dbus( + "id" => "1234", + "name" => "Wired", + "ipv4" => { "method" => "manual" } + ) + expect(connection.id).to eq("1234") + expect(connection.name).to eq("Wired") + expect(connection.ipv4.meth).to eq(DInstaller::Network::ConnectionMethod::MANUAL) + end + end + + describe "#to_dbus" do + it "returns a hash containing the information to send over D-Bus" do + expect(subject.to_dbus) + end + end +end diff --git a/service/test/dinstaller/network/connection_type_test.rb b/service/test/dinstaller/network/connection_type_test.rb new file mode 100644 index 0000000000..2e200208b6 --- /dev/null +++ b/service/test/dinstaller/network/connection_type_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "dinstaller/network/connection_type" + +describe DInstaller::Network::ConnectionType do + subject { DInstaller::Network::ConnectionType::ETHERNET } + + describe "#by_id" do + it "returns the type with the given ID" do + expect(described_class.by_id("802-3-ethernet")).to eq(subject) + end + + context "when the type does not exist" do + it "returns nil" do + expect(described_class.by_id("unknown")).to be_nil + end + end + end + + describe "#==" do + context "when the given type has the same ID" do + let(:other) { DInstaller::Network::ConnectionType::ETHERNET } + + it "returns true" do + expect(subject).to eq(other) + end + end + + context "when the given type has different ID" do + let(:other) { DInstaller::Network::ConnectionType::WIRELESS } + + it "returns false" do + expect(subject).to_not eq(other) + end + end + end +end diff --git a/service/test/dinstaller/network/ipv4_test.rb b/service/test/dinstaller/network/ipv4_test.rb new file mode 100644 index 0000000000..acf2372df9 --- /dev/null +++ b/service/test/dinstaller/network/ipv4_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "ipaddr" +require "dinstaller/network/ipv4" + +describe DInstaller::Network::IPv4 do + describe ".from_dbus" do + it "returns an IPv4 object from a hash from D-Bus" do + dbus_ipv4 = described_class.from_dbus( + "method" => "auto", + "addresses" => [{ "address" => "192.168.122.1", "prefix" => 24 }], + "gateway" => "192.168.122.1", + "nameServers" => ["192.168.122.10"] + ) + + ipv4 = described_class.new( + meth: DInstaller::Network::ConnectionMethod::AUTO, + addresses: [{ address: "192.168.122.1", prefix: 24 }], + gateway: IPAddr.new("192.168.122.1"), + nameservers: [IPAddr.new("192.168.122.10")] + ) + expect(ipv4).to eq(dbus_ipv4) + end + end +end diff --git a/service/test/dinstaller/network/network_test.rb b/service/test/dinstaller/network/network_test.rb index d118834b48..d0042b3a5a 100644 --- a/service/test/dinstaller/network/network_test.rb +++ b/service/test/dinstaller/network/network_test.rb @@ -19,7 +19,7 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. -require_relative "../test_helper" +require_relative "../../test_helper" require "dinstaller/network/manager" require "dinstaller/progress" From 30784b2614f166b515d419680a65a952bd57c269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 26 Oct 2022 06:27:06 +0100 Subject: [PATCH 08/12] [service] Expose network API through D-Bus --- .../dbus/clients/network_manager.rb | 281 +++++++++++++++--- service/lib/dinstaller/dbus/network.rb | 37 ++- service/lib/dinstaller/network/manager.rb | 12 +- .../dbus/clients/network_manager_test.rb | 11 +- service/test/dinstaller/dbus/network_test.rb | 9 +- 5 files changed, 286 insertions(+), 64 deletions(-) diff --git a/service/lib/dinstaller/dbus/clients/network_manager.rb b/service/lib/dinstaller/dbus/clients/network_manager.rb index 086cfd371c..e0cbd43b2c 100644 --- a/service/lib/dinstaller/dbus/clients/network_manager.rb +++ b/service/lib/dinstaller/dbus/clients/network_manager.rb @@ -21,55 +21,123 @@ require "dbus" require "dinstaller/network/active_connection" -require "dinstaller/network/ip_config" +require "dinstaller/network/connection" +require "dinstaller/network/ipv4" +require "ipaddr" module DInstaller module DBus module Clients # Implements a simple network manager which just exposes the part of the D-Bus # interface that it is needed by D-Installer + # + # @example Register a callback to run when a new active connection appears + # client = NetworkManager.new + # client.on_connection_added do |conn| + # puts "New connection: {conn.id}" + # end class NetworkManager - NM_SERVICE = "org.freedesktop.NetworkManager".freeze + NM_SERVICE = "org.freedesktop.NetworkManager" private_constant :NM_SERVICE - NM_IFACE = "org.freedesktop.NetworkManager".freeze - private_constant :NM_IFACE - - NM_PATH = "/org/freedesktop/NetworkManager".freeze - private_constant :NM_PATH - - NM_ACTIVE_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Connection.Active".freeze - private_constant :NM_ACTIVE_CONNECTION_IFACE - - NM_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection" - private_constant :NM_SETTINGS_CONNECTION - - CONNECTION_ADDED = "connection_added".freeze - CONNECTION_UPDATED = "connection_updated".freeze - CONNECTION_REMOVED = "connection_removed".freeze - - def initialize + # @param logger [Logger] + def initialize(logger) + @logger = logger @bus = ::DBus::SystemBus.instance @service = @bus.service(NM_SERVICE) @callbacks = {} - cache_active_connections + @active_connections_ids = {} register_handlers end + # Returns the list of active connections + # + # @return [Array] def active_connections - @active_connections.values + active_connections_paths.map do |path| + conn = find_active_connection(path) + @active_connections_ids[path] = conn.id + conn + end + end + + # Returns the list of connections (settings) + # + # @return [Array] + def connections + connections_paths.each do |path| + find_connection_by_path(path) + end + end + + # Returns the connection with the given ID + # + # @param [String] Connection ID + # @return [Connection,nil] The connection or nil if no + # connection with the given id is found + def find_connection(id) + path = settings_object.GetConnectionByUuid(id).first + find_connection_by_path(path) + end + + # Updates the connection + # + # @param conn [Connection] Connection to update + # @return [Boolean] true if the connection was updated; false otherwise + def update_connection(conn) + # FIXME: unify with the other call to GetConnectionByUuid + settings_path = settings_object.GetConnectionByUuid(conn.id).first + settings_obj = service.object(settings_path) + settings = settings_obj.GetSettings.first + clean_deprecated_settings(settings) + merge_connection(settings, conn) + adjust_dbus_types(settings) + settings_obj.Update(settings) + activate_connection(settings_path) + true + rescue ::DBus::Error => e + logger.error e.inspect.to_s + false end - def on_connection_added(&block) - add_callback(CONNECTION_ADDED, &block) + # Activates the connection in the given path + # + # @param path [String] + def activate_connection(path) + nm_object.ActivateConnection(path, "/", "/") + end + + ACTIVE_CONNECTION_ADDED = "active_connection_added" + private_constant :ACTIVE_CONNECTION_ADDED + + # Adds a callback to run when an active connection is added + # + # @param block [Proc] a block that receives a Connection + # instance when a new active connection appears + def on_active_connection_added(&block) + add_callback(ACTIVE_CONNECTION_ADDED, &block) end - def on_connection_updated(&block) - add_callback(CONNECTION_UPDATED, &block) + ACTIVE_CONNECTION_UPDATED = "active_connection_updated" + private_constant :ACTIVE_CONNECTION_UPDATED + + # Adds a callback to run when an active connection changes + # + # @param block [Proc] a block that receives a Connection + # instance when an active connection changes + def on_active_connection_updated(&block) + add_callback(ACTIVE_CONNECTION_UPDATED, &block) end - def on_connection_removed(&block) - add_callback(CONNECTION_REMOVED, &block) + ACTIVE_CONNECTION_REMOVED = "active_connection_removed" + private_constant :ACTIVE_CONNECTION_REMOVED + + # Adds a callback to run when an active connection is removed + # + # @param block [Proc] a block that receives a Connection ID + # when an active connection is removed + def on_active_connection_removed(&block) + add_callback(ACTIVE_CONNECTION_REMOVED, &block) end private @@ -81,54 +149,189 @@ def on_connection_removed(&block) attr_reader :service + # @return [Logger] + attr_reader :logger + + TYPES_MAP = { + "connection.permissions" => "as", + "802-3-ethernet.mac-address" => "ay", + "802-3-ethernet.mac-address-blacklist" => "as", + "802-3-ethernet.s390-options" => "a{ss}", + "802-11-wireless.mac-address" => "ay", + "802-11-wireless.mac-address-blacklist" => "as", + "802-11-wireless.ssid" => "ay", + "ipv4.address-data" => "aa{sv}", + "ipv4.addresses" => "aau", + "ipv4.dns" => "au", + "ipv4.dns-search" => "as", + "ipv4.route-data" => "aa{sv}", + "ipv4.routes" => "aau", + "ipv6.address-data" => "aa{sv}", + "ipv6.dns" => "aay", + "ipv6.dns-search" => "as", + "ipv6.route-data" => "aa{sv}", + "ipv6.routes" => "a(ayuayu)" + }.freeze + + # Adjusts settings D-Bus types + # + # @param settings [ProxyObject] Connection settings object + def adjust_dbus_types(settings) + TYPES_MAP.each do |dot_prop, type| + section, prop = dot_prop.split "." + next if settings.dig(section, prop).nil? + + settings[section][prop] = ::DBus::Data.make_typed(type, settings[section][prop]) + end + end + + NM_PATH = "/org/freedesktop/NetworkManager" + private_constant :NM_PATH + + # Main NetworkManager D-Bus object + # + # @return [::DBus::ProxyObject] def nm_object @nm_object ||= @service.object(NM_PATH) end + NM_SETTINGS_PATH = "/org/freedesktop/NetworkManager/Settings" + private_constant :NM_SETTINGS_PATH + + # NetworkManager settings D-Bus object + # + # @return [::DBus::ProxyObject] + def settings_object + @settings_object ||= @service.object(NM_SETTINGS_PATH) + end + + NM_IFACE = "org.freedesktop.NetworkManager" + private_constant :NM_IFACE + + # List of active connections D-Bus paths + # + # @return [Array] def active_connections_paths nm_object[NM_IFACE]["ActiveConnections"] end + NM_SETTINGS_IFACE = "org.freedesktop.NetworkManager.Settings" + private_constant :NM_SETTINGS_IFACE + + # List of connections D-Bus paths + # + # @return [Array] + def connections_paths + settings_object[NM_SETTINGS_IFACE].ListConnections.first + end + + NM_ACTIVE_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Connection.Active" + private_constant :NM_ACTIVE_CONNECTION_IFACE + # Returns an active connection object from the given path # # @param path [String] Connection path - # @return [Connection] + # @return [ActiveConnection,nil] ActiveConnection instance or nil if it does not exist def find_active_connection(path) obj = service.object(path) conn = obj[NM_ACTIVE_CONNECTION_IFACE] ip4_obj = service.object(conn["Ip4Config"]) ip4_config = ip4_obj["org.freedesktop.NetworkManager.IP4Config"] - + + type = DInstaller::Network::ConnectionType.by_id(conn["Type"]) + state = DInstaller::Network::ConnectionState.by_id(conn["State"]) DInstaller::Network::ActiveConnection.new( - conn["Id"], conn["Type"], conn["State"], ip4_config["AddressData"], ip4_config["Gateway"] + conn["Uuid"], conn["Id"], type: type, state: state, addresses: ip4_config["AddressData"] ) rescue ::DBus::Error nil end - def cache_active_connections - @active_connections ||= active_connections_paths.each_with_object({}) do |path, conns| - conns[path] = find_active_connection(path) - end + NM_SETTINGS_CONNECTION_IFACE = "org.freedesktop.NetworkManager.Settings.Connection" + private_constant :NM_SETTINGS_IFACE + + # Returns an active connection object from the given path + # + # @return [Connection,nil] Connection instance or nil if it does not exist + def find_connection_by_path(path) + obj = service.object(path) + conn = obj[NM_SETTINGS_CONNECTION_IFACE] + settings = conn.GetSettings.first + ipv4_hsh = settings.fetch("ipv4", {}) + ipv4 = DInstaller::Network::IPv4.new( + meth: DInstaller::Network::ConnectionMethod.by_id(ipv4_hsh["method"]), + addresses: ipv4_hsh["address-data"], + gateway: ipv4_hsh["gateway"] && IPAddr.new(settings["ipv4"]["gateway"]), + nameservers: ipv4_hsh.fetch("dns", []).map { |i| IPAddr.new(i, Socket::AF_INET) } + ) + + logger.info "settings for connection: #{settings.inspect}" + DInstaller::Network::Connection.new( + settings["connection"]["uuid"], + settings["connection"]["id"], + ipv4: ipv4 + ) + rescue ::DBus::Error + nil end # Registers D-Bus signal handlers + # + # Listens for active connections changes def register_handlers - mr = ::DBus::MatchRule.new.from_s "type='signal',interface='org.freedesktop.NetworkManager.Connection.Active',member='StateChanged'" + mr = ::DBus::MatchRule.new.from_s( + "type='signal'," \ + "interface='org.freedesktop.NetworkManager.Connection.Active'," \ + "member='StateChanged'" + ) + bus.add_match(mr) do |msg| conn = find_active_connection(msg.path) if conn.nil? - old_conn = @active_connections.delete(msg.path) - run_callbacks(CONNECTION_REMOVED, old_conn) unless old_conn.nil? + conn_id = @active_connections_ids.delete(msg.path) + run_callbacks(ACTIVE_CONNECTION_REMOVED, conn_id) unless conn_id.nil? else - callback_type = @active_connections.key?(msg.path) ? CONNECTION_UPDATED : CONNECTION_ADDED - @active_connections[msg.path] = conn + callback_type = if @active_connections_ids.key?(msg.path) + ACTIVE_CONNECTION_UPDATED + else + ACTIVE_CONNECTION_ADDED + end + @active_connections_ids[msg.path] = conn.id run_callbacks(callback_type, conn) end end end + DEPRECATED_KEYS = [ + "ipv4.gateway", "ipv4.addresses", "ipv4.routes", + "ipv6.gateway", "ipv6.addresses", "ipv6.routes" + ].freeze + + # Cleans deprecated keys + def clean_deprecated_settings(settings) + DEPRECATED_KEYS.each do |dot_prop| + section, key = dot_prop.split "." + settings[section]&.delete(key) + end + end + + # Merges the connection into the D-Bus settings object + # + # @param settings [ProxyObject] + # @param conn [Connection] + def merge_connection(settings, conn) + settings["ipv4"]["address-data"] = conn.ipv4.addresses.map do |a| + { "prefix" => ::DBus::Data.make_typed("u", a[:prefix]), "address" => a[:address] } + end + + gateway = conn.ipv4.gateway.to_s + settings["ipv4"].delete("gateway") + settings["ipv4"]["gateway"] = gateway if gateway.empty? + settings["ipv4"]["dns"] = conn.ipv4.nameservers.map(&:to_i) + settings["ipv4"]["method"] = conn.ipv4.meth.id + end + # Adds a callback for the given event type # # @param type [String] Event type diff --git a/service/lib/dinstaller/dbus/network.rb b/service/lib/dinstaller/dbus/network.rb index 66c2abd6e7..26b1f172d8 100644 --- a/service/lib/dinstaller/dbus/network.rb +++ b/service/lib/dinstaller/dbus/network.rb @@ -21,6 +21,7 @@ require "dbus" require "dinstaller/dbus/base_object" +require "dinstaller/network/connection" module DInstaller module DBus @@ -43,31 +44,51 @@ def initialize(backend, logger) dbus_interface NETWORK_INTERFACE do dbus_reader :active_connections, "aa{sv}" + dbus_reader :connections, "aa{sv}" + dbus_method :GetConnection, "in Id:s, out Result:a{sv}" do |id| + conn = backend.find_connection(id) + [conn.to_dbus] + end + dbus_method :UpdateConnection, "in data:a{sv}, out result:u" do |data| + conn = DInstaller::Network::Connection.from_dbus(data) + result = backend.update_connection(conn) + result ? 0 : 1 + end dbus_signal(:ConnectionAdded, "conn:a{sv}") dbus_signal(:ConnectionUpdated, "conn:a{sv}") - dbus_signal(:ConnectionRemoved, "conn:a{sv}") + dbus_signal(:ConnectionRemoved, "id:s") end # Returns the list of active connections # # @return [Array] def active_connections - @backend.active_connections.map do |conn| - conn.to_dbus - end + backend.active_connections.map(&:to_dbus) end + # Returns the list of connections + # + # @return [Array] + def connections + backend.connections.map(&:to_dbus) + end + + private + + # @return [DInstaller::Software] + attr_reader :backend + def register_callbacks - @backend.on_connection_added do |conn| + backend.on_active_connection_added do |conn| ConnectionAdded(conn.to_dbus) end - @backend.on_connection_updated do |conn| + backend.on_active_connection_updated do |conn| ConnectionUpdated(conn.to_dbus) end - @backend.on_connection_removed do |conn| - ConnectionRemoved(conn.to_dbus) + backend.on_active_connection_removed do |id| + ConnectionRemoved(id) end end end diff --git a/service/lib/dinstaller/network/manager.rb b/service/lib/dinstaller/network/manager.rb index 0a4ed256f0..bbd50ccb69 100644 --- a/service/lib/dinstaller/network/manager.rb +++ b/service/lib/dinstaller/network/manager.rb @@ -32,15 +32,15 @@ module Network class Manager extend Forwardable - def_delegators :@nm_client, :active_connections, :on_connection_added, - :on_connection_updated, :on_connection_removed + def_delegators :@nm_client, :active_connections, :connections, :find_connection, :update_connection, + :on_active_connection_added, :on_active_connection_updated, :on_active_connection_removed - # Constructor - # - # @param logger [Logger] + # Constructor + # + # @param logger [Logger] def initialize(logger) @logger = logger - @nm_client = DInstaller::DBus::Clients::NetworkManager.new + @nm_client = DInstaller::DBus::Clients::NetworkManager.new(logger) end # Probes the network configuration diff --git a/service/test/dinstaller/dbus/clients/network_manager_test.rb b/service/test/dinstaller/dbus/clients/network_manager_test.rb index 8247070d87..a909e61220 100644 --- a/service/test/dinstaller/dbus/clients/network_manager_test.rb +++ b/service/test/dinstaller/dbus/clients/network_manager_test.rb @@ -44,11 +44,10 @@ allow(connection_iface).to receive(:[]) { |k| connection_data[k] } end - let(:bus) { instance_double(::DBus::SystemBus, service: service) } let(:service) { instance_double(::DBus::Service) } let(:dbus_object) { instance_double(::DBus::ProxyObject) } - let(:network_iface) { instance_double(::DBus::ProxyObjectInterface, ) } + let(:network_iface) { instance_double(::DBus::ProxyObjectInterface) } let(:settings_iface) { instance_double(::DBus::ProxyObjectInterface) } let(:connection_object) { instance_double(::DBus::ProxyObject) } @@ -57,22 +56,22 @@ { "Id" => "enp1s0", "Type" => "802-3-ethernet", "State" => 2, "Connection" => settings_path } end - let(:settings_object) { instance_double(::DBus::ProxyObject) } + let(:settings_object) { double(::DBus::ProxyObject, ListConnections: [[settings_path]]) } let(:settings_iface) { double(::DBus::ProxyObjectInterface, GetSettings: [settings_data]) } let(:settings_data) do { "ipv4" => { "method" => "auto", "address-data" => addresses, "gateway" => "192.168.122.1" } } end let(:addresses) do - [ { "address" => "192.168.122.10", "prefix" => 24 }] + [{ "address" => "192.168.122.10", "prefix" => 24 }] end let(:active_connection_path) { "/org/freedesktop/NetworkManager/ActiveConnection/1" } let(:active_connections) { [active_connection_path] } let(:settings_path) { "/org/freedesktop/NetworkManager/Settings/1" } - describe "#active_connections" do + describe "#connections" do it "returns an array of NetworkManager active connections" do - connections = subject.active_connections + connections = subject.connections expected_connection = DInstaller::Network::Connection.new( connection_data["Id"], active_connection_path, diff --git a/service/test/dinstaller/dbus/network_test.rb b/service/test/dinstaller/dbus/network_test.rb index 4a6ad39465..1412f36850 100644 --- a/service/test/dinstaller/dbus/network_test.rb +++ b/service/test/dinstaller/dbus/network_test.rb @@ -48,12 +48,11 @@ it "returns an array of a hash-based representation of the connections" do expect(subject.active_connections).to eq( [ - { "id" => "enp1s0", - "ipv4" => { "addresses" => [], "gateway" => "192.168.122.1", "method" => "auto" }, - "path" => "/org/freedesktop/NetworkManager/ActiveConnection/1", + { "id" => "enp1s0", + "ipv4" => { "addresses" => [], "gateway" => "192.168.122.1", "method" => "auto" }, + "path" => "/org/freedesktop/NetworkManager/ActiveConnection/1", "state" => 2, - "type" => "802-3-ethernet" - } + "type" => "802-3-ethernet" } ] ) end From 463b65e6860153b140f4ba0f09101e2d96599e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 26 Oct 2022 06:29:18 +0100 Subject: [PATCH 09/12] [web] Adapt network handling to the new API --- web/src/ConnectionsDataList.jsx | 4 +- web/src/IpSettingsForm.jsx | 14 ++--- web/src/Network.jsx | 2 +- web/src/NetworkWiredStatus.jsx | 7 ++- web/src/client/network.js | 97 ++++++++++++++++++++++++++++++--- web/src/utils.js | 69 +---------------------- 6 files changed, 105 insertions(+), 88 deletions(-) diff --git a/web/src/ConnectionsDataList.jsx b/web/src/ConnectionsDataList.jsx index ab211beb3d..b026f9213e 100644 --- a/web/src/ConnectionsDataList.jsx +++ b/web/src/ConnectionsDataList.jsx @@ -62,7 +62,7 @@ export default function ConnectionsDataList({ conns, onSelect }) { return ( ); }; @@ -71,7 +71,7 @@ export default function ConnectionsDataList({ conns, onSelect }) { {conns.map(conn => { return ( - + method === METHODS.AUTO; export default function IpSettingsForm({ connection, onClose }) { const client = useInstallerClient(); const { ipv4 = {} } = connection; - const [addresses, setAddresses] = useState(addressesFromIpConfig(ipv4)); - // TODO: fill initial DNS Servers value from connection object - const [nameServers, setNameServers] = useState(dnsFromIpConfig(ipv4)); - const [method, setMethod] = useState(ipv4.method?.v || "auto"); - const [gateway, setGateway] = useState(ipv4.gateway?.v || ""); + const [addresses, setAddresses] = useState(ipv4.addresses); + const [nameServers, setNameServers] = useState(ipv4.nameServers); + const [method, setMethod] = useState(ipv4.method || "auto"); + const [gateway, setGateway] = useState(ipv4.gateway || ""); const [errors, setErrors] = useState({}); const isSetAsInvalid = field => Object.keys(errors).includes(field); @@ -104,7 +102,7 @@ export default function IpSettingsForm({ connection, onClose }) { addresses: sanitizedAddresses, method, gateway, - dns: sanitizedNameServers.map((s) => ip4_from_text(s.address)) + nameServers: sanitizedNameServers } }; @@ -123,7 +121,7 @@ export default function IpSettingsForm({ connection, onClose }) { }; return ( - + {/* FIXME: use a real onSubmit callback */}
diff --git a/web/src/Network.jsx b/web/src/Network.jsx index 85455922c4..5028e18eca 100644 --- a/web/src/Network.jsx +++ b/web/src/Network.jsx @@ -45,7 +45,7 @@ export default function Network() { }, [client.network]); useEffect(() => { - const onConnectionRemoved = ({ id }) => { + const onConnectionRemoved = id => { setConnections(conns => conns.filter(c => c.id !== id)); }; diff --git a/web/src/NetworkWiredStatus.jsx b/web/src/NetworkWiredStatus.jsx index 62503a055c..dd59efdc8c 100644 --- a/web/src/NetworkWiredStatus.jsx +++ b/web/src/NetworkWiredStatus.jsx @@ -22,6 +22,7 @@ import React, { useState } from "react"; import IpSettingsForm from "./IpSettingsForm"; import ConnectionsDataList from "./ConnectionsDataList"; +import { useInstallerClient } from "./context/installer"; import { CONNECTION_STATE } from "./client/network"; @@ -32,13 +33,17 @@ import { CONNECTION_STATE } from "./client/network"; * @param {import ("client/network").ActiveConnection[]} connections */ export default function NetworkWiredStatus({ connections }) { + const client = useInstallerClient(); const [connection, setConnection] = useState(null); const conns = connections.filter(c => c.state === CONNECTION_STATE.ACTIVATED); + const selectConnection = ({ id }) => { + client.network.getConnection(id).then(setConnection); + }; return ( <> - + { connection && setConnection(null)} /> } ); diff --git a/web/src/client/network.js b/web/src/client/network.js index 51c39e2da5..e1112b21f6 100644 --- a/web/src/client/network.js +++ b/web/src/client/network.js @@ -70,6 +70,21 @@ const CONNECTION_TYPES = { * @property {IPAddress} gateway */ +/** + * @typedef {object} Connection + * @property {string} id + * @property {string} name + * @property {IPv4} ipv4 + */ + +/** + * @typedef {object} IPv4 + * @property {string} method + * @property {IPAddress[]} addresses + * @property {IPAddress[]} nameServers + * @property {IPAddress} gateway + */ + /** @typedef {(conns: ActiveConnection[]) => void} ConnectionFn */ /** @typedef {(conns: string[]) => void} ConnectionPathsFn */ @@ -326,10 +341,8 @@ o * NetworkManagerAdapter. handler(this.activeConnectionFromDBus(conn)) ); }); - networkProxy.addEventListener("ConnectionRemoved", (event, conn) => { - this.handlers.connectionRemoved.forEach(handler => - handler(this.activeConnectionFromDBus(conn)) - ); + networkProxy.addEventListener("ConnectionRemoved", (event, id) => { + this.handlers.connectionRemoved.forEach(handler => handler(id)); }); } @@ -343,15 +356,25 @@ o * NetworkManagerAdapter. return proxy.ActiveConnections.map(this.activeConnectionFromDBus); } + /** + * Returns a connection with the given ID + */ + async getConnection(id) { + const proxy = await this.client.proxy(NETWORK_IFACE); + const conn = await proxy.GetConnection(id); + return this.connectionFromDBus(conn); + } + /** * Updates the connection * * It uses the 'path' to match the connection in the backend. * - * @param {ActiveConnection} connection - Connection to update + * @param {Connection} connection - Connection to update */ async updateConnection(connection) { - return this.adapter.updateConnection(connection); + const proxy = await this.client.proxy(NETWORK_IFACE); + return proxy.UpdateConnection(this.connectionToDBus(connection)); } /* @@ -373,16 +396,74 @@ o * NetworkManagerAdapter. * @return {ActiveConnection} */ activeConnectionFromDBus(conn) { - const { id, state, type, gateway } = conn; + const { id, name, state, type } = conn; const addresses = conn.addresses.v.map(({ v: { address, prefix } }) => { return { address: address.v, prefix: prefix.v }; }); return { id: id.v, + name: name.v, state: state.v, type: type.v, + addresses + }; + } + + /** + * @property {string} id + * @property {string} type + * @property {number} state + * @property {IPAddress[]} addresses + * @property {IPAddress} gateway +*/ + connectionToDBus({ id, name, ipv4 }) { + const addressesDBus = ipv4.addresses.map(a => { + return cockpit.variant("a{sv}", { + address: cockpit.variant("s", a.address), + prefix: cockpit.variant("u", parseInt(a.prefix)) + }); + }); + const nameServersDBus = ipv4.nameServers.map(a => cockpit.variant("s", a.address)); + const ipv4DBus = { + addresses: cockpit.variant("av", addressesDBus), + nameServers: cockpit.variant("av", nameServersDBus), + gateway: cockpit.variant("s", ipv4.gateway), + method: cockpit.variant("s", ipv4.method) + }; + const updatedConn = { + id: cockpit.variant("s", id), + name: cockpit.variant("s", name), + ipv4: cockpit.variant("a{sv}", ipv4DBus) + }; + return updatedConn; + } + + /** + * Converts a connection from D-Bus to a proper Connection object + * + * @param {object} conn - Connection as it comes from Cockpit + * @return {Connection} + */ + connectionFromDBus(conn) { + console.log("Connection from D-Bus", conn); + const { id, name, ipv4 } = conn; + const { + addresses: addrs, nameservers: ns, method, gateway + } = ipv4.v; + const addresses = addrs.v.map(({ v: { address, prefix } }) => { + return { address: address.v, prefix: prefix.v }; + }); + const nameServers = ns.v.map((a, idx) => { return { address: a.v, id: idx } }); + const ipv4_settings = { + method: method.v, + gateway: gateway.v, addresses, - gateway: gateway.v + nameServers + }; + return { + id: id.v, + name: name.v, + ipv4: ipv4_settings, }; } } diff --git a/web/src/utils.js b/web/src/utils.js index 846daee99b..0d19aec275 100644 --- a/web/src/utils.js +++ b/web/src/utils.js @@ -147,76 +147,9 @@ const isValidIpPrefix = (value) => { } }; -/** -* Converts an IP given in decimal format to text format -* -* @param {integer} address - An IP Address in Decimal format -* @return {string} the address given as a string -*/ -const int_to_text = (address) => { - const ip = ipaddr.parse(address.toString()); - - return ip.octets.reverse().join("."); -}; - -/** - * - * Returns a list of nameservers from the given Connection Ip settings - * - * @param {object} config - * @return { { address: string }[] } list of nameservers in IP format - * - */ -const dnsFromIpConfig = (config) => { - const dns = config.dns.v || []; - - return dns.map((address) => ({ address: int_to_text(address) })); -}; - -/** - * - * Returns a list of nameservers from the given Connection Ip settings - * - * @param {object} config - * @return { { address: string, prefix: string }[] } list of nameservers in IP format - * - */ -const addressesFromIpConfig = (config) => { - const addresses = config["address-data"]?.v || []; - - return addresses.map((address) => ({ address: address.address.v, prefix: address.prefix.v })); -}; - -/** Convert a IP address from text to network-byte-order integer -* -* FIXME: Currently it is assumed 'le' ordering which should be read from NetworkManager State -* -* @param {string} IPv4 address -* @return {integer} IP address as network byte order integer -* -*/ -const ip4_from_text = (text) => { - if (text === "") - return 0; - - console.log(text); - - const parts = text.split("."); - const bytes = parts.map((s) => parseInt(s.trim())); - - let num = 0; - const shift = (b) => 0x100 * num + b; - for (const n of bytes.reverse()) { num = shift(n) } - - return num; -}; - export { partition, useCancellablePromise, isValidIp, - isValidIpPrefix, - dnsFromIpConfig, - addressesFromIpConfig, - ip4_from_text + isValidIpPrefix }; From a53b3a1a74f9fff608a2a9fc424e012e10fafc3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 26 Oct 2022 06:29:57 +0100 Subject: [PATCH 10/12] [web] Fix a potential crash when handling products --- web/src/Overview.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/Overview.jsx b/web/src/Overview.jsx index 7722adf08d..35318fce1c 100644 --- a/web/src/Overview.jsx +++ b/web/src/Overview.jsx @@ -47,7 +47,7 @@ const ChangeProductButton = () => { const { products } = useSoftware(); const navigate = useNavigate(); - if (products.length === 1) { + if (products === undefined || products.length === 1) { return ""; } @@ -129,7 +129,7 @@ function Overview() { return ( <> - {selectedProduct.name} + {selectedProduct && selectedProduct.name} From 0a94f438df6657edf4b199862b54edc136fe41af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 26 Oct 2022 06:30:39 +0100 Subject: [PATCH 11/12] [service] Add RuboCop as a development dependency --- service/Gemfile | 1 + service/Gemfile.lock | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/service/Gemfile b/service/Gemfile index 67eb4fb79b..e6e7abe342 100644 --- a/service/Gemfile +++ b/service/Gemfile @@ -26,4 +26,5 @@ gemspec group :development do gem "byebug" + gem "rubocop", "1.24.1" end diff --git a/service/Gemfile.lock b/service/Gemfile.lock index 14fa82859e..a71d69153e 100644 --- a/service/Gemfile.lock +++ b/service/Gemfile.lock @@ -15,6 +15,7 @@ GEM remote: https://rubygems.org/ specs: abstract_method (1.2.1) + ast (2.4.2) byebug (11.1.3) cfa (1.0.2) ruby-augeas @@ -34,8 +35,13 @@ GEM racc (~> 1.4) packaging_rake_tasks (1.5.1) rake + parallel (1.22.1) + parser (3.1.2.1) + ast (~> 2.4.1) racc (1.6.0) + rainbow (3.1.1) rake (13.0.6) + regexp_parser (2.6.0) rexml (3.2.5) rspec (3.11.0) rspec-core (~> 3.11.0) @@ -50,9 +56,21 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.11.0) rspec-support (3.11.0) + rubocop (1.24.1) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.15.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.23.0) + parser (>= 3.1.1.0) ruby-augeas (0.5.0) ruby-dbus (0.18.1) rexml + ruby-progressbar (1.11.0) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -60,6 +78,7 @@ GEM simplecov-html (0.12.3) simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) + unicode-display_width (2.3.0) PLATFORMS ruby @@ -71,8 +90,9 @@ DEPENDENCIES packaging_rake_tasks (~> 1.5.1) rake (~> 13.0.6) rspec (~> 3.11.0) + rubocop (= 1.24.1) simplecov (~> 0.21.2) simplecov-lcov (~> 0.8.0) BUNDLED WITH - 2.3.3 + 2.3.23 From 243384e246ffc4d527302e1ea23e8a83bb659872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 26 Oct 2022 06:36:09 +0100 Subject: [PATCH 12/12] [service] Make Rubocop happy --- service/lib/dinstaller/dbus/network.rb | 1 + service/lib/dinstaller/network/manager.rb | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/service/lib/dinstaller/dbus/network.rb b/service/lib/dinstaller/dbus/network.rb index 26b1f172d8..cb5077b2f5 100644 --- a/service/lib/dinstaller/dbus/network.rb +++ b/service/lib/dinstaller/dbus/network.rb @@ -25,6 +25,7 @@ module DInstaller module DBus + # D-Bus object to manage network configuration (/org/opensuse/DInstaller/Network1) class Network < BaseObject PATH = "/org/opensuse/DInstaller/Network1" private_constant :PATH diff --git a/service/lib/dinstaller/network/manager.rb b/service/lib/dinstaller/network/manager.rb index bbd50ccb69..bc253f8e79 100644 --- a/service/lib/dinstaller/network/manager.rb +++ b/service/lib/dinstaller/network/manager.rb @@ -32,8 +32,9 @@ module Network class Manager extend Forwardable - def_delegators :@nm_client, :active_connections, :connections, :find_connection, :update_connection, - :on_active_connection_added, :on_active_connection_updated, :on_active_connection_removed + def_delegators :@nm_client, :active_connections, :connections, :find_connection, + :update_connection, :on_active_connection_added, :on_active_connection_updated, + :on_active_connection_removed # Constructor #