kitchen-vra 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f16b036a56b3f13f8def98498eb1a101c1feb02d
4
+ data.tar.gz: fabcc430c9d91f4720625af34eef073c295c49d1
5
+ SHA512:
6
+ metadata.gz: 5bf076a6e0b2f56330977dbb00267d311a382178b89085ad94e2133bcbc7229b4eb500f2669f7e2d7ea559bf17f3fa23fe92565bb56d31522320e5e22790e861
7
+ data.tar.gz: a6bbb96508c893bc1a022f3141d252a5abe40a54aa9b82abd37f147ff0143e37522abc478a04404fee4ada7bcd0b9cfdc163c5525c3c815927bab63542b340df
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,14 @@
1
+ Metrics/AbcSize:
2
+ Max: 50
3
+ Metrics/ClassLength:
4
+ Max: 125
5
+ Metrics/LineLength:
6
+ Max: 130
7
+ Metrics/MethodLength:
8
+ Max: 25
9
+ Style/Documentation:
10
+ Enabled: false
11
+ Style/SignalException:
12
+ Enabled: false
13
+ Style/SpaceInsideBrackets:
14
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,106 @@
1
+ # kitchen-vra
2
+
3
+ A driver to allow Test Kitchen to consume vRealize Automation (vRA) resources to perform testing.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'kitchen-vra'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install kitchen-vra
20
+
21
+ Or even better, install it via ChefDK:
22
+
23
+ $ chef gem install kitchen-vra
24
+
25
+ ## Usage
26
+
27
+ After installing the gem as described above, edit your .kitchen.yml file to set the driver to 'vra' and supply your login credentials:
28
+
29
+ ```yaml
30
+ driver:
31
+ name: vra
32
+ username: myuser@corp.local
33
+ password: mypassword
34
+ tenant: mytenant
35
+ base_url: https://vra.corp.local
36
+ verify_ssl: true
37
+ ```
38
+
39
+ Then configure your platforms. A catalog_id is required for each platform:
40
+
41
+ ```yaml
42
+ platforms:
43
+ - name: centos6
44
+ driver:
45
+ catalog_id: e9db1084-d1c6-4c1f-8e3c-eb8f3dc574f9
46
+ - name: centos7
47
+ driver:
48
+ catalog_id: c4211950-ab07-42b1-ba80-8f5d3f2c8251
49
+ ```
50
+
51
+ Other options that you can set include:
52
+
53
+ * **lease_days**: number of days to request for a lease, if your catalog item / blueprint requires it
54
+ * **request_timeout**: amount of time, in seconds, to wait for a vRA request to complete. Default is 600 seconds.
55
+ * **cpus**: number of CPUs the host should have
56
+ * **memory**: amount of RAM, in MB, the host should have
57
+ * **requested_for**: the vRA login ID to list as the owner of this resource. Defaults to the vRA username configured in the `driver` section.
58
+ * **subtenant_id**: the Business Group ID to list as the owner. This is required if the catalog item is a shared/global item; we are unable to determine the subtenant_id from the catalog, and vRA requires it to be set on every request.
59
+ * **private_key_path**: path to the SSH private key to use when logging in. Defaults to '~/.ssh/id_rsa' or '~/.ssh/id_dsa', preferring the RSA key. Only applies to instances where SSH transport is used (i.e. does not apply to Windows hosts with the WinRM transport configured).
60
+
61
+ These settings can be set globally under the top-level `driver` section, or they can be set on each platform, which allows you to set globals and then override them. For example, this configuration would set the CPU count to 1 except on the "large" platform:
62
+
63
+ ```yaml
64
+ driver:
65
+ name: vra
66
+ cpus: 1
67
+
68
+ platforms:
69
+ - name: small
70
+ driver:
71
+ catalog_id: 8a189191-fea6-43eb-981e-ee0fa40f8f57
72
+ - name: large
73
+ driver:
74
+ catalog_id: 1d7c6122-18fa-4ed6-bd13-8a33b6c6ed50
75
+ cpus: 2
76
+ ```
77
+
78
+ ## License and Authors
79
+
80
+ Author:: Chef Partner Engineering (<partnereng@chef.io>)
81
+
82
+ Copyright:: Copyright (c) 2015 Chef Software, Inc.
83
+
84
+ License:: Apache License, Version 2.0
85
+
86
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
87
+ this file except in compliance with the License. You may obtain a copy of the License at
88
+
89
+ ```
90
+ http://www.apache.org/licenses/LICENSE-2.0
91
+ ```
92
+
93
+ Unless required by applicable law or agreed to in writing, software distributed under the
94
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
95
+ either express or implied. See the License for the specific language governing permissions
96
+ and limitations under the License.
97
+
98
+ ## Contributing
99
+
100
+ We'd love to hear from you if this doesn't work in your vRA environment. Please log a GitHub issue, or even better, submit a Pull Request with a fix!
101
+
102
+ 1. Fork it ( https://github.com/chef-partners/kitchen-vra/fork )
103
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
104
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
105
+ 4. Push to the branch (`git push origin my-new-feature`)
106
+ 5. Create a new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen/driver/vra_version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kitchen-vra'
8
+ spec.version = Kitchen::Driver::VRA_VERSION
9
+ spec.authors = ['Chef Partner Engineering']
10
+ spec.email = ['partnereng@chef.io']
11
+ spec.summary = 'A Test Kitchen driver for VMware vRealize Automation (vRA)'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/chef-partners/kitchen-vra'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1'
22
+ spec.add_dependency 'vmware-vra', '~> 1.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.2'
27
+ spec.add_development_dependency 'simplecov', '~> 0.10'
28
+ spec.add_development_dependency 'webmock', '~> 1.21'
29
+ end
@@ -0,0 +1,170 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'kitchen'
20
+ require 'vra'
21
+ require_relative 'vra_version'
22
+
23
+ module Kitchen
24
+ module Driver
25
+ class Vra < Kitchen::Driver::Base
26
+ kitchen_driver_api_version 2
27
+ plugin_version Kitchen::Driver::VRA_VERSION
28
+
29
+ required_config :username
30
+ required_config :password
31
+ required_config :base_url
32
+ required_config :tenant
33
+ required_config :catalog_id
34
+
35
+ default_config :subtenant, nil
36
+ default_config :verify_ssl, true
37
+ default_config :request_timeout, 600
38
+ default_config :request_refresh_rate, 2
39
+ default_config :cpus, 1
40
+ default_config :memory, 1024
41
+ default_config :requested_for do |driver|
42
+ driver[:username]
43
+ end
44
+ default_config :lease_days, nil
45
+ default_config :notes, nil
46
+ default_config :extra_parameters, {}
47
+ default_config :private_key_path do
48
+ %w(id_rsa id_dsa).map do |key|
49
+ file = File.expand_path("~/.ssh/#{key}")
50
+ file if File.exist?(file)
51
+ end.compact.first
52
+ end
53
+
54
+ def name
55
+ 'vRA'
56
+ end
57
+
58
+ def create(state)
59
+ return if state[:resource_id]
60
+
61
+ server = request_server
62
+ state[:resource_id] = server.id
63
+
64
+ ip_address = server.ip_addresses.first
65
+ raise 'No IP address returned for the vRA request' if ip_address.nil?
66
+ state[:hostname] = server.ip_addresses.first
67
+ state[:ssh_key] = config[:private_key_path] unless config[:private_key_path].nil?
68
+
69
+ wait_for_server(state, server)
70
+ info("Server #{server.id} (#{server.name}) ready.")
71
+ end
72
+
73
+ def request_server
74
+ info('Building vRA catalog request...')
75
+ submitted_request = catalog_request.submit
76
+ info("Catalog request #{submitted_request.id} submitted.")
77
+
78
+ wait_for_request(submitted_request)
79
+ raise "The vRA request failed: #{submitted_request.completion_details}" if submitted_request.failed?
80
+
81
+ servers = submitted_request.resources.select(&:vm?)
82
+ raise 'The vRA request created more than one server. The catalog blueprint should only return one.' if servers.size > 1
83
+ raise 'the vRA request did not create any servers.' if servers.size == 0
84
+
85
+ servers.first
86
+ end
87
+
88
+ def wait_for_server(state, server)
89
+ info("Server #{server.id} (#{server.name}) created. Waiting until ready...")
90
+ begin
91
+ instance.transport.connection(state).wait_until_ready
92
+ rescue
93
+ error("Server #{server.id} (#{server.name}) not reachable. Destroying server...")
94
+ destroy(state)
95
+ raise
96
+ end
97
+ end
98
+
99
+ def destroy(state)
100
+ return if state[:resource_id].nil?
101
+
102
+ begin
103
+ server = vra_client.resources.by_id(state[:resource_id])
104
+ rescue ::Vra::Exception::NotFound
105
+ warn("No server found with ID #{state[:resource_id]}, assuming it has been destroyed already.")
106
+ return
107
+ end
108
+
109
+ begin
110
+ destroy_request = server.destroy
111
+ rescue ::Vra::Exception::NotFound
112
+ info('Server not found, or no destroy action available, perhaps because it is already destroyed.')
113
+ return
114
+ end
115
+ info("Destroy request #{destroy_request.id} submitted.")
116
+ wait_for_request(destroy_request)
117
+ info('Destroy request complete.')
118
+ end
119
+
120
+ def catalog_request
121
+ catalog_request = vra_client.catalog.request(config[:catalog_id])
122
+
123
+ catalog_request.cpus = config[:cpus]
124
+ catalog_request.memory = config[:memory]
125
+ catalog_request.requested_for = config[:requested_for]
126
+ catalog_request.lease_days = config[:lease_days] unless config[:lease_days].nil?
127
+ catalog_request.notes = config[:notes] unless config[:notes].nil?
128
+ catalog_request.subtenant_id = config[:subtenant_id] unless config[:subtenant_id].nil?
129
+
130
+ config[:extra_parameters].each do |key, value_data|
131
+ catalog_request.set_parameter(key, value_data[:type], value_data[:value])
132
+ end
133
+
134
+ catalog_request
135
+ end
136
+
137
+ def vra_client
138
+ @client ||= ::Vra::Client.new(
139
+ base_url: config[:base_url],
140
+ username: config[:username],
141
+ password: config[:password],
142
+ tenant: config[:tenant],
143
+ verify_ssl: config[:verify_ssl]
144
+ )
145
+ end
146
+
147
+ def wait_for_request(request)
148
+ last_status = ''
149
+ wait_time = config[:request_timeout]
150
+ sleep_time = config[:request_refresh_rate]
151
+ Timeout.timeout(wait_time) do
152
+ loop do
153
+ request.refresh
154
+ break if request.completed?
155
+
156
+ unless last_status == request.status
157
+ last_status = request.status
158
+ info("Current request status: #{request.status}")
159
+ end
160
+
161
+ sleep sleep_time
162
+ end
163
+ end
164
+ rescue Timeout::Error
165
+ error("Request did not complete in #{wait_time} seconds. Check the Requests tab in the vRA UI for more information.")
166
+ raise
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Kitchen
20
+ module Driver
21
+ VRA_VERSION = '1.0.0'
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'webmock/rspec'
20
+
21
+ WebMock.disable_net_connect!(allow_localhost: true)
22
+
23
+ if ENV['COVERAGE']
24
+ require 'simplecov'
25
+ SimpleCov.profiles.define 'gem' do
26
+ command_name 'Specs'
27
+ end
28
+ SimpleCov.start 'gem'
29
+ end
@@ -0,0 +1,417 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'kitchen/driver/vra'
21
+ require 'kitchen/provisioner/dummy'
22
+ require 'kitchen/transport/dummy'
23
+ require 'kitchen/verifier/dummy'
24
+
25
+ describe Kitchen::Driver::Vra do
26
+ let(:logged_output) { StringIO.new }
27
+ let(:logger) { Logger.new(logged_output) }
28
+ let(:platform) { Kitchen::Platform.new(name: 'fake_platform') }
29
+ let(:transport) { Kitchen::Transport::Dummy.new }
30
+ let(:driver) { Kitchen::Driver::Vra.new(config) }
31
+
32
+ let(:config) do
33
+ {
34
+ base_url: 'https://vra.corp.local',
35
+ username: 'myuser',
36
+ password: 'mypassword',
37
+ tenant: 'mytenant',
38
+ verify_ssl: true,
39
+ cpus: 2,
40
+ memory: 2048,
41
+ requested_for: 'override_user@corp.local',
42
+ notes: 'some notes',
43
+ subtenant_id: '160b473a-0ec9-473d-8156-28dd96c0b6b7',
44
+ lease_days: 5
45
+ }
46
+ end
47
+
48
+ let(:instance) do
49
+ instance_double(Kitchen::Instance,
50
+ logger: logger,
51
+ transport: transport,
52
+ platform: platform,
53
+ to_str: 'instance_str'
54
+ )
55
+ end
56
+
57
+ before do
58
+ allow(driver).to receive(:instance).and_return(instance)
59
+ end
60
+
61
+ it 'driver API version is 2' do
62
+ expect(driver.diagnose_plugin[:api_version]).to eq(2)
63
+ end
64
+
65
+ describe '#name' do
66
+ it 'has an overridden name' do
67
+ expect(driver.name).to eq('vRA')
68
+ end
69
+ end
70
+
71
+ describe '#create' do
72
+ context 'when the server is already created' do
73
+ let(:state) { { resource_id: '48959518-6a0d-46e1-8415-3749696b65f4' } }
74
+
75
+ it 'does not submit a catalog request' do
76
+ expect(driver).not_to receive(:request_server)
77
+ driver.create(state)
78
+ end
79
+ end
80
+
81
+ let(:state) { {} }
82
+ let(:resource) do
83
+ double('server1',
84
+ id: 'e8706351-cf4c-4c12-acb7-c90cc683b22c',
85
+ name: 'server1',
86
+ ip_addresses: [ '1.2.3.4' ],
87
+ vm?: true)
88
+ end
89
+
90
+ before do
91
+ allow(driver).to receive(:request_server).and_return(resource)
92
+ allow(driver).to receive(:wait_for_server)
93
+ end
94
+
95
+ it 'requests the server' do
96
+ expect(driver).to receive(:request_server).and_return(resource)
97
+ driver.create(state)
98
+ end
99
+
100
+ it 'sets the server ID in the state hash' do
101
+ driver.create(state)
102
+ expect(state[:resource_id]).to eq('e8706351-cf4c-4c12-acb7-c90cc683b22c')
103
+ end
104
+
105
+ describe 'getting the IP address from the server' do
106
+ context 'when no IP addresses are returned' do
107
+ it 'raises an exception' do
108
+ allow(resource).to receive(:ip_addresses).and_return([])
109
+ expect { driver.create(state) }.to raise_error(RuntimeError)
110
+ end
111
+ end
112
+
113
+ context 'when IP addresses are returned' do
114
+ it 'sets the IP address as the hostname in the state hash' do
115
+ driver.create(state)
116
+ expect(state[:hostname]).to eq('1.2.3.4')
117
+ end
118
+ end
119
+ end
120
+
121
+ it 'waits for the server to be ready' do
122
+ expect(driver).to receive(:wait_for_server)
123
+ driver.create(state)
124
+ end
125
+ end
126
+
127
+ describe '#request_server' do
128
+ let(:submitted_request) { double('submitted_request') }
129
+ let(:catalog_request) { double('catalog_request') }
130
+ let(:resource1) do
131
+ double('server1',
132
+ id: 'e8706351-cf4c-4c12-acb7-c90cc683b22c',
133
+ name: 'server1',
134
+ ip_addresses: [ '1.2.3.4' ],
135
+ vm?: true)
136
+ end
137
+ let(:resource2) do
138
+ double('server2',
139
+ id: '9e2364cf-7af4-4b85-93fd-1f03ee2ac865',
140
+ name: 'server2',
141
+ ip_addresses: [ '4.3.2.1' ],
142
+ vm?: true)
143
+ end
144
+ let(:resources) { [resource1] }
145
+
146
+ before do
147
+ allow(driver).to receive(:catalog_request).and_return(catalog_request)
148
+ allow(catalog_request).to receive(:submit).and_return(submitted_request)
149
+ allow(submitted_request).to receive(:id).and_return('74e26af9-2d2f-4889-a472-95dbcedb70b8')
150
+ allow(submitted_request).to receive(:resources).and_return(resources)
151
+ allow(submitted_request).to receive(:failed?).and_return(false)
152
+ allow(driver).to receive(:wait_for_request).with(submitted_request)
153
+ end
154
+
155
+ it 'submits a catalog request' do
156
+ expect(driver.catalog_request).to receive(:submit).and_return(submitted_request)
157
+ driver.request_server
158
+ end
159
+
160
+ it 'waits for the request to complete' do
161
+ expect(driver).to receive(:wait_for_request).with(submitted_request)
162
+ driver.request_server
163
+ end
164
+
165
+ it 'raises an exception if the request failed' do
166
+ allow(submitted_request).to receive(:failed?).and_return(true)
167
+ allow(submitted_request).to receive(:completion_details).and_return('it failed')
168
+ expect { driver.request_server }.to raise_error(RuntimeError)
169
+ end
170
+
171
+ describe 'getting the server from the request' do
172
+ context 'when only one server is returned' do
173
+ it 'does not raise an exception' do
174
+ expect { driver.request_server }.not_to raise_error
175
+ end
176
+ end
177
+
178
+ context 'when multiple servers are returned' do
179
+ it 'raises an exception' do
180
+ allow(submitted_request).to receive(:resources).and_return([ resource1, resource2 ])
181
+ expect { driver.request_server }.to raise_error(RuntimeError)
182
+ end
183
+ end
184
+
185
+ context 'when no servers are returned' do
186
+ it 'raises an exception' do
187
+ allow(submitted_request).to receive(:resources).and_return([])
188
+ expect { driver.request_server }.to raise_error(RuntimeError)
189
+ end
190
+ end
191
+ end
192
+
193
+ it 'returns the the single server resource object' do
194
+ expect(driver.request_server).to eq(resource1)
195
+ end
196
+ end
197
+
198
+ describe '#wait_for_server_to_be_ready' do
199
+ let(:connection) { instance.transport.connection(state) }
200
+ let(:state) { {} }
201
+ let(:resource1) do
202
+ double('server1',
203
+ id: 'e8706351-cf4c-4c12-acb7-c90cc683b22c',
204
+ name: 'server1',
205
+ ip_addresses: [ '1.2.3.4' ],
206
+ vm?: true)
207
+ end
208
+
209
+ before do
210
+ allow(transport).to receive(:connection).and_return(connection)
211
+ end
212
+
213
+ it 'waits for the server to be ready' do
214
+ expect(connection).to receive(:wait_until_ready)
215
+ driver.wait_for_server(state, resource1)
216
+ end
217
+
218
+ it 'destroys the server and raises an exception if it fails to become ready' do
219
+ allow(connection).to receive(:wait_until_ready).and_raise(Timeout::Error)
220
+ expect(driver).to receive(:destroy).with(state)
221
+ expect { driver.wait_for_server(state, resource1) }.to raise_error(Timeout::Error)
222
+ end
223
+ end
224
+
225
+ describe '#destroy' do
226
+ let(:resource_id) { '8c1a833a-5844-4100-b58c-9cab3543c958' }
227
+ let(:state) { { resource_id: resource_id } }
228
+ let(:vra_client) { double('vra_client') }
229
+ let(:resources) { double('resources') }
230
+ let(:destroy_request) { double('destroy_request') }
231
+ let(:resource) do
232
+ double('server1',
233
+ id: resource_id,
234
+ name: 'server1',
235
+ ip_addresses: [ '5.6.7.8' ],
236
+ vm?: true)
237
+ end
238
+
239
+ before do
240
+ allow(driver).to receive(:vra_client).and_return(vra_client)
241
+ allow(driver).to receive(:wait_for_request).with(destroy_request)
242
+ allow(vra_client).to receive(:resources).and_return(resources)
243
+ allow(resources).to receive(:by_id).and_return(resource)
244
+ allow(resource).to receive(:destroy).and_return(destroy_request)
245
+ allow(destroy_request).to receive(:id).and_return('6da65982-7c33-4e6e-b346-fdf4bcbf01ab')
246
+ end
247
+
248
+ context 'when the resource is not created' do
249
+ let(:state) { {} }
250
+ it 'does not look up the resource if no resource ID exists' do
251
+ expect(vra_client.resources).not_to receive(:by_id)
252
+ driver.destroy(state)
253
+ end
254
+ end
255
+
256
+ it 'looks up the resource record' do
257
+ expect(vra_client.resources).to receive(:by_id).with(resource_id).and_return(resource)
258
+ driver.destroy(state)
259
+ end
260
+
261
+ context 'when the resource record cannot be found' do
262
+ it 'does not raise an exception' do
263
+ allow(vra_client.resources).to receive(:by_id).with(resource_id).and_raise(Vra::Exception::NotFound)
264
+ expect { driver.destroy(state) }.not_to raise_error
265
+ end
266
+ end
267
+
268
+ describe 'creating the destroy request' do
269
+ context 'when the destroy method or server is not found' do
270
+ it 'does not raise an exception' do
271
+ allow(resource).to receive(:destroy).and_raise(Vra::Exception::NotFound)
272
+ expect { driver.destroy(state) }.not_to raise_error
273
+ end
274
+ end
275
+
276
+ it 'calls #destroy on the server' do
277
+ expect(resource).to receive(:destroy).and_return(destroy_request)
278
+ driver.destroy(state)
279
+ end
280
+ end
281
+
282
+ it 'waits for the destroy request to succeed' do
283
+ expect(driver).to receive(:wait_for_request).with(destroy_request)
284
+ driver.destroy(state)
285
+ end
286
+ end
287
+
288
+ describe '#catalog_request' do
289
+ let(:catalog_request) { double('catalog_request') }
290
+ let(:vra_client) { double('vra_client') }
291
+ let(:catalog) { double('catalog') }
292
+ before do
293
+ allow(driver).to receive(:vra_client).and_return(vra_client)
294
+ allow(vra_client).to receive(:catalog).and_return(catalog)
295
+ allow(catalog).to receive(:request).and_return(catalog_request)
296
+ [ :cpus=, :memory=, :requested_for=, :lease_days=, :notes=, :subtenant_id=, :set_parameter ].each do |method|
297
+ allow(catalog_request).to receive(method)
298
+ end
299
+ end
300
+
301
+ it 'creates a catalog_request' do
302
+ expect(vra_client.catalog).to receive(:request).and_return(catalog_request)
303
+ driver.catalog_request
304
+ end
305
+
306
+ it 'sets all the standard parameters on the request' do
307
+ expect(catalog_request).to receive(:cpus=).with(config[:cpus])
308
+ expect(catalog_request).to receive(:memory=).with(config[:memory])
309
+ expect(catalog_request).to receive(:requested_for=).with(config[:requested_for])
310
+ expect(catalog_request).to receive(:lease_days=).with(config[:lease_days])
311
+ expect(catalog_request).to receive(:notes=).with(config[:notes])
312
+ expect(catalog_request).to receive(:subtenant_id=).with(config[:subtenant_id])
313
+ driver.catalog_request
314
+ end
315
+
316
+ context 'when option parameters are not supplied' do
317
+ let(:config) do
318
+ {
319
+ base_url: 'https://vra.corp.local',
320
+ username: 'myuser',
321
+ password: 'mypassword',
322
+ tenant: 'mytenant',
323
+ verify_ssl: true,
324
+ cpus: 2,
325
+ memory: 2048,
326
+ requested_for: 'override_user@corp.local'
327
+ }
328
+ end
329
+
330
+ it 'does not attempt to set params on the catalog_request' do
331
+ expect(catalog_request).not_to receive(:lease_days=)
332
+ expect(catalog_request).not_to receive(:notes=)
333
+ expect(catalog_request).not_to receive(:subtenant_id=)
334
+ driver.catalog_request
335
+ end
336
+ end
337
+
338
+ context 'when extra parameters are set' do
339
+ let(:config) do
340
+ {
341
+ base_url: 'https://vra.corp.local',
342
+ username: 'myuser',
343
+ password: 'mypassword',
344
+ tenant: 'mytenant',
345
+ verify_ssl: true,
346
+ cpus: 2,
347
+ memory: 2048,
348
+ extra_parameters: { 'key1' => { type: 'string', value: 'value1' },
349
+ 'key2' => { type: 'integer', value: 123 }
350
+ }
351
+ }
352
+ end
353
+
354
+ it 'sets extra parmeters' do
355
+ expect(catalog_request).to receive(:set_parameter).with('key1', 'string', 'value1')
356
+ expect(catalog_request).to receive(:set_parameter).with('key2', 'integer', 123)
357
+ driver.catalog_request
358
+ end
359
+ end
360
+ end
361
+
362
+ describe '#vra_client' do
363
+ it 'sets up a client object' do
364
+ expect(Vra::Client).to receive(:new).with(base_url: config[:base_url],
365
+ username: config[:username],
366
+ password: config[:password],
367
+ tenant: config[:tenant],
368
+ verify_ssl: config[:verify_ssl])
369
+ driver.vra_client
370
+ end
371
+ end
372
+
373
+ describe '#wait_for_request' do
374
+ before do
375
+ # don't actually sleep
376
+ allow(driver).to receive(:sleep)
377
+ end
378
+
379
+ context 'when the requests completes normally, 3 loops' do
380
+ it 'only refreshes the request 3 times' do
381
+ request = double('request')
382
+ allow(request).to receive(:status)
383
+ allow(request).to receive(:completed?).exactly(3).times.and_return(false, false, true)
384
+ expect(request).to receive(:refresh).exactly(3).times
385
+
386
+ driver.wait_for_request(request)
387
+ end
388
+ end
389
+
390
+ context 'when the request is completed on the first loop' do
391
+ it 'only refreshes the request 1 time' do
392
+ request = double('request')
393
+ allow(request).to receive(:status)
394
+ allow(request).to receive(:completed?).once.and_return(true)
395
+ expect(request).to receive(:refresh).once
396
+
397
+ driver.wait_for_request(request)
398
+ end
399
+ end
400
+
401
+ context 'when the timeout is exceeded' do
402
+ it 'prints a warning and exits' do
403
+ request = double('request')
404
+ allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
405
+ expect { driver.wait_for_request(request) }.to raise_error(Timeout::Error)
406
+ end
407
+ end
408
+
409
+ context 'when a non-timeout exception is raised' do
410
+ it 'raises the original exception' do
411
+ request = double('request')
412
+ allow(request).to receive(:refresh).and_raise(RuntimeError)
413
+ expect { driver.wait_for_request(request) }.to raise_error(RuntimeError)
414
+ end
415
+ end
416
+ end
417
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-vra
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chef Partner Engineering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-kitchen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.4.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.4.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: vmware-vra
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.7'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '10.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '10.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.2'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.2'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.10'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.10'
103
+ - !ruby/object:Gem::Dependency
104
+ name: webmock
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.21'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '1.21'
117
+ description: A Test Kitchen driver for VMware vRealize Automation (vRA)
118
+ email:
119
+ - partnereng@chef.io
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - ".gitignore"
125
+ - ".rubocop.yml"
126
+ - Gemfile
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - kitchen-vra.gemspec
131
+ - lib/kitchen/driver/vra.rb
132
+ - lib/kitchen/driver/vra_version.rb
133
+ - spec/spec_helper.rb
134
+ - spec/vra_spec.rb
135
+ homepage: https://github.com/chef-partners/kitchen-vra
136
+ licenses:
137
+ - Apache 2.0
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.4.4
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: A Test Kitchen driver for VMware vRealize Automation (vRA)
159
+ test_files:
160
+ - spec/spec_helper.rb
161
+ - spec/vra_spec.rb
162
+ has_rdoc: