Class: ChefMetalFog::FogDriver

Inherits:
ChefMetal::Driver
  • Object
show all
Includes:
Chef::Mixin::ShellOut
Defined in:
lib/chef_metal_fog/fog_driver.rb

Overview

Provisions cloud machines with the Fog driver.

## Fog Driver URLs

All Metal drivers use URLs to uniquely identify a driver’s “bucket” of machines. Fog URLs are of the form fog:<provider>:<identifier:> - see individual providers for sample URLs.

Identifier is generally something uniquely identifying the account. If multiple users can access the account, the identifier should be the same for all of them (do not use the username in these cases, use an account ID or auth server URL).

In particular, the identifier should be specific enough that if you create a server with a driver with this URL, the server should be retrievable from the same URL *no matter what else changes*. For example, an AWS account ID is not enough for this–if you varied the region, you would no longer see your server in the list. Thus, AWS uses both the account ID and the region.

## Supporting a new Fog provider

The Fog driver does not immediately support all Fog providers out of the box. Some minor work needs to be done to plug them into metal.

To add a new supported Fog provider, pick an appropriate identifier, go to from_provider and compute_options_for, and add the new provider in the case statements so that URLs for your fog provider can be generated. If your cloud provider has environment variables or standard config files (like ~/.aws/config), you can read those and merge that information in the compute_options_for function.

## Location format

All machines have a location hash to find them. These are the keys used by the fog provisioner:

  • driver_url: fog:<driver>:<unique_account_info>

  • server_id: the ID of the server so it can be found again

  • created_at: timestamp server was created

  • started_at: timestamp server was last started

  • is_windows, ssh_username, sudo, use_private_ip_for_ssh: copied from machine_options

## Machine options

Machine options (for allocation and readying the machine) include:

  • bootstrap_options: hash of options to pass to compute.servers.create

  • is_windows: true if windows. TODO detect this from ami?

  • create_timeout: the time to wait for the instance to boot to ssh (defaults to 600)

  • start_timeout: the time to wait for the instance to start (defaults to 600)

  • ssh_timeout: the time to wait for ssh to be available if the instance is detected as up (defaults to 20)

  • ssh_username: username to use for ssh

  • sudo: true to prefix all commands with “sudo”

  • use_private_ip_for_ssh: hint to use private ip when available

  • convergence_options: hash of options for the convergence strategy

    • chef_client_timeout: the time to wait for chef-client to finish

    • chef_server - the chef server to point convergence at

Example bootstrap_options for ec2:

:bootstrap_options => {
  :image_id =>'ami-311f2b45',
  :flavor_id =>'t1.micro',
  :key_name => 'key-pair-name'
}

Constant Summary collapse

DEFAULT_OPTIONS =
{
  :create_timeout => 180,
  :start_timeout => 180,
  :ssh_timeout => 20
}
@@registered_provider_classes =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver_url, config) ⇒ FogDriver

Create a new fog driver.

## Parameters driver_url - URL of driver. “fog:<provider>:<provider_id>” config - configuration. :driver_options, :keys, :key_paths and :log_level are used.

driver_options is a hash with these possible options:
- compute_options: the hash of options to Fog::Compute.new.
- aws_config_file: aws config file (default: ~/.aws/config)
- aws_csv_file: aws csv credentials file downloaded from EC2 interface
- aws_profile: profile name to use for credentials
- aws_credentials: AWSCredentials object. (will be created for you by default)
- log_level: :debug, :info, :warn, :error


156
157
158
# File 'lib/chef_metal_fog/fog_driver.rb', line 156

def initialize(driver_url, config)
  super(driver_url, config)
end

Class Method Details

.__new__Object



98
# File 'lib/chef_metal_fog/fog_driver.rb', line 98

alias :__new__ :new

.canonicalize_url(driver_url, config) ⇒ Object



127
128
129
130
131
# File 'lib/chef_metal_fog/fog_driver.rb', line 127

def self.canonicalize_url(driver_url, config)
  _, provider, id = driver_url.split(':', 3)
  config, id = provider_class_for(provider).compute_options_for(provider, id, config)
  [ "fog:#{provider}:#{id}", config ]
end

.from_provider(provider, config) ⇒ Object

Passed in a config which is not merged with driver_url (because we don’t know what it is yet) but which has the same keys



135
136
137
138
139
140
141
142
# File 'lib/chef_metal_fog/fog_driver.rb', line 135

def self.from_provider(provider, config)
  # Figure out the options and merge them into the config
  config, id = provider_class_for(provider).compute_options_for(provider, nil, config)

  driver_url = "fog:#{provider}:#{id}"

  ChefMetal.driver_for_url(driver_url, config)
end

.from_url(driver_url, config) ⇒ Object

Passed in a driver_url, and a config in the format of Driver.config.



