Class: Distem::Node::Container

Inherits:
Object
  • Object
show all
Defined in:
lib/distem/node/container.rb

Overview

Class that allow to manage all container (cgroup/lxc) associated physical and virtual resources

Constant Summary

MAX_SIMULTANEOUS_ACTIONS =

The maximum simultaneous actions (start,stop)

32
MAX_SIMULTANEOUS_CONFIG =
32
SSH_KEY_PREFIX =

The prefix to set to the SSH key whick are copied on the virtual nodes

''
SSH_KEY_FILENAME =

The default file to save virtual node specific ssh key pair (see Distem::Resource::VNode::sshkey)

'identity'
@@cleanlock =

Clean only one time

Mutex.new
@@cleaned =

Was the system cleaned

false
@@filelock =

Only write on common files once at the same time

Mutex.new
@@contsem =

Max number of simultaneous action

Lib::Semaphore.new(MAX_SIMULTANEOUS_ACTIONS)
@@confsem =

Max number of simultaneous config action

Lib::Semaphore.new(MAX_SIMULTANEOUS_CONFIG)

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Container) initialize(vnode)

Create a new Container and associate it to a virtual node

Attributes

  • vnode The VNode object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/distem/node/container.rb', line 42

def initialize(vnode)
  raise unless vnode.is_a?(Resource::VNode)
  raise Lib::UninitializedResourceError, "vfilesystem/image" unless vnode.filesystem

  @vnode = vnode
  @fsforge = FileSystemForge.new(@vnode)
  raise Lib::ResourceNotFoundError, @vnode.filesystem.path if \
    !File.exist?(@vnode.filesystem.path) and
    !File.exist?(@vnode.filesystem.sharedpath)
  @cpuforge = CPUForge.new(@vnode,@vnode.host.algorithms[:cpu])
  @networkforges = {}
  @vnode.vifaces.each do |viface|
    @networkforges[viface] = NetworkForge.new(viface)
  end
  @curname = ""
  @configfile = ""
  @id = 0
  setup()
  @stopped = false
end

Instance Attribute Details

- (Object) cpuforge (readonly)

The object used to set up physical CPU limitations



32
33
34
# File 'lib/distem/node/container.rb', line 32

def cpuforge
  @cpuforge
end

- (Object) fsforge (readonly)

The object used to set up physical filesystem



34
35
36
# File 'lib/distem/node/container.rb', line 34

def fsforge
  @fsforge
end

- (Object) networkforges (readonly)

The object used to set up network limitations



36
37
38
# File 'lib/distem/node/container.rb', line 36

def networkforges
  @networkforges
end

- (Object) vnode (readonly)

The virtual node this container is set for



30
31
32
# File 'lib/distem/node/container.rb', line 30

def vnode
  @vnode
end

Class Method Details

+ (Object) clean

Clean every previously created containers (previous distem run, lxc, …)



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/distem/node/container.rb', line 128

def self.clean
  unless @@cleaned
    if (@@cleanlock.locked?)
      @@cleanlock.synchronize{}
    else
      @@cleanlock.synchronize {
        LXCWrapper::Command.clean()
      }
    end
    @@cleaned = true
  end
end

Instance Method Details

- (Object) configure(distempnode)

Congigure a virtual node (set LXC config files, …) on a physical machine



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/distem/node/container.rb', line 234

