vm_shepherd 0.1.2 → 0.2.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/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/lib/vm_shepherd/aws_manager.rb +27 -2
- data/lib/vm_shepherd/openstack_manager.rb +3 -0
- data/lib/vm_shepherd/shepherd.rb +40 -5
- data/lib/vm_shepherd/vcloud_manager.rb +15 -8
- data/lib/vm_shepherd/version.rb +1 -1
- data/lib/vm_shepherd/vsphere_manager.rb +40 -6
- data/spec/fixtures/shepherd/vcloud.yml +4 -1
- data/spec/fixtures/shepherd/vsphere.yml +5 -0
- data/spec/vm_shepherd/aws_manager_spec.rb +73 -3
- data/spec/vm_shepherd/shepherd_spec.rb +126 -13
- data/spec/vm_shepherd/vcloud_manager_spec.rb +4 -5
- data/spec/vm_shepherd/vsphere_manager_spec.rb +51 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6cdd5ba469fb3cfa60e574e87ef6c374432b22f
|
4
|
+
data.tar.gz: dd14790e5da479762cbb30a2d65b550df146d636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f9f862403579de226ee581bbc3ff9305ff95657e23e03c278a73972e8c74e21d43310b33f89be4e31914a7602b800dfe59b010297e1cdb8fbb86e8452bafb65
|
7
|
+
data.tar.gz: 1031f76997f1fdb383d1ac36de08c9edae15cad882df6290b9cb62d423e357b9d67b3947e34e2b8c77302125c39085768c780def915be2b180483f5d1fc363ae
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1
|
1
|
+
2.2.1
|
@@ -42,15 +42,30 @@ module VmShepherd
|
|
42
42
|
instance.add_tag('Name', value: aws_options.fetch(:vm_name))
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def clean_environment
|
46
46
|
subnets = [
|
47
47
|
AWS.ec2.subnets[aws_options.fetch(:public_subnet_id)],
|
48
48
|
AWS.ec2.subnets[aws_options.fetch(:private_subnet_id)]
|
49
49
|
]
|
50
50
|
|
51
|
+
volumes = []
|
51
52
|
subnets.each do |subnet|
|
52
53
|
subnet.instances.each do |instance|
|
53
|
-
|
54
|
+
unless instance.tags.to_h.fetch(DO_NOT_TERMINATE_TAG_KEY, false)
|
55
|
+
instance.attachments.each do |_, attachment|
|
56
|
+
volumes.push(attachment.volume) unless attachment.delete_on_termination
|
57
|
+
end
|
58
|
+
instance.terminate
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
destroy_volumes(volumes)
|
63
|
+
end
|
64
|
+
|
65
|
+
def destroy
|
66
|
+
AWS.ec2.elastic_ips.each do |elastic_ip|
|
67
|
+
if elastic_ip.allocation_id == aws_options.fetch(:elastic_ip_id)
|
68
|
+
elastic_ip.instance.terminate if elastic_ip.instance
|
54
69
|
end
|
55
70
|
end
|
56
71
|
end
|
@@ -58,6 +73,16 @@ module VmShepherd
|
|
58
73
|
private
|
59
74
|
attr_reader :aws_options
|
60
75
|
|
76
|
+
def destroy_volumes(volumes)
|
77
|
+
volumes.each do |volume|
|
78
|
+
begin
|
79
|
+
volume.delete
|
80
|
+
rescue AWS::EC2::Errors::VolumeInUse
|
81
|
+
sleep 5
|
82
|
+
retry
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
61
86
|
|
62
87
|
def retry_ignoring_error_until(exception_class, &block)
|
63
88
|
tries = 0
|
data/lib/vm_shepherd/shepherd.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
module VmShepherd
|
2
2
|
class Shepherd
|
3
|
-
class InvalidIaas < StandardError
|
4
|
-
|
5
|
-
end
|
3
|
+
class InvalidIaas < StandardError; end
|
6
4
|
|
7
5
|
def initialize(settings:)
|
8
6
|
@settings = settings
|
@@ -76,14 +74,14 @@ module VmShepherd
|
|
76
74
|
network: settings.vapp_deployer.vdc.network,
|
77
75
|
},
|
78
76
|
logger
|
79
|
-
).destroy(settings.vapp_deployer.vapp.
|
77
|
+
).destroy([settings.vapp_deployer.vapp.ops_manager_name] + settings.vapp_deployer.vapp.product_names)
|
80
78
|
when VmShepherd::VSPHERE_IAAS_TYPE then
|
81
79
|
VmShepherd::VsphereManager.new(
|
82
80
|
settings.vm_deployer.vcenter_creds.ip,
|
83
81
|
settings.vm_deployer.vcenter_creds.username,
|
84
82
|
settings.vm_deployer.vcenter_creds.password,
|
85
83
|
settings.vm_deployer.vsphere.datacenter,
|
86
|
-
).destroy(settings.vm_deployer.
|
84
|
+
).destroy(settings.vm_deployer.vm.ip)
|
87
85
|
when VmShepherd::AWS_IAAS_TYPE then
|
88
86
|
ami_manager.destroy
|
89
87
|
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
@@ -93,6 +91,43 @@ module VmShepherd
|
|
93
91
|
end
|
94
92
|
end
|
95
93
|
|
94
|
+
def clean_environment
|
95
|
+
case settings.iaas_type
|
96
|
+
when VmShepherd::VCLOUD_IAAS_TYPE then
|
97
|
+
VmShepherd::VcloudManager.new(
|
98
|
+
{
|
99
|
+
url: settings.vapp_deployer.creds.url,
|
100
|
+
organization: settings.vapp_deployer.creds.organization,
|
101
|
+
user: settings.vapp_deployer.creds.user,
|
102
|
+
password: settings.vapp_deployer.creds.password,
|
103
|
+
},
|
104
|
+
{
|
105
|
+
vdc: settings.vapp_deployer.vdc.name,
|
106
|
+
catalog: settings.vapp_deployer.vdc.catalog,
|
107
|
+
network: settings.vapp_deployer.vdc.network,
|
108
|
+
},
|
109
|
+
logger
|
110
|
+
).clean_environment
|
111
|
+
when VmShepherd::VSPHERE_IAAS_TYPE then
|
112
|
+
VmShepherd::VsphereManager.new(
|
113
|
+
settings.vm_deployer.vcenter_creds.ip,
|
114
|
+
settings.vm_deployer.vcenter_creds.username,
|
115
|
+
settings.vm_deployer.vcenter_creds.password,
|
116
|
+
settings.vm_deployer.vsphere.datacenter,
|
117
|
+
).clean_environment(
|
118
|
+
datacenter_folders_to_clean: settings.vm_deployer.vsphere.datacenter_folders_to_clean,
|
119
|
+
datastore: settings.vm_deployer.vsphere.datastore,
|
120
|
+
datastore_folders_to_clean: settings.vm_deployer.vsphere.datastore_folders_to_clean,
|
121
|
+
)
|
122
|
+
when VmShepherd::AWS_IAAS_TYPE then
|
123
|
+
ami_manager.clean_environment
|
124
|
+
when VmShepherd::OPENSTACK_IAAS_TYPE then
|
125
|
+
openstack_vm_manager.clean_environment
|
126
|
+
else
|
127
|
+
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
96
131
|
private
|
97
132
|
|
98
133
|
attr_reader :settings
|
@@ -27,11 +27,14 @@ module VmShepherd
|
|
27
27
|
FileUtils.remove_entry_secure(tmpdir, force: true)
|
28
28
|
end
|
29
29
|
|
30
|
-
def destroy(
|
31
|
-
|
30
|
+
def destroy(vapp_names)
|
31
|
+
delete_vapps(vapp_names)
|
32
32
|
delete_catalog
|
33
33
|
end
|
34
34
|
|
35
|
+
def clean_environment
|
36
|
+
end
|
37
|
+
|
35
38
|
private
|
36
39
|
|
37
40
|
def check_vapp_status(vapp_config)
|
@@ -155,12 +158,16 @@ module VmShepherd
|
|
155
158
|
@vdc ||= client.find_vdc_by_name(@location[:vdc])
|
156
159
|
end
|
157
160
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
161
|
+
def delete_vapps(vapp_names)
|
162
|
+
vapp_names.each do |vapp_name|
|
163
|
+
begin
|
164
|
+
vapp = vdc.find_vapp_by_name(vapp_name)
|
165
|
+
vapp.power_off
|
166
|
+
vapp.delete
|
167
|
+
rescue VCloudSdk::ObjectNotFoundError => e
|
168
|
+
@logger.debug "Could not delete vapp '#{vapp_name}': #{e.inspect}"
|
169
|
+
end
|
170
|
+
end
|
164
171
|
end
|
165
172
|
|
166
173
|
def delete_catalog
|
data/lib/vm_shepherd/version.rb
CHANGED
@@ -5,6 +5,9 @@ module VmShepherd
|
|
5
5
|
class VsphereManager
|
6
6
|
TEMPLATE_PREFIX = 'tpl'.freeze
|
7
7
|
VALID_FOLDER_REGEX = /\A([\w-]{1,80}\/)*[\w-]{1,80}\/?\z/
|
8
|
+
VALID_DISK_FOLDER_REGEX = /\A[\w-]{1,80}\z/
|
9
|
+
|
10
|
+
attr_writer :logger
|
8
11
|
|
9
12
|
def initialize(host, username, password, datacenter_name)
|
10
13
|
@host = host
|
@@ -26,10 +29,41 @@ module VmShepherd
|
|
26
29
|
FileUtils.remove_entry_secure(ovf_file_path, force: true) unless ovf_file_path.nil?
|
27
30
|
end
|
28
31
|
|
29
|
-
def
|
30
|
-
|
32
|
+
def clean_environment(datacenter_folders_to_clean:, datastore:, datastore_folders_to_clean:)
|
33
|
+
datacenter_folders_to_clean.each do |folder_name|
|
34
|
+
validate_folder_name!(folder_name)
|
35
|
+
delete_folder_and_vms(folder_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
datastore_folders_to_clean.each do |folder_name|
|
39
|
+
VALID_DISK_FOLDER_REGEX.match(folder_name) || fail("#{folder_name.inspect} is not a valid disk folder name")
|
40
|
+
begin
|
41
|
+
logger.info("BEGIN datastore_folder.destroy_task folder=#{folder_name}")
|
42
|
+
|
43
|
+
connection.serviceContent.fileManager.DeleteDatastoreFile_Task(
|
44
|
+
datacenter: datacenter,
|
45
|
+
name: "[#{datastore}] #{folder_name}"
|
46
|
+
).wait_for_completion
|
47
|
+
|
48
|
+
logger.info("END datastore_folder.destroy_task folder=#{folder_name}")
|
49
|
+
rescue RbVmomi::Fault => e
|
50
|
+
logger.info("ERROR datastore_folder.destroy_task folder=#{folder_name} #{e.inspect}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroy(ip_address)
|
56
|
+
vm = datacenter.vmFolder.findByIp(ip_address)
|
57
|
+
return unless vm
|
58
|
+
power_off_vm(vm)
|
59
|
+
destroy_vm(vm)
|
60
|
+
end
|
31
61
|
|
32
|
-
|
62
|
+
def destroy_vm(vm)
|
63
|
+
vm_name = vm.name
|
64
|
+
logger.info("BEGIN vm.destroy_task vm=#{vm_name}")
|
65
|
+
vm.Destroy_Task.wait_for_completion
|
66
|
+
logger.info("END vm.destroy_task vm=#{vm_name}")
|
33
67
|
end
|
34
68
|
|
35
69
|
private
|
@@ -79,7 +113,7 @@ module VmShepherd
|
|
79
113
|
folder = datacenter.vmFolder.traverse(folder_name) ||
|
80
114
|
fail("ERROR no folder found with name=#{folder_name.inspect}")
|
81
115
|
|
82
|
-
find_vms(folder).each { |vm|
|
116
|
+
find_vms(folder).each { |vm| power_off_vm(vm) }
|
83
117
|
|
84
118
|
logger.info("BEGIN folder.destroy_task folder=#{folder_name}")
|
85
119
|
folder.Destroy_Task.wait_for_completion
|
@@ -87,7 +121,7 @@ module VmShepherd
|
|
87
121
|
|
88
122
|
fail("#{folder_name.inspect} already exists") unless datacenter.vmFolder.traverse(folder_name).nil?
|
89
123
|
rescue RbVmomi::Fault => e
|
90
|
-
logger.info("ERROR folder.destroy_task folder=#{folder_name}
|
124
|
+
logger.info("ERROR folder.destroy_task folder=#{folder_name} #{e.inspect}")
|
91
125
|
raise
|
92
126
|
end
|
93
127
|
|
@@ -97,7 +131,7 @@ module VmShepherd
|
|
97
131
|
vms.flatten
|
98
132
|
end
|
99
133
|
|
100
|
-
def
|
134
|
+
def power_off_vm(vm)
|
101
135
|
2.times do
|
102
136
|
break if vm.runtime.powerState == 'poweredOff'
|
103
137
|
|
@@ -12,6 +12,11 @@ vm_deployer:
|
|
12
12
|
resource_pool: VSPHERE_RESOURCE_POOL
|
13
13
|
datastore: VSPHERE_DATASTORE
|
14
14
|
folder: VSPHERE_FOLDER
|
15
|
+
datacenter_folders_to_clean:
|
16
|
+
- DC_FOLDER_ONE
|
17
|
+
- DC_FOLDER_TWO
|
18
|
+
datastore_folders_to_clean:
|
19
|
+
- DS_DISK_FOLDER
|
15
20
|
vm:
|
16
21
|
ip: OVA_IP
|
17
22
|
gateway: OVA_GATEWAY
|
@@ -6,6 +6,7 @@ module VmShepherd
|
|
6
6
|
let(:secret_key) { 'secret-key' }
|
7
7
|
let(:ami_id) { 'ami-deadbeef' }
|
8
8
|
let(:ami_file_path) { Tempfile.new('ami-id-file').tap { |f| f.write("#{ami_id}\n"); f.close }.path }
|
9
|
+
let(:elastic_ip_id) { 'elastic-ip-id' }
|
9
10
|
let(:ec2) { double('AWS.ec2') }
|
10
11
|
|
11
12
|
let(:aws_options) do
|
@@ -16,7 +17,7 @@ module VmShepherd
|
|
16
17
|
security_group_id: 'security-group-id',
|
17
18
|
public_subnet_id: 'public-subnet-id',
|
18
19
|
private_subnet_id: 'private-subnet-id',
|
19
|
-
elastic_ip_id:
|
20
|
+
elastic_ip_id: elastic_ip_id,
|
20
21
|
vm_name: 'Ops Manager: clean_install_spec'
|
21
22
|
}
|
22
23
|
end
|
@@ -103,7 +104,7 @@ module VmShepherd
|
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
106
|
-
describe '#
|
107
|
+
describe '#clean_environment' do
|
107
108
|
let(:subnets) { instance_double(AWS::EC2::SubnetCollection) }
|
108
109
|
let(:subnet1) { instance_double(AWS::EC2::Subnet, instances: subnet1_instances) }
|
109
110
|
let(:subnet2) { instance_double(AWS::EC2::Subnet, instances: subnet2_instances) }
|
@@ -112,17 +113,25 @@ module VmShepherd
|
|
112
113
|
let(:subnet1_instances) { [instance1] }
|
113
114
|
let(:subnet2_instances) { [instance2] }
|
114
115
|
|
116
|
+
let(:instance1_volume) { instance_double(AWS::EC2::Volume)}
|
117
|
+
let(:instance1_attachment) do
|
118
|
+
instance_double(AWS::EC2::Attachment, volume: instance1_volume, delete_on_termination: true)
|
119
|
+
end
|
120
|
+
|
115
121
|
before do
|
116
122
|
allow(ec2).to receive(:subnets).and_return(subnets)
|
117
123
|
allow(subnets).to receive(:[]).with('public-subnet-id').and_return(subnet1)
|
118
124
|
allow(subnets).to receive(:[]).with('private-subnet-id').and_return(subnet2)
|
125
|
+
|
126
|
+
allow(instance1).to receive(:attachments).and_return({'/dev/test' => instance1_attachment})
|
127
|
+
allow(instance2).to receive(:attachments).and_return({})
|
119
128
|
end
|
120
129
|
|
121
130
|
it 'terminates all VMs in the subnet' do
|
122
131
|
expect(instance1).to receive(:terminate)
|
123
132
|
expect(instance2).to receive(:terminate)
|
124
133
|
|
125
|
-
ami_manager.
|
134
|
+
ami_manager.clean_environment
|
126
135
|
end
|
127
136
|
|
128
137
|
context 'when an instance has the magical tag' do
|
@@ -136,6 +145,67 @@ module VmShepherd
|
|
136
145
|
expect(instance2).to receive(:terminate)
|
137
146
|
expect(persistent_instance).not_to receive(:terminate)
|
138
147
|
|
148
|
+
ami_manager.clean_environment
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'when the instance has volumes that are NOT delete_on_termination' do
|
154
|
+
let(:instance1_attachment) do
|
155
|
+
instance_double(AWS::EC2::Attachment, volume: instance1_volume, delete_on_termination: false)
|
156
|
+
end
|
157
|
+
|
158
|
+
before do
|
159
|
+
allow(instance1).to receive(:terminate)
|
160
|
+
allow(instance2).to receive(:terminate)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'deletes the volumes' do
|
164
|
+
expect(instance1_volume).to receive(:delete)
|
165
|
+
|
166
|
+
ami_manager.clean_environment
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'when the instance has not finished termination' do
|
170
|
+
before do
|
171
|
+
expect(instance1_volume).to receive(:delete).and_raise(AWS::EC2::Errors::VolumeInUse)
|
172
|
+
expect(instance1_volume).to receive(:delete).and_return(nil)
|
173
|
+
allow(ami_manager).to receive(:sleep)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'retries the delete' do
|
177
|
+
ami_manager.clean_environment
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#destroy' do
|
184
|
+
let(:elastic_ips) { instance_double(AWS::EC2::ElasticIpCollection) }
|
185
|
+
let(:elastic_ip) { instance_double(AWS::EC2::ElasticIp, instance: instance, allocation_id: elastic_ip_id) }
|
186
|
+
let(:instance) { instance_double(AWS::EC2::Instance, tags: {}) }
|
187
|
+
|
188
|
+
before do
|
189
|
+
allow(ec2).to receive(:elastic_ips).and_return(elastic_ips)
|
190
|
+
allow(elastic_ips).to receive(:each).and_yield(elastic_ip)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'terminates the VM that matches the IP' do
|
194
|
+
expect(instance).to receive(:terminate)
|
195
|
+
|
196
|
+
ami_manager.destroy
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when an instance has the magical tag' do
|
200
|
+
let(:persistent_instance) { instance_double(AWS::EC2::Instance, tags: persist_tag) }
|
201
|
+
let(:instances) { [instance1, instance2, persistent_instance] }
|
202
|
+
|
203
|
+
context 'when there is no instance attached' do
|
204
|
+
before do
|
205
|
+
allow(elastic_ip).to receive(:instance).and_return(nil)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'does not explode' do
|
139
209
|
ami_manager.destroy
|
140
210
|
end
|
141
211
|
end
|
@@ -12,7 +12,7 @@ module VmShepherd
|
|
12
12
|
end
|
13
13
|
let(:vcloud_manager) { instance_double(VcloudManager) }
|
14
14
|
|
15
|
-
it 'uses VcloudManager
|
15
|
+
it 'uses VcloudManager to launch a vm' do
|
16
16
|
expect(VcloudManager).to receive(:new).
|
17
17
|
with(
|
18
18
|
{
|
@@ -51,7 +51,7 @@ module VmShepherd
|
|
51
51
|
end
|
52
52
|
let(:ova_manager) { instance_double(VsphereManager) }
|
53
53
|
|
54
|
-
it 'uses VsphereManager
|
54
|
+
it 'uses VsphereManager to launch a vm' do
|
55
55
|
expect(VsphereManager).to receive(:new).with(
|
56
56
|
settings.vm_deployer.vcenter_creds.ip,
|
57
57
|
settings.vm_deployer.vcenter_creds.username,
|
@@ -100,7 +100,7 @@ module VmShepherd
|
|
100
100
|
}
|
101
101
|
end
|
102
102
|
|
103
|
-
it 'uses AwsManager
|
103
|
+
it 'uses AwsManager to launch a VM' do
|
104
104
|
expect(AwsManager).to receive(:new).with(aws_options).and_return(ams_manager)
|
105
105
|
expect(ams_manager).to receive(:deploy).with(ami_file_path)
|
106
106
|
manager.deploy(path: ami_file_path)
|
@@ -137,7 +137,7 @@ module VmShepherd
|
|
137
137
|
}
|
138
138
|
end
|
139
139
|
|
140
|
-
it 'uses
|
140
|
+
it 'uses OpenstackManager to launch a VM' do
|
141
141
|
expect(OpenstackManager).to receive(:new).with(openstack_options).and_return(qcow2_manager)
|
142
142
|
expect(qcow2_manager).to receive(:deploy).with(qcow2_file_path, openstack_vm_options)
|
143
143
|
manager.deploy(path: qcow2_file_path)
|
@@ -162,7 +162,7 @@ module VmShepherd
|
|
162
162
|
end
|
163
163
|
let(:vcloud_manager) { instance_double(VcloudManager) }
|
164
164
|
|
165
|
-
it 'uses VcloudManager
|
165
|
+
it 'uses VcloudManager to destroy a vm' do
|
166
166
|
expect(VcloudManager).to receive(:new).with(
|
167
167
|
{
|
168
168
|
url: settings.vapp_deployer.creds.url,
|
@@ -177,7 +177,8 @@ module VmShepherd
|
|
177
177
|
},
|
178
178
|
instance_of(Logger)
|
179
179
|
).and_return(vcloud_manager)
|
180
|
-
|
180
|
+
vapp_names = [settings.vapp_deployer.vapp.ops_manager_name] + settings.vapp_deployer.vapp.product_names
|
181
|
+
expect(vcloud_manager).to receive(:destroy).with(vapp_names)
|
181
182
|
|
182
183
|
manager.destroy
|
183
184
|
end
|
@@ -189,14 +190,14 @@ module VmShepherd
|
|
189
190
|
end
|
190
191
|
let(:ova_manager) { instance_double(VsphereManager) }
|
191
192
|
|
192
|
-
it 'uses VsphereManager
|
193
|
+
it 'uses VsphereManager to destroy a vm' do
|
193
194
|
expect(VsphereManager).to receive(:new).with(
|
194
195
|
settings.vm_deployer.vcenter_creds.ip,
|
195
196
|
settings.vm_deployer.vcenter_creds.username,
|
196
197
|
settings.vm_deployer.vcenter_creds.password,
|
197
198
|
settings.vm_deployer.vsphere.datacenter,
|
198
199
|
).and_return(ova_manager)
|
199
|
-
expect(ova_manager).to receive(:destroy).with(settings.vm_deployer.
|
200
|
+
expect(ova_manager).to receive(:destroy).with(settings.vm_deployer.vm.ip)
|
200
201
|
|
201
202
|
manager.destroy
|
202
203
|
end
|
@@ -207,7 +208,7 @@ module VmShepherd
|
|
207
208
|
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'aws.yml')))
|
208
209
|
end
|
209
210
|
let(:ams_manager) { instance_double(AwsManager) }
|
210
|
-
let(:
|
211
|
+
let(:ami_options) do
|
211
212
|
{
|
212
213
|
aws_access_key: 'aws-access-key',
|
213
214
|
aws_secret_key: 'aws-secret-key',
|
@@ -220,8 +221,8 @@ module VmShepherd
|
|
220
221
|
}
|
221
222
|
end
|
222
223
|
|
223
|
-
it 'uses AwsManager
|
224
|
-
expect(AwsManager).to receive(:new).with(
|
224
|
+
it 'uses AwsManager to destroy a VM' do
|
225
|
+
expect(AwsManager).to receive(:new).with(ami_options).and_return(ams_manager)
|
225
226
|
expect(ams_manager).to receive(:destroy)
|
226
227
|
manager.destroy
|
227
228
|
end
|
@@ -257,7 +258,7 @@ module VmShepherd
|
|
257
258
|
}
|
258
259
|
end
|
259
260
|
|
260
|
-
it 'uses
|
261
|
+
it 'uses OpenstackManager to destroy a VM' do
|
261
262
|
expect(OpenstackManager).to receive(:new).with(openstack_options).and_return(qcow2_manager)
|
262
263
|
expect(qcow2_manager).to receive(:destroy).with(openstack_vm_options)
|
263
264
|
manager.destroy
|
@@ -270,7 +271,119 @@ module VmShepherd
|
|
270
271
|
end
|
271
272
|
|
272
273
|
it 'raises an exception' do
|
273
|
-
expect { manager.
|
274
|
+
expect { manager.destroy }.to raise_error(Shepherd::InvalidIaas)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
describe '#clean_environment' do
|
280
|
+
context 'when IAAS is vcloud' do
|
281
|
+
let(:settings) do
|
282
|
+
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vcloud.yml')))
|
283
|
+
end
|
284
|
+
let(:vcloud_manager) { instance_double(VcloudManager) }
|
285
|
+
|
286
|
+
it 'uses VcloudManager to destroy a vm' do
|
287
|
+
expect(VcloudManager).to receive(:new).with(
|
288
|
+
{
|
289
|
+
url: settings.vapp_deployer.creds.url,
|
290
|
+
organization: settings.vapp_deployer.creds.organization,
|
291
|
+
user: settings.vapp_deployer.creds.user,
|
292
|
+
password: settings.vapp_deployer.creds.password,
|
293
|
+
},
|
294
|
+
{
|
295
|
+
vdc: settings.vapp_deployer.vdc.name,
|
296
|
+
catalog: settings.vapp_deployer.vdc.catalog,
|
297
|
+
network: settings.vapp_deployer.vdc.network,
|
298
|
+
},
|
299
|
+
instance_of(Logger)
|
300
|
+
).and_return(vcloud_manager)
|
301
|
+
expect(vcloud_manager).to receive(:clean_environment)
|
302
|
+
|
303
|
+
manager.clean_environment
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'when IAAS is vsphere' do
|
308
|
+
let(:settings) do
|
309
|
+
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'vsphere.yml')))
|
310
|
+
end
|
311
|
+
let(:ova_manager) { instance_double(VsphereManager) }
|
312
|
+
let(:clean_environment_options) do
|
313
|
+
{
|
314
|
+
datacenter_folders_to_clean: settings.vm_deployer.vsphere.datacenter_folders_to_clean,
|
315
|
+
datastore: settings.vm_deployer.vsphere.datastore,
|
316
|
+
datastore_folders_to_clean: settings.vm_deployer.vsphere.datastore_folders_to_clean,
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'uses VsphereManager to destroy a vm' do
|
321
|
+
expect(VsphereManager).to receive(:new).with(
|
322
|
+
settings.vm_deployer.vcenter_creds.ip,
|
323
|
+
settings.vm_deployer.vcenter_creds.username,
|
324
|
+
settings.vm_deployer.vcenter_creds.password,
|
325
|
+
settings.vm_deployer.vsphere.datacenter,
|
326
|
+
).and_return(ova_manager)
|
327
|
+
expect(ova_manager).to receive(:clean_environment).with(clean_environment_options)
|
328
|
+
|
329
|
+
manager.clean_environment
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'when IAAS is AWS' do
|
334
|
+
let(:settings) do
|
335
|
+
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'aws.yml')))
|
336
|
+
end
|
337
|
+
let(:ams_manager) { instance_double(AwsManager) }
|
338
|
+
let(:ami_options) do
|
339
|
+
{
|
340
|
+
aws_access_key: 'aws-access-key',
|
341
|
+
aws_secret_key: 'aws-secret-key',
|
342
|
+
ssh_key_name: 'ssh-key-name',
|
343
|
+
security_group_id: 'security-group-id',
|
344
|
+
public_subnet_id: 'public-subnet-id',
|
345
|
+
private_subnet_id: 'private-subnet-id',
|
346
|
+
elastic_ip_id: 'elastic-ip-id',
|
347
|
+
vm_name: 'vm-name'
|
348
|
+
}
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'uses AwsManager to destroy a VM' do
|
352
|
+
expect(AwsManager).to receive(:new).with(ami_options).and_return(ams_manager)
|
353
|
+
expect(ams_manager).to receive(:clean_environment)
|
354
|
+
manager.clean_environment
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'when IAAS is Openstack' do
|
359
|
+
let(:settings) do
|
360
|
+
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'openstack.yml')))
|
361
|
+
end
|
362
|
+
let(:qcow2_manager) { instance_double(OpenstackManager) }
|
363
|
+
let(:qcow2_file_path) { 'PATH_TO_QCOW2_FILE' }
|
364
|
+
let(:openstack_options) do
|
365
|
+
{
|
366
|
+
auth_url: 'http://example.com/version/tokens',
|
367
|
+
username: 'username',
|
368
|
+
api_key: 'api-key',
|
369
|
+
tenant: 'tenant',
|
370
|
+
}
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'uses OpenstackManager to destroy a VM' do
|
374
|
+
expect(OpenstackManager).to receive(:new).with(openstack_options).and_return(qcow2_manager)
|
375
|
+
expect(qcow2_manager).to receive(:clean_environment)
|
376
|
+
manager.clean_environment
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
context 'when IAAS is unknown' do
|
381
|
+
let(:settings) do
|
382
|
+
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', 'unknown.yml')))
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'raises an exception' do
|
386
|
+
expect { manager.clean_environment }.to raise_error(Shepherd::InvalidIaas)
|
274
387
|
end
|
275
388
|
end
|
276
389
|
end
|
@@ -29,7 +29,6 @@ module VmShepherd
|
|
29
29
|
gateway: 'FAKE_GATEWAY',
|
30
30
|
dns: 'FAKE_DNS',
|
31
31
|
ntp: 'FAKE_NTP',
|
32
|
-
ip: 'FAKE_IP',
|
33
32
|
netmask: 'FAKE_NETMASK',
|
34
33
|
}
|
35
34
|
end
|
@@ -308,7 +307,7 @@ module VmShepherd
|
|
308
307
|
logger,
|
309
308
|
).and_return(client)
|
310
309
|
|
311
|
-
vcloud_manager.destroy(vapp_name)
|
310
|
+
vcloud_manager.destroy([vapp_name])
|
312
311
|
end
|
313
312
|
|
314
313
|
context 'when an VCloudSdk::ObjectNotFoundError is thrown' do
|
@@ -325,13 +324,13 @@ module VmShepherd
|
|
325
324
|
it 'catches the error' do
|
326
325
|
allow(client).to receive(:find_vdc_by_name).and_raise(VCloudSdk::ObjectNotFoundError)
|
327
326
|
|
328
|
-
expect { vcloud_manager.destroy(vapp_name) }.not_to raise_error
|
327
|
+
expect { vcloud_manager.destroy([vapp_name]) }.not_to raise_error
|
329
328
|
end
|
330
329
|
|
331
330
|
it 'deletes to catalog' do
|
332
331
|
expect(client).to receive(:delete_catalog_by_name).with(location.fetch(:catalog))
|
333
332
|
|
334
|
-
vcloud_manager.destroy(vapp_name)
|
333
|
+
vcloud_manager.destroy([vapp_name])
|
335
334
|
end
|
336
335
|
end
|
337
336
|
end
|
@@ -356,7 +355,7 @@ module VmShepherd
|
|
356
355
|
logger,
|
357
356
|
).and_return(client)
|
358
357
|
|
359
|
-
vcloud_manager.destroy(vapp_name)
|
358
|
+
vcloud_manager.destroy([vapp_name])
|
360
359
|
end
|
361
360
|
end
|
362
361
|
end
|
@@ -6,11 +6,61 @@ module VmShepherd
|
|
6
6
|
let(:username) { 'FAKE_USERNAME' }
|
7
7
|
let(:password) { 'FAKE_PASSWORD' }
|
8
8
|
let(:datacenter_name) { 'FAKE_DATACENTER_NAME' }
|
9
|
+
let(:vm) { instance_double(RbVmomi::VIM::VirtualMachine, name: 'vm_name') }
|
9
10
|
|
10
|
-
subject(:vsphere_manager)
|
11
|
+
subject(:vsphere_manager) do
|
12
|
+
manager = VsphereManager.new(host, username, password, datacenter_name)
|
13
|
+
manager.logger = Logger.new(StringIO.new)
|
14
|
+
manager
|
15
|
+
end
|
11
16
|
|
12
17
|
it 'loads' do
|
13
18
|
expect { vsphere_manager }.not_to raise_error
|
14
19
|
end
|
20
|
+
|
21
|
+
describe 'destroy' do
|
22
|
+
let(:datacenter) { instance_double(RbVmomi::VIM::Datacenter, vmFolder: vm_folder) }
|
23
|
+
let(:vm_folder) { instance_double(RbVmomi::VIM::Folder) }
|
24
|
+
let(:ip_address) { '127.0.0.1' }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(vsphere_manager).to receive(:datacenter).and_return(datacenter)
|
28
|
+
allow(vm_folder).to receive(:findByIp).with(ip_address).and_return(vm)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'destroys the VM that matches the given ip address' do
|
32
|
+
expect(vsphere_manager).to receive(:power_off_vm).with(vm)
|
33
|
+
expect(vsphere_manager).to receive(:destroy_vm).with(vm)
|
34
|
+
|
35
|
+
vsphere_manager.destroy(ip_address)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when the vm does not exist' do
|
39
|
+
before do
|
40
|
+
allow(vm_folder).to receive(:findByIp).and_return(nil)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not explode' do
|
44
|
+
expect(vsphere_manager).not_to receive(:power_off_vm)
|
45
|
+
expect(vsphere_manager).not_to receive(:destroy_vm)
|
46
|
+
|
47
|
+
vsphere_manager.destroy(ip_address)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'destroy_vm' do
|
53
|
+
let(:destroy_task) { instance_double(RbVmomi::VIM::Task) }
|
54
|
+
|
55
|
+
before do
|
56
|
+
allow(vm).to receive(:Destroy_Task).and_return(destroy_task)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'runs the Destroy_Task and waits for completion' do
|
60
|
+
expect(destroy_task).to receive(:wait_for_completion)
|
61
|
+
|
62
|
+
vsphere_manager.destroy_vm(vm)
|
63
|
+
end
|
64
|
+
end
|
15
65
|
end
|
16
66
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vm_shepherd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ops Manager Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-v1
|