Class: OsCtld::Devices::GroupManager

Inherits:
Manager
  • Object
show all
Includes:
OsCtl::Lib::Utils::Log
Defined in:
lib/osctld/devices/group_manager.rb

Instance Attribute Summary

Attributes inherited from Manager

#devices, #owner

Instance Method Summary collapse

Methods inherited from Manager

#add_new, #check_availability!, #clear, #detect, #do_add, #do_allow_dev, #do_apply_changes, #do_deny_all, #do_deny_dev, #dump, #dup, #each, #export, #find, #get, #include?, #inherit_all_from, #initialize, load, #promote, #replace, #select, #sync, #used?

Constructor Details

This class inherits a constructor from OsCtld::Devices::Manager

Instance Method Details

#add(device, parent = nil) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/osctld/devices/group_manager.rb', line 58

def add(device, parent = nil)
  sync do
    super

    if device.inherit?
      group.descendants.each { |grp| grp.devices.inherit(device) }

      group.containers.each do |ct|
        ct.devices.inherit(device)
      end
    end
  end
end

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

Apply cgroup parameters of the group and all its parents



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/osctld/devices/group_manager.rb', line 246

def apply(parents: true, descendants: false, containers: false)
  sync do
    if parents
      group.parents.each do |grp|
        grp.devices.apply(parents: false)
      end
    end

    super()

    if descendants
      group.descendants.each do |grp|
        grp.devices.apply(parents: false, descendants: false, containers: containers)
      end
    end

    if containers
      group.containers.each do |ct|
        ct.devices.apply
      end
    end
  end
end

#can_unset_inherit?(device) ⇒ Boolean (protected)

Check if the inheritance can be disabled

We have to forbid disabling the inheritance if a container has the device promoted, but its group does not. Thus, the device cannot be deleted from the group, because the container needs it. The same applies for promoted group devices.

Returns:

  • (Boolean)


342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/osctld/devices/group_manager.rb', line 342

def can_unset_inherit?(device)
  # Check my containers
  group.containers.each do |ct|
    return false if ct.devices.used?(device)
  end

  # Check all descendants and their containers
  group.descendants.each do |grp|
    # Find the group's device
    dev = grp.devices.get(device)

    # Find the device of the parent
    parent = grp.parent

    # Skip the parent group check, if the parent is actually the changed
    # group, i.e. self.
    if parent != group
      pdev = parent.devices.get(device)

      # If the parent's device is promoted, we don't have to deal with this
      # group, as it will not be affected.
      next if !pdev.inherited?

      return false if grp.devices.used?(device)
    end

    # If the group's device is promoted, its containers are safe
    next unless dev.inherited?

    grp.containers.each do |ct|
      return false if ct.devices.used?(device)
    end
  end

  true
end

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



311
312
313
314
315
316
# File 'lib/osctld/devices/group_manager.rb', line 311

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

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

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



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/osctld/devices/group_manager.rb', line 270

def check_descendants!(device, mode: nil)
  sync do
    # Check child groups and their containers
    group.descendants.each do |grp|
      check_descendant_mode!(grp, device, mode || device.mode)

      grp.containers.each do |ct|
        check_descendant_mode!(ct, device, mode || device.mode)
      end
    end

    # Check containers in self
    group.containers.each do |ct|
      check_descendant_mode!(ct, device, mode || device.mode)
    end
  end
end

#chmod(device, mode, opts = {}) ⇒ Object

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :parents (Boolean)
  • :descendants (Boolean)
  • :containers (Boolean)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/osctld/devices/group_manager.rb', line 109

def chmod(device, mode, opts = {})
  sync do
    # Parents
    if opts[:parents]
      dev = device.clone
      dev.mode = mode

      group.parent.devices.provide(dev)
    end

    # Self
    changes = super

    # Descendants
    if opts[:descendants]
      group.descendants.each do |grp|
        dev = grp.devices.get(device)
        grp.devices.chmod(dev, mode, containers: true) if dev
      end
    end

    if opts[:containers]
      group.containers.each do |ct|
        dev = ct.devices.get(device)
        ct.devices.chmod(dev, mode, group_changes: changes) if dev
      end
    end
  end
end

#do_update_inherited_descendants(device, mode, changes) ⇒ Object (protected)



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/osctld/devices/group_manager.rb', line 318

def do_update_inherited_descendants(device, mode, changes)
  # Update access modes in child groups that inherit this device
  group.children.each do |grp|
    cdev = grp.devices.get(device)
    next unless cdev.inherited?

    grp.devices.update_inherited_mode(cdev, mode.clone, changes)
  end

  # Update access modes in child containers that inherit this device
  group.containers.each do |ct|
    cdev = ct.devices.get(device)
    next unless cdev.inherited?

    ct.devices.update_inherited_mode(cdev, mode.clone, changes)
  end
end

#inherit(device, opts = {}) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/osctld/devices/group_manager.rb', line 72

def inherit(device, opts = {})
  sync do
    super

    group.containers.each do |ct|
      ct.devices.inherit(device)
    end
  end
end

#inherit_promoted(device) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/osctld/devices/group_manager.rb', line 139

def inherit_promoted(device)
  sync do
    pdev = group.parent.devices.get(device) unless group.root?

    if pdev && pdev.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 != pdev.mode
        changes = device.chmod(pdev.mode.clone)
        do_apply_changes(changes)

        # Update descendants that inherit the device as well
        do_update_inherited_descendants(device, pdev.mode, changes)
      end

      group.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_recursive(device)
  end
end

#inherit_recursive(device) ⇒ Object

Add the device to all direct child groups and containers, then let the child groups do the same



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/osctld/devices/group_manager.rb', line 204

def inherit_recursive(device)
  sync do
    # Add the device to direct children
    group.descendants.each do |grp|
      next if grp.parent != group || grp.devices.include?(device)

      # Add from the top down
      grp.devices.inherit(device)
      grp.devices.apply(parents: false)
      grp.devices.inherit_recursive(device)
    end

    # Add the device to containers
    group.containers.each do |ct|
      # grp.devices.inherit will also pass the device to all containers
      ct.devices.apply
    end
  end
end

#init(opts = {}) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/osctld/devices/group_manager.rb', line 8

def init(opts = {})
  sync do
    super
    inherit_all_from(group.parent, opts) unless group.root?

    log(:info, group, "Configuring cgroup #{group.cgroup_path} for devices")
    if CGroup.mkpath('devices', group.cgroup_path.split('/'))
      apply(parents: false)
    end
  end
end

#provide(device) ⇒ Object

Add device to make it available to child groups

Parameters:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/osctld/devices/group_manager.rb', line 22

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
      group.parent.devices.provide(device) unless group.root?

      # Apply cgroup
      # Since the mode was complemented, we don't have to deny existing
      # access modes, only allow new ones
      apply(parents: false)

      # Save it
      group.save_config

      return
    end

    # Device does not exist, ask the parent to provide it and create it
    group.parent.devices.provide(device) unless group.root?

    dev = device.clone
    dev.inherit = false
    do_add(dev)
    apply(parents: false)
    group.save_config
  end
end

#remove(device) ⇒ Object



82
83
84
85
86
87
88
89
90
# File 'lib/osctld/devices/group_manager.rb', line 82

def remove(device)
  sync do
    super

    group.containers.each do |ct|
      ct.devices.remove(device)
    end
  end
end

#remove_recursive(device) ⇒ Object

Remove device from self and all descendants

Parameters:



94
95
96
97
98
99
100
101
102
103
# File 'lib/osctld/devices/group_manager.rb', line 94

def remove_recursive(device)
  sync do
    group.descendants.reverse_each do |grp|
      dev = grp.devices.get(device)
      grp.devices.remove(dev) if dev
    end

    remove(device)
  end
end

#set_inherit(device) ⇒ Object



181
182
183
184
185
186
187
# File 'lib/osctld/devices/group_manager.rb', line 181

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

#uninherit_recursive(device) ⇒ Object

Remove the device from all direct child groups and containers, then let the child groups do the same



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/osctld/devices/group_manager.rb', line 226

def uninherit_recursive(device)
  sync do
    # Remove the device from direct children
    group.descendants.each do |grp|
      next if grp.parent != group || grp.devices.used?(device)

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

    # Remove the device from containers
    group.containers.each do |ct|
      next if ct.devices.used?(device)
      ct.devices.remove(device)
    end
  end
end

#unset_inherit(device) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/osctld/devices/group_manager.rb', line 189

def unset_inherit(device)
  sync do
    unless can_unset_inherit?(device)
      raise DeviceInUse,
            'unsetting inheritance would break device access requirements'
    end

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

#update_inherited_mode(device, mode, changes) ⇒ Object



171
172
173
174
175
176
177
178
179
# File 'lib/osctld/devices/group_manager.rb', line 171

def update_inherited_mode(device, mode, changes)
  sync do
    # Update self
    super

    # Update descendants that inherit the device as well
    do_update_inherited_descendants(device, mode, changes)
  end
end

#used_by_descendants?(device) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/osctld/devices/group_manager.rb', line 290

def used_by_descendants?(device)
  sync do
    group.descendants.each do |grp|
      return true if grp.devices.used?(device)

      grp.containers.each do |ct|
        return true if ct.devices.used?(device)
      end
    end

    group.containers.each do |ct|
      return true if ct.devices.used?(device)
    end

    false
  end
end