vcloud-core 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.
Files changed (40) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +9 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.md +103 -0
  5. data/Rakefile +18 -0
  6. data/bin/vcloud-query +84 -0
  7. data/jenkins.sh +10 -0
  8. data/lib/vcloud/core.rb +23 -0
  9. data/lib/vcloud/core/compute_metadata.rb +13 -0
  10. data/lib/vcloud/core/edge_gateway.rb +46 -0
  11. data/lib/vcloud/core/entity.rb +23 -0
  12. data/lib/vcloud/core/metadata_helper.rb +29 -0
  13. data/lib/vcloud/core/org_vdc_network.rb +102 -0
  14. data/lib/vcloud/core/query.rb +142 -0
  15. data/lib/vcloud/core/vapp.rb +118 -0
  16. data/lib/vcloud/core/vapp_template.rb +39 -0
  17. data/lib/vcloud/core/vdc.rb +37 -0
  18. data/lib/vcloud/core/version.rb +5 -0
  19. data/lib/vcloud/core/vm.rb +162 -0
  20. data/lib/vcloud/fog.rb +5 -0
  21. data/lib/vcloud/fog/content_types.rb +20 -0
  22. data/lib/vcloud/fog/model_interface.rb +33 -0
  23. data/lib/vcloud/fog/relation.rb +8 -0
  24. data/lib/vcloud/fog/service_interface.rb +257 -0
  25. data/spec/spec_helper.rb +26 -0
  26. data/spec/support/stub_fog_interface.rb +59 -0
  27. data/spec/vcloud/core/edge_gateway_spec.rb +79 -0
  28. data/spec/vcloud/core/metadata_helper_spec.rb +89 -0
  29. data/spec/vcloud/core/org_vdc_network_spec.rb +257 -0
  30. data/spec/vcloud/core/query_spec.rb +111 -0
  31. data/spec/vcloud/core/vapp_spec.rb +173 -0
  32. data/spec/vcloud/core/vapp_template_spec.rb +77 -0
  33. data/spec/vcloud/core/vdc_spec.rb +68 -0
  34. data/spec/vcloud/core/vm_spec.rb +290 -0
  35. data/spec/vcloud/data/basic_preamble_test.erb +8 -0
  36. data/spec/vcloud/data/basic_preamble_test.erb.OUT +8 -0
  37. data/spec/vcloud/fog/fog_model_interface_spec.rb +25 -0
  38. data/spec/vcloud/fog/service_interface_spec.rb +30 -0
  39. data/vcloud-core.gemspec +30 -0
  40. metadata +198 -0
@@ -0,0 +1,142 @@
1
+ require 'csv'
2
+
3
+ module Vcloud
4
+ class Query
5
+
6
+ attr_reader :type
7
+ attr_reader :options
8
+
9
+ def initialize(type=nil, options={})
10
+ @type = type
11
+ @options = options
12
+ @options[:output_format] ||= 'tsv'
13
+ Fog.mock! if ENV['FOG_MOCK'] || options[:mock]
14
+ @fsi = Vcloud::Fog::ServiceInterface.new
15
+ end
16
+
17
+ def filter
18
+ options[:filter]
19
+ end
20
+
21
+ def output_format
22
+ options[:output_format]
23
+ end
24
+
25
+ def fields
26
+ options[:fields]
27
+ end
28
+
29
+ def run()
30
+
31
+ puts "options:" if @options[:debug]
32
+ pp @options if @options[:debug]
33
+
34
+ if @type.nil?
35
+ output_potential_query_types
36
+ else
37
+ output_query_results
38
+ end
39
+ end
40
+
41
+ def get_all_results
42
+ results = []
43
+ (1..get_num_pages).each do |page|
44
+ results += get_results_page(page) || []
45
+ end
46
+ results
47
+ end
48
+
49
+ private
50
+
51
+ def get_num_pages
52
+ body = @fsi.get_execute_query(type=@type, @options)
53
+ last_page = body[:lastPage] || 1
54
+ raise "Invalid lastPage (#{last_page}) in query results" unless last_page.is_a? Integer
55
+ return last_page.to_i
56
+ end
57
+
58
+ def get_results_page(page)
59
+ raise "Must supply a page number" if page.nil?
60
+
61
+ begin
62
+ body = @fsi.get_execute_query(type=@type, @options.merge({:page=>page}))
63
+ pp body if @options[:debug]
64
+ rescue ::Fog::Compute::VcloudDirector::BadRequest, ::Fog::Compute::VcloudDirector::Forbidden => e
65
+ raise "Access denied: #{e.message}"
66
+ end
67
+
68
+ records = body.keys.detect {|key| key.to_s =~ /Record|Reference$/}
69
+ body[records] = [body[records]] if body[records].is_a?(Hash)
70
+ return nil if body[records].nil? || body[records].empty?
71
+ body[records]
72
+
73
+ end
74
+
75
+ def output_query_results
76
+ results = get_all_results
77
+ output_header(results)
78
+ output_results(results)
79
+ end
80
+
81
+ def output_potential_query_types
82
+
83
+ query_list = @fsi.get_execute_query
84
+ queries = {}
85
+ type_width = 0
86
+ query_list[:Link].select do |link|
87
+ link[:rel] == 'down'
88
+ end.map do |link|
89
+ href = Nokogiri::XML.fragment(link[:href])
90
+ query = CGI.parse(URI.parse(href.text).query)
91
+ [query['type'].first, query['format'].first]
92
+ end.each do |type, format|
93
+ queries[type] ||= []
94
+ queries[type] << format
95
+ type_width = [type_width, type.size].max
96
+ end
97
+ queries.keys.sort.each do |type|
98
+ puts "%-#{type_width}s %s" % [type, queries[type].sort.join(',')]
99
+ end
100
+
101
+ end
102
+
103
+ def output_header(results)
104
+ return if results.size == 0
105
+ case @options[:output_format]
106
+ when 'csv'
107
+ csv_string = CSV.generate do |csv|
108
+ csv << results.first.keys
109
+ end
110
+ puts csv_string
111
+ when 'tsv'
112
+ puts results.first.keys.join("\t")
113
+ end
114
+ end
115
+
116
+ def output_results(results)
117
+ return if results.size == 0
118
+
119
+ case @options[:output_format]
120
+ when 'yaml'
121
+ puts YAML.dump(results)
122
+ when 'csv'
123
+ csv_string = CSV.generate do |csv|
124
+ results.each do |record|
125
+ csv << record.values
126
+ end
127
+ end
128
+ puts csv_string
129
+ when 'tsv'
130
+ puts results.first.keys.join("\t") if @options[:page] == 1
131
+ results.each do |record|
132
+ puts record.values.join("\t")
133
+ end
134
+ else
135
+ raise "Unsupported output format #{@options[:output_format]}"
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+ end
142
+
@@ -0,0 +1,118 @@
1
+ module Vcloud
2
+ module Core
3
+ class Vapp
4
+ extend ComputeMetadata
5
+
6
+ attr_reader :id
7
+
8
+ def initialize(id)
9
+ unless id =~ /^#{self.class.id_prefix}-[-0-9a-f]+$/
10
+ raise "#{self.class.id_prefix} id : #{id} is not in correct format"
11
+ end
12
+ @id = id
13
+ end
14
+
15
+ def self.get_by_name(name)
16
+ q = Query.new('vApp', :filter => "name==#{name}")
17
+ unless res = q.get_all_results
18
+ raise "Error finding vApp by name #{name}"
19
+ end
20
+ case res.size
21
+ when 0
22
+ raise "vApp #{name} not found"
23
+ when 1
24
+ return self.new(res.first[:href].split('/').last)
25
+ else
26
+ raise "found multiple vApp entities with name #{name}!"
27
+ end
28
+ end
29
+
30
+ def vcloud_attributes
31
+ Vcloud::Fog::ServiceInterface.new.get_vapp(id)
32
+ end
33
+
34
+ module STATUS
35
+ RUNNING = 4
36
+ end
37
+
38
+ def name
39
+ vcloud_attributes[:name]
40
+ end
41
+
42
+ def href
43
+ vcloud_attributes[:href]
44
+ end
45
+
46
+ def vdc_id
47
+ link = vcloud_attributes[:Link].detect { |l| l[:rel] == Fog::RELATION::PARENT && l[:type] == Fog::ContentTypes::VDC }
48
+ link ? link[:href].split('/').last : raise('a vapp without parent vdc found')
49
+ end
50
+
51
+ def fog_vms
52
+ vcloud_attributes[:Children][:Vm]
53
+ end
54
+
55
+ def networks
56
+ vcloud_attributes[:'ovf:NetworkSection'][:'ovf:Network']
57
+ end
58
+
59
+ def self.get_by_name_and_vdc_name(name, vdc_name)
60
+ fog_interface = Vcloud::Fog::ServiceInterface.new
61
+ attrs = fog_interface.get_vapp_by_name_and_vdc_name(name, vdc_name)
62
+ self.new(attrs[:href].split('/').last) if attrs && attrs.key?(:href)
63
+ end
64
+
65
+ def self.instantiate(name, network_names, template_id, vdc_name)
66
+ Vcloud::Core.logger.info("Instantiating new vApp #{name} in vDC '#{vdc_name}'")
67
+ fog_interface = Vcloud::Fog::ServiceInterface.new
68
+ networks = get_networks(network_names, vdc_name)
69
+
70
+ attrs = fog_interface.post_instantiate_vapp_template(
71
+ fog_interface.vdc(vdc_name),
72
+ template_id,
73
+ name,
74
+ InstantiationParams: build_network_config(networks)
75
+ )
76
+ self.new(attrs[:href].split('/').last) if attrs and attrs.key?(:href)
77
+ end
78
+
79
+ def power_on
80
+ raise "Cannot power on a missing vApp." unless id
81
+ return true if running?
82
+ Vcloud::Fog::ServiceInterface.new.power_on_vapp(id)
83
+ running?
84
+ end
85
+
86
+ private
87
+ def running?
88
+ raise "Cannot call running? on a missing vApp." unless id
89
+ vapp = Vcloud::Fog::ServiceInterface.new.get_vapp(id)
90
+ vapp[:status].to_i == STATUS::RUNNING ? true : false
91
+ end
92
+
93
+ def self.build_network_config(networks)
94
+ return {} unless networks
95
+ instantiation = { NetworkConfigSection: {NetworkConfig: []} }
96
+ networks.compact.each do |network|
97
+ instantiation[:NetworkConfigSection][:NetworkConfig] << {
98
+ networkName: network[:name],
99
+ Configuration: {
100
+ ParentNetwork: {href: network[:href]},
101
+ FenceMode: 'bridged',
102
+ }
103
+ }
104
+ end
105
+ instantiation
106
+ end
107
+
108
+ def self.id_prefix
109
+ 'vapp'
110
+ end
111
+
112
+ def self.get_networks(network_names, vdc_name)
113
+ fsi = Vcloud::Fog::ServiceInterface.new
114
+ fsi.find_networks(network_names, vdc_name) if network_names
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,39 @@
1
+ module Vcloud
2
+ module Core
3
+ class VappTemplate
4
+
5
+ attr_reader :id
6
+
7
+ def initialize(id)
8
+ unless id =~ /^#{self.class.id_prefix}-[-0-9a-f]+$/
9
+ raise "#{self.class.id_prefix} id : #{id} is not in correct format"
10
+ end
11
+ @id = id
12
+ end
13
+
14
+ def vcloud_attributes
15
+ Vcloud::Fog::ServiceInterface.new.get_vapp_template(id)
16
+ end
17
+
18
+ def href
19
+ vcloud_attributes[:href]
20
+ end
21
+
22
+ def name
23
+ vcloud_attributes[:name]
24
+ end
25
+
26
+ def self.get catalog_name, catalog_item_name
27
+ raise "provide catalog and catalog item name to load vappTemplate" unless catalog_name && catalog_item_name
28
+ body = Vcloud::Fog::ServiceInterface.new.template(catalog_name, catalog_item_name)
29
+ raise 'Could not find template vApp' unless body && body.key?(:href)
30
+ self.new(body[:href].split('/').last)
31
+ end
32
+
33
+ def self.id_prefix
34
+ 'vappTemplate'
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ module Vcloud
2
+ module Core
3
+ class Vdc
4
+
5
+ attr_reader :id
6
+
7
+ def initialize(id)
8
+ unless id =~ /^[-0-9a-f]+$/
9
+ raise "vdc id : #{id} is not in correct format"
10
+ end
11
+ @id = id
12
+ end
13
+
14
+ def self.get_by_name(name)
15
+ q = Query.new('orgVdc', :filter => "name==#{name}")
16
+ unless res = q.get_all_results
17
+ raise "Error finding vDC by name #{name}"
18
+ end
19
+ raise "vDc #{name} not found" unless res.size == 1
20
+ return self.new(res.first[:href].split('/').last)
21
+ end
22
+
23
+ def vcloud_attributes
24
+ Vcloud::Fog::ServiceInterface.new.get_vdc(id)
25
+ end
26
+
27
+ def name
28
+ vcloud_attributes[:name]
29
+ end
30
+
31
+ def href
32
+ vcloud_attributes[:href]
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Vcloud
2
+ module Core
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,162 @@
1
+ module Vcloud
2
+ module Core
3
+ class Vm
4
+ extend ComputeMetadata
5
+
6
+ attr_reader :id
7
+
8
+ def initialize(id, vapp)
9
+ unless id =~ /^#{self.class.id_prefix}-[-0-9a-f]+$/
10
+ raise "#{self.class.id_prefix} id : #{id} is not in correct format"
11
+ end
12
+ @id = id
13
+ @vapp = vapp
14
+ end
15
+
16
+ def vcloud_attributes
17
+ Vcloud::Fog::ServiceInterface.new.get_vapp(id)
18
+ end
19
+
20
+ def update_memory_size_in_mb(new_memory)
21
+ return if new_memory.nil?
22
+ return if new_memory.to_i < 64
23
+ unless memory.to_i == new_memory.to_i
24
+ Vcloud::Fog::ServiceInterface.new.put_memory(id, new_memory)
25
+ end
26
+ end
27
+
28
+ def memory
29
+ memory_item = virtual_hardware_section.detect { |i| i[:'rasd:ResourceType'] == '4' }
30
+ memory_item[:'rasd:VirtualQuantity']
31
+ end
32
+
33
+ def cpu
34
+ cpu_item = virtual_hardware_section.detect { |i| i[:'rasd:ResourceType'] == '3' }
35
+ cpu_item[:'rasd:VirtualQuantity']
36
+ end
37
+
38
+ def name
39
+ vcloud_attributes[:name]
40
+ end
41
+
42
+ def href
43
+ vcloud_attributes[:href]
44
+ end
45
+
46
+ def update_name(new_name)
47
+ fsi = Vcloud::Fog::ServiceInterface.new
48
+ fsi.put_vm(id, new_name) unless name == new_name
49
+ end
50
+
51
+ def vapp_name
52
+ @vapp.name
53
+ end
54
+
55
+ def update_cpu_count(new_cpu_count)
56
+ return if new_cpu_count.nil?
57
+ return if new_cpu_count.to_i == 0
58
+ unless cpu.to_i == new_cpu_count.to_i
59
+ Vcloud::Fog::ServiceInterface.new.put_cpu(id, new_cpu_count)
60
+ end
61
+ end
62
+
63
+ def update_metadata(metadata)
64
+ return if metadata.nil?
65
+ fsi = Vcloud::Fog::ServiceInterface.new
66
+ metadata.each do |k, v|
67
+ fsi.put_vapp_metadata_value(@vapp.id, k, v)
68
+ fsi.put_vapp_metadata_value(id, k, v)
69
+ end
70
+ end
71
+
72
+ def add_extra_disks(extra_disks)
73
+ vm = Vcloud::Fog::ModelInterface.new.get_vm_by_href(href)
74
+ if extra_disks
75
+ extra_disks.each do |extra_disk|
76
+ Vcloud::Core.logger.info("adding a disk of size #{extra_disk[:size]}MB into VM #{id}")
77
+ vm.disks.create(extra_disk[:size])
78
+ end
79
+ end
80
+ end
81
+
82
+ def configure_network_interfaces(networks_config)
83
+ return unless networks_config
84
+ section = {PrimaryNetworkConnectionIndex: 0}
85
+ section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
86
+ connection = {
87
+ network: network[:name],
88
+ needsCustomization: true,
89
+ NetworkConnectionIndex: i,
90
+ IsConnected: true
91
+ }
92
+ ip_address = network[:ip_address]
93
+ connection[:IpAddress] = ip_address unless ip_address.nil?
94
+ connection[:IpAddressAllocationMode] = ip_address ? 'MANUAL' : 'DHCP'
95
+ connection
96
+ end
97
+ Vcloud::Fog::ServiceInterface.new.put_network_connection_system_section_vapp(id, section)
98
+ end
99
+
100
+ def configure_guest_customization_section(name, bootstrap_config, extra_disks)
101
+ if bootstrap_config.nil? or bootstrap_config[:script_path].nil?
102
+ interpolated_preamble = ''
103
+ else
104
+ preamble_vars = bootstrap_config[:vars].merge(:extra_disks => extra_disks)
105
+ interpolated_preamble = generate_preamble(
106
+ bootstrap_config[:script_path],
107
+ bootstrap_config[:script_post_processor],
108
+ preamble_vars,
109
+ )
110
+ end
111
+ Vcloud::Fog::ServiceInterface.new.put_guest_customization_section(id, name, interpolated_preamble)
112
+ end
113
+
114
+ def generate_preamble(script_path, script_post_processor, vars)
115
+ vapp_name = @vapp.name
116
+ script = ERB.new(File.read(File.expand_path(script_path)), nil, '>-')
117
+ .result(binding)
118
+ if script_post_processor
119
+ script = Open3.capture2(File.expand_path(script_post_processor),
120
+ stdin_data: script).first
121
+ end
122
+ script
123
+ end
124
+
125
+ def update_storage_profile storage_profile
126
+ storage_profile_href = get_storage_profile_href_by_name(storage_profile, @vapp.name)
127
+ Vcloud::Fog::ServiceInterface.new.put_vm(id, name, {
128
+ :StorageProfile => {
129
+ name: storage_profile,
130
+ href: storage_profile_href
131
+ }
132
+ })
133
+ end
134
+
135
+ private
136
+ def virtual_hardware_section
137
+ vcloud_attributes[:'ovf:VirtualHardwareSection'][:'ovf:Item']
138
+ end
139
+
140
+ def get_storage_profile_href_by_name(storage_profile_name, vapp_name)
141
+ q = Query.new('vApp', :filter => "name==#{vapp_name}")
142
+ vdc_results = q.get_all_results
143
+ vdc_name = vdc_results.first[:vdcName]
144
+
145
+ q = Query.new('orgVdcStorageProfile', :filter => "name==#{storage_profile_name};vdcName==#{vdc_name}")
146
+ sp_results = q.get_all_results
147
+
148
+ if sp_results.empty? or !sp_results.first.has_key?(:href)
149
+ raise "storage profile not found"
150
+ else
151
+ return sp_results.first[:href]
152
+ end
153
+ end
154
+
155
+ def self.id_prefix
156
+ 'vm'
157
+ end
158
+
159
+ end
160
+
161
+ end
162
+ end