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.
- data/.gitignore +2 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +103 -0
- data/Rakefile +18 -0
- data/bin/vcloud-query +84 -0
- data/jenkins.sh +10 -0
- data/lib/vcloud/core.rb +23 -0
- data/lib/vcloud/core/compute_metadata.rb +13 -0
- data/lib/vcloud/core/edge_gateway.rb +46 -0
- data/lib/vcloud/core/entity.rb +23 -0
- data/lib/vcloud/core/metadata_helper.rb +29 -0
- data/lib/vcloud/core/org_vdc_network.rb +102 -0
- data/lib/vcloud/core/query.rb +142 -0
- data/lib/vcloud/core/vapp.rb +118 -0
- data/lib/vcloud/core/vapp_template.rb +39 -0
- data/lib/vcloud/core/vdc.rb +37 -0
- data/lib/vcloud/core/version.rb +5 -0
- data/lib/vcloud/core/vm.rb +162 -0
- data/lib/vcloud/fog.rb +5 -0
- data/lib/vcloud/fog/content_types.rb +20 -0
- data/lib/vcloud/fog/model_interface.rb +33 -0
- data/lib/vcloud/fog/relation.rb +8 -0
- data/lib/vcloud/fog/service_interface.rb +257 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/stub_fog_interface.rb +59 -0
- data/spec/vcloud/core/edge_gateway_spec.rb +79 -0
- data/spec/vcloud/core/metadata_helper_spec.rb +89 -0
- data/spec/vcloud/core/org_vdc_network_spec.rb +257 -0
- data/spec/vcloud/core/query_spec.rb +111 -0
- data/spec/vcloud/core/vapp_spec.rb +173 -0
- data/spec/vcloud/core/vapp_template_spec.rb +77 -0
- data/spec/vcloud/core/vdc_spec.rb +68 -0
- data/spec/vcloud/core/vm_spec.rb +290 -0
- data/spec/vcloud/data/basic_preamble_test.erb +8 -0
- data/spec/vcloud/data/basic_preamble_test.erb.OUT +8 -0
- data/spec/vcloud/fog/fog_model_interface_spec.rb +25 -0
- data/spec/vcloud/fog/service_interface_spec.rb +30 -0
- data/vcloud-core.gemspec +30 -0
- 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,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
|