Class: OsCtld::Devices::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/osctld/devices/manager.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(owner, devices: []) ⇒ Manager

Returns a new instance of Manager.

Parameters:



38
39
40
41
42
# File 'lib/osctld/devices/manager.rb', line 38

def initialize(owner, devices: [])
  @owner = owner
  @devices = devices
  @configurator = configurator_class.new(owner)
end

Instance Attribute Details

#configuratorObject (readonly, protected)

Returns the value of attribute configurator.



562
563
564
# File 'lib/osctld/devices/manager.rb', line 562

def configurator
  @configurator
end

#devicesObject (readonly, protected)

Returns the value of attribute devices.



562
563
564
# File 'lib/osctld/devices/manager.rb', line 562

def devices
  @devices
end

#ownerObject (readonly, protected)

Returns the value of attribute owner.



562
563
564
# File 'lib/osctld/devices/manager.rb', line 562

def owner
  @owner
end

Class Method Details

.class_for(owner) ⇒ Class

Get manager class group/container on cgroup v1/v2

Parameters:

Returns:

  • (Class)


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/osctld/devices/manager.rb', line 6

def self.class_for(owner)
  mod =
    if CGroup.v1?
      Devices::V1
    else
      Devices::V2
    end

  case owner
  when Group
    mod::GroupManager
  when Container
    mod::ContainerManager
  else
    raise "unsupported device owner #{owner.inspect}"
  end
end

.load(owner, cfg) ⇒ Object

Parameters:



32
33
34
# File 'lib/osctld/devices/manager.rb', line 32

def self.load(owner, cfg)
  new_for(owner, devices: cfg.map { |v| Devices::Device.load(v) })
end

.new_for(owner) ⇒ Devices::Manager

Parameters:

Returns:



26
27
28
# File 'lib/osctld/devices/manager.rb', line 26

def self.new_for(owner, **)
  class_for(owner).new(owner, **)
end

Instance Method Details

#add(device) ⇒ Object

Add new device and ensure that parent groups provide it

Parameters:



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/osctld/devices/manager.rb', line 82

def add(device)
  sync do
    parent.devices.provide(device) if parent
    do_add(device)

    add_to_changeset
    configurator.add_device(device)

    if device.inherit?
      inherit_recursive(device)
    end
  end
end

#add_new(type, major, minor, mode) ⇒ Object

Add new device

Parameters:

  • type (:char, :block)
  • major (Integer, Symbol)
  • minor (Integer, Symbol)
  • mode (String)
  • opts (Hash)


76
77
78
# File 'lib/osctld/devices/manager.rb', line 76

def add_new(type, major, minor, mode, **)
  add(Devices::Device.new(type, major.to_s, minor.to_s, mode, **))
end

#add_to_changesetObject (protected)



610
611
612
# File 'lib/osctld/devices/manager.rb', line 610

def add_to_changeset
  Devices::ChangeSet.add(owner.pool, self, changeset_sort_key)
end

#apply(parents: false, descendants: false) ⇒ Object

Configure devices in cgroup

Parameters:

  • parents (Boolean) (defaults to: false)
  • descendants (Boolean) (defaults to: false)


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/osctld/devices/manager.rb', line 327

def apply(parents: false, descendants: false)
  sync do
    if parents && parent
      parent.devices.apply(parents: true)
    end

    configurator.reconfigure(devices)

    if descendants
      children.each do |child|
        child.devices.apply(descendants: true)
      end
    end
  end
end

#assets(add) ⇒ Object

Parameters:



45
# File 'lib/osctld/devices/manager.rb', line 45

def assets(add); end

#changeset_sort_keyany

Returns:

  • (any)

Raises:

  • (NotImplementedError)


534
535
536
# File 'lib/osctld/devices/manager.rb', line 534

def changeset_sort_key
  raise NotImplementedError
end

#check_availability!(device, mode: nil, parent: nil) ⇒ Object

Check whether the device is available in parents

Parameters:



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/osctld/devices/manager.rb', line 403

def check_availability!(device, mode: nil, parent: nil)
  sync do
    p = parent || self.parent

    loop do
      break if p.nil?

      dev = p.devices.detect { |v| v == device }

      if dev.nil?
        raise DeviceNotAvailable.new(device, p)
      elsif !dev.mode.compatible?(mode || device.mode)
        raise DeviceModeInsufficient.new(device, p, dev.mode)
      end

      p = p.devices.parent
    end
  end
end

#check_descendant_mode!(device, mode) ⇒ Object (protected)



603
604
605
606
607
608
# File 'lib/osctld/devices/manager.rb', line 603

def check_descendant_mode!(device, mode)
  dev = get(device)
  return if !dev || mode.compatible?(dev.mode)

  raise DeviceDescendantRequiresMode.new(owner, dev.mode)
