bosh_vsphere_cpi 1.1868.0 → 1.1975.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ module VSphereCloud
10
10
  class NotLoggedInException < StandardError; end
11
11
 
12
12
  attr_accessor :service_content
13
- attr_accessor :stub
13
+ attr_accessor :soap_stub
14
14
  attr_accessor :service_instance
15
15
 
16
16
  def initialize(host, options = {})
@@ -26,9 +26,9 @@ module VSphereCloud
26
26
  http_client.connect_timeout = 4
27
27
  http_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
28
28
 
29
- @stub = Soap::StubAdapter.new(host, "vim.version.version6", http_client)
29
+ @soap_stub = Soap::StubAdapter.new(host, "vim.version.version6", http_client)
30
30
 
31
- @service_instance = Vim::ServiceInstance.new("ServiceInstance", stub)
31
+ @service_instance = Vim::ServiceInstance.new("ServiceInstance", soap_stub)
32
32
  @service_content = service_instance.content
33
33
  @metrics_cache = {}
34
34
  @lock = Mutex.new
@@ -16,6 +16,8 @@ require 'cloud/vsphere/resources/scorer'
16
16
  require 'cloud/vsphere/resources/util'
17
17
  require 'cloud/vsphere/models/disk'
18
18
  require 'cloud/vsphere/path_finder'
19
+ require 'cloud/vsphere/vm_creator_builder'
20
+ require 'cloud/vsphere/fixed_cluster_placer'
19
21
 
20
22
  module VSphereCloud
21
23
 
@@ -28,12 +30,12 @@ module VSphereCloud
28
30
  attr_accessor :client
29
31
 
30
32
  def initialize(options)
31
- Config.configure(options)
33
+ @config = Config.build(options)
32
34
 
33
- @logger = Config.logger
34
- @client = Config.client
35
- @rest_client = Config.rest_client
36
- @resources = Resources.new
35
+ @logger = config.logger
36
+ @client = config.client
37
+ @rest_client = config.rest_client
38
+ @resources = Resources.new(config)
37
39
 
38
40
  # Global lock
39
41
  @lock = Mutex.new
@@ -163,108 +165,10 @@ module VSphereCloud
163
165
  client.find_by_inventory_path([dc.name, 'vm', dc.template_folder.name, name])
164
166
  end
165
167
 
166
- def create_vm(agent_id, stemcell, resource_pool, networks, disk_locality = nil, environment = nil)
168
+ def create_vm(agent_id, stemcell, cloud_properties, networks, disk_locality = nil, environment = nil)
167
169
  with_thread_name("create_vm(#{agent_id}, ...)") do
