Class: Kitchen::Driver::Vro

Inherits:
Base
  • Object
show all
Defined in:
lib/kitchen/driver/vro.rb

Overview

Test Kitchen driver for VMware vRealize Orchestrator (vRO).

This driver does not build a machine itself. It runs two vRO workflows you supply -- one to create a server and one to destroy it -- and treats their output parameters as the machine's identity.

The create workflow must return two output parameters, server_id and ip_address, both non-empty. server_id is stored in instance state and handed back to the destroy workflow, and ip_address becomes the address the transport connects to. A workflow that completes without those two fails the create, since there would be nothing to connect to and nothing to tear down later.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#workflow_idString?

Returns id of the workflow currently being run, used to disambiguate when several workflows share a name.

Returns:

  • (String, nil)

    id of the workflow currently being run, used to disambiguate when several workflows share a name



46
# File 'lib/kitchen/driver/vro.rb', line 46

attr_accessor :workflow_name, :workflow_id

#workflow_nameString

Returns name of the workflow currently being run.

Returns:

  • (String)

    name of the workflow currently being run



46
47
48
# File 'lib/kitchen/driver/vro.rb', line 46

def workflow_name
  @workflow_name
end

Instance Method Details

#create(state) ⇒ void

This method returns an undefined value.

Runs the create workflow and waits for the server to be reachable.

Returns immediately if state already names a server, so a re-run does not create a second one.

Parameters:

  • state (Hash)

    mutable instance state; gains server_id and hostname



77
78
79
80
81
82
83
84
85
86
# File 'lib/kitchen/driver/vro.rb', line 77

def create(state)
  return unless state[:server_id].nil?

  info("Executing the create-server workflow...")
  execute_create_workflow(state)

  info("Server #{state[:hostname]} (#{state[:server_id]}) created.  Waiting for it to be ready...")
  wait_for_server(state)
  info("Server #{state[:hostname]} (#{state[:server_id]}) ready.")
end

#destroy(state) ⇒ void

This method returns an undefined value.

Runs the destroy workflow for the server named in state.

Parameters:

  • state (Hash)

    instance state naming the server



92
93
94
95
96
97
98
# File 'lib/kitchen/driver/vro.rb', line 92

def destroy(state)
  return if state[:server_id].nil?

  info("Executing the destroy-server workflow for #{state[:hostname]} (#{state[:server_id]})...")
  execute_destroy_workflow(state)
  info("Server #{state[:hostname]} (#{state[:server_id]}) destroyed.")
end

#execute_create_workflow(state) ⇒ void

This method returns an undefined value.

Runs the create workflow and records what it produced.

Parameters:

  • state (Hash)

    mutable instance state; gains server_id and hostname

Raises:

  • (RuntimeError)

    if the workflow did not complete successfully, or did not return a usable server_id and ip_address



153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/kitchen/driver/vro.rb', line 153

def execute_create_workflow(state)
  set_workflow_vars(config[:create_workflow_name], config[:create_workflow_id])
  set_workflow_parameters(config[:create_workflow_parameters])
  execute_workflow
  wait_for_workflow

  raise "The workflow did not complete successfully. Check the vRO UI for more info." unless workflow_successful?

  validate_create_output_parameters!

  state[:server_id] = output_parameter_value("server_id")
  state[:hostname]  = output_parameter_value("ip_address")
end

#execute_destroy_workflow(state) ⇒ void

This method returns an undefined value.

Runs the destroy workflow, passing it the stored server_id.

Parameters:

  • state (Hash)

    instance state naming the server

Raises:

  • (RuntimeError)

    if the workflow did not complete successfully



172
173
174
175
176
177
178
179
180
# File 'lib/kitchen/driver/vro.rb', line 172

def execute_destroy_workflow(state)
  set_workflow_vars(config[:destroy_workflow_name], config[:destroy_workflow_id])
  set_workflow_parameters(config[:destroy_workflow_parameters])
  vro_client.parameter("server_id", state[:server_id])
  execute_workflow
  wait_for_workflow

  raise "The workflow did not complete successfully. Check the vRO UI for more info." unless workflow_successful?
end

#execute_workflowvoid

This method returns an undefined value.

Submits the current workflow for execution.

Raises:

  • (RestClient::BadRequest)

    if vRO rejects the request, after logging the response body, which is where vRO puts the reason



187
188
189
190
191
192
193
194
195
# File 'lib/kitchen/driver/vro.rb', line 187

def execute_workflow
  vro_client.execute
rescue RestClient::BadRequest => e
  error("The workflow execution request failed: #{e.response}")
  raise
rescue => e
  error("The workflow execution request failed: #{e.message}")
  raise
end

#nameString

Returns the driver's display name in kitchen list.

Returns:

  • (String)

    the driver's display name in kitchen list



65
66
67
# File 'lib/kitchen/driver/vro.rb', line 65

def name
  "vRO"
end

#output_parameter_empty?(key) ⇒ Boolean

Returns true when the parameter is missing or empty.

Parameters:

  • key (String)

    output parameter name

Returns:

  • (Boolean)

    true when the parameter is missing or empty



255
256
257
# File 'lib/kitchen/driver/vro.rb', line 255

def output_parameter_empty?(key)
  output_parameter_value(key).nil? || output_parameter_value(key).empty?
end

#output_parameter_value(key) ⇒ String

Returns the parameter's value, stringified.

Parameters:

  • key (String)

    output parameter name

Returns:

  • (String)

    the parameter's value, stringified



249
250
251
# File 'lib/kitchen/driver/vro.rb', line 249

def output_parameter_value(key)
  output_parameters[key].value.to_s
end

#output_parametersHash{String => VcoWorkflows::WorkflowParameter}

Output parameters from the finished workflow run.

Returns:

  • (Hash{String => VcoWorkflows::WorkflowParameter})


243
244
245
# File 'lib/kitchen/driver/vro.rb', line 243

def output_parameters
  @output_parameters ||= vro_client.token.output_parameters
end

#set_workflow_parameters(data) ⇒ void

This method returns an undefined value.

Sets input parameters on the current workflow.

Parameters:

  • data (Hash)

    parameter names to values; names are stringified



234
235
236
237
238
# File 'lib/kitchen/driver/vro.rb', line 234

def set_workflow_parameters(data) # rubocop:disable Style/AccessorMethodName
  data.each do |key, value|
    vro_client.parameter(key.to_s, value)
  end
end

#set_workflow_vars(name, id) ⇒ void

This method returns an undefined value.

Points the driver at a different workflow.

Clears the memoized client, which is what makes it safe to run the destroy workflow after the create workflow in the same process.

Parameters:

  • name (String)

    workflow name

  • id (String, nil)

    workflow id, when the name is ambiguous



140
141
142
143
144
# File 'lib/kitchen/driver/vro.rb', line 140

def set_workflow_vars(name, id)
  @vro_client    = nil
  @workflow_name = name
  @workflow_id   = id
end

#validate_create_output_parameters!void

This method returns an undefined value.

Checks that the create workflow returned what the driver needs.

Raises:

  • (RuntimeError)

    if server_id or ip_address is absent or empty



263
264
265
266
267
268
269
# File 'lib/kitchen/driver/vro.rb', line 263

def validate_create_output_parameters!
  raise "The workflow output did not contain a server_id and ip_address parameter." unless
    output_parameters.key?("server_id") && output_parameters.key?("ip_address")

  raise "The server_id parameter was empty." if output_parameter_empty?("server_id")
  raise "The ip_address parameter was empty." if output_parameter_empty?("ip_address")
end

#verify_ssl?Boolean

Returns whether to verify the vRO server's TLS certificate.

Returns:

  • (Boolean)

    whether to verify the vRO server's TLS certificate



128
129
130
# File 'lib/kitchen/driver/vro.rb', line 128

def verify_ssl?
  !config[:vro_disable_ssl_verify]
end

#vro_clientVcoWorkflows::Workflow

Client for the workflow named by #workflow_name and #workflow_id.

Memoized, and reset by #set_workflow_vars, so switching from the create workflow to the destroy workflow builds a fresh client rather than reusing the previous workflow's.

Returns:

  • (VcoWorkflows::Workflow)


119
120
121
122
123
124
125
# File 'lib/kitchen/driver/vro.rb', line 119

def vro_client
  @vro_client ||= VcoWorkflows::Workflow.new(
    workflow_name,
    id: workflow_id,
    config: vro_config
  )
end

#vro_configVcoWorkflows::Config

Connection settings for the vRO API.

Returns:

  • (VcoWorkflows::Config)


103
104
105
106
107
108
109
110
# File 'lib/kitchen/driver/vro.rb', line 103

def vro_config
  @vro_config ||= VcoWorkflows::Config.new(
    url: config[:vro_base_url],
    username: config[:vro_username],
    password: config[:vro_password],
    verify_ssl: verify_ssl?
  )
end

#wait_for_server(state) ⇒ void

This method returns an undefined value.

Waits for the transport to accept a connection.

A server that never becomes reachable is destroyed before the error is re-raised, so a failed create does not leave a machine behind.

Parameters:

  • state (Hash)

    instance state describing how to connect



222
223
224
225
226
227
228
# File 'lib/kitchen/driver/vro.rb', line 222

def wait_for_server(state)
  instance.transport.connection(state).wait_until_ready
rescue
  error("Server #{state[:hostname]} (#{state[:server_id]}) not reachable. Destroying server...")
  destroy(state)
  raise
end

#wait_for_workflowvoid

This method returns an undefined value.

Polls until the running workflow's token is no longer alive.

Raises:

  • (Timeout::Error)

    if it does not finish within request_timeout



201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/kitchen/driver/vro.rb', line 201

def wait_for_workflow
  wait_time = config[:request_timeout]
  Timeout.timeout(wait_time) do
    loop do
      token = vro_client.token
      break unless token.alive?

      sleep 2
    end
  end
rescue Timeout::Error
  raise Timeout::Error, "Workflow did not complete in #{wait_time} seconds. Please check the vRO UI for more information."
end

#workflow_successful?Boolean

Returns whether the workflow run reached the "completed" state.

Returns:

  • (Boolean)

    whether the workflow run reached the "completed" state



272
273
274
# File 'lib/kitchen/driver/vro.rb', line 272

def workflow_successful?
  vro_client.token.state == "completed"
end