vcloud-launcher 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +16 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +127 -0
- data/Rakefile +47 -0
- data/bin/vcloud-launch +47 -0
- data/examples/.fog-example.fog +15 -0
- data/examples/vcloud-launch/basic_preamble.erb +8 -0
- data/examples/vcloud-launch/complete_vapp_config.yaml +58 -0
- data/examples/vcloud-launch/minimal_vapp_config.yaml +11 -0
- data/examples/vcloud-launch/multiple_vapps_simple.yaml +45 -0
- data/examples/vcloud-launch/yaml_anchors_example.yaml +87 -0
- data/features/step_definitions/vcloud-launch_steps.rb +3 -0
- data/features/support/env.rb +16 -0
- data/features/vcloud-launch.feature +16 -0
- data/jenkins.sh +11 -0
- data/jenkins_integration_tests.sh +7 -0
- data/lib/vcloud/launcher.rb +22 -0
- data/lib/vcloud/launcher/launch.rb +46 -0
- data/lib/vcloud/launcher/vapp_orchestrator.rb +44 -0
- data/lib/vcloud/launcher/version.rb +5 -0
- data/lib/vcloud/launcher/vm_orchestrator.rb +85 -0
- data/scripts/basic.erb +13 -0
- data/scripts/generate_fog_conf_file.sh +6 -0
- data/spec/erb_helper.rb +11 -0
- data/spec/integration/launcher/data/basic_preamble_test.erb +8 -0
- data/spec/integration/launcher/data/happy_path.yaml.erb +32 -0
- data/spec/integration/launcher/data/minimum_data_setup.yaml.erb +6 -0
- data/spec/integration/launcher/data/storage_profile.yaml.erb +24 -0
- data/spec/integration/launcher/storage_profile_integration_spec.rb +90 -0
- data/spec/integration/launcher/vcloud_launcher_spec.rb +157 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/stub_fog_interface.rb +59 -0
- data/spec/vcloud/launcher/launch_spec.rb +48 -0
- data/spec/vcloud/launcher/vapp_orchestrator_spec.rb +57 -0
- data/spec/vcloud/launcher/vm_orchestrator_spec.rb +80 -0
- data/vcloud-launcher.gemspec +30 -0
- metadata +214 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'methadone/cucumber'
|
3
|
+
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
6
|
+
|
7
|
+
Before do
|
8
|
+
# Using "announce" causes massive warnings on 1.9.2
|
9
|
+
@puts = true
|
10
|
+
@original_rubylib = ENV['RUBYLIB']
|
11
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
After do
|
15
|
+
ENV['RUBYLIB'] = @original_rubylib
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: "vcloud-launch" works as a useful command-line tool
|
2
|
+
In order to use "vcloud-launch" from the CLI
|
3
|
+
I want to have it behave like a typical Unix tool
|
4
|
+
So I don't get surpised
|
5
|
+
|
6
|
+
Scenario: Common arguments work
|
7
|
+
When I get help for "vcloud-launch"
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the banner should be present
|
10
|
+
And the banner should document that this app takes options
|
11
|
+
And the following options should be documented:
|
12
|
+
|--version|
|
13
|
+
|--dont-power-on|
|
14
|
+
|--continue-on-error|
|
15
|
+
And the banner should document that this app's arguments are:
|
16
|
+
|org_config_file|
|
data/jenkins.sh
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/bin/bash -x
|
2
|
+
set -e
|
3
|
+
bundle install --path "${HOME}/bundles/${JOB_NAME}"
|
4
|
+
bundle exec rake
|
5
|
+
|
6
|
+
./scripts/generate_fog_conf_file.sh
|
7
|
+
export FOG_RC=fog_integration_test.config
|
8
|
+
bundle exec rake integration:all
|
9
|
+
rm fog_integration_test.config
|
10
|
+
|
11
|
+
bundle exec rake publish_gem
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'vcloud/fog'
|
2
|
+
require 'vcloud/core'
|
3
|
+
|
4
|
+
require 'vcloud/launcher/launch'
|
5
|
+
require 'vcloud/launcher/vm_orchestrator'
|
6
|
+
require 'vcloud/launcher/vapp_orchestrator'
|
7
|
+
|
8
|
+
require 'vcloud/launcher/version'
|
9
|
+
|
10
|
+
module Vcloud
|
11
|
+
module Launcher
|
12
|
+
|
13
|
+
def self.logger
|
14
|
+
@logger ||= Logger.new(STDOUT)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.clone_object object
|
18
|
+
Marshal.load(Marshal.dump(object))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Vcloud
|
2
|
+
module Launcher
|
3
|
+
class Launch
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@config_loader = Vcloud::Core::ConfigLoader.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(config_file = nil, cli_options = {})
|
10
|
+
config = @config_loader.load_config(config_file, config_schema)
|
11
|
+
config[:vapps].each do |vapp_config|
|
12
|
+
Vcloud::Launcher.logger.info("\n")
|
13
|
+
Vcloud::Launcher.logger.info("Provisioning vApp #{vapp_config[:name]}.")
|
14
|
+
begin
|
15
|
+
vapp = ::Vcloud::Launcher::VappOrchestrator.provision(vapp_config)
|
16
|
+
#methadone sends option starting with 'no' as false.
|
17
|
+
vapp.power_on unless cli_options["dont-power-on"]
|
18
|
+
Vcloud::Launcher.logger.info("Done! Provisioned vApp #{vapp_config[:name]} successfully.")
|
19
|
+
Vcloud::Launcher.logger.info("=" * 70)
|
20
|
+
rescue RuntimeError => e
|
21
|
+
Vcloud::Launcher.logger.error("Failure : Could not provision vApp: #{e.message}")
|
22
|
+
Vcloud::Launcher.logger.info("=" * 70)
|
23
|
+
break unless cli_options["continue-on-error"]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def config_schema
|
30
|
+
{
|
31
|
+
type: 'hash',
|
32
|
+
allowed_empty: false,
|
33
|
+
permit_unknown_parameters: true,
|
34
|
+
internals: {
|
35
|
+
vapps: {
|
36
|
+
type: 'array',
|
37
|
+
required: false,
|
38
|
+
allowed_empty: true,
|
39
|
+
each_element_is: ::Vcloud::Launcher::VappOrchestrator.provision_schema
|
40
|
+
},
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Vcloud
|
2
|
+
module Launcher
|
3
|
+
class VappOrchestrator
|
4
|
+
|
5
|
+
def self.provision(vapp_config)
|
6
|
+
name, vdc_name = vapp_config[:name], vapp_config[:vdc_name]
|
7
|
+
|
8
|
+
if vapp = Vcloud::Core::Vapp.get_by_name_and_vdc_name(name, vdc_name)
|
9
|
+
Vcloud::Launcher.logger.info("Found existing vApp #{name} in vDC '#{vdc_name}'. Skipping.")
|
10
|
+
else
|
11
|
+
template = Vcloud::Core::VappTemplate.get(vapp_config[:catalog], vapp_config[:catalog_item])
|
12
|
+
template_id = template.id
|
13
|
+
|
14
|
+
network_names = extract_vm_networks(vapp_config)
|
15
|
+
vapp = Vcloud::Core::Vapp.instantiate(name, network_names, template_id, vdc_name)
|
16
|
+
Vcloud::Launcher::VmOrchestrator.new(vapp.fog_vms.first, vapp).customize(vapp_config[:vm]) if vapp_config[:vm]
|
17
|
+
end
|
18
|
+
vapp
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.provision_schema
|
22
|
+
{
|
23
|
+
type: 'hash',
|
24
|
+
required: true,
|
25
|
+
allowed_empty: false,
|
26
|
+
internals: {
|
27
|
+
name: { type: 'string', required: true, allowed_empty: false },
|
28
|
+
vdc_name: { type: 'string', required: true, allowed_empty: false },
|
29
|
+
catalog: { type: 'string', required: true, allowed_empty: false },
|
30
|
+
catalog_item: { type: 'string', required: true, allowed_empty: false },
|
31
|
+
vm: Vcloud::Launcher::VmOrchestrator.customize_schema,
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.extract_vm_networks(config)
|
37
|
+
if (config[:vm] && config[:vm][:network_connections])
|
38
|
+
config[:vm][:network_connections].collect { |h| h[:name] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Vcloud
|
2
|
+
module Launcher
|
3
|
+
class VmOrchestrator
|
4
|
+
def initialize fog_vm, vapp
|
5
|
+
vm_id = fog_vm[:href].split('/').last
|
6
|
+
@vm = Core::Vm.new(vm_id, vapp)
|
7
|
+
end
|
8
|
+
|
9
|
+
def customize(vm_config)
|
10
|
+
@vm.update_name(@vm.vapp_name)
|
11
|
+
@vm.configure_network_interfaces vm_config[:network_connections]
|
12
|
+
@vm.update_storage_profile(vm_config[:storage_profile]) if vm_config[:storage_profile]
|
13
|
+
if hardware_config = vm_config[:hardware_config]
|
14
|
+
@vm.update_cpu_count(hardware_config[:cpu])
|
15
|
+
@vm.update_memory_size_in_mb(hardware_config[:memory])
|
16
|
+
end
|
17
|
+
@vm.add_extra_disks(vm_config[:extra_disks])
|
18
|
+
@vm.update_metadata(vm_config[:metadata])
|
19
|
+
@vm.configure_guest_customization_section(
|
20
|
+
@vm.vapp_name,
|
21
|
+
vm_config[:bootstrap],
|
22
|
+
vm_config[:extra_disks]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.customize_schema
|
27
|
+
{
|
28
|
+
type: 'hash',
|
29
|
+
required: false,
|
30
|
+
allowed_empty: false,
|
31
|
+
internals: {
|
32
|
+
network_connections: {
|
33
|
+
type: 'array',
|
34
|
+
required: false,
|
35
|
+
each_element_is: {
|
36
|
+
type: 'hash',
|
37
|
+
internals: {
|
38
|
+
name: { type: 'string', required: true },
|
39
|
+
ip_address: { type: 'ip_address', required: false },
|
40
|
+
},
|
41
|
+
},
|
42
|
+
},
|
43
|
+
storage_profile: { type: 'string', required: false },
|
44
|
+
hardware_config: {
|
45
|
+
type: 'hash',
|
46
|
+
required: false,
|
47
|
+
internals: {
|
48
|
+
cpu: { type: 'string_or_number', required: false },
|
49
|
+
memory: { type: 'string_or_number', required: false },
|
50
|
+
},
|
51
|
+
},
|
52
|
+
extra_disks: {
|
53
|
+
type: 'array',
|
54
|
+
required: false,
|
55
|
+
allowed_empty: false,
|
56
|
+
each_element_is: {
|
57
|
+
type: 'hash',
|
58
|
+
internals: {
|
59
|
+
name: { type: 'string', required: false },
|
60
|
+
size: { type: 'string_or_number', required: false },
|
61
|
+
},
|
62
|
+
},
|
63
|
+
},
|
64
|
+
bootstrap: {
|
65
|
+
type: 'hash',
|
66
|
+
required: false,
|
67
|
+
allowed_empty: false,
|
68
|
+
internals: {
|
69
|
+
script_path: { type: 'string', required: false },
|
70
|
+
script_post_processor: { type: 'string', required: false },
|
71
|
+
vars: { type: 'hash', required: false, allowed_empty: true },
|
72
|
+
},
|
73
|
+
},
|
74
|
+
metadata: {
|
75
|
+
type: 'hash',
|
76
|
+
required: false,
|
77
|
+
allowed_empty: true,
|
78
|
+
},
|
79
|
+
},
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/scripts/basic.erb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
(
|
4
|
+
echo "============================================================"
|
5
|
+
echo "in ${1}:"
|
6
|
+
echo "vapp_name: <%= vapp_name -%>"
|
7
|
+
echo "role: <%= vars[:role] -%>"
|
8
|
+
echo "environment: <%= vars[:environment] -%>"
|
9
|
+
echo
|
10
|
+
env
|
11
|
+
) >> /PREAMBLE_OUT
|
12
|
+
|
13
|
+
|
data/spec/erb_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class ErbHelper
|
2
|
+
def self.convert_erb_template_to_yaml test_namespace, input_erb_config
|
3
|
+
input_erb_config = input_erb_config
|
4
|
+
e = ERB.new(File.open(input_erb_config).read)
|
5
|
+
output_yaml_config = File.join(File.dirname(input_erb_config), "output_#{Time.now.strftime('%s')}.yaml")
|
6
|
+
File.open(output_yaml_config, 'w') { |f|
|
7
|
+
f.write e.result(OpenStruct.new(test_namespace).instance_eval { binding })
|
8
|
+
}
|
9
|
+
output_yaml_config
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
vapps:
|
3
|
+
- name: <%= vapp_name %>
|
4
|
+
vdc_name: <%= vdc_name %>
|
5
|
+
catalog: <%= catalog %>
|
6
|
+
catalog_item: <%= vapp_template %>
|
7
|
+
vm:
|
8
|
+
hardware_config:
|
9
|
+
memory: 8192
|
10
|
+
cpu: 4
|
11
|
+
metadata:
|
12
|
+
is_integer: -999
|
13
|
+
is_string: Hello World
|
14
|
+
is_datetime: <%= date_metadata %>
|
15
|
+
is_true: true
|
16
|
+
is_false: false
|
17
|
+
integration_test_vm: true
|
18
|
+
extra_disks:
|
19
|
+
- size: '1024'
|
20
|
+
name: Hard disk 2
|
21
|
+
- size: '2048'
|
22
|
+
name: Hard disk 3
|
23
|
+
network_connections:
|
24
|
+
- name: <%= network1 %>
|
25
|
+
ip_address: <%= network1_ip %>
|
26
|
+
- name: <%= network2 %>
|
27
|
+
ip_address: <%= network2_ip %>
|
28
|
+
bootstrap:
|
29
|
+
script_path: <%= bootstrap_script %>
|
30
|
+
vars:
|
31
|
+
message: hello world
|
32
|
+
storage_profile: <%= storage_profile %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
vapps:
|
3
|
+
- name: <%= vapp_name_1 %>
|
4
|
+
vdc_name: <%= vdc_name_1 %>
|
5
|
+
catalog: <%= catalog %>
|
6
|
+
catalog_item: <%= vapp_template %>
|
7
|
+
vm:
|
8
|
+
storage_profile: <%= storage_profile %>
|
9
|
+
- name: <%= vapp_name_2 %>
|
10
|
+
vdc_name: <%= vdc_name_2 %>
|
11
|
+
catalog: <%= catalog %>
|
12
|
+
catalog_item: <%= vapp_template %>
|
13
|
+
vm:
|
14
|
+
storage_profile: <%= storage_profile %>
|
15
|
+
- name: <%= vapp_name_3 %>
|
16
|
+
vdc_name: <%= vdc_name_1 %>
|
17
|
+
catalog: <%= catalog %>
|
18
|
+
catalog_item: <%= vapp_template %>
|
19
|
+
- name: <%= vapp_name_4 %>
|
20
|
+
vdc_name: <%= vdc_name_1 %>
|
21
|
+
catalog: <%= catalog %>
|
22
|
+
catalog_item: <%= vapp_template %>
|
23
|
+
vm:
|
24
|
+
storage_profile: <%= nonsense_storage_profile %>
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Vcloud::Launcher::Launch do
|
4
|
+
context "storage profile", :take_too_long => true do
|
5
|
+
before(:all) do
|
6
|
+
@test_data = define_test_data
|
7
|
+
@config_yaml = ErbHelper.convert_erb_template_to_yaml(@test_data, File.join(File.dirname(__FILE__), 'data/storage_profile.yaml.erb'))
|
8
|
+
@fog_interface = Vcloud::Fog::ServiceInterface.new
|
9
|
+
Vcloud::Launcher::Launch.new.run(@config_yaml, {'dont-power-on' => true})
|
10
|
+
|
11
|
+
@vapp_query_result_1 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_1], @test_data[:vdc_name_1])
|
12
|
+
@vapp_id_1 = @vapp_query_result_1[:href].split('/').last
|
13
|
+
@vapp_1 = @fog_interface.get_vapp @vapp_id_1
|
14
|
+
@vm_1 = @vapp_1[:Children][:Vm].first
|
15
|
+
|
16
|
+
@vapp_query_result_2 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_2], @test_data[:vdc_name_2])
|
17
|
+
@vapp_id_2 = @vapp_query_result_2[:href].split('/').last
|
18
|
+
@vapp_2 = @fog_interface.get_vapp @vapp_id_2
|
19
|
+
@vm_2 = @vapp_2[:Children][:Vm].first
|
20
|
+
|
21
|
+
@vapp_query_result_3 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_3], @test_data[:vdc_name_1])
|
22
|
+
@vapp_id_3 = @vapp_query_result_3[:href].split('/').last
|
23
|
+
@vapp_3 = @fog_interface.get_vapp @vapp_id_3
|
24
|
+
@vm_3 = @vapp_3[:Children][:Vm].first
|
25
|
+
|
26
|
+
@vapp_query_result_4 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_4], @test_data[:vdc_name_1])
|
27
|
+
@vapp_id_4 = @vapp_query_result_4[:href].split('/').last
|
28
|
+
@vapp_4 = @fog_interface.get_vapp @vapp_id_4
|
29
|
+
@vm_4 = @vapp_4[:Children][:Vm].first
|
30
|
+
end
|
31
|
+
|
32
|
+
it "vdc 1 should have a storage profile without the href being specified" do
|
33
|
+
@vm_1[:StorageProfile][:name].should == @test_data[:storage_profile]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "vdc 1's storage profile should have the expected href" do
|
37
|
+
@vm_1[:StorageProfile][:href].should == @test_data[:vdc_1_sp_href]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "vdc 2 should have the same named storage profile as vdc 1" do
|
41
|
+
@vm_2[:StorageProfile][:name].should == @test_data[:storage_profile]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "the storage profile in vdc 2 should have a different href to the storage profile in vdc 1" do
|
45
|
+
@vm_2[:StorageProfile][:href].should == @test_data[:vdc_2_sp_href]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "when a storage profile is not specified, vm uses the default and continues" do
|
49
|
+
@vm_3[:StorageProfile][:name].should == @test_data[:default_storage_profile_name]
|
50
|
+
@vm_3[:StorageProfile][:href].should == @test_data[:default_storage_profile_href]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "when a storage profile specified does not exist, vm uses the default" do
|
54
|
+
@vm_4[:StorageProfile][:name].should == @test_data[:default_storage_profile_name]
|
55
|
+
@vm_4[:StorageProfile][:href].should == @test_data[:default_storage_profile_href]
|
56
|
+
end
|
57
|
+
|
58
|
+
after(:all) do
|
59
|
+
unless ENV['VCLOUD_TOOLS_RSPEC_NO_DELETE_VAPP']
|
60
|
+
File.delete @config_yaml
|
61
|
+
@fog_interface.delete_vapp(@vapp_id_1).should == true
|
62
|
+
@fog_interface.delete_vapp(@vapp_id_2).should == true
|
63
|
+
@fog_interface.delete_vapp(@vapp_id_3).should == true
|
64
|
+
@fog_interface.delete_vapp(@vapp_id_4).should == true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_test_data
|
73
|
+
{
|
74
|
+
vapp_name_1: "vdc-1-sp-#{Time.now.strftime('%s')}",
|
75
|
+
vapp_name_2: "vdc-2-sp-#{Time.now.strftime('%s')}",
|
76
|
+
vapp_name_3: "vdc-3-sp-#{Time.now.strftime('%s')}",
|
77
|
+
vapp_name_4: "vdc-4-sp-#{Time.now.strftime('%s')}",
|
78
|
+
vdc_name_1: ENV['VDC_NAME_1'],
|
79
|
+
vdc_name_2: ENV['VDC_NAME_2'],
|
80
|
+
catalog: ENV['VCLOUD_CATALOG_NAME'],
|
81
|
+
vapp_template: ENV['VCLOUD_TEMPLATE_NAME'],
|
82
|
+
storage_profile: ENV['VCLOUD_STORAGE_PROFILE_NAME'],
|
83
|
+
vdc_1_sp_href: ENV['VDC_1_STORAGE_PROFILE_HREF'],
|
84
|
+
vdc_2_sp_href: ENV['VDC_2_STORAGE_PROFILE_HREF'],
|
85
|
+
default_storage_profile_name: ENV['DEFAULT_STORAGE_PROFILE_NAME'],
|
86
|
+
default_storage_profile_href: ENV['DEFAULT_STORAGE_PROFILE_HREF'],
|
87
|
+
nonsense_storage_profile: "nonsense-storage-profile-name",
|
88
|
+
bootstrap_script: File.join(File.dirname(__FILE__), "data/basic_preamble_test.erb"),
|
89
|
+
}
|
90
|
+
end
|