168
- memory = resource_pool['ram']
169
- disk = resource_pool['disk']
170
- cpu = resource_pool['cpu']
171
-
172
- # Make sure number of cores is a power of 2. kb.vmware.com/kb/2003484
173
- if cpu & cpu - 1 != 0
174
- raise "Number of vCPUs: #{cpu} is not a power of 2."
175
- end
176
-
177
- stemcell_vm = stemcell_vm(stemcell)
178
- raise "Could not find stemcell: #{stemcell}" if stemcell_vm.nil?
179
-
180
- stemcell_size =
181
- client.get_property(stemcell_vm, Vim::VirtualMachine, 'summary.storage.committed', ensure_all: true)
182
- stemcell_size /= 1024 * 1024
183
-
184
- disks = disk_spec(disk_locality)
185
- # need to include swap and linked clone log
186
- ephemeral = disk + memory + stemcell_size
187
- cluster, datastore = @resources.place(memory, ephemeral, disks)
188
-
189
- name = "vm-#{generate_unique_name}"
190
- @logger.info("Creating vm: #{name} on #{cluster.mob} stored in #{datastore.mob}")
191
-
192
- replicated_stemcell_vm = replicate_stemcell(cluster, datastore, stemcell)
193
- replicated_stemcell_properties = client.get_properties(replicated_stemcell_vm, Vim::VirtualMachine,
194
- ['config.hardware.device', 'snapshot'],
195
- ensure_all: true)
196
-
197
- devices = replicated_stemcell_properties['config.hardware.device']
198
- snapshot = replicated_stemcell_properties['snapshot']
199
-
200
- config = Vim::Vm::ConfigSpec.new(memory_mb: memory, num_cpus: cpu)
201
- config.device_change = []
202
-
203
- system_disk = devices.find { |device| device.kind_of?(Vim::Vm::Device::VirtualDisk) }
204
- pci_controller = devices.find { |device| device.kind_of?(Vim::Vm::Device::VirtualPCIController) }
205
-
206
- file_name = "[#{datastore.name}] #{name}/ephemeral_disk.vmdk"
207
- ephemeral_disk_config =
208
- create_disk_config_spec(datastore.mob, file_name, system_disk.controller_key, disk, create: true)
209
- config.device_change << ephemeral_disk_config
210
-
211
- dvs_index = {}
212
- networks.each_value do |network|
213
- v_network_name = network['cloud_properties']['name']
214
- network_mob = client.find_by_inventory_path([cluster.datacenter.name, 'network', v_network_name])
215
- nic_config = create_nic_config_spec(v_network_name, network_mob, pci_controller.key, dvs_index)
216
- config.device_change << nic_config
217
- end
218
-
219
- nics = devices.select { |device| device.kind_of?(Vim::Vm::Device::VirtualEthernetCard) }
220
- nics.each do |nic|
221
- nic_config = create_delete_device_spec(nic)
222
- config.device_change << nic_config
223
- end
224
-
225
- fix_device_unit_numbers(devices, config.device_change)
226
-
227
- @logger.info("Cloning vm: #{replicated_stemcell_vm} to #{name}")
228
-
229
- task = clone_vm(replicated_stemcell_vm,
230
- name,
231
- cluster.datacenter.vm_folder.mob,
232
- cluster.resource_pool.mob,
233
- datastore: datastore.mob, linked: true, snapshot: snapshot.current_snapshot, config: config)
234
- vm = client.wait_for_task(task)
235
-
236
- begin
237
- upload_file(cluster.datacenter.name, datastore.name, "#{name}/env.iso", '')
238
-
239
- vm_properties = client.get_properties(vm, Vim::VirtualMachine, ['config.hardware.device'], ensure_all: true)
240
- devices = vm_properties['config.hardware.device']
241
-
242
- # Configure the ENV CDROM
243
- config = Vim::Vm::ConfigSpec.new
244
- config.device_change = []
245
- file_name = "[#{datastore.name}] #{name}/env.iso"
246
- cdrom_change = configure_env_cdrom(datastore.mob, devices, file_name)
247
- config.device_change << cdrom_change
248
- client.reconfig_vm(vm, config)
249
-
250
- network_env = generate_network_env(devices, networks, dvs_index)
251
- disk_env = generate_disk_env(system_disk, ephemeral_disk_config.device)
252
- env = generate_agent_env(name, vm, agent_id, network_env, disk_env)
253
- env['env'] = environment
254
- @logger.info("Setting VM env: #{env.pretty_inspect}")
255
-
256
- location =
257
- get_vm_location(vm, datacenter: cluster.datacenter.name, datastore: datastore.name, vm: name)
258
- set_agent_env(vm, location, env)
259
-
260
- @logger.info("Powering on VM: #{vm} (#{name})")
261
- client.power_on_vm(cluster.datacenter.mob, vm)
262
- rescue => e
263
- @logger.info("#{e} - #{e.backtrace.join("\n")}")
264
- delete_vm(name)
265
- raise e
266
- end
267
- name
170
+ VmCreatorBuilder.new.build(choose_placer(cloud_properties), cloud_properties, @client, @logger, self).
171
+ create(agent_id, stemcell, networks, disk_locality, environment)
268
172
  end
269
173
  end
270
174
 
@@ -541,7 +445,7 @@ module VSphereCloud
541
445
  destination_path = "[#{persistent_datastore.name}] #{datacenter_disk_path}/#{disk.uuid}"
542
446
  @logger.info("Moving #{disk.datacenter}/#{source_path} to #{datacenter_name}/#{destination_path}")
543
447
 
544
- if Config.copy_disks
448
+ if config.copy_disks
545
449
  client.copy_disk(source_datacenter, source_path, datacenter, destination_path)
546
450
  @logger.info('Copied disk successfully')
