Skip to content

Latest commit

 

History

History
307 lines (226 loc) · 10.7 KB

extensions.md

File metadata and controls

307 lines (226 loc) · 10.7 KB

Extensions

Note: This feature is available in versions >=1.2. Please keep in mind that the extension API is still under heavy development and subject to change!

In case you have custom validations that you need to run against OpenStack you can create extensions to the validator.

How to Write an Extension

This is a step-by-step guide to implement an extension.

Create Extensions File

You can configure a directory from which extensions are read. Create a directory validator_extensions for example in your home directory and add a my_extension_spec.rb file. Extensions are in fact RSpec tests. That's why they have to be called *_spec.rb. An example extension comes with the validator.

Add the path of your validator_extensions directory into your validator.yml under the extensions section. You can also configure multiple paths if you have multiple extensions:

extensions:
  paths: [extensions/flavors, /home/my-user/validator-extensions/]

Paths are resolved relative to the validator.yml.

Write a Simple Test

Let's get started with a minimal RSpec test:

fdescribe 'My extension' do

  it 'is true' do
    expect(true).to be(true)
  end

end

Important: To ensure that only your test runs during development, we use fdescribe instead of describe. Set the cli option --tag focus to only run tests focused with the f prefix as you can see in the next step. Don't forget to remove those, before publishing your extension.

Run Your Test

Let's run your test. Start the validator execution as usual, just with the focus tag.

$ ./validate --tag focus --stemcell bosh-stemcell-<xxx>-openstack-kvm-ubuntu-xenial-go_agent.tgz --config validator.yml

Access OpenStack in Your Test

We want to ensure that we can create a security group in OpenStack. The validator provides an API that enables us to interact with OpenStack.

fdescribe 'My extension' do

  before(:all) { @compute = Validator::Api::FogOpenStack.compute }

  it 'can create a security group allowing SSH' do
    ssh_security_group = @compute.security_groups.create({ 'name' => 'allow-ssh', 'description' => '' })
    ssh_security_group.security_group_rules.create({
      from_port: '22',
      ip_protocol: 'tcp',
      ip_range: { 'cidr' => '0.0.0.0/0' },
      to_port: '22',
      parent_group_id: ssh_security_group.id
    })

    expect(@compute.security_groups.get(ssh_security_group.id)).to_not be_nil

    ssh_security_group.destroy
  end

end

Automatically Clean up Resources Created by Your Tests

If our test would fail, it would leak resources: Nobody is cleaning up the security group we created. To ensure that resources are cleaned up, even when the test is not completely executed, the validator provides a resource tracking API.

The resource tracking API supports automatic cleanup of OpenStack resources. For debugging, cleanup can be skipped by setting the cli option --skip-cleanup.

To use the resource tracking, add a statement as follows:

fdescribe 'My extension' do

  include_context "resource tracker"

  before(:all) do
    @compute = Validator::Api::FogOpenStack.compute
  end

  it 'can create a security group allowing SSH' do
    ssh_security_group = nil
    ssh_security_group_id = @resource_tracker.produce(:security_groups, provide_as: :my_security_group_id) do
      ssh_security_group = @compute.security_groups.create({ 'name' => 'allow-ssh', 'description' => '' })
      ssh_security_group.id
    end

    ssh_security_group.security_group_rules.create({
      from_port: '22',
      ip_protocol: 'tcp',
      ip_range: { 'cidr' => '0.0.0.0/0' },
      to_port: '22',
      parent_group_id: ssh_security_group_id
    })

    expect(@compute.security_groups.get(ssh_security_group_id)).to_not be_nil
  end

end

If you now run the tests with --skip-cleanup, it will not clean up the security group and you will see output similar to:

...
Finished in 3.28 seconds (files took 0.27329 seconds to load)
1 example, 0 failures
Resources: The following resources might not have been cleaned up:
  Security groups:
    - Name: allow-ssh
      UUID: 6a400796-32ad-42d7-8124-2e73c27b01e3
      Created by test: Your OpenStack Extensions My extension can create a security group allowing SSH

Note that you have to manually clean up the security group in the OpenStack UI or with the OpenStack command line client in this case.

Share Resources Between Tests

You can consume resources you produced in your test using the resource tracker API in other tests by using the consumes call:

it 'uses security group' do
  ssh_security_group_id = @resource_tracker.consumes(:my_security_group_id)
  expect(@compute.security_groups.get(ssh_security_group_id)).to_not be_nil
end

Passing Parameters to your Extension

If your extension needs any configuration you can add it to the validator.yml. Let's read the port for the security group rule from the configuration:

extensions:
  paths: ['path_to/my_extension/']
  config:
    my_extension:
     port: 22

The complete hash at config can be retrieved from your test by calling Validator::Api.configuration.extensions.

Note that the configuration will be globally available to all running extensions.

fdescribe 'My extension' do
  include_context "resource tracker"

  before(:all) do
    @compute = Validator::Api::FogOpenStack.compute
  end

  let(:config) { Validator::Api.configuration.extensions }

  it 'can create a security group allowing SSH' do
    ssh_security_group = nil
    ssh_security_group_id = @resource_tracker.produce(:security_groups, provide_as: :my_security_group_id) do
      ssh_security_group = @compute.security_groups.create({ 'name' => 'allow-ssh', 'description' => '' })
      ssh_security_group.id
    end

    ssh_security_group.security_group_rules.create({
      from_port: config['my_extension']['port'],
      ip_protocol: 'tcp',
      ip_range: { 'cidr' => '0.0.0.0/0' },
      to_port: config['my_extension']['port'],
      parent_group_id: ssh_security_group_id
    })

    expect(@compute.security_groups.get(ssh_security_group_id)).to_not be_nil
  end

end

If you need multiple parameters, we recommend to store all of them in one extension-specific configuration file next to your spec files. Then you just hand in the path to your config file as config parameter into validator.yml.

security_group_name: 'allow-ssh'
protocol: 'tcp'
port: 22
extensions:
  paths: ['path_to/my_extension/']
  config:
    my_extension:
     path: 'path_to/my_extension_config.yml'

Access your own config parameters by loading the file in your spec:

fdescribe 'My extension' do
  include_context "resource tracker"

  before(:all) do
    @compute = Validator::Api::FogOpenStack.compute
  end

  let(:config) { Validator::Api.configuration.extensions }
  let(:my_config) { YAML.load_file(config['my_extension']['path']) }

  it 'can create a security group allowing SSH' do
    ssh_security_group = nil
    ssh_security_group_id = @resource_tracker.produce(:security_groups, provide_as: :my_security_group_id) do
      ssh_security_group = @compute.security_groups.create({ 'name' => my_config['security_group_name'], 'description' => '' })
      ssh_security_group.id
    end

    ssh_security_group.security_group_rules.create({
      from_port: my_config['port'],
      ip_protocol: my_config['protocol'],
      ip_range: { 'cidr' => '0.0.0.0/0' },
      to_port: my_config['port'],
      parent_group_id: ssh_security_group_id
    })

    expect(@compute.security_groups.get(ssh_security_group_id)).to_not be_nil
  end

end

Finishing Up

That's it, you have implemented your first validator extension. As a last step don't forget to remove all f prefixes in front of describe, context or it steps, so that the whole test suite is executed.

If you publish your extension, make sure to include a README that describes all available configuration options. For an example, have a look at Flavors Extension.

Extension API Details

Interact with OpenStack

To interact with OpenStack the validator provides access via an API. Currently the API exposes compute/nova, image/glance, volume/cinder and network/neutron instances using Fog. Fog is a Ruby library that offers bindings for different IaaS platforms, including OpenStack. To create instances do:

# Create a new compute instance
compute = Validator::Api::FogOpenStack.compute
# List all servers
server_collection = compute.servers

# Create a new network instance
network = Validator::Api::FogOpenStack.network
# List all networks
network_collection = network.networks

# Create a new image instance
image = Validator::Api::FogOpenStack.image
# List all images
image_collection = image.images

# Create a new volume instance
volume = Validator::Api::FogOpenStack.volume
# List all volumes
volume_collection = volume.volumes

The factory methods create a new instance each time you call them. Be aware that creating an instance, will already do an authentication call to keystone. For this reason it might be useful to just create one instance in a before hook.

The options used to create those instances are the same that are used in the validator core tests. They are derived from the validator.yml the user provided.

To learn more about the usage of Fog OpenStack please have a look at its documentation.

The OpenStack Resource Tracker

The validator offers a central handling of OpenStack resources that are created during test runs. It takes care of cleaning up all resources at the end of a test run. The user can configure to skip this cleanup for debugging purposes (--skip-cleanup). Any leftover resources are reported at the end of the test run.

To hook into this resource management, every extension can create a ResourceTracker.

# create a resource tracker instance
resource_tracker = Validator::Api::ResourceTracker.create

Such an instance provides produce and consumes methods to manage resources tied to the resource tracker. Each resource tracker is responsible for its own set of resources. Checkout the methods documentation here.

Remark: Only the following collections are supported:

  • compute: flavors, key_pairs, servers, server_groups
  • network: networks, ports, subnets, floating_ips, routers, security_groups, security_group_rules
  • image: images
  • volume: volumes, snapshots
  • storage: files, directories

This means one can still use other collections the Fog Api offers, but the resource tracker cannot track and clean them up. This would then have to be done manually.