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.



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

def configurator
  @configurator
end

#devicesObject (readonly, protected)

Returns the value of attribute devices.



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

def devices
  @devices
end

#ownerObject (readonly, protected)

Returns the value of attribute owner.



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

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
    fail "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, **kwargs) ⇒ Devices::Manager

Parameters:

Returns:



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

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

Instance Method Details

#add(device) ⇒ Object

Add new device and ensure that parent groups provide it

Parameters:



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

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, **opts) ⇒ Object

Add new device

Parameters:

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

Options Hash (**opts):

  • :name (String)
  • :inherit (Boolean)

    should the child groups/containers inherit this device?

  • :inherited (Boolean)

    was this device inherited from the parent group?



78
79
80
# File 'lib/osctld/devices/manager.rb', line 78

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

#add_to_changesetObject (protected)



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

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)


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

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

    configurator.reconfigure(devices)

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

#assets(add) ⇒ Object

Parameters:



45
46
47
# 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:



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

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)



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

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



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

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.



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

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|
      if child.devices.used?(device)
        raise DeviceInheritNeeded, child
      else
        check_unset_inherit!(device, check_owner: child)
      end
    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)


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

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)



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

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 { |dev| dev.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 { |dev| dev.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 { |dev| dev.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:



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

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:



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

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)


477
478
479
# File 'lib/osctld/devices/manager.rb', line 477

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

#inherit(device, **opts) ⇒ Object

Inherit device from a parent

Parameters:



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

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:



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
250
251
# File 'lib/osctld/devices/manager.rb', line 215

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



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

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)


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

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



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

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:



208
209
210
211
# File 'lib/osctld/devices/manager.rb', line 208

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

#provide(device) ⇒ Object

Add device to make it available to child groups

Parameters:



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
153
154
# File 'lib/osctld/devices/manager.rb', line 118

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:



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

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:



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
398
399
# File 'lib/osctld/devices/manager.rb', line 357

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:



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

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

#sync(&block) ⇒ Object (protected)



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

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

#uninherit_recursive(device) ⇒ Object

Remove the device from all descendats



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

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:



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

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:



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

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)


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

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)


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

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