547
451
  else
@@ -790,7 +694,7 @@ module VSphereCloud
790
694
  env['agent_id'] = agent_id
791
695
  env['networks'] = networking_env
792
696
  env['disks'] = disk_env
793
- env.merge!(Config.agent)
697
+ env.merge!(config.agent)
794
698
  env
795
699
  end
796
700
 
@@ -906,7 +810,7 @@ module VSphereCloud
906
810
  def fetch_file(datacenter_name, datastore_name, path)
907
811
  retry_block do
908
812
  url =
909
- "https://#{Config.vcenter.host}/folder/#{path}?dcPath=#{URI.escape(datacenter_name)}&dsName=#{URI.escape(datastore_name)}"
813
+ "https://#{config.vcenter_host}/folder/#{path}?dcPath=#{URI.escape(datacenter_name)}&dsName=#{URI.escape(datastore_name)}"
910
814
 
911
815
  response = @rest_client.get(url)
912
816
 
@@ -923,7 +827,7 @@ module VSphereCloud
923
827
  def upload_file(datacenter_name, datastore_name, path, contents)
924
828
  retry_block do
925
829
  url =
926
- "https://#{Config.vcenter.host}/folder/#{path}?dcPath=#{URI.escape(datacenter_name)}&dsName=#{URI.escape(datastore_name)}"
830
+ "https://#{config.vcenter_host}/folder/#{path}?dcPath=#{URI.escape(datacenter_name)}&dsName=#{URI.escape(datastore_name)}"
927
831
  response = @rest_client.put(url,
928
832
  contents,
929
833
  { 'Content-Type' => 'application/octet-stream', 'Content-Length' => contents.length })
@@ -1143,5 +1047,22 @@ module VSphereCloud
1143
1047
 
1144
1048
  vms
1145
1049
  end
1050
+
1051
+ private
1052
+
1053
+ def choose_placer(cloud_properties)
1054
+ datacenter_spec = cloud_properties.fetch('datacenters', []).first
1055
+ cluster_spec = datacenter_spec.fetch('clusters', []).first if datacenter_spec
1056
+ placer = FixedClusterPlacer.new(find_cluster(cluster_spec)) unless cluster_spec.nil?
1057
+
1058
+ placer.nil? ? @resources : placer
1059
+ end
1060
+
1061
+ def find_cluster(cluster_spec)
1062
+ datacenter = Resources::Datacenter.new(config)
1063
+ datacenter.clusters[cluster_spec.keys.first]
1064
+ end
1065
+
1066
+ attr_reader :config
1146
1067
  end
1147
1068
  end
@@ -0,0 +1,15 @@
1
+ module VSphereCloud
2
+ class ClusterConfig
3
+
4
+ attr_reader :name
5
+
6
+ def initialize(name, config_hash)
7
+ @name = name
8
+ @config = config_hash
9
+ end
10
+
11
+ def resource_pool
12
+ @config['resource_pool']
13
+ end
14
+ end
15
+ end
@@ -1,250 +1,192 @@
1
- # Copyright (c) 2009-2012 VMware, Inc.
1
+ require 'cloud/vsphere/cluster_config'
2
2
 
3
3
  module VSphereCloud
4
-
5
- # vSphere CPI Config
6
4
  class Config
7
-
8
- # Membrane schema for the provided config.
9
- @schema = Membrane::SchemaParser.parse do
10
- {
11
- "agent" => dict(String, Object), # passthrough to the agent
12
- optional("cpi_log") => String,
13
- optional("soap_log") => String,
14
- optional("mem_overcommit_ratio") => Numeric,
15
- optional("copy_disks") => bool,
16
- "vcenters" => [{
17
- "host" => String,
18
- "user" => String,
19
- "password" => String,
20
- "datacenters" => [{
21
- "name" => String,
22
- "vm_folder" => String,
23
- "template_folder" => String,
24
- optional("use_sub_folder") => bool,
25
- "disk_path" => String,
26
- "datastore_pattern" => String,
27
- "persistent_datastore_pattern" => String,
28
- optional("allow_mixed_datastores") => bool,
29
- "clusters" => [enum(String,
30
- dict(String, {"resource_pool" => String}))]
31
- }]
32
- }]
33
- }
5
+ def self.build(config_hash)
6
+ config = new(config_hash)
7
+ config.validate
8
+ config
34
9
  end