123
124
125
# File 'lib/chef_metal_fog/fog_driver.rb', line 123

def self.from_url(driver_url, config)
  FogDriver.new(driver_url, config)
end

.inherited(klass) ⇒ Object



100
101
102
103
104
# File 'lib/chef_metal_fog/fog_driver.rb', line 100

def inherited(klass)
  class << klass
    alias :new :__new__
  end
end

.new(driver_url, config) ⇒ Object



117
118
119
120
# File 'lib/chef_metal_fog/fog_driver.rb', line 117

def self.new(driver_url, config)
  provider = driver_url.split(':')[1]
  provider_class_for(provider).new(driver_url, config)
end

.provider_class_for(provider) ⇒ Object



112
113
114
115
# File 'lib/chef_metal_fog/fog_driver.rb', line 112

def self.provider_class_for(provider)
  require "chef_metal_fog/providers/#{provider.downcase}"
  @@registered_provider_classes[provider]
end

.register_provider_class(name, driver) ⇒ Object



108
109
110
# File 'lib/chef_metal_fog/fog_driver.rb', line 108

def self.register_provider_class(name, driver)
  @@registered_provider_classes[name] = driver
end

Instance Method Details

#allocate_machine(action_handler, machine_spec, machine_options) ⇒ Object

Acquire a machine, generally by provisioning it. Returns a Machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory.



171
172
173
174
# File 'lib/chef_metal_fog/fog_driver.rb', line 171

def allocate_machine(action_handler, machine_spec, machine_options)
  # If the server does not exist, create it
  create_server(action_handler, machine_spec, machine_options)
end

#computeObject



234
235
236
# File 'lib/chef_metal_fog/fog_driver.rb', line 234

def compute
  @compute ||= Fog::Compute.new(compute_options)
end

#compute_optionsObject



160
161
162
# File 'lib/chef_metal_fog/fog_driver.rb', line 160

def compute_options
  driver_options[:compute_options].to_hash || {}
end

#connect_to_machine(machine_spec, machine_options) ⇒ Object

Connect to machine without acquiring it



209
210
211
# File 'lib/chef_metal_fog/fog_driver.rb', line 209

def connect_to_machine(machine_spec, machine_options)
  machine_for(machine_spec, machine_options)
end

#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/chef_metal_fog/fog_driver.rb', line 213

def destroy_machine(action_handler, machine_spec, machine_options)
  server = server_for(machine_spec)
  if server
    action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
      server.destroy
      machine_spec.location = nil
    end
  end
  strategy = convergence_strategy_for(machine_spec, machine_options)
  strategy.cleanup_convergence(action_handler, machine_spec)
end

#providerObject



164
165
166
# File 'lib/chef_metal_fog/fog_driver.rb', line 164

def provider
  compute_options[:provider]
end

#ready_machine(action_handler, machine_spec, machine_options) ⇒ Object



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
# File 'lib/chef_metal_fog/fog_driver.rb', line 176

def ready_machine(action_handler, machine_spec, machine_options)
  server = server_for(machine_spec)
  if server.nil?
    raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
  end

  # Attach floating IPs if necessary
  attach_floating_ips(action_handler, machine_spec, machine_options, server)

  # Start the server if needed, and wait for it to start
  start_server(action_handler, machine_spec, server)
  wait_until_ready(action_handler, machine_spec, machine_options, server)
  begin
    wait_for_transport(action_handler, machine_spec, machine_options, server)
  rescue Fog::Errors::TimeoutError
    # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
    if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
      raise
    else
      # Sometimes (on EC2) the machine comes up but gets stuck or has
      # some other problem.  If this is the case, we restart the server
      # to unstick it.  Reboot covers a multitude of sins.
      Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up.  Rebooting machine in an attempt to unstick it ..."
      restart_server(action_handler, machine_spec, server)
      wait_until_ready(action_handler, machine_spec, machine_options, server)
      wait_for_transport(action_handler, machine_spec, machine_options, server)
    end
  end

  machine_for(machine_spec, machine_options, server)
end

#stop_machine(action_handler, machine_spec, machine_options) ⇒ Object



225
226
227
228
229
230
231
232
# File 'lib/chef_metal_fog/fog_driver.rb', line 225

def stop_machine(action_handler, machine_spec, machine_options)
  server = server_for(machine_spec)
  if server
    action_handler.perform_action "stop machine #{machine_spec.name} (#{server.id} at #{driver_url})" do
      server.stop
    end
  end
end

#transport_for(machine_spec, machine_options, server) ⇒ Object

Not meant to be part of public interface



239
240
241
242
# File 'lib/chef_metal_fog/fog_driver.rb', line 239

def transport_for(machine_spec, machine_options, server)
  # TODO winrm
  create_ssh_transport(machine_spec, machine_options, server)
end