Class: OsCtld::ContainerControl::Frontend

Inherits:
Object
  • Object
show all
Defined in:
lib/osctld/container_control/frontend.rb

Overview

Frontend is run from osctld in daemon mode, when it is running as root

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command_class, ct) ⇒ Frontend

Returns a new instance of Frontend.

Parameters:



12
13
14
15
# File 'lib/osctld/container_control/frontend.rb', line 12

def initialize(command_class, ct)
  @command_class = command_class
  @ct = ct
end

Instance Attribute Details

#command_classClass (readonly)

Returns:

  • (Class)


5
6
7
# File 'lib/osctld/container_control/frontend.rb', line 5

def command_class
  @command_class
end

#ctContainer (readonly)

Returns:



8
9
10
# File 'lib/osctld/container_control/frontend.rb', line 8

def ct
  @ct
end

Instance Method Details

#exec_runner(opts = {}) ⇒ ContainerControl::Result (protected)

Fork&exec to the container user and invoke the runner.

#exec_runner forks from osctld and then execs into osctld-ct-runner. The runner then switches to the container’s user and enters its cgroups. This runner is safe to use when you need to attach to the container, e.g. with LXC::Container#attach.

It is however more costly than #fork_runner as it makes the Ruby runtime to start all over again. Use #fork_runner when you don’t need to attach to the container.

Parameters:

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

Options Hash (opts):

  • :args (Array)

    command arguments

  • :kwargs (Hash)

    command arguments

  • :stdin (IO, nil)
  • :stdout (IO, nil)
  • :stderr (IO, nil)

Returns:



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
126
127
128
129
130
131
# File 'lib/osctld/container_control/frontend.rb', line 45

def exec_runner(opts = {})
  # Used to send command to the runner
  cmd_r, cmd_w = IO.pipe

  # Used to read return value
  ret_r, ret_w = IO.pipe

  # File descriptors to capture output/feed input
  stdin = opts[:stdin]
  stdout = opts.fetch(:stdout, $stdout)
  stderr = opts.fetch(:stderr, $stderr)

  # User configuration
  sysuser = ct.user.sysusername
  ugid = ct.user.ugid
  homedir = ct.user.homedir
  cgroup_path = ct.entry_cgroup_path
  prlimits = ct.prlimits.export

  # Runner configuration
  runner_opts = {
    name: command_class.name,

    pool: ct.pool.name,
    id: ct.id,
    lxc_home: ct.lxc_home,
    user_home: ct.user.homedir,
    log_file: ct.log_path,

    args: opts.fetch(:args, []),
    kwargs: opts.fetch(:kwargs, {}),

    return: ret_w.fileno,
    stdin: stdin && stdin.fileno,
    stdout: stdout.fileno,
    stderr: stderr.fileno
  }

  CGroup.mkpath_all(cgroup_path.split('/'), chown: ugid)

  pid = SwitchUser.fork(
    keep_fds: [
      cmd_r,
      ret_w,
      stdin,
      stdout,
      stderr
    ].compact
  ) do
    # Closed by SwitchUser.fork
    # cmd_w.close
    # ret_r.close

    $stdin.reopen(cmd_r)

    [cmd_r, ret_w, stdin, stdout, stderr].compact.each do |io|
      io.close_on_exec = false
    end

    SwitchUser.apply_prlimits(Process.pid, prlimits)
    SwitchUser.switch_to(sysuser, ugid, homedir, cgroup_path)
    Process.exec(::OsCtld.bin('osctld-ct-runner'))
    exit
  end

  stdin.close if stdin
  stdout.close if stdout != $stdout
  stderr.close if stderr != $stderr

  cmd_w.write(runner_opts.to_json)
  cmd_w.close

  ret_w.close

  begin
    ret = JSON.parse(ret_r.readline, symbolize_names: true)
    Process.wait(pid)
    ContainerControl::Result.from_runner(ret)
  rescue EOFError
    Process.wait(pid)
    ContainerControl::Result.new(
      false,
      message: 'user runner failed',
      user_runner: true
    )
  end
end

#execute(*args, **kwargs) ⇒ Object

Implement this method

Parameters:

  • args (Array)

    command arguments

  • kwargs (Array)

    command arguments

Raises:

  • (NotImplementedError)


20
21
22
# File 'lib/osctld/container_control/frontend.rb', line 20

def execute(*args, **kwargs)
  raise NotImplementedError
end

#fork_runner(opts = {}) ⇒ ContainerControl::Result (protected)

Fork to the container user and invoke the runner.

#fork_runner can be used only when we do not need to enter the container itself. It does not attach to its cgroups, because a forked osctld can have a large memory footprint, which we do not want to charge to the container. It can be used only to interact with LXC from the outside.

Parameters:

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

Options Hash (opts):

  • :args (Array)

    command arguments

  • :kwargs (Hash)

    command arguments

  • :stdin (IO, nil)
  • :stdout (IO, nil)
  • :stderr (IO, nil)
  • :switch_to_system (Boolean)

Returns:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
203
204
205
206
207
# File 'lib/osctld/container_control/frontend.rb', line 149

def fork_runner(opts = {})
  r, w = IO.pipe

  stdin = opts[:stdin]
  stdout = opts.fetch(:stdout, $stdout)
  stderr = opts.fetch(:stderr, $stderr)

  runner_opts = {
    id: ct.id,
    lxc_home: ct.lxc_home,
    user_home: ct.user.homedir,
    log_file: ct.log_path,
    stdin:,
    stdout:,
    stderr:
  }

  ctid = ct.ident
  args = opts.fetch(:args, [])
  kwargs = opts.fetch(:kwargs, {})
  sysuser = ct.user.sysusername
  ugid = ct.user.ugid
  homedir = ct.user.homedir

  pid = SwitchUser.fork(keep_fds: [w, stdin, stdout, stderr].compact) do
    # Closed by SwitchUser.fork
    # r.close

    Process.setproctitle(
      "osctld: #{ctid} " \
      "runner:#{command_class.name.split('::').last.downcase}"
    )

    if opts.fetch(:switch_to_system, true)
      SwitchUser.switch_to_system(sysuser, ugid, ugid, homedir)
    end

    runner = command_class::Runner.new(**runner_opts)
    ret = runner.execute(*args, **kwargs)
    w.write("#{ret.to_json}\n")

    exit
  end

  w.close

  begin
    ret = JSON.parse(r.readline, symbolize_names: true)
    Process.wait(pid)
    ContainerControl::Result.from_runner(ret)
  rescue EOFError
    Process.wait(pid)
    ContainerControl::Result.new(
      false,
      message: 'user runner failed',
      user_runner: true
    )
  end
end