def configure(distempnode)
  @@confsem.synchronize do
    rootfspath = nil
    if @vnode.filesystem.shared
      rootfspath = @vnode.filesystem.sharedpath
    else
      rootfspath = @vnode.filesystem.path
    end

    @curname = "#{@vnode.name}-#{@id}"

    # Generate lxc configfile
    configfile = File.join(FileSystemForge::PATH_DEFAULT_CONFIGFILE, "config-#{@curname}")
    LXCWrapper::ConfigFile.generate(@vnode,configfile,distempnode)

    etcpath = File.join(rootfspath,'etc')

    # Make hostname local
    unless @vnode.filesystem.shared
      File.open(File.join(etcpath,'hosts'),'a') do |f|
        f.puts("127.0.0.1\t#{@vnode.name}")
        f.puts("::1\t#{@vnode.name}")
      end
    end

    block = Proc.new {
      # Make address local
      @vnode.vifaces.each do |viface|
        if viface.vnetwork
          File.open(File.join(etcpath,'hosts'),'a') do |f|
            f.puts("#{viface.address.address.to_s}\t#{@vnode.name}")
          end
        end
      end

      # Load config in rc.local
      filename = File.join(etcpath,'rc.local')
      cmd = '. /etc/rc.local-`hostname`'
      ret = Lib::Shell.run("grep '#{cmd}' #{filename}; true",true)
      if ret.empty?
        File.open(filename,File::WRONLY|File::TRUNC|File::CREAT, 0755) { |f|
          f.puts("#!/bin/sh -ex\n")
          f.puts(cmd)
          f.puts("exit 0")
        }
      end
      # Make the file executable even if it was already existing. rc.local is 644 by default.
      FileUtils.chmod(0755, filename)
    }

    if @vnode.filesystem.shared
      @@filelock.synchronize { block.call }
    else
      block.call
    end
    # Node specific rc.local
    filename = File.join(etcpath,"rc.local-#{@vnode.name}")
    File.open(filename,File::WRONLY|File::TRUNC|File::CREAT, 0755) { |f|
      f.puts("#!/bin/sh -ex\n")
      f.puts("echo 1 > /proc/sys/net/ipv4/ip_forward") if @vnode.gateway?
      @vnode.vifaces.each do |viface|
        if viface.vnetwork
          addr = viface.address
          f.puts("ifconfig #{viface.name} #{addr.address.to_s} netmask #{addr.netmask.to_s} broadcast #{addr.broadcast.to_s}")
          f.puts("ip route flush dev #{viface.name}")
          f.puts("ip route add #{viface.vnetwork.address.to_string} dev #{viface.name}")
          #compute all routes
          viface.vnetwork.vroutes.each_value do |vroute|
            f.puts("ip route add #{vroute.dstnet.address.to_string} via #{vroute.gw.address.to_s} dev #{viface.name}") unless vroute.gw.address.to_s == viface.address.to_s
          end
        end
        f.puts("#iptables -t nat -A POSTROUTING -o #{viface.name} -j MASQUERADE") if vnode.gateway?
      end
      f.puts("exit 0")
    }
    LXCWrapper::Command.create(@vnode.name,configfile)

    @id += 1
  end
end

- (Object) destroy

Remove and shutdown the virtual node, remove it's filesystem, …



198
199
200
201
# File 'lib/distem/node/container.rb', line 198

def destroy
  stop() if !@stopped
  remove()
end

- (Object) freeze



203
204
205
# File 'lib/distem/node/container.rb', line 203

def freeze
  LXCWrapper::Command.freeze(@vnode.name)
end

- (Object) reconfigure

Update and reconfigure a virtual node (if the was some changes in the virtual resources description)



212
213
214
215
# File 'lib/distem/node/container.rb', line 212

def reconfigure
    @cpuforge.apply
    @networkforges.each_value { |netforge| netforge.apply }
end

- (Object) remove

Stop and Remove every physical resources that should be associated to the virtual node associated with this container (cgroups,lxc,…)



187
188
189
190
191
192
193
194
195
# File 'lib/distem/node/container.rb', line 187

def remove
  LXCWrapper::Command.destroy(@vnode.name,true)
  if !@vnode.filesystem.shared && @vnode.filesystem.cow
    # The subvolume deletion is performed automatically by LXC in the Jessie version.
    if File.exist?(@vnode.filesystem.path)
      Lib::Shell.run("btrfs subvolume delete #{@vnode.filesystem.path}")
    end
  end
end

- (Object) set_global_arptable(data, file)



225
226
227
228
229
230
231
# File 'lib/distem/node/container.rb', line 225

def set_global_arptable(data, file)
  rootfspath = @vnode.filesystem.shared ? @vnode.filesystem.sharedpath : @vnode.filesystem.path
  path = File.join(rootfspath, file)
  File.open(path,'w') {|f|
    f.write(data + "\n")
  }
end

- (Object) set_global_etchosts(data)



217
218
219
220
221
222
223
# File 'lib/distem/node/container.rb', line 217

def set_global_etchosts(data)
  rootfspath = @vnode.filesystem.shared ? @vnode.filesystem.sharedpath : @vnode.filesystem.path
  etcpath = File.join(rootfspath,'etc')
  File.open(File.join(etcpath,'hosts'),'w') {|f|
    f.write(data + "\n")
  }
end

- (Object) setup

Setup the virtual node container (copy ssh keys, …)



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/distem/node/container.rb', line 65

