vmpooler 0.18.0 → 1.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 +4 -4
- data/lib/vmpooler.rb +9 -14
- data/lib/vmpooler/api/dashboard.rb +18 -20
- data/lib/vmpooler/api/helpers.rb +11 -13
- data/lib/vmpooler/api/v1.rb +93 -52
- data/lib/vmpooler/metrics/dummy_statsd.rb +0 -3
- data/lib/vmpooler/metrics/graphite.rb +2 -0
- data/lib/vmpooler/metrics/promstats.rb +17 -15
- data/lib/vmpooler/metrics/promstats/collector_middleware.rb +1 -1
- data/lib/vmpooler/metrics/statsd.rb +5 -3
- data/lib/vmpooler/pool_manager.rb +48 -98
- data/lib/vmpooler/providers/dummy.rb +20 -38
- data/lib/vmpooler/providers/vsphere.rb +18 -27
- data/lib/vmpooler/version.rb +1 -1
- metadata +38 -46
@@ -79,7 +79,7 @@ module Vmpooler
|
|
79
79
|
return current_vm['vm_host'] if rand(1..100) > provider_config['migratevm_couldmove_percent']
|
80
80
|
|
81
81
|
# Simulate a 10 node cluster and randomly pick a different one
|
82
|
-
new_host =
|
82
|
+
new_host = "HOST#{rand(1..10)}" while new_host == current_vm['vm_host']
|
83
83
|
|
84
84
|
new_host
|
85
85
|
end
|
@@ -95,9 +95,7 @@ module Vmpooler
|
|
95
95
|
end
|
96
96
|
|
97
97
|
# Inject clone failure
|
98
|
-
|
99
|
-
raise('Dummy Failure for migratevm_fail_percent') if rand(1..100) <= provider_config['migratevm_fail_percent']
|
100
|
-
end
|
98
|
+
raise('Dummy Failure for migratevm_fail_percent') if !provider_config['migratevm_fail_percent'].nil? && rand(1..100) <= provider_config['migratevm_fail_percent']
|
101
99
|
|
102
100
|
@write_lock.synchronize do
|
103
101
|
current_vm = get_dummy_vm(pool_name, vm_name)
|
@@ -116,27 +114,23 @@ module Vmpooler
|
|
116
114
|
return nil if dummy.nil?
|
117
115
|
|
118
116
|
# Randomly power off the VM
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
write_backing_file
|
125
|
-
end
|
126
|
-
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy Powered Off")
|
117
|
+
if !(dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?) && rand(1..100) <= provider_config['getvm_poweroff_percent']
|
118
|
+
@write_lock.synchronize do
|
119
|
+
dummy = get_dummy_vm(pool_name, vm_name)
|
120
|
+
dummy['powerstate'] = 'PoweredOff'
|
121
|
+
write_backing_file
|
127
122
|
end
|
123
|
+
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy Powered Off")
|
128
124
|
end
|
129
125
|
|
130
126
|
# Randomly rename the host
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
write_backing_file
|
137
|
-
end
|
138
|
-
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy renamed")
|
127
|
+
if !(dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?) && rand(1..100) <= provider_config['getvm_rename_percent']
|
128
|
+
@write_lock.synchronize do
|
129
|
+
dummy = get_dummy_vm(pool_name, vm_name)
|
130
|
+
dummy['hostname'] = "DUMMY#{dummy['name']}"
|
131
|
+
write_backing_file
|
139
132
|
end
|
133
|
+
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy renamed")
|
140
134
|
end
|
141
135
|
|
142
136
|
obj['name'] = dummy['name']
|
@@ -196,9 +190,7 @@ module Vmpooler
|
|
196
190
|
|
197
191
|
begin
|
198
192
|
# Inject clone failure
|
199
|
-
|
200
|
-
raise('Dummy Failure for createvm_fail_percent') if rand(1..100) <= provider_config['createvm_fail_percent']
|
201
|
-
end
|
193
|
+
raise('Dummy Failure for createvm_fail_percent') if !provider_config['createvm_fail_percent'].nil? && rand(1..100) <= provider_config['createvm_fail_percent']
|
202
194
|
|
203
195
|
# Assert the VM is ready for use
|
204
196
|
@write_lock.synchronize do
|
@@ -229,9 +221,7 @@ module Vmpooler
|
|
229
221
|
end
|
230
222
|
|
231
223
|
# Inject create failure
|
232
|
-
|
233
|
-
raise('Dummy Failure for createdisk_fail_percent') if rand(1..100) <= provider_config['createdisk_fail_percent']
|
234
|
-
end
|
224
|
+
raise('Dummy Failure for createdisk_fail_percent') if !provider_config['createdisk_fail_percent'].nil? && rand(1..100) <= provider_config['createdisk_fail_percent']
|
235
225
|
|
236
226
|
@write_lock.synchronize do
|
237
227
|
vm_object = get_dummy_vm(pool_name, vm_name)
|
@@ -255,9 +245,7 @@ module Vmpooler
|
|
255
245
|
end
|
256
246
|
|
257
247
|
# Inject create failure
|
258
|
-
|
259
|
-
raise('Dummy Failure for createsnapshot_fail_percent') if rand(1..100) <= provider_config['createsnapshot_fail_percent']
|
260
|
-
end
|
248
|
+
raise('Dummy Failure for createsnapshot_fail_percent') if !provider_config['createsnapshot_fail_percent'].nil? && rand(1..100) <= provider_config['createsnapshot_fail_percent']
|
261
249
|
|
262
250
|
@write_lock.synchronize do
|
263
251
|
vm_object = get_dummy_vm(pool_name, vm_name)
|
@@ -282,9 +270,7 @@ module Vmpooler
|
|
282
270
|
end
|
283
271
|
|
284
272
|
# Inject create failure
|
285
|
-
|
286
|
-
raise('Dummy Failure for revertsnapshot_fail_percent') if rand(1..100) <= provider_config['revertsnapshot_fail_percent']
|
287
|
-
end
|
273
|
+
raise('Dummy Failure for revertsnapshot_fail_percent') if !provider_config['revertsnapshot_fail_percent'].nil? && rand(1..100) <= provider_config['revertsnapshot_fail_percent']
|
288
274
|
end
|
289
275
|
|
290
276
|
vm_object['snapshots'].include?(snapshot_name)
|
@@ -320,9 +306,7 @@ module Vmpooler
|
|
320
306
|
end
|
321
307
|
|
322
308
|
# Inject destroy VM failure
|
323
|
-
|
324
|
-
raise('Dummy Failure for migratevm_fail_percent') if rand(1..100) <= provider_config['destroyvm_fail_percent']
|
325
|
-
end
|
309
|
+
raise('Dummy Failure for migratevm_fail_percent') if !provider_config['destroyvm_fail_percent'].nil? && rand(1..100) <= provider_config['destroyvm_fail_percent']
|
326
310
|
|
327
311
|
# 'Destroy' the VM
|
328
312
|
@write_lock.synchronize do
|
@@ -354,9 +338,7 @@ module Vmpooler
|
|
354
338
|
# it's ready to receive a connection
|
355
339
|
sleep(2)
|
356
340
|
|
357
|
-
|
358
|
-
raise('Dummy Failure for vmready_fail_percent') if rand(1..100) <= provider_config['vmready_fail_percent']
|
359
|
-
end
|
341
|
+
raise('Dummy Failure for vmready_fail_percent') if !provider_config['vmready_fail_percent'].nil? && rand(1..100) <= provider_config['vmready_fail_percent']
|
360
342
|
|
361
343
|
@write_lock.synchronize do
|
362
344
|
vm_object['ready'] = true
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'vmpooler/providers/base'
|
4
|
+
require 'bigdecimal'
|
5
|
+
require 'bigdecimal/util'
|
4
6
|
|
5
7
|
module Vmpooler
|
6
8
|
class PoolManager
|
@@ -49,9 +51,7 @@ module Vmpooler
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def folder_configured?(folder_title, base_folder, configured_folders, whitelist)
|
52
|
-
if whitelist
|
53
|
-
return true if whitelist.include?(folder_title)
|
54
|
-
end
|
54
|
+
return true if whitelist&.include?(folder_title)
|
55
55
|
return false unless configured_folders.keys.include?(folder_title)
|
56
56
|
return false unless configured_folders[folder_title] == base_folder
|
57
57
|
|
@@ -68,7 +68,7 @@ module Vmpooler
|
|
68
68
|
redis.hset("vmpooler__vm__#{vm_name}", 'destroy', Time.now)
|
69
69
|
|
70
70
|
# Auto-expire metadata key
|
71
|
-
redis.expire(
|
71
|
+
redis.expire("vmpooler__vm__#{vm_name}", (data_ttl * 60 * 60))
|
72
72
|
redis.exec
|
73
73
|
end
|
74
74
|
|
@@ -203,9 +203,7 @@ module Vmpooler
|
|
203
203
|
return
|
204
204
|
end
|
205
205
|
wait_for_host_selection(dc, target, loop_delay, max_age) if target[dc].key?('checking')
|
206
|
-
if target[dc].key?('check_time_finished')
|
207
|
-
select_target_hosts(target, cluster, datacenter) if now - target[dc]['check_time_finished'] > max_age
|
208
|
-
end
|
206
|
+
select_target_hosts(target, cluster, datacenter) if target[dc].key?('check_time_finished') && now - target[dc]['check_time_finished'] > max_age
|
209
207
|
end
|
210
208
|
|
211
209
|
def wait_for_host_selection(dc, target, maxloop = 0, loop_delay = 1, max_age = 60)
|
@@ -418,15 +416,15 @@ module Vmpooler
|
|
418
416
|
# Determine network device type
|
419
417
|
# All possible device type options here: https://vdc-download.vmware.com/vmwb-repository/dcr-public/98d63b35-d822-47fe-a87a-ddefd469df06/2e3c7b58-f2bd-486e-8bb1-a75eb0640bee/doc/vim.vm.device.VirtualEthernetCard.html
|
420
418
|
network_device =
|
421
|
-
if template_vm_network_device.
|
419
|
+
if template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet2
|
422
420
|
RbVmomi::VIM.VirtualVmxnet2
|
423
|
-
elsif template_vm_network_device.
|
421
|
+
elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualVmxnet3
|
424
422
|
RbVmomi::VIM.VirtualVmxnet3
|
425
|
-
elsif template_vm_network_device.
|
423
|
+
elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000
|
426
424
|
RbVmomi::VIM.VirtualE1000
|
427
|
-
elsif template_vm_network_device.
|
425
|
+
elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualE1000e
|
428
426
|
RbVmomi::VIM.VirtualE1000e
|
429
|
-
elsif template_vm_network_device.
|
427
|
+
elsif template_vm_network_device.instance_of? RbVmomi::VIM::VirtualSriovEthernetCard
|
430
428
|
RbVmomi::VIM.VirtualSriovEthernetCard
|
431
429
|
else
|
432
430
|
RbVmomi::VIM.VirtualPCNet32
|
@@ -560,7 +558,7 @@ module Vmpooler
|
|
560
558
|
boottime = vm_object.runtime.bootTime if vm_object.runtime&.bootTime
|
561
559
|
powerstate = vm_object.runtime.powerState if vm_object.runtime&.powerState
|
562
560
|
|
563
|
-
|
561
|
+
{
|
564
562
|
'name' => vm_object.name,
|
565
563
|
'hostname' => hostname,
|
566
564
|
'template' => pool_configuration['template'],
|
@@ -568,8 +566,6 @@ module Vmpooler
|
|
568
566
|
'boottime' => boottime,
|
569
567
|
'powerstate' => powerstate
|
570
568
|
}
|
571
|
-
|
572
|
-
hash
|
573
569
|
end
|
574
570
|
|
575
571
|
# vSphere helper methods
|
@@ -794,7 +790,7 @@ module Vmpooler
|
|
794
790
|
}
|
795
791
|
|
796
792
|
folder_object = connection.searchIndex.FindByInventoryPath(propSpecs) # rubocop:disable Naming/VariableName
|
797
|
-
return nil unless folder_object.
|
793
|
+
return nil unless folder_object.instance_of? RbVmomi::VIM::Folder
|
798
794
|
|
799
795
|
folder_object
|
800
796
|
end
|
@@ -810,9 +806,7 @@ module Vmpooler
|
|
810
806
|
# the cpu or memory utilization is bigger than the limit param
|
811
807
|
def get_host_utilization(host, model = nil, limit = 90)
|
812
808
|
limit = @config[:config]['utilization_limit'] if @config[:config].key?('utilization_limit')
|
813
|
-
if model
|
814
|
-
return nil unless host_has_cpu_model?(host, model)
|
815
|
-
end
|
809
|
+
return nil if model && !host_has_cpu_model?(host, model)
|
816
810
|
return nil if host.runtime.inMaintenanceMode
|
817
811
|
return nil unless host.overallStatus == 'green'
|
818
812
|
return nil unless host.configIssue.empty?
|
@@ -821,9 +815,9 @@ module Vmpooler
|
|
821
815
|
memory_utilization = memory_utilization_for host
|
822
816
|
|
823
817
|
return nil if cpu_utilization.nil?
|
824
|
-
return nil if cpu_utilization == 0.0
|
818
|
+
return nil if cpu_utilization.to_d == 0.0.to_d
|
825
819
|
return nil if memory_utilization.nil?
|
826
|
-
return nil if memory_utilization == 0.0
|
820
|
+
return nil if memory_utilization.to_d == 0.0.to_d
|
827
821
|
|
828
822
|
return nil if cpu_utilization > limit
|
829
823
|
return nil if memory_utilization > limit
|
@@ -838,8 +832,7 @@ module Vmpooler
|
|
838
832
|
def get_host_cpu_arch_version(host)
|
839
833
|
cpu_model = host.hardware.cpuPkg[0].description
|
840
834
|
cpu_model_parts = cpu_model.split
|
841
|
-
|
842
|
-
arch_version
|
835
|
+
cpu_model_parts[4]
|
843
836
|
end
|
844
837
|
|
845
838
|
def cpu_utilization_for(host)
|
@@ -931,8 +924,7 @@ module Vmpooler
|
|
931
924
|
target_hosts = get_cluster_host_utilization(cluster_object)
|
932
925
|
raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty?
|
933
926
|
|
934
|
-
|
935
|
-
least_used_host
|
927
|
+
target_hosts.min[1]
|
936
928
|
end
|
937
929
|
|
938
930
|
def find_cluster(cluster, connection, datacentername)
|
@@ -979,11 +971,10 @@ module Vmpooler
|
|
979
971
|
end
|
980
972
|
|
981
973
|
def build_propSpecs(datacenter, folder, vmname) # rubocop:disable Naming/MethodName
|
982
|
-
|
974
|
+
{
|
983
975
|
entity => self,
|
984
976
|
:inventoryPath => "#{datacenter}/vm/#{folder}/#{vmname}"
|
985
977
|
}
|
986
|
-
propSpecs # rubocop:disable Naming/VariableName
|
987
978
|
end
|
988
979
|
|
989
980
|
def find_vm(pool_name, vmname, connection)
|
data/lib/vmpooler/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vmpooler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -66,104 +66,90 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.10'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: opentelemetry-api
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 0.7.0
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 0.7.0
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: opentelemetry-exporter-jaeger
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
|
-
- -
|
73
|
+
- - '='
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
75
|
+
version: 0.15.0
|
90
76
|
type: :runtime
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
|
-
- -
|
80
|
+
- - '='
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
82
|
+
version: 0.15.0
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: opentelemetry-instrumentation-concurrent_ruby
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
|
-
- -
|
87
|
+
- - '='
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
89
|
+
version: 0.15.0
|
104
90
|
type: :runtime
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
|
-
- -
|
94
|
+
- - '='
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
96
|
+
version: 0.15.0
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: opentelemetry-instrumentation-redis
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
|
-
- -
|
101
|
+
- - '='
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
103
|
+
version: 0.15.0
|
118
104
|
type: :runtime
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
|
-
- -
|
108
|
+
- - '='
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
110
|
+
version: 0.15.0
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: opentelemetry-instrumentation-sinatra
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
|
-
- -
|
115
|
+
- - '='
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: 0.
|
117
|
+
version: 0.15.0
|
132
118
|
type: :runtime
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
|
-
- -
|
122
|
+
- - '='
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: 0.
|
124
|
+
version: 0.15.0
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: opentelemetry-resource_detectors
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- -
|
129
|
+
- - '='
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version: 0.
|
131
|
+
version: 0.15.0
|
146
132
|
type: :runtime
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
|
-
- -
|
136
|
+
- - '='
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version: 0.
|
138
|
+
version: 0.15.0
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: opentelemetry-sdk
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
156
142
|
requirements:
|
157
|
-
- -
|
143
|
+
- - '='
|
158
144
|
- !ruby/object:Gem::Version
|
159
|
-
version: 0.
|
145
|
+
version: 0.15.0
|
160
146
|
type: :runtime
|
161
147
|
prerelease: false
|
162
148
|
version_requirements: !ruby/object:Gem::Requirement
|
163
149
|
requirements:
|
164
|
-
- -
|
150
|
+
- - '='
|
165
151
|
- !ruby/object:Gem::Version
|
166
|
-
version: 0.
|
152
|
+
version: 0.15.0
|
167
153
|
- !ruby/object:Gem::Dependency
|
168
154
|
name: pickup
|
169
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -198,14 +184,20 @@ dependencies:
|
|
198
184
|
requirements:
|
199
185
|
- - "~>"
|
200
186
|
- !ruby/object:Gem::Version
|
201
|
-
version: '
|
187
|
+
version: '5.0'
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: 5.0.4
|
202
191
|
type: :runtime
|
203
192
|
prerelease: false
|
204
193
|
version_requirements: !ruby/object:Gem::Requirement
|
205
194
|
requirements:
|
206
195
|
- - "~>"
|
207
196
|
- !ruby/object:Gem::Version
|
208
|
-
version: '
|
197
|
+
version: '5.0'
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: 5.0.4
|
209
201
|
- !ruby/object:Gem::Dependency
|
210
202
|
name: rack
|
211
203
|
requirement: !ruby/object:Gem::Requirement
|
@@ -384,16 +376,16 @@ dependencies:
|
|
384
376
|
name: rubocop
|
385
377
|
requirement: !ruby/object:Gem::Requirement
|
386
378
|
requirements:
|
387
|
-
- - "
|
379
|
+
- - "~>"
|
388
380
|
- !ruby/object:Gem::Version
|
389
|
-
version:
|
381
|
+
version: 1.1.0
|
390
382
|
type: :development
|
391
383
|
prerelease: false
|
392
384
|
version_requirements: !ruby/object:Gem::Requirement
|
393
385
|
requirements:
|
394
|
-
- - "
|
386
|
+
- - "~>"
|
395
387
|
- !ruby/object:Gem::Version
|
396
|
-
version:
|
388
|
+
version: 1.1.0
|
397
389
|
- !ruby/object:Gem::Dependency
|
398
390
|
name: simplecov
|
399
391
|
requirement: !ruby/object:Gem::Requirement
|