Class: OsCtld::AutoStart::Plan

Inherits:
Object
  • Object
show all
Includes:
OsCtl::Lib::Utils::Log
Defined in:
lib/osctld/auto_start/plan.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pool) ⇒ Plan

Returns a new instance of Plan.



10
11
12
13
14
15
16
17
# File 'lib/osctld/auto_start/plan.rb', line 10

def initialize(pool)
  @pool = pool
  @plan = ContinuousExecutor.new(pool.parallel_start)
  @state = AutoStart::State.load(pool)
  @reboot = AutoStart::Reboot.load(pool)
  @stop = false
  @nproc = Etc.nprocessors
end

Instance Attribute Details

#planObject (readonly, protected)

Returns the value of attribute plan.



208
209
210
# File 'lib/osctld/auto_start/plan.rb', line 208

def plan
  @plan
end

#poolObject (readonly)

Returns the value of attribute pool.



8
9
10
# File 'lib/osctld/auto_start/plan.rb', line 8

def pool
  @pool
end

#rebootObject (readonly, protected)

Returns the value of attribute reboot.



208
209
210
# File 'lib/osctld/auto_start/plan.rb', line 208

def reboot
  @reboot
end

#stateObject (readonly, protected)

Returns the value of attribute state.



208
209
210
# File 'lib/osctld/auto_start/plan.rb', line 208

def state
  @state
end

Instance Method Details

#assets(add) ⇒ Object



19
20
21
22
# File 'lib/osctld/auto_start/plan.rb', line 19

def assets(add)
  state.assets(add)
  reboot.assets(add)
end

#clearObject



181
182
183
# File 'lib/osctld/auto_start/plan.rb', line 181

def clear
  plan.clear
end

#clear_ct(ct) ⇒ Object



176
177
178
179
# File 'lib/osctld/auto_start/plan.rb', line 176

def clear_ct(ct)
  state.clear(ct)
  reboot.clear(ct)
end

#delay_after_start?Boolean (protected)

Returns:

  • (Boolean)


256
257
258
259
# File 'lib/osctld/auto_start/plan.rb', line 256

def delay_after_start?
  lavg = OsCtl::Lib::LoadAvg.new
  lavg.avg[1] >= @nproc
end

#do_try_start_ct(ct, attempts: 5, cooldown: 5, start_opts: {}) ⇒ Object (protected)



210
211
212
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
# File 'lib/osctld/auto_start/plan.rb', line 210

def do_try_start_ct(ct, attempts: 5, cooldown: 5, start_opts: {})
  attempts.times do |i|
    break if stop?

    ret = Commands::Container::Start.run(**start_opts.merge(
      pool: ct.pool.name,
      id: ct.id,
      wait: 'infinity'
    ))

    if ret[:status]
      state.set_started(ct)
      break if stop?

      if delay_after_start?
        log(:info, ct, "Autostart delay for #{ct.autostart.delay} seconds")
        sleep(ct.autostart.delay)
      else
        log(:info, ct, 'Skipping autostart delay thanks to low system load average')
      end

      break
    end

    if i + 1 == attempts
      log(:warn, ct, 'All attempts to start the container have failed')
      break
    end

    if stop?
      log(:warn, ct, 'Unable to start the container, giving up to stop')
      break
    else
      pause = cooldown + (i * cooldown)
      log(:warn, ct, "Unable to start the container, retrying in #{pause} seconds")
      sleep(pause)
    end
  end
end

#enqueue(ct, priority: 10, start_opts: {}) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/osctld/auto_start/plan.rb', line 127

def enqueue(ct, priority: 10, start_opts: {})
  plan << (
    ContinuousExecutor::Command.new(id: ct.id, priority:) do |cmd|
      cur_ct = DB::Containers.find(cmd.id, pool)
      next if cur_ct.nil? || cur_ct.running?

      prestart_delay(cur_ct)
      log(:info, ct, 'Starting enqueued container')
      do_try_start_ct(
        cur_ct,
        start_opts: start_opts.merge(queue: false)
      )
    end
  )
end

#fulfil_reboot(ct) ⇒ Object



168
169
170
# File 'lib/osctld/auto_start/plan.rb', line 168

def fulfil_reboot(ct)
  reboot.clear(ct)
end

#log_typeObject



202
203
204
# File 'lib/osctld/auto_start/plan.rb', line 202

def log_type
  "#{pool.name}:auto-start"
end

#prestart_delay(ct) ⇒ Object (protected)



250
251
252
253
254
# File 'lib/osctld/auto_start/plan.rb', line 250

def prestart_delay(ct)
  delay = rand(0.0..3.0)
  log(:info, ct, "Delaying auto-start by #{delay.round(2)}s")
  sleep(delay)
end

#queueObject



198
199
200
# File 'lib/osctld/auto_start/plan.rb', line 198

def queue
  plan.queue
end

#request_reboot(ct) ⇒ Object



