• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / d-installer / 4512499803

pending completion
4512499803

Pull #501

github

Unknown Committer
Unknown Commit Message
Pull Request #501: [web] Add DASD UI - first version

462 of 863 branches covered (53.53%)

Branch coverage included in aggregate %.

64 of 235 new or added lines in 9 files covered. (27.23%)

14 existing lines in 1 file now uncovered.

4668 of 6129 relevant lines covered (76.16%)

10.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.26
/web/src/client/storage.js
1
/*
2
 * Copyright (c) [2022-2023] SUSE LLC
3
 *
4
 * All Rights Reserved.
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of version 2 of the GNU General Public License as published
8
 * by the Free Software Foundation.
9
 *
10
 * This program is distributed in the hope that it will be useful, but WITHOUT
11
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13
 * more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, contact SUSE LLC.
17
 *
18
 * To contact SUSE LLC about this file by physical or electronic mail, you may
19
 * find current contact information at www.suse.com.
20
 */
21

22
// @ts-check
23

24
import DBusClient from "./dbus";
25
import { WithStatus, WithProgress, WithValidation } from "./mixins";
26
import { hex } from "~/utils";
27

28
const STORAGE_IFACE = "org.opensuse.DInstaller.Storage1";
59✔
29
const PROPOSAL_CALCULATOR_IFACE = "org.opensuse.DInstaller.Storage1.Proposal.Calculator";
59✔
30
const ISCSI_NODE_IFACE = "org.opensuse.DInstaller.Storage1.ISCSI.Node";
59✔
31
const ISCSI_NODES_NAMESPACE = "/org/opensuse/DInstaller/Storage1/iscsi_nodes";
59✔
32
const ISCSI_INITIATOR_IFACE = "org.opensuse.DInstaller.Storage1.ISCSI.Initiator";
59✔
33
const DASD_DEVICE_IFACE = "org.opensuse.DInstaller.Storage1.DASD.Device";
59✔
34
const DASD_DEVICES_NAMESPACE = "/org/opensuse/DInstaller/Storage1/dasds";
59✔
35
const DASD_MANAGER_IFACE = "org.opensuse.DInstaller.Storage1.DASD.Manager";
59✔
36
const DASD_STATUS_IFACE = "org.opensuse.DInstaller.Storage1.DASD.Format";
59✔
37
const PROPOSAL_IFACE = "org.opensuse.DInstaller.Storage1.Proposal";
59✔
38
const STORAGE_OBJECT = "/org/opensuse/DInstaller/Storage1";
59✔
39
const STORAGE_JOB_IFACE = "org.opensuse.DInstaller.Storage1.Job";
59✔
40
const STORAGE_JOBS_NAMESPACE = "/org/opensuse/DInstaller/Storage1/jobs";
59✔
41

42
/**
43
 * Removes properties with undefined value
44
 *
45
 * @example
46
 * removeUndefinedCockpitProperties({
47
 *  property1: { t: "s", v: "foo" },
48
 *  property2: { t: b, v: false },
49
 *  property3: { t: "s", v: undefined }
50
 * });
51
 * //returns { property1: { t: "s", v: "foo" }, property2: { t: "b", v: false } }
52
 *
53
 * @param {object} cockpitObject
54
 * @returns {object}
55
 */
56
const removeUndefinedCockpitProperties = (cockpitObject) => {
59✔
57
  const filtered = Object.entries(cockpitObject).filter(([, { v }]) => v !== undefined);
31✔
58
  return Object.fromEntries(filtered);
6✔
59
};
60

61
/**
62
 * Class providing an API for managing the storage proposal through D-Bus
63
 */