35
10
 
36
- # vCenter config.
37
- class VCenterConfig
11
+ def initialize(config_hash)
12
+ @config = config_hash
13
+ @vcenter_host = nil
14
+ @vcenter_user = nil
15
+ @vcenter_password = nil
16
+ @rest_client = nil
17
+ @default_overcommit_ratio = 1.0
38
18
 
39
- # @!attribute host
40
- # @return [String] vCenter host.
41
- attr_accessor :host
19
+ @is_validated = false
20
+ end
42
21
 
43
- # @!attribute user
44
- # @return [String] vCenter user.
45
- attr_accessor :user
22
+ def validate
23
+ return true if @is_validated
46
24
 
47
- # @!attribute password
48
- # @return [String] vCenter password.
49
- attr_accessor :password
25
+ unless config['vcenters'].size == 1
26
+ raise 'vSphere CPI only supports a single vCenter'
27
+ end
50
28
 
51
- # @!attribute datacenters
52
- # @return [Hash<String, DatacenterConfig>] child datacenters.
53
- attr_accessor :datacenters
29
+ unless config['vcenters'].first['datacenters'].size ==1
30
+ raise 'vSphere CPI only supports a single datacenter'
31
+ end
54
32
 
55
- # Creates a new vCenter Config model from parsed YAML.
56
- #
57
- # @param [Hash] config parsed YAML.
58
- def initialize(config)
59
- @host = config["host"]
60
- @user = config["user"]
61
- @password = config["password"]
62
- @datacenters = {}
33
+ validate_schema
63
34
 
64
- unless config["datacenters"].size == 1
65
- raise "vSphere CPI only supports a single datacenter."
66
- end
35
+ @is_validated = true
36
+ end
67
37
 
68
- config["datacenters"].each do |dc|
69
- dc_config = DatacenterConfig.new(dc)
70
- @datacenters[dc_config.name] = dc_config
71
- end
72
- end
38
+ def logger
39
+ @logger ||= Bosh::Clouds::Config.logger
73
40
  end
74
41
 
75
- # Folder config.
76
- class FolderConfig
77
- # @!attribute vm
78
- # @return [String] vm folder path.
79
- attr_accessor :vm
42
+ def client
43
+ unless @client
44
+ @client = Client.new("https://#{vcenter['host']}/sdk/vimService", {
45
+ 'soap_log' => config['soap_log'] || config['cpi_log']
46
+ })
80
47
 
81
- # @!attribute template
82
- # @return [String] template/stemcell folder path.
83
- attr_accessor :template
48
+ @client.login(vcenter['user'], vcenter['password'], 'en')
49
+ end
84
50
 
85
- # @!attribute shared
86
- # @return [true, false] boolean indicating shared folders, so an
87
- # additional namespace should be used.
88
- attr_accessor :shared
51
+ @client
89
52
  end
90
53
 
91
- # Datastore config.
92
- class DatastoreConfig
93
-
94
- # @!attribute ephemeral_pattern
95
- # @return [Regexp] regexp pattern for ephemeral datastores.
96
- attr_accessor :ephemeral_pattern
97
-
98
- # @!attribute persistent_pattern
99
- # @return [Regexp] regexp pattern for persistent datastores.
100
- attr_accessor :persistent_pattern
54
+ def rest_client
55
+ unless @rest_client
56
+ @rest_client = HTTPClient.new
57
+ @rest_client.send_timeout = 14400 # 4 hours, for stemcell uploads
58
+ @rest_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
101
59
 
102
- # @!attribute disk_path
103
- # @return [String] VMDK datastore path.
104
- attr_accessor :disk_path
60
+ # HACK: read the session from the SOAP client so we don't leak sessions
61
+ # when using the REST client
62
+ cookie_str = client.soap_stub.cookie
63
+ @rest_client.cookie_manager.parse(cookie_str, URI.parse("https://#{vcenter_host}"))
64
+ end
105
65
 
