vcloud-launcher 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +16 -0
  2. data/Gemfile +9 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.md +127 -0
  5. data/Rakefile +47 -0
  6. data/bin/vcloud-launch +47 -0
  7. data/examples/.fog-example.fog +15 -0
  8. data/examples/vcloud-launch/basic_preamble.erb +8 -0
  9. data/examples/vcloud-launch/complete_vapp_config.yaml +58 -0
  10. data/examples/vcloud-launch/minimal_vapp_config.yaml +11 -0
  11. data/examples/vcloud-launch/multiple_vapps_simple.yaml +45 -0
  12. data/examples/vcloud-launch/yaml_anchors_example.yaml +87 -0
  13. data/features/step_definitions/vcloud-launch_steps.rb +3 -0
  14. data/features/support/env.rb +16 -0
  15. data/features/vcloud-launch.feature +16 -0
  16. data/jenkins.sh +11 -0
  17. data/jenkins_integration_tests.sh +7 -0
  18. data/lib/vcloud/launcher.rb +22 -0
  19. data/lib/vcloud/launcher/launch.rb +46 -0
  20. data/lib/vcloud/launcher/vapp_orchestrator.rb +44 -0
  21. data/lib/vcloud/launcher/version.rb +5 -0
  22. data/lib/vcloud/launcher/vm_orchestrator.rb +85 -0
  23. data/scripts/basic.erb +13 -0
  24. data/scripts/generate_fog_conf_file.sh +6 -0
  25. data/spec/erb_helper.rb +11 -0
  26. data/spec/integration/launcher/data/basic_preamble_test.erb +8 -0
  27. data/spec/integration/launcher/data/happy_path.yaml.erb +32 -0
  28. data/spec/integration/launcher/data/minimum_data_setup.yaml.erb +6 -0
  29. data/spec/integration/launcher/data/storage_profile.yaml.erb +24 -0
  30. data/spec/integration/launcher/storage_profile_integration_spec.rb +90 -0
  31. data/spec/integration/launcher/vcloud_launcher_spec.rb +157 -0
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/stub_fog_interface.rb +59 -0
  34. data/spec/vcloud/launcher/launch_spec.rb +48 -0
  35. data/spec/vcloud/launcher/vapp_orchestrator_spec.rb +57 -0
  36. data/spec/vcloud/launcher/vm_orchestrator_spec.rb +80 -0
  37. data/vcloud-launcher.gemspec +30 -0
  38. metadata +214 -0
@@ -0,0 +1,3 @@
1
+ Then(/^the banner should document that this app takes (\d+) argument$/) do |arg1|
2
+ pending # express the regexp above with the code you wish you had
3
+ end
@@ -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,7 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
4
+ ./scripts/generate_fog_conf_file.sh
5
+ export FOG_RC=fog_integration_test.config
6
+ bundle exec rake integration:all
7
+ rm fog_integration_test.config
@@ -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,5 @@
1
+ module Vcloud
2
+ module Launcher
3
+ VERSION = '0.0.1'
4
+ end
5
+ 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
+
@@ -0,0 +1,6 @@
1
+ cat <<EOF >fog_integration_test.config
2
+ default:
3
+ vcloud_director_username: '$API_USERNAME'
4
+ vcloud_director_password: '$API_PASSWORD'
5
+ vcloud_director_host: '$API_HOST'
6
+ EOF
@@ -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,8 @@
1
+ #!/bin/bash
2
+
3
+ # Default preamble
4
+
5
+ (
6
+ echo "in $*"
7
+ echo "message: <%= vars[:message] -%>"
8
+ ) >> /PREAMBLE_OUTPUT
@@ -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,6 @@
1
+ ---
2
+ vapps:
3
+ - name: <%= vapp_name %>
4
+ vdc_name: <%= vdc_name %>
5
+ catalog: <%= catalog %>
6
+ catalog_item: <%= vapp_template %>
@@ -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