64
class ProposalManager {
65
  /**
66
   * @param {DBusClient} client
67
   */
68
  constructor(client) {
69
    this.client = client;
119✔
70
    this.proxies = {
119✔
71
      proposalCalculator: this.client.proxy(PROPOSAL_CALCULATOR_IFACE, STORAGE_OBJECT)
72
    };
73
  }
74

75
  /**
76
   * Gets data associated to the proposal
77
   *
78
   * @returns {Promise<ProposalData>}
79
   *
80
   * @typedef {object} ProposalData
81
   * @property {AvailableDevice[]} availableDevices
82
   * @property {Result} result
83
   */
84
  async getData() {
85
    const availableDevices = await this.getAvailableDevices();
1✔
86
    const result = await this.getResult();
1✔
87

88
    return { availableDevices, result };
1✔
89
  }
90

91
  /**
92
   * Gets the list of available devices
93
   *
94
   * @returns {Promise<AvailableDevice[]>}
95
   *
96
   * @typedef {object} AvailableDevice
97
   * @property {string} id - Device kernel name
98
   * @property {string} label - Device description
99
   */
100
  async getAvailableDevices() {
101
    const buildDevice = dbusDevice => {
2✔
102
      return {
4✔
103
        id: dbusDevice[0],
104
        label: dbusDevice[1]
105
      };
106
    };
107

108
    const proxy = await this.proxies.proposalCalculator;
2✔
109
    return proxy.AvailableDevices.map(buildDevice);
2✔
110
  }
111

112
  /**
113
   * Gets the values of the current proposal
114
   *
115
   * @return {Promise<Result>}
116
   *
117
   * @typedef {object} Result
118
   * @property {string[]} candidateDevices
119
   * @property {boolean} lvm
120
   * @property {string} encryptionPassword
121
   * @property {Volume[]} volumes
122
   * @property {Action[]} actions
123
   *
124
   * @typedef {object} Volume
125
   * @property {string} [deviceType]
126
   * @property {boolean} [optional]
127
   * @property {string} [mountPoint]
128
   * @property {boolean} [fixedSizeLimits]
129
   * @property {number} [minSize]
130
   * @property {number} [maxSize]
131
   * @property {string[]} [fsTypes]
132
   * @property {string} [fsType]
133
   * @property {boolean} [snapshots]
134
   * @property {boolean} [snapshotsConfigurable]
135
   * @property {boolean} [snapshotsAffectSizes]
136
   * @property {string[]} [sizeRelevantVolumes]
137
   *
138
   * @typedef {object} Action
139
   * @property {string} text
140
   * @property {boolean} subvol
141
   * @property {boolean} delete
142
  */
143
  async getResult() {
144
    const proxy = await this.proposalProxy();
3✔
145

146
    if (!proxy) return undefined;
3✔
147

148
    const buildResult = (proxy) => {
2✔
149
      const buildVolume = dbusVolume => {
2✔
150
        const buildList = (value) => {
4✔
151
          if (value === undefined) return [];
8✔
152

153
          return value.map(val => val.v);
4✔
154
        };
155

156
        return {
4✔
157
          deviceType: dbusVolume.DeviceType?.v,
158
          optional: dbusVolume.Optional?.v,
159
          encrypted: dbusVolume.Encrypted?.v,
160
          mountPoint: dbusVolume.MountPoint?.v,
161
          fixedSizeLimits: dbusVolume.FixedSizeLimits?.v,
162
          adaptiveSizes: dbusVolume.AdaptiveSizes?.v,
163
          minSize: dbusVolume.MinSize?.v,
164
          maxSize: dbusVolume.MaxSize?.v,
165
          fsTypes: buildList(dbusVolume.FsTypes?.v),
166
          fsType: dbusVolume.FsType?.v,
167
          snapshots: dbusVolume.Snapshots?.v,
168
          snapshotsConfigurable: dbusVolume.SnapshotsConfigurable?.v,
169
          snapshotsAffectSizes: dbusVolume.SnapshotsAffectSizes?.v,
170
          sizeRelevantVolumes: buildList(dbusVolume.SizeRelevantVolumes?.v)
171
        };
172
      };
173

174
      const buildAction = dbusAction => {
2✔
175
        return {
2✔
176
          text: dbusAction.Text.v,
177
          subvol: dbusAction.Subvol.v,
178
          delete: dbusAction.Delete.v
179
        };
180
      };
181

182
      return {
2✔
183
        candidateDevices: proxy.CandidateDevices,
184
        lvm: proxy.LVM,
185
        encryptionPassword: proxy.EncryptionPassword,
186
        volumes: proxy.Volumes.map(buildVolume),
187
        actions: proxy.Actions.map(buildAction)
188
      };
189
    };
190

191
    return buildResult(proxy);
2✔
192
  }
193

194
  /**
195
   * Calculates a new proposal
196
   *
197
   * @param {Settings} settings
198
   *
199
   * @typedef {object} Settings
200
   * @property {string[]} [candidateDevices] - Devices to use for the proposal
201
   * @property {string} [encryptionPassword] - Password for encrypting devices
202
   * @property {boolean} [lvm] - Whether to calculate the proposal with LVM volumes
203
   * @property {Volume[]} [volumes] - Volumes to create
204
   *
205
   * @returns {Promise<number>} 0 on success, 1 on failure
206
   */
207
  async calculate({ candidateDevices, encryptionPassword, lvm, volumes }) {
208
    const dbusVolume = (volume) => {
2✔
209
      return removeUndefinedCockpitProperties({
2✔
210
        MountPoint: { t: "s", v: volume.mountPoint },
211
        Encrypted: { t: "b", v: volume.encrypted },
212
        FsType: { t: "s", v: volume.fsType },
213
        MinSize: { t: "x", v: volume.minSize },
214
        MaxSize: { t: "x", v: volume.maxSize },
215
        FixedSizeLimits: { t: "b", v: volume.fixedSizeLimits },
216
        Snapshots: { t: "b", v: volume.snapshots }
217
      });
218
    };
219

220
    const settings = removeUndefinedCockpitProperties({
2✔
221
      CandidateDevices: { t: "as", v: candidateDevices },
222
      EncryptionPassword: { t: "s", v: encryptionPassword },
223
      LVM: { t: "b", v: lvm },
224
      Volumes: { t: "aa{sv}", v: volumes?.map(dbusVolume) }
225
    });
226

227
    const proxy = await this.proxies.proposalCalculator;
2✔
228
    return proxy.Calculate(settings);
2✔
229
  }
230

231
  /**
232
   * @private
233
   * Proxy for org.opensuse.DInstaller.Storage1.Proposal iface
234
   *
235
   * @note The proposal object implementing this iface is dynamically exported.
236
   *
237
   * @returns {Promise<object|null>} null if the proposal object is not exported yet
238
   */
239
  async proposalProxy() {
240
    try {
3✔
241
      return await this.client.proxy(PROPOSAL_IFACE);
3✔
242
    } catch {
243
      return null;
×
244
    }
245
  }
246
}
247