164
165
166
# File 'lib/osctld/auto_start/plan.rb', line 164

def request_reboot(ct)
  reboot.add(ct)
end

#resize(new_size) ⇒ Object



185
186
187
# File 'lib/osctld/auto_start/plan.rb', line 185

def resize(new_size)
  plan.resize(new_size)
end

#start(force: false) ⇒ Object



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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/osctld/auto_start/plan.rb', line 24

def start(force: false)
  @stop = false

  log(
    :info,
    "Auto-starting containers, #{pool.parallel_start} containers at a time"
  )

  # Select containers for autostart
  cts = DB::Containers.get.select do |ct|
    next(false) if ct.pool != pool
    next(true) if reboot.include?(ct) && ct.can_start?
    next(true) if ct.autostart && ct.can_start? && (force || !state.is_started?(ct))

    next(false)
  end

  log(:info, "#{cts.size} containers to start")

  # Preschedule the containers
  if CpuScheduler.use?
    cts.reject(&:running?).sort do |a, b|
      b.hints.cpu_daily.usage_us <=> a.hints.cpu_daily.usage_us
    end.each do |ct|
      CpuScheduler.preschedule_ct(ct)
    end
  end

  # Start the containers
  #
  # If the CPU scheduler is in use, we want to start containers from the
  # first CPU package first. {CpuScheduler.preschedule_ct} already put
  # prioritized containers to the first package.
  start_cts =
    if CpuScheduler.use_sequential_start_stop?
      log(:info, 'Using sequential auto-start')

      cts.sort do |a, b|
        a_package = CpuScheduler.get_preschedule_package_id(a)
        b_package = CpuScheduler.get_preschedule_package_id(b)

        # Start containers with a CPU package first
        if a_package && !b_package
          -1
        elsif !a_package && b_package
          1

        # Neither container has CPU package, sort by start priority
        elsif !a_package && !b_package
          a.autostart <=> b.autostart

        # Same CPU package, sort by start priority
        elsif a_package == b_package # rubocop:disable Lint/DuplicateBranch
          a.autostart <=> b.autostart

        # Sort by CPU package, lower package first
        else
          a_package <=> b_package
        end
      end.each_with_index
    else
      log(:info, 'Using priority auto-start')
      cts
    end

  debug = Daemon.get.config.debug?

  if debug
    counter = 1 # we cannot use i, because it is used only for sequential auto-start
    total = start_cts.size
  end

  plan << (start_cts.map do |ct, i|
    if debug
      log(
        :debug,
        "[#{counter.to_s.rjust(4)}/#{total}] #{ct.id} priority=#{ct.autostart.priority} cpu-package=#{CpuScheduler.get_preschedule_package_id(ct) || '-'}"
      )
      counter += 1
    end

    ContinuousExecutor::Command.new(
      id: ct.id,
      priority: i || ct.autostart.priority
    ) do |cmd|
      cur_ct = DB::Containers.find(cmd.id, pool)

      if cur_ct.nil? || !cur_ct.can_start?
        CpuScheduler.cancel_preschedule_ct(ct)
        next
      elsif cur_ct.running?
        CpuScheduler.cancel_preschedule_ct(ct)
        state.set_started(cur_ct)
        next
      end

      prestart_delay(cur_ct)
      log(:info, cur_ct, 'Auto-starting container')
      do_try_start_ct(cur_ct)
    end
  end)
end

#start_ct(ct, priority: 10, start_opts: {}, client_handler: nil) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/osctld/auto_start/plan.rb', line 143

def start_ct(ct, priority: 10, start_opts: {}, client_handler: nil)
  plan.execute(
    ContinuousExecutor::Command.new(id: ct.id, priority:) do |cmd|
      cur_ct = DB::Containers.find(cmd.id, pool)
      next if cur_ct.nil? || cur_ct.running?

      prestart_delay(cur_ct)
      log(:info, ct, 'Starting enqueued container')
      Commands::Container::Start.run(
        **start_opts.merge(
          pool: cur_ct.pool.name,
          id: cur_ct.id,
          queue: false,
          internal: { handler: client_handler }
        )
      )
    end,
    timeout: start_opts ? (start_opts[:wait] || Container::DEFAULT_START_TIMEOUT) : nil
  )
end

#started?Boolean

Returns:

  • (Boolean)


194
195
196
# File 'lib/osctld/auto_start/plan.rb', line 194

def started?
  !@stop
end

#stopObject



189
190
191
192
# File 'lib/osctld/auto_start/plan.rb', line 189

def stop
  @stop = true
  plan.stop
end

#stop?Boolean (protected)

Returns:

  • (Boolean)


261
262
263
# File 'lib/osctld/auto_start/plan.rb', line 261

def stop?
  @stop
end

#stop_ct(ct) ⇒ Object



172
173
174
# File 'lib/osctld/auto_start/plan.rb', line 172

def stop_ct(ct)
  plan.remove(ct.id)
end