end

#check_descendants!(device, mode: nil) ⇒ Object

Check whether descendants do not have broader mode requirements



424
425
426
427
428
429
430
# File 'lib/osctld/devices/manager.rb', line 424

def check_descendants!(device, mode: nil)
  sync do
    children.each do |child|
      child.devices.check_descendant_mode!(device, mode || device.mode)
    end
  end
end

#check_unset_inherit!(device, check_owner: nil) ⇒ Object (protected)

Check if the inheritance can be disabled

We have to forbid disabling the inheritance if any grandchild has the device promoted, but its parent does not. Thus, the device cannot be deleted from self, because the grandchild needs it.



585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/osctld/devices/manager.rb', line 585

def check_unset_inherit!(device, check_owner: nil)
  if check_owner.nil?
    children.each do |child|
      if child.devices.inherited?(device)
        # Since our child inherits the device, his descendants must not use it
        check_unset_inherit!(device, check_owner: child)
      end
    end

  else
    check_owner.devices.children.each do |child|
      raise DeviceInheritNeeded, child if child.devices.used?(device)

      check_unset_inherit!(device, check_owner: child)
    end
  end
end

#childrenArray<Devices::Owner>

Returns:

Raises:

  • (NotImplementedError)


524
525
526
# File 'lib/osctld/devices/manager.rb', line 524

def children
  raise NotImplementedError
end

#chmod(device, mode, parents: false, promote: false, descendants: false, **_opts) ⇒ Object

Parameters:

  • device (Devices::Device)
  • mode (Devices::Mode)
  • parents (Boolean) (defaults to: false)
  • promote (Boolean) (defaults to: false)
  • descendants (Boolean) (defaults to: false)


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/osctld/devices/manager.rb', line 178

def chmod(device, mode, parents: false, promote: false, descendants: false, **_opts)
  sync do
    if parents && parent
      dev = device.clone
      dev.mode = mode
      parent.devices.provide(dev)
    end

    changes = device.chmod(mode)
    device.inherited = false if promote && device.inherited?
    owner.save_config

    add_to_changeset
    configurator.apply_changes(changes)

    if descendants
      children.each do |child|
        dev = child.devices.get(device)
        next if dev.nil?

        child.devices.chmod(dev, mode, descendants: true)
      end
    end
  end
end

#configurator_classClass

Returns:

  • (Class)

Raises:

  • (NotImplementedError)


529
530
531
# File 'lib/osctld/devices/manager.rb', line 529

def configurator_class
  raise NotImplementedError
end

#detect {|| ... } ⇒ Object

Yield Parameters:



509
510
511
# File 'lib/osctld/devices/manager.rb', line 509

def detect(&block)
  sync { devices.detect(&block) }
end

#do_add(device) ⇒ Object (protected)



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# File 'lib/osctld/devices/manager.rb', line 564

def do_add(device)
  if device.inherited?
    devices << device
  else
    # Add promoted devices before inherited devices, as those are always
    # added later when read from configuration files.
    i = devices.index(&:inherited?)

    if i.nil?
      devices << device
    else
      devices.insert(i, device)
    end
  end
end

#dumpArray<Hash>

Dump device configuration into the config

Returns:

  • (Array<Hash>)


546
547
548
# File 'lib/osctld/devices/manager.rb', line 546

def dump
  sync { devices.reject(&:inherited?).map(&:dump) }
end

#dup(new_owner) ⇒ Object



550
551
552
553
554
555
556
557
558
# File 'lib/osctld/devices/manager.rb', line 550

def dup(new_owner)
  sync do
    ret = super()
    ret.instance_variable_set('@owner', new_owner)
    ret.instance_variable_set('@devices', devices.map(&:clone))
    ret.instance_variable_set('@configurator', configurator.dup(new_owner))
    ret
  end
end

#each {|| ... } ⇒ Object

Yield Parameters:



504
505
506
# File 'lib/osctld/devices/manager.rb', line 504

def each(&block)
  sync { devices.each(&block) }
end

#exportArray<Hash>

Export devices to clients

Returns:

  • (Array<Hash>)


540
541
542
# File 'lib/osctld/devices/manager.rb', line 540

def export
  sync { devices.map(&:export) }
end

#find(type, major, minor) ⇒ Devices::Device?

Find device by ‘type` and `major` and `minor` numbers

Parameters:

  • type (Symbol)

    ‘:char`, `:block`

  • major (String)
  • minor (String)

Returns:



451
452
453
454
455
456
457
# File 'lib/osctld/devices/manager.rb', line 451

def find(type, major, minor)
  sync do
    devices.detect do |dev|
      dev.type == type && dev.major == major && dev.minor == minor
    end
  end
end

#get(device) ⇒ Devices::Device?

Find and return device

The point of this method is to return the manager’s own device’s instance. Devices equality is tested by comparing its type and major and minor numbers, but its devnode name or mode can be different.

Returns:



465
466
467
468
469
470
# File 'lib/osctld/devices/manager.rb', line 465

def get(device)
  sync do
    i = devices.index(device)
    i ? devices[i] : nil
  end
end

#include?(device) ⇒ Boolean

Check if we have a particular device

Parameters:

Returns:

  • (Boolean)


475
476
477
# File 'lib/osctld/devices/manager.rb', line 475

def include?(device)
  sync { devices.include?(device) }
end

#inherit(device, **_opts) ⇒ Object

Inherit device from a parent

Parameters:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/osctld/devices/manager.rb', line 99

def inherit(device, **_opts)
  sync do
    dev = device.clone
    dev.inherited = true
    do_add(dev)

    add_to_changeset
    configurator.add_device(dev)

    children.each do |child|
      child.devices.inherit(dev)
    end
  end
end

#inherit_promoted(device) ⇒ Object

Inherit a promoted device

Parameters:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/osctld/devices/manager.rb', line 213

def inherit_promoted(device)
  sync do
    parent_dev = parent.devices.get(device) if parent

    if parent_dev && parent_dev.inherit?
      # We can keep the device and descendants unchanged
      device.inherited = true

      # Parent group can have broader access mode, so we need to expand it
      if device.mode != parent_dev.mode
        add_to_changeset
        changes = device.chmod(parent_dev.mode.clone)
        configurator.apply_changes(changes)

        # Update descendants that inherit the device as well
        children.each do |child|
          child_dev = child.devices.get(device)
          next unless child_dev.inherited?

          child.devices.update_inherited_mode(child_dev, parent_dev.mode.clone, changes)
        end
      end

      owner.save_config
      return
    end

    # Parent does not provide the device, remove it
    if used_by_descendants?(device)
      raise DeviceInUse,
            'the device would be removed, but child groups or containers ' \
            'are using it'
    end

    remove(device)
  end
end

#inherit_recursive(device) ⇒ Object

Add the device to all descendants



299
300
301
302
303
304
305
306
307
308
309
# File 'lib/osctld/devices/manager.rb', line 299

def inherit_recursive(device)
  sync do
    children.each do |child|
      next if child.devices.include?(device)

      # Add from the top down
      child.devices.inherit(device)
      child.devices.inherit_recursive(device)
    end
  end
end

#inherited?(device) ⇒ Boolean

Check if device exists and is inherited

Parameters:

Returns:

  • (Boolean)


482
483
484
485
486
487
488
489
# File 'lib/osctld/devices/manager.rb', line 482

def inherited?(device)
  sync do
    dev = devices.detect { |v| v == device }
    next(false) unless dev

    dev.inherited?
  end
end

#init(**_opts) ⇒ Object

Initialize device list

Parameters:

  • opts (Hash)

    can be used by subclasses



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/osctld/devices/manager.rb', line 49

def init(**_opts)
  sync do
    if parent
      parent.devices.each do |parent_dev|
        next if !parent_dev.inherit? || include?(parent_dev)

        dev = parent_dev.clone
        dev.inherited = true
        do_add(dev)
      end
    end

    configurator.init(devices)
  end
end

#parentDevices::Owner?

Returns:

Raises:

  • (NotImplementedError)


519
520
521
# File 'lib/osctld/devices/manager.rb', line 519

def parent
  raise NotImplementedError
end

#promote(device) ⇒ Object

Promote device, i.e. remove its inherited status and save it in config

Parameters:



206
207
208
209
# File 'lib/osctld/devices/manager.rb', line 206

def promote(device)
  device.inherited = false
  owner.save_config
end

#provide(device) ⇒ Object

Add device to make it available to child groups

Parameters:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/osctld/devices/manager.rb', line 116

def provide(device)
  sync do
    dev = get(device)

    if dev
      # Check if we have compatible modes
      return if dev.mode.compatible?(device.mode)

      # Broader access mode is required
      dev.mode.complement(device.mode)

      # Propagate the mode change to parent groups
      parent.devices.provide(device) if parent

      # Apply cgroup
      # Since the mode was complemented, we don't have to deny existing
      # access modes, only allow new ones
      add_to_changeset
      configurator.add_device(dev)

      # Save it
      owner.save_config

      return
    end

    # Device does not exist, ask the parent to provide it and create it
    parent.devices.provide(device) if parent

    dev = device.clone
    dev.inherit = false
    do_add(dev)
    add_to_changeset
    configurator.add_device(dev)
    owner.save_config
  end
end

#remove(device) ⇒ Object

Remove device from self

Parameters:



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/osctld/devices/manager.rb', line 156

def remove(device)
  sync do
    children.each do |child|
      child_dev = child.devices.get(device)
      next if child_dev.nil?

      child.devices.remove(child_dev)
    end

    devices.delete(device)
    owner.save_config

    add_to_changeset
    configurator.remove_device(device)
  end
end

#replace(new_devices) ⇒ Object

Replace configured devices by a new set

‘new_devices` has to contain devices that are to be promoted. Devices that were promoted but are no longer in `new_devices` will be removed. Devices that are inherited from parent groups are promoted if they’re in ‘new_devices`, otherwire they’re left alone.

Note that this method does not enforce nor manage proper parent/descendant dependencies. It is possible to add a device which is not provided by parents or to remove a device that is needed by descendants.

Parameters:



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/osctld/devices/manager.rb', line 355

def replace(new_devices)
  sync do
    to_add = []
    to_inherit = []
    to_promote = []
    to_chmod = []

    # Find devices to promote, chmod and remove/inherit
    devices.each do |cur_dev|
      found = new_devices.detect { |new_dev| new_dev == cur_dev }

      if found.nil?
        to_inherit << cur_dev unless cur_dev.inherited?

      elsif found.inherited?
        if found.mode == cur_dev.mode
          to_promote << cur_dev
        else
          to_chmod << [cur_dev, found.mode]
        end

      elsif found.mode != cur_dev.mode
        to_chmod << [cur_dev, found.mode]
      end
    end

    # Find devices to add
    new_devices.each do |new_dev|
      found = devices.detect { |cur_dev| cur_dev == new_dev }
      to_add << new_dev if found.nil?
    end

    # Apply changes
    to_add.each { |dev| add(dev) }
    to_promote.each { |dev| promote(dev) }
    to_chmod.each do |dev, mode|
      chmod(dev, mode, promote: true, descendants: true)
    end
    to_inherit.each { |dev| inherit_promoted(dev) }

    apply(descendants: true)
  end
end

#select {|| ... } ⇒ Object

Yield Parameters:



514
515
516
# File 'lib/osctld/devices/manager.rb', line 514

def select(&block)
  sync { devices.select(&block) }
end

#set_inherit(device) ⇒ Object

Mark device as inheritable

Parameters:



278
279
280
281
282
283
284
# File 'lib/osctld/devices/manager.rb', line 278

def set_inherit(device)
  sync do
    device.inherit = true
    inherit_recursive(device)
    owner.save_config
  end
end

#syncObject (protected)



614
615
616
# File 'lib/osctld/devices/manager.rb', line 614

def sync(&)
  Devices::Lock.sync(owner.pool, &)
end

#uninherit_recursive(device) ⇒ Object

Remove the device from all descendats



312
313
314
315
316
317
318
319
320
321
322
# File 'lib/osctld/devices/manager.rb', line 312

def uninherit_recursive(device)
  sync do
    children.each do |child|
      next if child.devices.used?(device)

      # Remove from the bottom up
      child.devices.uninherit_recursive(device)
      child.devices.remove(device)
    end
  end
end

#unset_inherit(device) ⇒ Object

Remove inheritable mark

Parameters:



288
289
290
291
292
293
294
295
296
# File 'lib/osctld/devices/manager.rb', line 288

def unset_inherit(device)
  sync do
    check_unset_inherit!(device)

    device.inherit = false
    uninherit_recursive(device)
    owner.save_config
  end
end

#update_inherited_mode(device, mode, changes) ⇒ Object

Called when the access mode of the device in the parent group changes

This method should update the mode and pass the information to its own descendants.

Parameters:



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/osctld/devices/manager.rb', line 259

def update_inherited_mode(device, mode, changes)
  sync do
    device.mode = mode
    owner.save_config

    add_to_changeset
    configurator.apply_changes(changes)

    children.each do |child|
      child_dev = child.devices.get(device)
      next unless child_dev.inherited?

      child.devices.update_inherited_mode(child_dev, mode.clone, changes)
    end
  end
end

#used?(device) ⇒ Boolean

Check if device exists and is used, not just inherited

Parameters:

Returns:

  • (Boolean)


494
495
496
497
498
499
500
501
# File 'lib/osctld/devices/manager.rb', line 494

def used?(device)
  sync do
    dev = devices.detect { |v| v == device }
    next(false) unless dev

    !dev.inherited?
  end
end

#used_by_descendants?(device) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


434
435
436
437
438
439
440
441
442
443
444
# File 'lib/osctld/devices/manager.rb', line 434

def used_by_descendants?(device)
  sync do
    children.each do |child|
      if child.devices.used?(device) || child.devices.used_by_descendants?(device)
        return true
      end
    end
  end

  false
end