248
/**
249
 * Class providing an API for managing Direct Access Storage Devices (DASDs)
250
 */
251
class DASDManager {
252
  /**
253
   * @param {string} service - D-Bus service name
254
   * @param {string} address - D-Bus address
255
   */
256
  constructor(service, address) {
257
    this.service = service;
119✔
258
    this.address = address;
119✔
259
    this.proxies = {};
119✔
260
  }
261

262
  /**
263
   * @return {DBusClient} client
264
   */
265
  client() {
266
    // return this.assigned_client;
267
    if (!this._client) {
9✔
268
      this._client = new DBusClient(this.service, this.address);
6✔
269
    }
270

271
    return this._client;
9✔
272
  }
273

274
  async isSupported() {
NEW
275
    const proxy = await this.managerProxy();
×
276

NEW
277
    return proxy !== undefined;
×
278
  }
279

280
  /**
281
   * Build a job
282
   *
283
   * @returns {StorageJob}
284
   *
285
   * @typedef {object} StorageJob
286
   * @property {string} path
287
   * @property {boolean} running
288
   * @property {number} exitCode
289
   */
290
  buildJob(job) {
NEW
291
    return {
×
292
      path: job.path,
293
      running: job.Running,
294
      exitCode: job.ExitCode
295
    };
296
  }
297

298
  /**
299
   * Triggers a DASD probing
300
   */
301
  async probe() {
302
    const proxy = await this.managerProxy();
3✔
303
    await proxy?.Probe();
3✔
304
  }
305

306
  /**
307
   * Gets the list of DASD devices
308
   *
309
   * @returns {Promise<DASDDevice[]>}
310
   */
311
  async getDevices() {
312
    // FIXME: should we do the probing here?
313
    await this.probe();
3✔
314
    const devices = await this.devicesProxy();
3✔
315
    return Object.values(devices).map(this.buildDevice);
3✔
316
  }
317

318
  /**
319
   * Requests the format action for given devices
320
   *
321
   * @param {DASDDevice[]} devices
322
   */
323
  async format(devices) {
NEW
324
    const proxy = await this.managerProxy();
×
NEW
325
    const devicesPath = devices.map(d => this.devicePath(d));
×
NEW
326
    proxy.Format(devicesPath);
×
327
  }
328

329
  /**
330
   * Set DIAG for given devices
331
   *
332
   * @param {DASDDevice[]} devices
333
   * @param {boolean} value
334
   */
335
  async setDIAG(devices, value) {
336
    const proxy = await this.managerProxy();
2✔
337
    const devicesPath = devices.map(d => this.devicePath(d));
2✔
338
    proxy.SetDiag(devicesPath, value);
2✔
339
  }
340

341
  /**
342
   * Enables given DASD devices
343
   *
344
   * @param {DASDDevice[]} devices
345
   */
346
  async enableDevices(devices) {
347
    const proxy = await this.managerProxy();
1✔
348
    const devicesPath = devices.map(d => this.devicePath(d));
1✔
349
    proxy.Enable(devicesPath);
1✔
350
  }
351

352
  /**
353
   * Disables given DASD devices
354
   *
355
   * @param {DASDDevice[]} devices
356
   */
357
  async disableDevices(devices) {
358
    const proxy = await this.managerProxy();
1✔
359
    const devicesPath = devices.map(d => this.devicePath(d));
1✔
360
    proxy.Disable(devicesPath);
1✔
361
  }
362

363
  /**
364
   * @private
365
   * Proxy for objects implementing org.opensuse.DInstaller.Storage1.Job iface
366
   *
367
   * @note The jobs are dynamically exported.
368
   *
369
   * @returns {Promise<object>}
370
   */
371
  async jobsProxy() {
NEW
372
    if (!this.proxies.jobs)
×
NEW
373
      this.proxies.jobs = await this.client().proxies(STORAGE_JOB_IFACE, STORAGE_JOBS_NAMESPACE);
×
374

NEW
375
    return this.proxies.jobs;
×
376
  }
377

378
  async getJobs() {
NEW
379
    const proxy = await this.jobsProxy();
×
NEW
380
    return Object.values(proxy).filter(p => p.Running)
×
381
      .map(this.buildJob);
382
  }
383

384
  async onJobAdded(handler) {
NEW
385
    const proxy = await this.jobsProxy();
×
NEW
386
    proxy.addEventListener("added", (_, proxy) => handler(this.buildJob(proxy)));
×
387
  }
388

389
  async onJobChanged(handler) {
NEW
390
    const proxy = await this.jobsProxy();
×
NEW
391
    proxy.addEventListener("changed", (_, proxy) => handler(this.buildJob(proxy)));
×
392
  }
393

394
  /**
395
   * @private
396
   * Proxy for objects implementing org.opensuse.DInstaller.Storage1.Job iface
397
   *
398
   * @note The jobs are dynamically exported.
399
   *
400
   * @returns {Promise<object>}
401
   */
402
  async formatProxy(jobPath) {
NEW
403
    const proxy = await this.client().proxy(DASD_STATUS_IFACE, jobPath);
×
NEW
404
    return proxy;
×
405
  }
406

407
  async onFormatProgress(jobPath, handler) {
NEW
408
    const proxy = await this.formatProxy(jobPath);
×
NEW
409
    proxy.addEventListener("changed", (_, proxy) => {
×
NEW
410
      handler(proxy.Summary);
×
411
    });
412
  }
413

414
  /**
415
   * @private
416
   * Proxy for objects implementing org.opensuse.DInstaller.Storage1.DASD.Device iface
417
   *
418
   * @note The DASD devices are dynamically exported.
419
   *
420
   * @returns {Promise<object>}
421
   */
422
  async devicesProxy() {
423
    if (!this.proxies.devices)
3!
424
      this.proxies.devices = await this.client().proxies(DASD_DEVICE_IFACE, DASD_DEVICES_NAMESPACE);
3✔
425

426
    return this.proxies.devices;
3✔
427
  }
428

429
  /**
430
   * @private
431
   * Proxy for org.opensuse.DInstaller.Storage1.DASD.Manager iface
432
   *
433
   * @returns {Promise<object>}
434
   */
435
  async managerProxy() {
436
    if (!this.proxies.dasdManager)
7✔
437
      this.proxies.dasdManager = await this.client().proxy(DASD_MANAGER_IFACE, STORAGE_OBJECT);
6✔
438

439
    return this.proxies.dasdManager;
7✔
440
  }
441

442
  async deviceEventListener(signal, handler) {
NEW
443
    const proxy = await this.devicesProxy();
×
NEW
444
    const action = (_, proxy) => handler(this.buildDevice(proxy));
×
445

NEW
446
    proxy.addEventListener(signal, action);
×
NEW
447
    return () => proxy.removeEventListener(signal, action);
×
448
  }
449

450
  /**
451
   * Build a list of DASD devices
452
   *
453
   * @returns {DASDDevice}
454
   *
455
   * @typedef {object} DASDDevice
456
   * @property {string} id
457
   * @property {number} hexId
458
   * @property {string} accessType
459
   * @property {string} channelId
460
   * @property {boolean} diag
461
   * @property {boolean} enabled
462
   * @property {boolean} formatted
463
   * @property {string} name
464
   * @property {string} partitionInfo
465
   * @property {string} status
466
   * @property {string} type
467
   */
468
  buildDevice(device) {
469
    const id = device.path.split("/").slice(-1)[0];
4✔
470
    const enabled = device.Enabled;
4✔
471

472
    return {
4✔
473
      id,
474
      accessType: enabled ? device.AccessType : "offline",
4!
475
      channelId: device.Id,
476
      diag: device.Diag,
477
      enabled,
478
      formatted: device.Formatted,
479
      hexId: hex(device.Id),
480
      name: device.DeviceName,
481
      partitionInfo: enabled ? device.PartitionInfo : "",
4!
482
      status: device.Status,
483
      type: device.Type
484
    };
485
  }
486

487
  /**
488
   * @private
489
   * Builds the D-Bus path for the given DASD device
490
   *
491
   * @param {DASDDevice} device
492
   * @returns {string}
493
   */
494
  devicePath(device) {
495
    return DASD_DEVICES_NAMESPACE + "/" + device.id;
4✔
496
  }
497
}
498

499
/**
500
 * Class providing an API for managing iSCSI through D-Bus
501
 */
502
class ISCSIManager {
503
  /**
504
   * @param {string} service - D-Bus service name
505
   * @param {string} address - D-Bus address
506
   */
507
  constructor(service, address) {
508
    this.service = service;
119✔
509
    this.address = address;
119✔
510
    this.proxies = {};
119✔
511
  }
512

513
  /**
514
   * @return {DBusClient} client
515
   */
516
  client() {
517
    // return this.assigned_client;
518
    if (!this._client) {
8!
519
      this._client = new DBusClient(this.service, this.address);
8✔
520
    }
521

522
    return this._client;
8✔
523
  }
524

525
  async getInitiatorIbft() {
UNCOV
526
    const proxy = await this.iscsiInitiatorProxy();
×
UNCOV
527
    return proxy.IBFT;
×
528
  }
529

530
  /**
531
   * Gets the iSCSI initiator name
532
   *
533
   * @returns {Promise<string>}
534
   */
535
  async getInitiatorName() {
536
    const proxy = await this.iscsiInitiatorProxy();
2✔
537
    return proxy.InitiatorName;
2✔
538
  }
539

540
  /**
541
   * Sets the iSCSI initiator name
542
   *
543
   * @param {string} value
544
   */
545
  async setInitiatorName(value) {
546
    const proxy = await this.iscsiInitiatorProxy();
1✔
547
    proxy.InitiatorName = value;
1✔
548
  }
549

550
  /**
551
   * Gets the list of exported iSCSI nodes
552
   *
553
   * @returns {Promise<ISCSINode[]>}
554
   *
555
   * @typedef {object} ISCSINode
556
   * @property {string} id
557
   * @property {string} target
558
   * @property {string} address
559
   * @property {number} port
560
   * @property {string} interface
561
   * @property {boolean} ibft
562
   * @property {boolean} connected
563
   * @property {string} startup
564
   */
565
  async getNodes() {
566
    const proxy = await this.iscsiNodesProxy();
2✔
567
    return Object.values(proxy).map(this.buildNode);
2✔
568
  }
569

570
  /**
571
   * Performs an iSCSI discovery
572
   *
573
   * @param {string} address - IP address of the iSCSI server
574
   * @param {number} port - Port of the iSCSI server
575
   * @param {DiscoverOptions} [options]
576
   *
577
   * @typedef {object} DiscoverOptions
578
   * @property {string} [username] - Username for authentication by target
579
   * @property {string} [password] - Password for authentication by target
580
   * @property {string} [reverseUsername] - Username for authentication by initiator
581
   * @property {string} [reversePassword] - Password for authentication by initiator
582
   *
583
   * @returns {Promise<number>} 0 on success, 1 on failure
584
   */
585
  async discover(address, port, options = {}) {
×
586
    const auth = removeUndefinedCockpitProperties({
1✔
587
      Username: { t: "s", v: options.username },
588
      Password: { t: "s", v: options.password },
589
      ReverseUsername: { t: "s", v: options.reverseUsername },
590
      ReversePassword: { t: "s", v: options.reversePassword }
591
    });
592

593
    const proxy = await this.iscsiInitiatorProxy();
1✔
594
    return proxy.Discover(address, port, auth);
1✔
595
  }
596

597
  /**
598
   * Sets the startup status of the connection
599
   *
600
   * @param {ISCSINode} node
601
   * @param {String} startup
602
   */
603
  async setStartup(node, startup) {
UNCOV
604
    const path = this.nodePath(node);
×
605

606
    const proxy = await this.client().proxy(ISCSI_NODE_IFACE, path);
×
UNCOV
607
    proxy.Startup = startup;
×
608
  }
609

610
  /**
611
   * Deletes the given iSCSI node
612
   *
613
   * @param {ISCSINode} node
614
   * @returns {Promise<number>} 0 on success, 1 on failure if the given path is not exported, 2 on
615
   *  failure because any other reason.
616
   */
617
  async delete(node) {
618
    const path = this.nodePath(node);
1✔
619

620
    const proxy = await this.iscsiInitiatorProxy();
1✔
621
    return proxy.Delete(path);
1✔
622
  }
623

624
  /**
625
   * Creates an iSCSI session
626
   *
627
   * @param {ISCSINode} node
628
   * @param {LoginOptions} options
629
   *
630
   * @typedef {object} LoginOptions
631
   * @property {string} [username] - Username for authentication by target
632
   * @property {string} [password] - Password for authentication by target
633
   * @property {string} [reverseUsername] - Username for authentication by initiator
634
   * @property {string} [reversePassword] - Password for authentication by initiator
635
   * @property {string} [startup] - Startup status for the session
636
   *
637
   * @returns {Promise<number>} 0 on success, 1 on failure if the given startup value is not
638
   *  valid, and 2 on failure because any other reason
639
   */
640
  async login(node, options = {}) {
×
641
    const path = this.nodePath(node);
1✔
642

643
    const dbusOptions = removeUndefinedCockpitProperties({
1✔
644
      Username: { t: "s", v: options.username },
645
      Password: { t: "s", v: options.password },
646
      ReverseUsername: { t: "s", v: options.reverseUsername },
647
      ReversePassword: { t: "s", v: options.reversePassword },
648
      Startup: { t: "s", v: options.startup }
649
    });
650

651
    const proxy = await this.client().proxy(ISCSI_NODE_IFACE, path);
1✔
652
    return proxy.Login(dbusOptions);
1✔
653
  }
654

655
  /**
656
   * Closes an iSCSI session
657
   *
658
   * @param {ISCSINode} node
659
   * @returns {Promise<number>} 0 on success, 1 on failure
660
   */
661
  async logout(node) {
662
    const path = this.nodePath(node);
1✔
663
    // const iscsiNode = new ISCSINodeObject(this.client, path);
664
    // return await iscsiNode.iface.logout();
665
    const proxy = await this.client().proxy(ISCSI_NODE_IFACE, path);
1✔
666
    return proxy.Logout();
1✔
667
  }
668

669
  onInitiatorChanged(handler) {
UNCOV
670
    return this.client().onObjectChanged(STORAGE_OBJECT, ISCSI_INITIATOR_IFACE, (changes) => {
×
UNCOV
671
      const data = {
×
672
        name: changes.InitiatorName?.v,
673
        ibft: changes.IBFT?.v
674
      };
675

UNCOV
676
      const filtered = Object.entries(data).filter(([, v]) => v !== undefined);
×
UNCOV
677
      return handler(Object.fromEntries(filtered));
×
678
    });
679
  }
680

681
  async onNodeAdded(handler) {
UNCOV
682
    const proxy = await this.iscsiNodesProxy();
×
UNCOV
683
    proxy.addEventListener("added", (_, proxy) => handler(this.buildNode(proxy)));
×
684
  }
685

686
  async onNodeChanged(handler) {
UNCOV
687
    const proxy = await this.iscsiNodesProxy();
×
UNCOV
688
    proxy.addEventListener("changed", (_, proxy) => handler(this.buildNode(proxy)));
×
689
  }
690

691
  async onNodeRemoved(handler) {
UNCOV
692
    const proxy = await this.iscsiNodesProxy();
×
UNCOV
693
    proxy.addEventListener("removed", (_, proxy) => handler(this.buildNode(proxy)));
×
694
  }
695

696
  buildNode(proxy) {
697
    const id = path => path.split("/").slice(-1)[0];
2✔
698

699
    return {
2✔
700
      id: id(proxy.path),
701
      target: proxy.Target,
702
      address: proxy.Address,
703
      port: proxy.Port,
704
      interface: proxy.Interface,
705
      ibft: proxy.IBFT,
706
      connected: proxy.Connected,
707
      startup: proxy.Startup
708
    };
709
  }
710

711
  /**
712
   * @private
713
   * Proxy for org.opensuse.DInstaller.Storage1.ISCSI.Initiator iface
714
   *
715
   * @returns {Promise<object>}
716
   */
717
  async iscsiInitiatorProxy() {
718
    if (!this.proxies.iscsiInitiator) {
5✔
719
      this.proxies.iscsiInitiator = await this.client().proxy(ISCSI_INITIATOR_IFACE, STORAGE_OBJECT);
4✔
720
    }
721

722
    return this.proxies.iscsiInitiator;
5✔
723
  }
724

725
  /**
726
   * @private
727
   * Proxy for objects implementing org.opensuse.DInstaller.Storage1.ISCSI.Node iface
728
   *
729
   * @note The ISCSI nodes are dynamically exported.
730
   *
731
   * @returns {Promise<object>}
732
   */
733
  async iscsiNodesProxy() {
734
    if (!this.proxies.iscsiNodes)
2!
735
      this.proxies.iscsiNodes = await this.client().proxies(ISCSI_NODE_IFACE, ISCSI_NODES_NAMESPACE);
2✔
736

737
    return this.proxies.iscsiNodes;
2✔
738
  }
739

740
  /**
741
   * @private
742
   * Builds the D-Bus path for the given iSCSI node
743
   *
744
   * @param {ISCSINode} node
745
   * @returns {string}
746
   */
747
  nodePath(node) {
748
    return ISCSI_NODES_NAMESPACE + "/" + node.id;
3✔
749
  }
750
}
751

752
/**
753
 * Storage base client
754
 *
755
 * @ignore
756
 */
757
class StorageBaseClient {
758
  static SERVICE = "org.opensuse.DInstaller.Storage";
59✔
759

760
  /**
761
   * @param {string|undefined} address - D-Bus address; if it is undefined, it uses the system bus.
762
   */
763
  constructor(address = undefined) {
24✔
764
    this.client = new DBusClient(StorageBaseClient.SERVICE, address);
119✔
765
    this.proposal = new ProposalManager(this.client);
119✔
766
    this.iscsi = new ISCSIManager(StorageBaseClient.SERVICE, address);
119✔
767
    this.dasd = new DASDManager(StorageBaseClient.SERVICE, address);
119✔
768
    this.proxies = {
119✔
769
      storage: this.client.proxy(STORAGE_IFACE)
770
    };
771
  }
772

773
  /**
774
   * Probes the system
775
   */
776
  async probe() {
777
    const proxy = await this.proxies.storage;
1✔
778
    return proxy.Probe();
1✔
779
  }
780

781
  /**
782
   * Whether the system is in a deprecated status
783
   *
784
   * @returns {Promise<boolean>}
785
   */
786
  async isDeprecated() {
787
    const proxy = await this.proxies.storage;
1✔
788
    return proxy.DeprecatedSystem;
1✔
789
  }
790

791
  /**
792
   * Runs a handler function when the system becomes deprecated
793
   *
794
   * @callback handlerFn
795
   *
796
   * @param {handlerFn} handler
797
   */
798
  onDeprecate(handler) {
799
    return this.client.onObjectChanged(STORAGE_OBJECT, STORAGE_IFACE, (changes) => {
2✔
800
      if (changes.DeprecatedSystem?.v) return handler();
2!
801
    });
802
  }
803
}
804

805
/**
806
 * Allows interacting with the storage settings
807
 */
808
class StorageClient extends WithValidation(
809
  WithProgress(
810
    WithStatus(StorageBaseClient, STORAGE_OBJECT), STORAGE_OBJECT
811
  ), STORAGE_OBJECT
812
) { }
813

814
export { StorageClient };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc