class TestLab

TestLab - A framework for building lightweight virtual infrastructure using LXC

The core concept with the TestLab is the Labfile. This file dictates the topology of your virtual infrastructure. With simple commands you can build and demolish this infrastructure on the fly for all sorts of purposes from automating infrastructure testing to testing new software to experimenting in general where you want to spin up alot of servers but do not want the overhead of virtualization. At it's core TestLab uses Linux Containers (LXC) to accomplish this.

@example Sample Labfile:

node 'vagrant' do

  provider      TestLab::Provider::Vagrant
  provisioners  [
    TestLab::Provisioner::Raring,
    TestLab::Provisioner::Bind
  ]

  config      ({
    :vagrant => {
      :id       => "chef-rubygem-#{TestLab.hostname}".downcase,
      :cpus     => ZTK::Parallel::MAX_FORKS.div(2),                    # use half of the available processors
      :memory   => ZTK::Parallel::MAX_MEMORY.div(3).div(1024 * 1024),  # use a third of available RAM
      :box      => 'raring64',
      :box_url  => 'https://dl.dropboxusercontent.com/u/22904185/boxes/raring64.box',
      :file     => File.dirname(__FILE__)
    },
    :bind => {
      :domain => "default.zone"
    }
  })

  network 'labnet' do
    provisioners  [TestLab::Provisioner::Route]
    address       '10.10.0.1/16'
    bridge        :br0
  end

  container "chef-server" do
    distro        "ubuntu"
    release       "precise"

    provisioners   [
      TestLab::Provisioner::Resolv,
      TestLab::Provisioner::AptCacherNG,
      TestLab::Provisioner::Apt,
      TestLab::Provisioner::Chef::RubyGemServer
    ]

    user 'deployer' do
      password         'deployer'
      identity         File.join(ENV['HOME'], '.ssh', 'id_rsa')
      public_identity  File.join(ENV['HOME'], '.ssh', 'id_rsa.pub')
      uid              2600
      gid              2600
    end

    interface do
      network_id  'labnet'
      name        :eth0
      address     '10.10.0.254/16'
      mac         '00:00:5e:63:b5:9f'
    end
  end

  container "chef-client" do
    distro        "ubuntu"
    release       "precise"

    provisioners  [
      TestLab::Provisioner::Resolv,
      TestLab::Provisioner::AptCacherNG,
      TestLab::Provisioner::Apt,
      TestLab::Provisioner::Chef::RubyGemClient
    ]

    user 'deployer' do
      password         'deployer'
      identity         File.join(ENV['HOME'], '.ssh', 'id_rsa')
      public_identity  File.join(ENV['HOME'], '.ssh', 'id_rsa.pub')
      uid              2600
      gid              2600
    end

    interface do
      network_id  'labnet'
      name        :eth0
      address     '10.10.0.20/16'
      mac         '00:00:5e:b7:e5:15'
    end
  end

end

@example TestLab can be instantiated easily:

log_file = File.join(Dir.pwd, "testlab.log")
logger = ZTK::Logger.new(log_file)
ui = ZTK::UI.new(:logger => logger)
testlab = TestLab.new(:ui => ui)

@example We can control things via code easily as well:

testlab.create   # creates the lab
testlab.up       # ensures the lab is up and running
testlab.build    # build the lab, creating all networks and containers
testlab.demolish # demolish the lab, destroy all networks and containers

@author Zachary Patten <zachary AT jovelabs DOT com>

Constants

VERSION

TestLab Gem Version

Attributes

config_dir[RW]
labfile_path[RW]
repo_dir[RW]

Public Class Methods

new(options={}) click to toggle source
# File lib/testlab.rb, line 147
def initialize(options={})
  self.ui        = (options[:ui] || ZTK::UI.new)
  self.class.ui  = self.ui

  _labfile_path  = (options[:labfile_path] || ENV['LABFILE'] || 'Labfile')
  @labfile_path  = ZTK::Locator.find(_labfile_path)

  @repo_dir      = (options[:repo_dir] || File.dirname(@labfile_path))

  @config_dir    = (options[:config_dir] || File.join(@repo_dir, ".testlab-#{TestLab.hostname}"))
  File.exists?(@config_dir) or FileUtils.mkdir_p(@config_dir)
end

Public Instance Methods

alive?() click to toggle source

Test Lab Alive?

Are all of our nodes up and running?

@return [Boolean] True if all nodes are running; false otherwise.

# File lib/testlab.rb, line 252
def alive?
  nodes.map(&:state).all?{ |state| state == :running }
end
boot() click to toggle source

Boot TestLab

Change to the defined repository directory and load the Labfile.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 165
def boot
  TestLab::Utility.log_header(self).each { |line| self.ui.logger.info { line } }

  # Raise how many files we can have open to the hard limit.
  nofile_cur, nofile_max = Process.getrlimit(Process::RLIMIT_NOFILE)
  if nofile_cur != nofile_max

    # OSX likes to indicate we can set the infinity value here.
    #
    # Doing so causes the following exception to throw:
    #   Errno::EINVAL: Invalid argument - setrlimit
    #
    # In the event infinity is returned as the max value, use 4096 as the max
    # value.
    if (nofile_max == Process::RLIM_INFINITY)
      nofile_max = 4096
    end

    # Attempt to increment the number of open files we can have.  Now we just
    # rescue this, because OSX doesn't seem to like us doing this in general.
    if (nofile_max > nofile_cur)
      begin
        self.ui.logger.info { "Attempting to change maximum open file descriptors from #{nofile_cur.inspect} to #{nofile_max.inspect}" }
        Process.setrlimit(Process::RLIMIT_NOFILE, nofile_max)
      rescue Exception => e
        self.ui.logger.warn { "Failed to change maximum open file descriptors from #{nofile_cur.inspect} to #{nofile_max.inspect}!" }
      end
    end
  end

  @labfile         = TestLab::Labfile.load(labfile_path)
  @labfile.testlab = self

  Dir.chdir(@repo_dir)
end
bounce() click to toggle source

Test Lab Bounce

Attempts to bounce our lab topology. This calls various methods on all of our nodes, networks and containers.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 367
def bounce
  self.down
  self.up

  true
end
build(force=false) click to toggle source

Test Lab Build

Attempts to build our lab topology. This calls various methods on all of our nodes, networks and containers.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 343
def build(force=false)
  method_proxy(:build, force)

  true
end
config() click to toggle source

Test Lab Configuration

The hash defined in our Labfile DSL object which represents any high-level lab configuration options.

@return [Hash] A hash representing the labs configuration options.

# File lib/testlab.rb, line 243
def config
  @labfile.config
end
containers() click to toggle source

Test Lab Containers

Returns an array of our defined containers.

@return [Array<TestLab::Container>] An array of all defined containers.

# File lib/testlab.rb, line 215
def containers
  TestLab::Container.all
end
create() click to toggle source

Test Lab Create

Attempts to create our lab topology. This calls the create method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 271
def create
  method_proxy(:create)

  true
end
dead?() click to toggle source

Test Lab Dead?

Are any of our nodes not up and running?

@return [Boolean] False is all nodes are running; true otherwise.

# File lib/testlab.rb, line 261
def dead?
  !alive?
end
demolish() click to toggle source

Test Lab Demolish

Attempts to demolish our lab topology. This calls various methods on all of our nodes, networks and containers.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 355
def demolish
  reverse_method_proxy(:demolish)

  true
end
deprovision() click to toggle source

Test Lab Deprovision

Attempts to tearddown our lab topology. This calls the deprovision method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 331
def deprovision
  reverse_method_proxy(:deprovision)

  true
end
destroy() click to toggle source

Test Lab Destroy

Attempts to destroy our lab topology. This calls the destroy method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 283
def destroy
  reverse_method_proxy(:destroy)

  true
end
doctor() click to toggle source

Test Lab Doctor

Attempts to analyze the lab for issues.

@return [Boolean] True if everything is OK; false otherwise.

# File lib/testlab.rb, line 392
def doctor
  results = Array.new

  if ((rlimit_nofile = Process.getrlimit(Process::RLIMIT_NOFILE)[0]) < 1024)
    @ui.stderr.puts(format_message("The maximum number of file handles is set to #{rlimit_nofile}!  Please raise it to 1024 or higher!".red.bold))
    results << false
  end

  results << nodes.all? do |node|
    node.doctor
  end

  results.flatten.compact.all?
end
down() click to toggle source

Test Lab Down

Attempts to down our lab topology. This calls the down method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 307
def down
  reverse_method_proxy(:down)

  true
end
labfile() click to toggle source

Test Lab Labfile

Returns our top-level Labfile instance.

@return [TestLab::Labfile] The top-level Labfile instance.

# File lib/testlab.rb, line 233
def labfile
  @labfile
end
method_missing(method_name, *method_args) click to toggle source

Provider Method Handler

Proxies missing provider method calls to all nodes.

@see TestLab::Provider::PROXY_METHODS

Calls superclass method
# File lib/testlab.rb, line 466
def method_missing(method_name, *method_args)
  self.ui.logger.debug { "TESTLAB METHOD MISSING: #{method_name.inspect}(#{method_args.inspect})" }

  if TestLab::Provider::PROXY_METHODS.include?(method_name)
    node_method_proxy(method_name, *method_args)
  else
    super(method_name, *method_args)
  end
end
method_proxy(method_name, *method_args) click to toggle source

Method Proxy

Iterates all of the lab objects sending the supplied method name and arguments to each object.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 427
def method_proxy(method_name, *method_args)
  nodes.each do |node|
    node.send(method_name, *method_args)

    node.networks.each do |network|
      network.send(method_name, *method_args)
    end

    do_parallel_actions(TestLab::Container, node.containers, method_name) do |object, action, klass|
      object.send(method_name, *method_args)
    end
  end
end
networks() click to toggle source

Test Lab Networks

Returns an array of our defined networks.

@return [Array<TestLab::Network>] An array of all defined networks.

# File lib/testlab.rb, line 224
def networks
  TestLab::Network.all
end
node_method_proxy(method_name, *method_args) click to toggle source

Node Method Proxy

Iterates all of the lab nodes, sending the supplied method name and arguments to each node.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 413
def node_method_proxy(method_name, *method_args)
  nodes.each do |node|
    node.send(method_name.to_sym, *method_args)
  end

  true
end
nodes() click to toggle source

Test Lab Nodes

Returns an array of our defined nodes.

@return [Array<TestLab::Node>] An array of all defined nodes.

# File lib/testlab.rb, line 206
def nodes
  TestLab::Node.all
end
provision() click to toggle source

Test Lab Provision

Attempts to provision our lab topology. This calls the provision method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 319
def provision
  method_proxy(:provision)

  true
end
recycle(force=false) click to toggle source

Test Lab Recycle

Attempts to recycle our lab topology. This calls various methods on all of our nodes, networks and containers.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 380
def recycle(force=false)
  self.demolish
  self.build(force)

  true
end
reverse_method_proxy(method_name, *method_args) click to toggle source

Reverse Method Proxy

Iterates all of the lab objects sending the supplied method name and arguments to each object.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 447
def reverse_method_proxy(method_name, *method_args)
  nodes.reverse.each do |node|
    do_parallel_actions(TestLab::Container, node.containers.reverse, method_name, true) do |object, action, klass|
      object.send(method_name, *method_args)
    end

    node.networks.reverse.each do |network|
      network.send(method_name, *method_args)
    end

    node.send(method_name, *method_args)
  end
end
up() click to toggle source

Test Lab Up

Attempts to up our lab topology. This calls the up method on all of our nodes.

@return [Boolean] True if successful.

# File lib/testlab.rb, line 295
def up
  method_proxy(:up)

  true
end