vmpooler 0.1.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.
- checksums.yaml +7 -0
- data/bin/vmpooler +54 -0
- data/lib/vmpooler.rb +161 -0
- data/lib/vmpooler/api.rb +53 -0
- data/lib/vmpooler/api/dashboard.rb +143 -0
- data/lib/vmpooler/api/helpers.rb +431 -0
- data/lib/vmpooler/api/reroute.rb +71 -0
- data/lib/vmpooler/api/v1.rb +938 -0
- data/lib/vmpooler/dashboard.rb +14 -0
- data/lib/vmpooler/dummy_statsd.rb +20 -0
- data/lib/vmpooler/generic_connection_pool.rb +53 -0
- data/lib/vmpooler/graphite.rb +42 -0
- data/lib/vmpooler/logger.rb +22 -0
- data/lib/vmpooler/pool_manager.rb +1029 -0
- data/lib/vmpooler/providers.rb +7 -0
- data/lib/vmpooler/providers/base.rb +231 -0
- data/lib/vmpooler/providers/dummy.rb +402 -0
- data/lib/vmpooler/providers/vsphere.rb +929 -0
- data/lib/vmpooler/public/bootstrap.min.css +5 -0
- data/lib/vmpooler/public/img/bg.png +0 -0
- data/lib/vmpooler/public/img/logo.gif +0 -0
- data/lib/vmpooler/public/img/spinner.svg +38 -0
- data/lib/vmpooler/public/img/subtle_dots.png +0 -0
- data/lib/vmpooler/public/img/textured_paper.png +0 -0
- data/lib/vmpooler/public/lib/bootstrap.min.js +7 -0
- data/lib/vmpooler/public/lib/d3.min.js +5 -0
- data/lib/vmpooler/public/lib/dashboard.js +738 -0
- data/lib/vmpooler/public/lib/jquery.min.js +4 -0
- data/lib/vmpooler/public/vmpooler.css +125 -0
- data/lib/vmpooler/statsd.rb +37 -0
- data/lib/vmpooler/version.rb +4 -0
- data/lib/vmpooler/views/dashboard.erb +63 -0
- data/lib/vmpooler/views/layout.erb +48 -0
- metadata +218 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
module Vmpooler
|
2
|
+
class PoolManager
|
3
|
+
class Provider
|
4
|
+
class Base
|
5
|
+
# These defs must be overidden in child classes
|
6
|
+
|
7
|
+
# Helper Methods
|
8
|
+
# Global Logger object
|
9
|
+
attr_reader :logger
|
10
|
+
# Global Metrics object
|
11
|
+
attr_reader :metrics
|
12
|
+
# Provider options passed in during initialization
|
13
|
+
attr_reader :provider_options
|
14
|
+
|
15
|
+
def initialize(config, logger, metrics, name, options)
|
16
|
+
@config = config
|
17
|
+
@logger = logger
|
18
|
+
@metrics = metrics
|
19
|
+
@provider_name = name
|
20
|
+
|
21
|
+
# Ensure that there is not a nil provider configuration
|
22
|
+
@config[:providers] = {} if @config[:providers].nil?
|
23
|
+
@config[:providers][@provider_name] = {} if provider_config.nil?
|
24
|
+
|
25
|
+
# Ensure that there is not a nil pool configuration
|
26
|
+
@config[:pools] = {} if @config[:pools].nil?
|
27
|
+
|
28
|
+
@provider_options = options
|
29
|
+
logger.log('s', "[!] Creating provider '#{name}'")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Helper Methods
|
33
|
+
|
34
|
+
# inputs
|
35
|
+
# [String] pool_name : Name of the pool to get the configuration
|
36
|
+
# returns
|
37
|
+
# [Hashtable] : The pools configuration from the config file. Returns nil if the pool does not exist
|
38
|
+
def pool_config(pool_name)
|
39
|
+
# Get the configuration of a specific pool
|
40
|
+
@config[:pools].each do |pool|
|
41
|
+
return pool if pool['name'] == pool_name
|
42
|
+
end
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns
|
48
|
+
# [Hashtable] : This provider's configuration from the config file. Returns nil if the provider does not exist
|
49
|
+
def provider_config
|
50
|
+
@config[:providers].each do |provider|
|
51
|
+
# Convert the symbol from the config into a string for comparison
|
52
|
+
return (provider[1].nil? ? {} : provider[1]) if provider[0].to_s == @provider_name
|
53
|
+
end
|
54
|
+
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns
|
59
|
+
# [Hashtable] : The entire VMPooler configuration
|
60
|
+
def global_config
|
61
|
+
# This entire VM Pooler config
|
62
|
+
@config
|
63
|
+
end
|
64
|
+
|
65
|
+
# returns
|
66
|
+
# [String] : Name of the provider service
|
67
|
+
def name
|
68
|
+
@provider_name
|
69
|
+
end
|
70
|
+
|
71
|
+
# returns
|
72
|
+
# Array[String] : Array of pool names this provider services
|
73
|
+
def provided_pools
|
74
|
+
list = []
|
75
|
+
@config[:pools].each do |pool|
|
76
|
+
list << pool['name'] if pool['provider'] == name
|
77
|
+
end
|
78
|
+
list
|
79
|
+
end
|
80
|
+
|
81
|
+
# Pool Manager Methods
|
82
|
+
|
83
|
+
# inputs
|
84
|
+
# [String] pool_name : Name of the pool
|
85
|
+
# returns
|
86
|
+
# Array[Hashtable]
|
87
|
+
# Hash contains:
|
88
|
+
# 'name' => [String] Name of VM
|
89
|
+
def vms_in_pool(_pool_name)
|
90
|
+
raise("#{self.class.name} does not implement vms_in_pool")
|
91
|
+
end
|
92
|
+
|
93
|
+
# inputs
|
94
|
+
# [String]pool_name : Name of the pool
|
95
|
+
# [String] vm_name : Name of the VM
|
96
|
+
# returns
|
97
|
+
# [String] : Name of the host computer running the vm. If this is not a Virtual Machine, it returns the vm_name
|
98
|
+
def get_vm_host(_pool_name, _vm_name)
|
99
|
+
raise("#{self.class.name} does not implement get_vm_host")
|
100
|
+
end
|
101
|
+
|
102
|
+
# inputs
|
103
|
+
# [String] pool_name : Name of the pool
|
104
|
+
# [String] vm_name : Name of the VM
|
105
|
+
# returns
|
106
|
+
# [String] : Name of the most appropriate host computer to run this VM. Useful for load balancing VMs in a cluster
|
107
|
+
# If this is not a Virtual Machine, it returns the vm_name
|
108
|
+
def find_least_used_compatible_host(_pool_name, _vm_name)
|
109
|
+
raise("#{self.class.name} does not implement find_least_used_compatible_host")
|
110
|
+
end
|
111
|
+
|
112
|
+
# inputs
|
113
|
+
# [String] pool_name : Name of the pool
|
114
|
+
# [String] vm_name : Name of the VM to migrate
|
115
|
+
# [String] dest_host_name : Name of the host to migrate `vm_name` to
|
116
|
+
# returns
|
117
|
+
# [Boolean] : true on success or false on failure
|
118
|
+
def migrate_vm_to_host(_pool_name, _vm_name, _dest_host_name)
|
119
|
+
raise("#{self.class.name} does not implement migrate_vm_to_host")
|
120
|
+
end
|
121
|
+
|
122
|
+
# inputs
|
123
|
+
# [String] pool_name : Name of the pool
|
124
|
+
# [String] vm_name : Name of the VM to migrate
|
125
|
+
# [Class] redis : Redis object
|
126
|
+
def migrate_vm(_pool_name, _vm_name, _redis)
|
127
|
+
raise("#{self.class.name} does not implement migrate_vm")
|
128
|
+
end
|
129
|
+
|
130
|
+
# inputs
|
131
|
+
# [String] pool_name : Name of the pool
|
132
|
+
# [String] vm_name : Name of the VM to find
|
133
|
+
# returns
|
134
|
+
# nil if VM doesn't exist
|
135
|
+
# [Hastable] of the VM
|
136
|
+
# [String] name : Name of the VM
|
137
|
+
# [String] hostname : Name reported by Vmware tools (host.summary.guest.hostName)
|
138
|
+
# [String] template : This is the name of template exposed by the API. It must _match_ the poolname
|
139
|
+
# [String] poolname : Name of the pool the VM is located
|
140
|
+
# [Time] boottime : Time when the VM was created/booted
|
141
|
+
# [String] powerstate : Current power state of a VM. Valid values (as per vCenter API)
|
142
|
+
# - 'PoweredOn','PoweredOff'
|
143
|
+
def get_vm(_pool_name, _vm_name)
|
144
|
+
raise("#{self.class.name} does not implement get_vm")
|
145
|
+
end
|
146
|
+
|
147
|
+
# inputs
|
148
|
+
# [String] pool : Name of the pool
|
149
|
+
# [String] new_vmname : Name to give the new VM
|
150
|
+
# returns
|
151
|
+
# [Hashtable] of the VM as per get_vm
|
152
|
+
# Raises RuntimeError if the pool_name is not supported by the Provider
|
153
|
+
def create_vm(_pool_name, _new_vmname)
|
154
|
+
raise("#{self.class.name} does not implement create_vm")
|
155
|
+
end
|
156
|
+
|
157
|
+
# inputs
|
158
|
+
# [String] pool_name : Name of the pool
|
159
|
+
# [String] vm_name : Name of the VM to create the disk on
|
160
|
+
# [Integer] disk_size : Size of the disk to create in Gigabytes (GB)
|
161
|
+
# returns
|
162
|
+
# [Boolean] : true if success, false if disk could not be created
|
163
|
+
# Raises RuntimeError if the Pool does not exist
|
164
|
+
# Raises RuntimeError if the VM does not exist
|
165
|
+
def create_disk(_pool_name, _vm_name, _disk_size)
|
166
|
+
raise("#{self.class.name} does not implement create_disk")
|
167
|
+
end
|
168
|
+
|
169
|
+
# inputs
|
170
|
+
# [String] pool_name : Name of the pool
|
171
|
+
# [String] new_vmname : Name of the VM to create the snapshot on
|
172
|
+
# [String] new_snapshot_name : Name of the new snapshot to create
|
173
|
+
# returns
|
174
|
+
# [Boolean] : true if success, false if snapshot could not be created
|
175
|
+
# Raises RuntimeError if the Pool does not exist
|
176
|
+
# Raises RuntimeError if the VM does not exist
|
177
|
+
def create_snapshot(_pool_name, _vm_name, _new_snapshot_name)
|
178
|
+
raise("#{self.class.name} does not implement create_snapshot")
|
179
|
+
end
|
180
|
+
|
181
|
+
# inputs
|
182
|
+
# [String] pool_name : Name of the pool
|
183
|
+
# [String] new_vmname : Name of the VM to restore
|
184
|
+
# [String] snapshot_name : Name of the snapshot to restore to
|
185
|
+
# returns
|
186
|
+
# [Boolean] : true if success, false if snapshot could not be revertted
|
187
|
+
# Raises RuntimeError if the Pool does not exist
|
188
|
+
# Raises RuntimeError if the VM does not exist
|
189
|
+
# Raises RuntimeError if the snapshot does not exist
|
190
|
+
def revert_snapshot(_pool_name, _vm_name, _snapshot_name)
|
191
|
+
raise("#{self.class.name} does not implement revert_snapshot")
|
192
|
+
end
|
193
|
+
|
194
|
+
# inputs
|
195
|
+
# [String] pool_name : Name of the pool
|
196
|
+
# [String] vm_name : Name of the VM to destroy
|
197
|
+
# returns
|
198
|
+
# [Boolean] : true if success, false on error. Should returns true if the VM is missing
|
199
|
+
def destroy_vm(_pool_name, _vm_name)
|
200
|
+
raise("#{self.class.name} does not implement destroy_vm")
|
201
|
+
end
|
202
|
+
|
203
|
+
# inputs
|
204
|
+
# [String] pool_name : Name of the pool
|
205
|
+
# [String] vm_name : Name of the VM to check if ready
|
206
|
+
# returns
|
207
|
+
# [Boolean] : true if ready, false if not
|
208
|
+
def vm_ready?(_pool_name, _vm_name)
|
209
|
+
raise("#{self.class.name} does not implement vm_ready?")
|
210
|
+
end
|
211
|
+
|
212
|
+
# inputs
|
213
|
+
# [String] pool_name : Name of the pool
|
214
|
+
# [String] vm_name : Name of the VM to check if it exists
|
215
|
+
# returns
|
216
|
+
# [Boolean] : true if it exists, false if not
|
217
|
+
def vm_exists?(pool_name, vm_name)
|
218
|
+
!get_vm(pool_name, vm_name).nil?
|
219
|
+
end
|
220
|
+
|
221
|
+
# inputs
|
222
|
+
# [Hash] pool : Configuration for the pool
|
223
|
+
# returns
|
224
|
+
# nil when successful. Raises error when encountered
|
225
|
+
def create_template_delta_disks(pool)
|
226
|
+
raise("#{self.class.name} does not implement create_template_delta_disks")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Vmpooler
|
4
|
+
class PoolManager
|
5
|
+
class Provider
|
6
|
+
class Dummy < Vmpooler::PoolManager::Provider::Base
|
7
|
+
# Fake VM Provider for testing
|
8
|
+
|
9
|
+
def initialize(config, logger, metrics, name, options)
|
10
|
+
super(config, logger, metrics, name, options)
|
11
|
+
dummyfilename = provider_config['filename']
|
12
|
+
|
13
|
+
# This initial_state option is only intended to be used by spec tests
|
14
|
+
@dummylist = provider_options['initial_state'].nil? ? {} : provider_options['initial_state']
|
15
|
+
|
16
|
+
@dummylist = YAML.load_file(dummyfilename) if !dummyfilename.nil? && File.exist?(dummyfilename)
|
17
|
+
|
18
|
+
# Even though this code is using Mutexes, it's still no 100% atomic i.e. it's still possible for
|
19
|
+
# duplicate actions to put the @dummylist hashtable into a bad state, for example;
|
20
|
+
# Deleting a VM while it's in the middle of adding a disk.
|
21
|
+
@write_lock = Mutex.new
|
22
|
+
|
23
|
+
# Create a dummy connection pool
|
24
|
+
connpool_size = provider_config['connection_pool_size'].nil? ? 1 : provider_config['connection_pool_size'].to_i
|
25
|
+
connpool_timeout = provider_config['connection_pool_timeout'].nil? ? 10 : provider_config['connection_pool_timeout'].to_i
|
26
|
+
logger.log('d', "[#{name}] ConnPool - Creating a connection pool of size #{connpool_size} with timeout #{connpool_timeout}")
|
27
|
+
@connection_pool = Vmpooler::PoolManager::GenericConnectionPool.new(
|
28
|
+
metrics: metrics,
|
29
|
+
metric_prefix: "#{name}_provider_connection_pool",
|
30
|
+
size: connpool_size,
|
31
|
+
timeout: connpool_timeout
|
32
|
+
) do
|
33
|
+
# Create a mock connection object
|
34
|
+
new_conn = { create_timestamp: Time.now, conn_id: rand(2048).to_s }
|
35
|
+
logger.log('d', "[#{name}] ConnPool - Creating a connection object ID #{new_conn[:conn_id]}")
|
36
|
+
new_conn
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def name
|
41
|
+
'dummy'
|
42
|
+
end
|
43
|
+
|
44
|
+
def vms_in_pool(pool_name)
|
45
|
+
vmlist = []
|
46
|
+
|
47
|
+
@connection_pool.with_metrics do |_conn|
|
48
|
+
get_dummy_pool_object(pool_name).each do |vm|
|
49
|
+
vmlist << { 'name' => vm['name'] }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
vmlist
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_vm_host(pool_name, vm_name)
|
57
|
+
current_vm = nil
|
58
|
+
@connection_pool.with_metrics do |_conn|
|
59
|
+
current_vm = get_dummy_vm(pool_name, vm_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
current_vm.nil? ? raise("VM #{vm_name} does not exist") : current_vm['vm_host']
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_least_used_compatible_host(pool_name, vm_name)
|
66
|
+
current_vm = nil
|
67
|
+
@connection_pool.with_metrics do |_conn|
|
68
|
+
current_vm = get_dummy_vm(pool_name, vm_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Unless migratevm_couldmove_percent is specified, don't migrate
|
72
|
+
return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil?
|
73
|
+
|
74
|
+
# Only migrate if migratevm_couldmove_percent is met
|
75
|
+
return current_vm['vm_host'] if 1 + rand(100) > provider_config['migratevm_couldmove_percent']
|
76
|
+
|
77
|
+
# Simulate a 10 node cluster and randomly pick a different one
|
78
|
+
new_host = 'HOST' + (1 + rand(10)).to_s while new_host == current_vm['vm_host']
|
79
|
+
|
80
|
+
new_host
|
81
|
+
end
|
82
|
+
|
83
|
+
def migrate_vm_to_host(pool_name, vm_name, dest_host_name)
|
84
|
+
@connection_pool.with_metrics do |_conn|
|
85
|
+
current_vm = get_dummy_vm(pool_name, vm_name)
|
86
|
+
|
87
|
+
# Inject migration delay
|
88
|
+
unless provider_config['migratevm_max_time'].nil?
|
89
|
+
migrate_time = 1 + rand(provider_config['migratevm_max_time'])
|
90
|
+
sleep(migrate_time)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Inject clone failure
|
94
|
+
unless provider_config['migratevm_fail_percent'].nil?
|
95
|
+
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['migratevm_fail_percent']
|
96
|
+
end
|
97
|
+
|
98
|
+
@write_lock.synchronize do
|
99
|
+
current_vm = get_dummy_vm(pool_name, vm_name)
|
100
|
+
current_vm['vm_host'] = dest_host_name
|
101
|
+
write_backing_file
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_vm(pool_name, vm_name)
|
109
|
+
obj = {}
|
110
|
+
@connection_pool.with_metrics do |_conn|
|
111
|
+
dummy = get_dummy_vm(pool_name, vm_name)
|
112
|
+
return nil if dummy.nil?
|
113
|
+
|
114
|
+
# Randomly power off the VM
|
115
|
+
unless dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?
|
116
|
+
if 1 + rand(100) <= provider_config['getvm_poweroff_percent']
|
117
|
+
@write_lock.synchronize do
|
118
|
+
dummy = get_dummy_vm(pool_name, vm_name)
|
119
|
+
dummy['powerstate'] = 'PoweredOff'
|
120
|
+
write_backing_file
|
121
|
+
end
|
122
|
+
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy Powered Off")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Randomly rename the host
|
127
|
+
unless dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?
|
128
|
+
if 1 + rand(100) <= provider_config['getvm_rename_percent']
|
129
|
+
@write_lock.synchronize do
|
130
|
+
dummy = get_dummy_vm(pool_name, vm_name)
|
131
|
+
dummy['hostname'] = 'DUMMY' + dummy['name']
|
132
|
+
write_backing_file
|
133
|
+
end
|
134
|
+
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy renamed")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
obj['name'] = dummy['name']
|
139
|
+
obj['hostname'] = dummy['hostname']
|
140
|
+
obj['boottime'] = dummy['boottime']
|
141
|
+
obj['template'] = dummy['template']
|
142
|
+
obj['poolname'] = dummy['poolname']
|
143
|
+
obj['powerstate'] = dummy['powerstate']
|
144
|
+
obj['snapshots'] = dummy['snapshots']
|
145
|
+
end
|
146
|
+
|
147
|
+
obj
|
148
|
+
end
|
149
|
+
|
150
|
+
def create_vm(pool_name, dummy_hostname)
|
151
|
+
pool = pool_config(pool_name)
|
152
|
+
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
|
153
|
+
|
154
|
+
template_name = pool['template']
|
155
|
+
|
156
|
+
vm = {}
|
157
|
+
vm['name'] = dummy_hostname
|
158
|
+
vm['hostname'] = dummy_hostname
|
159
|
+
vm['domain'] = 'dummy.local'
|
160
|
+
# 'vm_template' is the name of the template to use to clone the VM from <----- Do we need this?!?!?
|
161
|
+
vm['vm_template'] = template_name
|
162
|
+
# 'template' is the Template name in VM Pooler API, in our case that's the poolname.
|
163
|
+
vm['template'] = pool_name
|
164
|
+
vm['poolname'] = pool_name
|
165
|
+
vm['ready'] = false
|
166
|
+
vm['boottime'] = Time.now
|
167
|
+
vm['powerstate'] = 'PoweredOn'
|
168
|
+
vm['vm_host'] = 'HOST1'
|
169
|
+
vm['dummy_state'] = 'UNKNOWN'
|
170
|
+
vm['snapshots'] = []
|
171
|
+
vm['disks'] = []
|
172
|
+
|
173
|
+
# Make sure the pool exists in the dummy list
|
174
|
+
@write_lock.synchronize do
|
175
|
+
get_dummy_pool_object(pool_name)
|
176
|
+
@dummylist['pool'][pool_name] << vm
|
177
|
+
write_backing_file
|
178
|
+
end
|
179
|
+
|
180
|
+
logger.log('d', "[ ] [#{pool_name}] '#{dummy_hostname}' is being cloned from '#{template_name}'")
|
181
|
+
|
182
|
+
@connection_pool.with_metrics do |_conn|
|
183
|
+
# Inject clone time delay
|
184
|
+
unless provider_config['createvm_max_time'].nil?
|
185
|
+
@write_lock.synchronize do
|
186
|
+
vm['dummy_state'] = 'CLONING'
|
187
|
+
write_backing_file
|
188
|
+
end
|
189
|
+
clone_time = 1 + rand(provider_config['createvm_max_time'])
|
190
|
+
sleep(clone_time)
|
191
|
+
end
|
192
|
+
|
193
|
+
begin
|
194
|
+
# Inject clone failure
|
195
|
+
unless provider_config['createvm_fail_percent'].nil?
|
196
|
+
raise('Dummy Failure for createvm_fail_percent') if 1 + rand(100) <= provider_config['createvm_fail_percent']
|
197
|
+
end
|
198
|
+
|
199
|
+
# Assert the VM is ready for use
|
200
|
+
@write_lock.synchronize do
|
201
|
+
vm['dummy_state'] = 'RUNNING'
|
202
|
+
write_backing_file
|
203
|
+
end
|
204
|
+
rescue => _err
|
205
|
+
@write_lock.synchronize do
|
206
|
+
remove_dummy_vm(pool_name, dummy_hostname)
|
207
|
+
write_backing_file
|
208
|
+
end
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
get_vm(pool_name, dummy_hostname)
|
214
|
+
end
|
215
|
+
|
216
|
+
def create_disk(pool_name, vm_name, disk_size)
|
217
|
+
@connection_pool.with_metrics do |_conn|
|
218
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
219
|
+
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
|
220
|
+
|
221
|
+
# Inject create time delay
|
222
|
+
unless provider_config['createdisk_max_time'].nil?
|
223
|
+
delay = 1 + rand(provider_config['createdisk_max_time'])
|
224
|
+
sleep(delay)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Inject create failure
|
228
|
+
unless provider_config['createdisk_fail_percent'].nil?
|
229
|
+
raise('Dummy Failure for createdisk_fail_percent') if 1 + rand(100) <= provider_config['createdisk_fail_percent']
|
230
|
+
end
|
231
|
+
|
232
|
+
@write_lock.synchronize do
|
233
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
234
|
+
vm_object['disks'] << disk_size
|
235
|
+
write_backing_file
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
true
|
240
|
+
end
|
241
|
+
|
242
|
+
def create_snapshot(pool_name, vm_name, snapshot_name)
|
243
|
+
@connection_pool.with_metrics do |_conn|
|
244
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
245
|
+
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
|
246
|
+
|
247
|
+
# Inject create time delay
|
248
|
+
unless provider_config['createsnapshot_max_time'].nil?
|
249
|
+
delay = 1 + rand(provider_config['createsnapshot_max_time'])
|
250
|
+
sleep(delay)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Inject create failure
|
254
|
+
unless provider_config['createsnapshot_fail_percent'].nil?
|
255
|
+
raise('Dummy Failure for createsnapshot_fail_percent') if 1 + rand(100) <= provider_config['createsnapshot_fail_percent']
|
256
|
+
end
|
257
|
+
|
258
|
+
@write_lock.synchronize do
|
259
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
260
|
+
vm_object['snapshots'] << snapshot_name
|
261
|
+
write_backing_file
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
def revert_snapshot(pool_name, vm_name, snapshot_name)
|
269
|
+
vm_object = nil
|
270
|
+
@connection_pool.with_metrics do |_conn|
|
271
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
272
|
+
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
|
273
|
+
|
274
|
+
# Inject create time delay
|
275
|
+
unless provider_config['revertsnapshot_max_time'].nil?
|
276
|
+
delay = 1 + rand(provider_config['revertsnapshot_max_time'])
|
277
|
+
sleep(delay)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Inject create failure
|
281
|
+
unless provider_config['revertsnapshot_fail_percent'].nil?
|
282
|
+
raise('Dummy Failure for revertsnapshot_fail_percent') if 1 + rand(100) <= provider_config['revertsnapshot_fail_percent']
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
vm_object['snapshots'].include?(snapshot_name)
|
287
|
+
end
|
288
|
+
|
289
|
+
def destroy_vm(pool_name, vm_name)
|
290
|
+
@connection_pool.with_metrics do |_conn|
|
291
|
+
vm = get_dummy_vm(pool_name, vm_name)
|
292
|
+
return false if vm.nil?
|
293
|
+
return false if vm['poolname'] != pool_name
|
294
|
+
|
295
|
+
# Shutdown down the VM if it's poweredOn
|
296
|
+
if vm['powerstate'] == 'PoweredOn'
|
297
|
+
logger.log('d', "[ ] [#{pool_name}] '#{vm_name}' is being shut down")
|
298
|
+
|
299
|
+
# Inject shutdown delay time
|
300
|
+
unless provider_config['destroyvm_max_shutdown_time'].nil?
|
301
|
+
shutdown_time = 1 + rand(provider_config['destroyvm_max_shutdown_time'])
|
302
|
+
sleep(shutdown_time)
|
303
|
+
end
|
304
|
+
|
305
|
+
@write_lock.synchronize do
|
306
|
+
vm = get_dummy_vm(pool_name, vm_name)
|
307
|
+
vm['powerstate'] = 'PoweredOff'
|
308
|
+
write_backing_file
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Inject destroy VM delay
|
313
|
+
unless provider_config['destroyvm_max_time'].nil?
|
314
|
+
destroy_time = 1 + rand(provider_config['destroyvm_max_time'])
|
315
|
+
sleep(destroy_time)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Inject destroy VM failure
|
319
|
+
unless provider_config['destroyvm_fail_percent'].nil?
|
320
|
+
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['destroyvm_fail_percent']
|
321
|
+
end
|
322
|
+
|
323
|
+
# 'Destroy' the VM
|
324
|
+
@write_lock.synchronize do
|
325
|
+
remove_dummy_vm(pool_name, vm_name)
|
326
|
+
write_backing_file
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
true
|
331
|
+
end
|
332
|
+
|
333
|
+
def vm_ready?(pool_name, vm_name)
|
334
|
+
@connection_pool.with_metrics do |_conn|
|
335
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
336
|
+
return false if vm_object.nil?
|
337
|
+
return false if vm_object['poolname'] != pool_name
|
338
|
+
return true if vm_object['ready']
|
339
|
+
|
340
|
+
timeout = provider_config['is_ready_timeout'] || 5
|
341
|
+
|
342
|
+
Timeout.timeout(timeout) do
|
343
|
+
while vm_object['dummy_state'] != 'RUNNING'
|
344
|
+
sleep(2)
|
345
|
+
vm_object = get_dummy_vm(pool_name, vm_name)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Simulate how long it takes from a VM being powered on until
|
350
|
+
# it's ready to receive a connection
|
351
|
+
sleep(2)
|
352
|
+
|
353
|
+
unless provider_config['vmready_fail_percent'].nil?
|
354
|
+
raise('Dummy Failure for vmready_fail_percent') if 1 + rand(100) <= provider_config['vmready_fail_percent']
|
355
|
+
end
|
356
|
+
|
357
|
+
@write_lock.synchronize do
|
358
|
+
vm_object['ready'] = true
|
359
|
+
write_backing_file
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
true
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
# Note - NEVER EVER use the @write_lock object in the private methods!!!! Deadlocks will ensue
|
369
|
+
|
370
|
+
def remove_dummy_vm(pool_name, vm_name)
|
371
|
+
return if @dummylist['pool'][pool_name].nil?
|
372
|
+
new_poollist = @dummylist['pool'][pool_name].delete_if { |vm| vm['name'] == vm_name }
|
373
|
+
@dummylist['pool'][pool_name] = new_poollist
|
374
|
+
end
|
375
|
+
|
376
|
+
# Get's the pool config safely from the in-memory hashtable
|
377
|
+
def get_dummy_pool_object(pool_name)
|
378
|
+
@dummylist['pool'] = {} if @dummylist['pool'].nil?
|
379
|
+
@dummylist['pool'][pool_name] = [] if @dummylist['pool'][pool_name].nil?
|
380
|
+
|
381
|
+
@dummylist['pool'][pool_name]
|
382
|
+
end
|
383
|
+
|
384
|
+
def get_dummy_vm(pool_name, vm_name)
|
385
|
+
return nil if @dummylist['pool'][pool_name].nil?
|
386
|
+
|
387
|
+
@dummylist['pool'][pool_name].each do |poolvm|
|
388
|
+
return poolvm if poolvm['name'] == vm_name
|
389
|
+
end
|
390
|
+
|
391
|
+
nil
|
392
|
+
end
|
393
|
+
|
394
|
+
def write_backing_file
|
395
|
+
dummyfilename = provider_config['filename']
|
396
|
+
return if dummyfilename.nil?
|
397
|
+
File.open(dummyfilename, 'w') { |file| file.write(YAML.dump(@dummylist)) }
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|