bosh_vsphere_cpi 0.4.9 → 0.5.0

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.
@@ -0,0 +1,294 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module VSphereCloud
4
+ class Resources
5
+
6
+ # Cluster resource.
7
+ class Cluster
8
+ include VimSdk
9
+
10
+ PROPERTIES = %w(name datastore resourcePool host)
11
+ HOST_PROPERTIES = %w(hardware.memorySize runtime.inMaintenanceMode)
12
+ HOST_COUNTERS = %w(cpu.usage.average mem.usage.average)
13
+
14
+ # @!attribute mob
15
+ # @return [Vim::ClusterComputeResource] cluster vSphere MOB.
16
+ attr_accessor :mob
17
+
18
+ # @!attribute config
19
+ # @return [ClusterConfig] cluster config.
20
+ attr_accessor :config
21
+
22
+ # @!attribute datacenter
23
+ # @return [Datacenter] parent datacenter.
24
+ attr_accessor :datacenter
25
+
26
+ # @!attribute resource_pool
27
+ # @return [ResourcePool] resource pool.
28
+ attr_accessor :resource_pool
29
+
30
+ # @!attribute ephemeral_datastores
31
+ # @return [Hash<String, Datastore>] ephemeral datastores.
32
+ attr_accessor :ephemeral_datastores
33
+
34
+ # @!attribute persistent_datastores
35
+ # @return [Hash<String, Datastore>] persistent datastores.
36
+ attr_accessor :persistent_datastores
37
+
38
+ # @!attribute shared_datastores
39
+ # @return [Hash<String, Datastore>] shared datastores.
40
+ attr_accessor :shared_datastores
41
+
42
+ # @!attribute idle_cpu
43
+ # @return [Float] idle cpu ratio.
44
+ attr_accessor :idle_cpu
45
+
46
+ # @!attribute total_memory
47
+ # @return [Integer] memory capacity in MB.
48
+ attr_accessor :total_memory
49
+
50
+ # @!attribute synced_free_memory
51
+ # @return [Integer] cached memory utilization in MB.
52
+ attr_accessor :synced_free_memory
53
+
54
+ # @!attribute allocated_after_sync
55
+ # @return [Integer] memory allocated since utilization sync in MB.
56
+ attr_accessor :allocated_after_sync
57
+
58
+ # Creates a new Cluster resource from the specified datacenter, cluster
59
+ # configuration, and prefetched properties.
60
+ #
61
+ # @param [Datacenter] datacenter parent datacenter.
62
+ # @param [ClusterConfig] config cluster configuration as specified by the
63
+ # operator.
64
+ # @param [Hash] properties prefetched vSphere properties for the cluster.
65
+ def initialize(datacenter, config, properties)
66
+ @logger = Config.logger
67
+ @client = Config.client
68
+
69
+ @datacenter = datacenter
70
+ @config = config
71
+ @mob = properties[:obj]
72
+ @resource_pool = ResourcePool.new(self, properties["resourcePool"])
73
+
74
+ @allocated_after_sync = 0
75
+ @ephemeral_datastores = {}
76
+ @persistent_datastores = {}
77
+ @shared_datastores = {}
78
+
79
+ datastores_properties = @client.get_properties(
80
+ properties["datastore"], Vim::Datastore, Datastore::PROPERTIES)
81
+ datastores_properties.each_value do |datastore_properties|
82
+ name = datastore_properties["name"]
83
+ datastore_config = datacenter.config.datastores
84
+ ephemeral = !!(name =~ datastore_config.ephemeral_pattern)
85
+ persistent = !!(name =~ datastore_config.persistent_pattern)
86
+
87
+ if ephemeral && persistent &&
88
+ !datastore_config.allow_mixed
89
+ raise "Datastore patterns are not mutually exclusive: #{name}"
90
+ end
91
+
92
+ if ephemeral || persistent
93
+ datastore = Datastore.new(datastore_properties)
94
+ if ephemeral && persistent
95
+ @shared_datastores[datastore.name] = datastore
96
+ elsif ephemeral
97
+ @ephemeral_datastores[datastore.name] = datastore
98
+ else
99
+ @persistent_datastores[datastore.name] = datastore
100
+ end
101
+ end
102
+ end
103
+
104
+ @logger.debug(
105
+ "Datastores - ephemeral: #{@ephemeral_datastores.keys.inspect}, " +
106
+ "persistent: #{@persistent_datastores.keys.inspect}, " +
107
+ "shared: #{@shared_datastores.keys.inspect}.")
108
+
109
+ # Have to use separate mechanisms for fetching utilization depending on
110
+ # whether we're using resource pools or raw clusters.
111
+ if @config.resource_pool.nil?
112
+ fetch_cluster_utilization(properties["host"])
113
+ else
114
+ fetch_res_pool_utilization
115
+ end
116
+ end
117
+
118
+ # Returns the persistent datastore by name. This could be either from the
119
+ # exclusive or shared datastore pools.
120
+ #
121
+ # @param [String] datastore_name name of the datastore.
122
+ # @return [Datastore, nil] the requested persistent datastore.
123
+ def persistent(datastore_name)
124
+ @persistent_datastores[datastore_name] ||
125
+ @shared_datastores[datastore_name]
126
+ end
127
+
128
+ # @return [Integer] amount of free memory in the cluster
129
+ def free_memory
130
+ @synced_free_memory -
131
+ (@allocated_after_sync * Config.mem_overcommit).to_i
132
+ end
133
+
134
+ # Picks the best datastore for the specified persistent disk.
135
+ #
136
+ # @param [Integer] size persistent disk size.
137
+ # @return [Datastore, nil] best datastore if available for the requested
138
+ # size.
139
+ def pick_persistent(size)
140
+ pick_store(size, :persistent)
141
+ end
142
+
143
+ # Picks the best datastore for the specified ephemeral disk.
144
+ #
145
+ # @param [Integer] size ephemeral disk size.
146
+ # @return [Datastore, nil] best datastore if available for the requested
147
+ # size.
148
+ def pick_ephemeral(size)
149
+ pick_store(size, :ephemeral)
150
+ end
151
+
152
+ # Marks the memory reservation against the cached utilization data.
153
+ #
154
+ # @param [Integer] memory size of memory reservation.
155
+ # @return [void]
156
+ def allocate(memory)
157
+ @allocated_after_sync += memory
158
+ end
159
+
160
+ # @return [String] cluster name.
161
+ def name
162
+ @config.name
163
+ end
164
+
165
+ # @return [String] debug cluster information.
166
+ def inspect
167
+ "<Cluster: #@mob / #{@config.name}>"
168
+ end
169
+
170
+ private
171
+
172
+ # Picks the best datastore for the specified disk size and type.
173
+ #
174
+ # First the exclusive datastore pool is used. If it's empty or doesn't
175
+ # have enough capacity then the shared pool will be used.
176
+ #
177
+ # @param [Integer] size disk size.
178
+ # @param [Symbol] type disk type.
179
+ # @return [Datastore, nil] best datastore if available for the requested
180
+ # size.
181
+ def pick_store(size, type)
182
+ weighted_datastores = []
183
+ datastores =
184
+ type == :persistent ? @persistent_datastores : @ephemeral_datastores
185
+ datastores.each_value do |datastore|
186
+ if datastore.free_space - size >= DISK_THRESHOLD
187
+ weighted_datastores << [datastore, datastore.free_space]
188
+ end
189
+ end
190
+
191
+ if weighted_datastores.empty?
192
+ @shared_datastores.each_value do |datastore|
193
+ if datastore.free_space - size >= DISK_THRESHOLD
194
+ weighted_datastores << [datastore, datastore.free_space]
195
+ end
196
+ end
197
+ end
198
+
199
+ Util.weighted_random(weighted_datastores)
200
+ end
201
+
202
+ # Fetches the raw cluster utilization from vSphere.
203
+ #
204
+ # First filter out any hosts that are in maintenance mode. Then aggregate
205
+ # individual host capacity and its utilization using the performance
206
+ # manager.
207
+ #
208
+ # @param [Array<Vim::HostSystem>] host_mobs cluster hosts.
209
+ # @return [void]
210
+ def fetch_cluster_utilization(host_mobs)
211
+ hosts_properties = @client.get_properties(
212
+ host_mobs, Vim::HostSystem, HOST_PROPERTIES, :ensure_all => true)
213
+ host_mobs = filter_inactive_hosts(hosts_properties)
214
+
215
+ if host_mobs.empty?
216
+ @idle_cpu = 0
217
+ @total_memory = 0
218
+ @synced_free_memory = 0
219
+ return
220
+ end
221
+
222
+ samples = 0
223
+ cluster_total_memory = 0
224
+ cluster_free_memory = 0
225
+ cluster_cpu_usage = 0
226
+
227
+ counters = @client.get_perf_counters(
228
+ host_mobs, HOST_COUNTERS, :max_sample => 5)
229
+ counters.each do |host_mob, counter|
230
+ host_properties = hosts_properties[host_mob]
231
+ total_memory = host_properties["hardware.memorySize"].to_i
232
+ percent_used = Util.average_csv(counter["mem.usage.average"]) / 10000
233
+ free_memory = ((1.0 - percent_used) * total_memory).to_i
234
+
235
+ samples += 1
236
+ cluster_total_memory += total_memory
237
+ cluster_free_memory += free_memory
238
+ cluster_cpu_usage +=
239
+ Util.average_csv(counter["cpu.usage.average"]) / 100
240
+ end
241
+
242
+ @idle_cpu = (100 - cluster_cpu_usage / samples) / 100
243
+ @total_memory = cluster_total_memory / BYTES_IN_MB
244
+ @synced_free_memory = cluster_free_memory / BYTES_IN_MB
245
+ end
246
+
247
+ # Fetches the resource pool utilization from vSphere.
248
+ #
249
+ # We can only rely on the vSphere data if the resource pool is healthy.
250
+ # Otherwise we mark the resources as unavailable.
251
+ #
252
+ # Unfortunately this method does not work for the root resource pool,
253
+ # so we can't use it for the raw clusters.
254
+ #
255
+ # @return [void]
256
+ def fetch_res_pool_utilization
257
+ properties = @client.get_properties(
258
+ @resource_pool.mob, Vim::ResourcePool, %w(summary))
259
+ if properties.nil?
260
+ raise "Failed to get utilization for resource pool #{resource_pool}"
261
+ end
262
+
263
+ runtime_info = properties["summary"].runtime
264
+ if runtime_info.overall_status == "green"
265
+ cpu = runtime_info.cpu
266
+ @idle_cpu = 1 - (cpu.overall_usage.to_f / cpu.max_usage)
267
+ memory = runtime_info.memory
268
+ @total_memory = memory.max_usage / BYTES_IN_MB
269
+ @synced_free_memory =
270
+ (memory.max_usage - memory.overall_usage) / BYTES_IN_MB
271
+ else
272
+ @logger.warn("Ignoring cluster: #{config.name} resource_pool: " +
273
+ "#{@resource_pool.mob} as its state is " +
274
+ "unreliable: #{runtime_info.overall_status}")
275
+ # resource pool is in an unreliable state
276
+ @idle_cpu = 0
277
+ @total_memory = 0
278
+ @synced_free_memory = 0
279
+ end
280
+ end
281
+
282
+ # Filters out the hosts that are in maintenance mode.
283
+ #
284
+ # @param [Hash] host_properties host properties that already fetched
285
+ # inMaintenanceMode from vSphere.
286
+ # @return [Array<Vim::HostSystem>] list of hosts that are active
287
+ def filter_inactive_hosts(host_properties)
288
+ host_properties.values.
289
+ select { |p| p["runtime.inMaintenanceMode"] != "true" }.
290
+ collect { |p| p[:obj] }
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,86 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module VSphereCloud
4
+ class Resources
5
+
6
+ # Datacenter resource.
7
+ class Datacenter
8
+ include VimSdk
9
+
10
+ # @!attribute mob
11
+ # @return [Vim::Datacenter] datacenter vSphere MOB.
12
+ attr_accessor :mob
13
+
14
+ # @!attribute clusters
15
+ # @return [Hash<String, Cluster>] hash of cluster names to clusters.
16
+ attr_accessor :clusters
17
+
18
+ # @!attribute vm_folder
19
+ # @return [Folder] inventory folder for VMs.
20
+ attr_accessor :vm_folder
21
+
22
+ # @!attribute template_folder
23
+ # @return [Folder] inventory folder for stemcells/templates.
24
+ attr_accessor :template_folder
25
+
26
+ # @!attribute config
27
+ # @return [DatacenterConfig] datacenter config.
28
+ attr_accessor :config
29
+
30
+ # Creates a new Datacenter resource from the operator provided datacenter
31
+ # configuration.
32
+ #
33
+ # This traverses the provided datacenter/resource pools/datastores and
34
+ # builds the underlying resources and utilization.
35
+ #
36
+ # @param [DatacenterConfig] config datacenter configuration.
37
+ def initialize(config)
38
+ client = Config.client
39
+ @config = config
40
+ @mob = client.find_by_inventory_path(name)
41
+ raise "Datacenter: #{name} not found" if @mob.nil?
42
+
43
+ @vm_folder = Folder.new(self, config.folders.vm,
44
+ config.folders.shared)
45
+ @template_folder = Folder.new(self, config.folders.template,
46
+ config.folders.shared)
47
+
48
+ cluster_mobs = client.get_managed_objects(
49
+ Vim::ClusterComputeResource, :root => @mob, :include_name => true)
50
+ cluster_mobs.delete_if { |name, _| !config.clusters.has_key?(name) }
51
+ cluster_mobs = Hash[*cluster_mobs.flatten]
52
+
53
+ clusters_properties = client.get_properties(
54
+ cluster_mobs.values, Vim::ClusterComputeResource,
55
+ Cluster::PROPERTIES, :ensure_all => true)
56
+
57
+ @clusters = {}
58
+ config.clusters.each do |name, cluster_config|
59
+ cluster_mob = cluster_mobs[name]
60
+ raise "Can't find cluster: #{name}" if cluster_mob.nil?
61
+ cluster_properties = clusters_properties[cluster_mob]
62
+ if cluster_properties.nil?
63
+ raise "Can't find properties for cluster: #{name}"
64
+ end
65
+ cluster = Cluster.new(self, cluster_config, cluster_properties)
66
+ @clusters[cluster.name] = cluster
67
+ end
68
+ end
69
+
70
+ # @return [String] datacenter name.
71
+ def name
72
+ @config.name
73
+ end
74
+
75
+ # @return [String] vCenter path/namespace for VMDKs.
76
+ def disk_path
77
+ @config.datastores.disk_path
78
+ end
79
+
80
+ # @return [String] debug datacenter information.
81
+ def inspect
82
+ "<Datacenter: #@mob / #{@config.name}>"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module VSphereCloud
4
+ class Resources
5
+
6
+ # Datastore resource.
7
+ class Datastore
8
+ PROPERTIES = %w(summary.freeSpace summary.capacity name)
9
+
10
+ # @!attribute mob
11
+ # @return [Vim::Datastore] datastore vSphere MOB.
12
+ attr_accessor :mob
13
+
14
+ # @!attribute name
15
+ # @return [String] datastore name.
16
+ attr_accessor :name
17
+
18
+ # @!attribute total_space
19
+ # @return [Integer] datastore capacity.
20
+ attr_accessor :total_space
21
+
22
+ # @!attribute synced_free_space
23
+ # @return [Integer] datastore free space when fetched from vSphere.
24
+ attr_accessor :synced_free_space
25
+
26
+ # @!attribute allocated_after_sync
27
+ # @return [Integer] allocated space since vSphere fetch.
28
+ attr_accessor :allocated_after_sync
29
+
30
+ # Creates a Datastore resource from the prefetched vSphere properties.
31
+ #
32
+ # @param [Hash] properties prefetched vSphere properties to build the
33
+ # model.
34
+ def initialize(properties)
35
+ @mob = properties[:obj]
36
+ @name = properties["name"]
37
+ @total_space = properties["summary.capacity"].to_i / BYTES_IN_MB
38
+ @synced_free_space = properties["summary.freeSpace"].to_i / BYTES_IN_MB
39
+ @allocated_after_sync = 0
40
+ end
41
+
42
+ # @return [Integer] free disk space available for allocation
43
+ def free_space
44
+ @synced_free_space - @allocated_after_sync
45
+ end
46
+
47
+ # Marks the disk space against the cached utilization data.
48
+ #
49
+ # @param [Integer] space requested disk space.
50
+ # @return [void]
51
+ def allocate(space)
52
+ @allocated_after_sync += space
53
+ end
54
+
55
+ # @return [String] debug datastore information.
56
+ def inspect
57
+ "<Datastore: #@mob / #@name>"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module VSphereCloud
4
+ class Resources
5
+
6
+ # Folder resource.
7
+ class Folder
8
+
9
+ # @!attribute mob
10
+ # @return [Vim::Folder] folder vSphere MOB.
11
+ attr_accessor :mob
12
+
13
+ # @!attribute name
14
+ # @return [String] folder name.
15
+ attr_accessor :name
16
+
17
+ # Creates a new Folder resource given the parent datacenter and folder
18
+ # name.
19
+ #
20
+ # @param [Datacenter] datacenter parent datacenter.
21
+ # @param [String] name folder name.
22
+ # @param [true, false] shared flag signaling to use the director guid as
23
+ # the namespace for multi tenancy.
24
+ def initialize(datacenter, name, shared)
25
+ client = Config.client
26
+ logger = Config.logger
27
+
28
+ folder = client.find_by_inventory_path([datacenter.name, "vm", name])
29
+ raise "Missing folder: #{name}" if folder.nil?
30
+
31
+ if shared
32
+ shared_folder = folder
33
+
34
+ uuid = Bosh::Clouds::Config.uuid
35
+ name = [name, uuid]
36
+
37
+ logger.debug("Search for folder #{name.join("/")}")
38
+ folder = client.find_by_inventory_path([datacenter.name, "vm", name])
39
+ if folder.nil?
40
+ logger.debug("Creating folder #{name.join("/")}")
41
+ folder = shared_folder.create_folder(uuid)
42
+ end
43
+ logger.debug("Found folder #{name.join("/")}: #{folder}")
44
+
45
+ @mob = folder
46
+ @name = name
47
+ else
48
+ @mob = folder
49
+ @name = name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end