def setup()
  rootfspath = nil
  if @vnode.filesystem.shared
    rootfspath = @vnode.filesystem.sharedpath
  else
    rootfspath = @vnode.filesystem.path
  end
  sshpath = File.join(rootfspath,'root','.ssh')

  # Creating SSH directory
  unless File.exist?(sshpath)
    Lib::Shell.run("mkdir -p #{sshpath}")
  end

  # Copying every private keys if not already existing
  Daemon::Admin.ssh_keys_priv.each do |keyfile|
    keypath=File.join(sshpath,"#{SSH_KEY_PREFIX}#{File.basename(keyfile)}")
    Lib::Shell.run("cp #{keyfile} #{keypath}") unless File.exist?(keypath)
  end
  File.open(File.join(sshpath,SSH_KEY_FILENAME),'w') do |f|
    f.puts @vnode.sshkey['private']
  end if @vnode.sshkey and @vnode.sshkey['private']

  # Copying every public keys if not already existing
  Daemon::Admin.ssh_keys_pub.each do |keyfile|
    keypath=File.join(sshpath,"#{SSH_KEY_PREFIX}#{File.basename(keyfile)}")
    Lib::Shell.run("cp #{keyfile} #{keypath}") unless File.exist?(keypath)
  end
  File.open(File.join(sshpath,"#{SSH_KEY_FILENAME}.pub"),'w') do |f|
    f.puts @vnode.sshkey['public']
  end if @vnode.sshkey and @vnode.sshkey['public']

  # Copying authorized_keys file of the host
  hostauthfile = File.join(Daemon::Admin::PATH_SSH,'authorized_keys')
  authfile = File.join(sshpath,'authorized_keys')
  if File.exist?(authfile)
    authkeys = IO.readlines(authfile).collect{|v| v.chomp}
    hostauthkeys = IO.readlines(hostauthfile).collect{|v| v.chomp}
    hostauthkeys.each do |key|
      File.open(authfile,'a') { |f| f.puts key } unless \
        authkeys.include?(key)
    end
  else
    Lib::Shell.run("cp -f #{hostauthfile} #{authfile}") if File.exist?(hostauthfile)
  end

  # Adding public keys to SSH authorized_keys file
  pubkeys = Daemon::Admin.ssh_keys_pub.collect{ |v| IO.read(v).chomp }
  pubkeys << @vnode.sshkey['public'] if @vnode.sshkey and @vnode.sshkey['public']
  if File.exist?(authfile)
    authkeys = IO.readlines(authfile).collect{|v| v.chomp} unless authkeys
    pubkeys.each do |key|
      File.open(authfile,'a') { |f| f.puts key } unless \
        authkeys.include?(key)
    end
  else
    pubkeys.each do |key|
      File.open(authfile,'a') { |f| f.puts key }
    end
  end
end

- (Object) start

Start all the resources associated to a virtual node (Run the virtual node)



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/distem/node/container.rb', line 142

def start
  raise @vnode.name if @vnode.status == Resource::Status::RUNNING
  @@contsem.synchronize do
    LXCWrapper::Command.start(@vnode.name)
    @vnode.vifaces.each do |viface|
      Lib::Shell::run("ethtool -K #{Lib::NetTools.get_iface_name(@vnode,viface)} gso off || true")
      Lib::Shell::run("ethtool -K #{Lib::NetTools.get_iface_name(@vnode,viface)} tso off || true")
    end
    @cpuforge.apply
    @networkforges.each_value { |netforge| netforge.apply }
  end
  if @vnode.filesystem.disk_throttling
    # On jessie, this does not return a correct value ...
    #major = File.stat(@vnode.filesystem.disk_throttling['device']).dev_major
    major = `stat --printf %t #{@vnode.filesystem.disk_throttling['device']}`
    cgroup_path = File.join(Distem::Node::Admin::PATH_CGROUP, 'lxc', @vnode.name)
    # In the description, read and write limits are supposed to be specified in bytes
    if @vnode.filesystem.disk_throttling['read_limit']
      limit = @vnode.filesystem.disk_throttling['read_limit'].to_i
      Lib::Shell::run("echo \"#{major}:0 #{limit}\" > #{cgroup_path}/blkio.throttle.read_bps_device")
    end
    if @vnode.filesystem.disk_throttling['write_limit']
      limit = @vnode.filesystem.disk_throttling['write_limit'].to_i
      Lib::Shell::run("echo \"#{major}:0 #{limit}\" > #{cgroup_path}/blkio.throttle.write_bps_device")
    end
  end
  @stopped = false
end

- (Object) stop

Stop all the resources associated to a virtual node (Shutdown the virtual node)



172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/distem/node/container.rb', line 172

def stop
  if @vnode.filesystem.disk_throttling
    cgroup_path = File.join(Distem::Node::Admin::PATH_CGROUP, 'lxc', @vnode.name)
    Lib::Shell::run("echo -n > #{cgroup_path}/blkio.throttle.read_bps_device") if @vnode.filesystem.disk_throttling['read_limit']
    Lib::Shell::run("echo -n > #{cgroup_path}/blkio.throttle.write_bps_device") if @vnode.filesystem.disk_throttling['write_limit']
  end
  @@contsem.synchronize do
    @cpuforge.undo
    @networkforges.each_value { |netforge| netforge.undo }
    LXCWrapper::Command.stop(@vnode.name)
  end
  @stopped = true
end

- (Object) unfreeze



207
208
209
# File 'lib/distem/node/container.rb', line 207

def unfreeze
  LXCWrapper::Command.unfreeze(@vnode.name)
end