106
- # @!attribute allow_mixed
107
- # @return [true, false] boolean indicating whether persistent and
108
- # ephemeral datastores can overlap..
109
- attr_accessor :allow_mixed
66
+ @rest_client
110
67
  end
111
68
 
112
- # Datacenter config.
113
- class DatacenterConfig
114
-
115
- # @!attribute name
116
- # @return [String] datacenter name.
117
- attr_accessor :name
69
+ def mem_overcommit
70
+ config.fetch('mem_overcommit_ratio', @default_overcommit_ratio)
71
+ end
118
72
 
119
- # @!attribute folders
120
- # @return [FolderConfig] folder config.
121
- attr_accessor :folders
73
+ def copy_disks
74
+ !!config['copy_disks']
75
+ end
122
76
 
123
- # @!attribute datastores
124
- # @return [DatastoreConfig] datastore config.
125
- attr_accessor :datastores
77
+ def agent
78
+ config['agent']
79
+ end
126
80
 
127
- # @!attribute clusters
128
- # @return [Hash<String, ClusterConfig>] child clusters.
129
- attr_accessor :clusters
81
+ def vcenter_host
82
+ vcenter['host']
83
+ end
130
84
 
131
- # Creates a new Datacenter Config model from parsed YAML.
132
- #
133
- # @param [Hash] config parsed YAML.
134
- def initialize(config)
135
- @name = config["name"]
85
+ def vcenter_user
86
+ vcenter['user']
87
+ end
136
88
 
137
- @folders = FolderConfig.new
138
- @folders.template = config["template_folder"]
139
- @folders.vm = config["vm_folder"]
140
- @folders.shared = !!config["use_sub_folder"]
89
+ def vcenter_password
90
+ vcenter['password']
91
+ end
141
92
 
142
- @datastores = DatastoreConfig.new
143
- @datastores.ephemeral_pattern = Regexp.new(config["datastore_pattern"])
144
- @datastores.persistent_pattern = Regexp.new(
145
- config["persistent_datastore_pattern"])
146
- @datastores.disk_path = config["disk_path"]
147
- @datastores.allow_mixed = !!config["allow_mixed_datastores"]
93
+ def datacenter_name
94
+ vcenter_datacenter['name']
95
+ end
148
96
 
149
- @clusters = {}
150
- config["clusters"].each do |cluster|
151
- cluster_config = ClusterConfig.new(cluster)
152
- @clusters[cluster_config.name] = cluster_config
153
- end
97
+ def datacenter_vm_folder
98
+ vcenter_datacenter['vm_folder']
99
+ end
154
100
 
155
- if @clusters.any? { |_, cluster| cluster.resource_pool }
156
- @folders.shared = true
157
- end
158
- end
101
+ def datacenter_template_folder
102
+ vcenter_datacenter['template_folder']
159
103
  end
160
104
 
161
- # Cluster config.
162
- class ClusterConfig
105
+ def datacenter_disk_path
106
+ vcenter_datacenter['disk_path']
107
+ end
163
108
 
164
- # @!attribute name
165
- # @return [String] cluster name.
166
- attr_accessor :name
109
+ def datacenter_datastore_pattern
110
+ Regexp.new(vcenter_datacenter['datastore_pattern'])
111
+ end
167
112
 
168
- # @!attribute resource_pool
169
- # @return [String?] optional resource pool to use instead of root.
170
- attr_accessor :resource_pool
113
+ def datacenter_persistent_datastore_pattern
114
+ Regexp.new(vcenter_datacenter['persistent_datastore_pattern'])
115
+ end
171
116
 
172
- # Creates a new Cluster Config model from parsed YAML.
173
- #
174
- # @param [Hash] config parsed YAML.
175
- def initialize(config)
176
- case config
177
- when String
178
- @name = config
179
- else
180
- @name = config.keys.first
181
- @resource_pool = config[@name]["resource_pool"]
182
- end
183
- end
117
+ def datacenter_clusters
118
+ @cluster_objs ||= cluster_objs
184
119
  end
185
120
 
186
- class << self
121
+ def datacenter_allow_mixed_datastores
122
+ !!vcenter_datacenter['allow_mixed_datastores']
123
+ end
187
124
 
188
- CONFIG_OPTIONS = [
189
- :logger,
190
- :client,
191
- :rest_client,
192
- :agent,
193
- :copy_disks,
194
- :vcenter,
195
- :mem_overcommit,
196
- ]
125
+ def datacenter_use_sub_folder
126
+ datacenter_clusters.any? { |_, cluster| cluster.resource_pool } ||
127
+ !!vcenter_datacenter['use_sub_folder']
128
+ end
197
129
 
198
- CONFIG_OPTIONS.each do |option|
199
- attr_accessor option
200
- end
130
+ private
201
131
 
202
- # Clear all of the properties.
203
- #
204
- # Used by unit tests.
205
- # @return [void]
206
- def clear
207
- CONFIG_OPTIONS.each do |option|
208
- self.instance_variable_set("@#{option}".to_sym, nil)
209
- end
210
- end
132
+ attr_reader :config
211
133
 
212
- # Setup the Config context based on the parsed YAML.
213
- #
214
- # @return [void]
215
- def configure(config)
216
- @logger = Bosh::Clouds::Config.logger
217
- @schema.validate(config)
218
- @agent = config["agent"]
134
+ def is_validated?
135
+ raise 'Configuration has not been validated' unless @is_validated
136
+ end
219
137
 
220
- unless config["vcenters"].size == 1
221
- raise "vSphere CPI only supports a single vCenter."
222
- end
223
- @vcenter = VCenterConfig.new(config["vcenters"].first)
138
+ def vcenter
139
+ config['vcenters'].first
140
+ end
224
141
 
225
- @client = Client.new("https://#{@vcenter.host}/sdk/vimService", {
226
- "soap_log" => config["soap_log"] || config["cpi_log"]
227
- })
228
- @client.login(@vcenter.user, @vcenter.password, "en")
142
+ def vcenter_datacenter
143
+ vcenter['datacenters'].first
144
+ end
229
145
 
230
- @rest_client = HTTPClient.new
231
- @rest_client.send_timeout = 14400 # 4 hours, for stemcell uploads
232
- @rest_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
146
+ def validate_schema
147
+ # Membrane schema for the provided config.
148
+ schema = Membrane::SchemaParser.parse do
149
+ {
150
+ 'agent' => dict(String, Object), # passthrough to the agent
151
+ optional('cpi_log') => String,
152
+ optional('soap_log') => String,
153
+ optional('mem_overcommit_ratio') => Numeric,
154
+ optional('copy_disks') => bool,
155
+ 'vcenters' => [{
156
+ 'host' => String,
157
+ 'user' => String,
158
+ 'password' => String,
159
+ 'datacenters' => [{
160
+ 'name' => String,
161
+ 'vm_folder' => String,
162
+ 'template_folder' => String,
163
+ optional('use_sub_folder') => bool,
164
+ 'disk_path' => String,
165
+ 'datastore_pattern' => String,
166
+ 'persistent_datastore_pattern' => String,
167
+ optional('allow_mixed_datastores') => bool,
168
+ 'clusters' => [enum(String,
169
+ dict(String, { 'resource_pool' => String }))]
170
+ }]
171
+ }]
172
+ }
173
+ end
233
174
 
234
- # HACK: read the session from the SOAP client so we don't leak sessions
235
- # when using the REST client
236
- cookie_str = @client.stub.cookie
237
- @rest_client.cookie_manager.parse(
238
- cookie_str, URI.parse("https://#{@vcenter.host}"))
175
+ schema.validate(config)
176
+ end
239
177
 
240
- if config["mem_overcommit_ratio"]
241
- @mem_overcommit = config["mem_overcommit_ratio"].to_f
178
+ def cluster_objs
179
+ cluster_objs = {}
180
+ vcenter_datacenter['clusters'].each do |cluster|
181
+ if cluster.is_a?(Hash)
182
+ name = cluster.keys.first
183
+ cluster_objs[name] = ClusterConfig.new(name, cluster[name])
242
184
  else
243
- @mem_overcommit = 1.0
185
+ cluster_objs[cluster] = ClusterConfig.new(cluster, {})
244
186
  end
245
-
246
- @copy_disks = !!config["copy_disks"]
247
187
  end
188
+ cluster_objs
248
189
  end
249
190
  end
191
+
250
192
  end