From 561017eae70941a7862f98dec493c97c27d71065 Mon Sep 17 00:00:00 2001 From: "J.R. Garcia" Date: Thu, 29 Oct 2015 14:28:51 -0500 Subject: [PATCH] Initial provider extraction from fog/fog --- .gitignore | 23 + .travis.yml | 28 + CONTRIBUTING.md | 18 + CONTRIBUTORS.md | 57 ++ Gemfile | 4 + LICENSE.md | 20 + README.md | 31 + Rakefile | 15 + fog-vsphere.gemspec | 30 + gemfiles/Gemfile.1.9.2+ | 6 + gemfiles/Gemfile.1.9.2- | 10 + lib/fog/vsphere.rb | 40 + lib/fog/vsphere/compute.rb | 465 +++++++++++ lib/fog/vsphere/models/compute/cluster.rb | 28 + lib/fog/vsphere/models/compute/clusters.rb | 23 + lib/fog/vsphere/models/compute/customfield.rb | 16 + .../vsphere/models/compute/customfields.rb | 24 + lib/fog/vsphere/models/compute/customvalue.rb | 14 + .../vsphere/models/compute/customvalues.rb | 34 + lib/fog/vsphere/models/compute/datacenter.rb | 44 ++ lib/fog/vsphere/models/compute/datacenters.rb | 20 + lib/fog/vsphere/models/compute/datastore.rb | 21 + lib/fog/vsphere/models/compute/datastores.rb | 22 + lib/fog/vsphere/models/compute/folder.rb | 24 + lib/fog/vsphere/models/compute/folders.rb | 24 + lib/fog/vsphere/models/compute/interface.rb | 91 +++ lib/fog/vsphere/models/compute/interfaces.rb | 67 ++ .../vsphere/models/compute/interfacetype.rb | 22 + .../vsphere/models/compute/interfacetypes.rb | 35 + lib/fog/vsphere/models/compute/network.rb | 18 + lib/fog/vsphere/models/compute/networks.rb | 23 + .../vsphere/models/compute/resource_pool.rb | 19 + .../vsphere/models/compute/resource_pools.rb | 23 + .../vsphere/models/compute/scsicontroller.rb | 16 + lib/fog/vsphere/models/compute/server.rb | 296 +++++++ lib/fog/vsphere/models/compute/servers.rb | 37 + lib/fog/vsphere/models/compute/servertype.rb | 36 + lib/fog/vsphere/models/compute/servertypes.rb | 24 + lib/fog/vsphere/models/compute/template.rb | 11 + lib/fog/vsphere/models/compute/templates.rb | 20 + lib/fog/vsphere/models/compute/volume.rb | 99 +++ lib/fog/vsphere/models/compute/volumes.rb | 54 ++ .../compute/cloudinit_to_customspec.rb | 65 ++ .../vsphere/requests/compute/create_folder.rb | 22 + lib/fog/vsphere/requests/compute/create_vm.rb | 169 ++++ .../vsphere/requests/compute/current_time.rb | 18 + .../vsphere/requests/compute/get_cluster.rb | 25 + .../requests/compute/get_compute_resource.rb | 41 + .../requests/compute/get_datacenter.rb | 31 + .../vsphere/requests/compute/get_datastore.rb | 30 + .../vsphere/requests/compute/get_folder.rb | 74 ++ .../requests/compute/get_interface_type.rb | 15 + .../vsphere/requests/compute/get_network.rb | 59 ++ .../requests/compute/get_resource_pool.rb | 26 + .../requests/compute/get_server_type.rb | 32 + .../vsphere/requests/compute/get_template.rb | 16 + .../requests/compute/get_virtual_machine.rb | 57 ++ .../compute/get_vm_first_scsi_controller.rb | 26 + .../vsphere/requests/compute/list_clusters.rb | 72 ++ .../compute/list_compute_resources.rb | 92 +++ .../requests/compute/list_customfields.rb | 21 + .../requests/compute/list_datacenters.rb | 53 ++ .../requests/compute/list_datastores.rb | 40 + .../vsphere/requests/compute/list_folders.rb | 44 ++ .../requests/compute/list_interface_types.rb | 25 + .../vsphere/requests/compute/list_networks.rb | 38 + .../requests/compute/list_resource_pools.rb | 38 + .../requests/compute/list_server_types.rb | 54 ++ .../requests/compute/list_templates.rb | 48 ++ .../requests/compute/list_virtual_machines.rb | 80 ++ .../requests/compute/list_vm_customvalues.rb | 20 + .../requests/compute/list_vm_interfaces.rb | 63 ++ .../requests/compute/list_vm_volumes.rb | 52 ++ .../requests/compute/modify_vm_interface.rb | 59 ++ .../requests/compute/modify_vm_volume.rb | 25 + .../requests/compute/set_vm_customvalue.rb | 17 + lib/fog/vsphere/requests/compute/vm_clone.rb | 727 ++++++++++++++++++ .../vsphere/requests/compute/vm_config_vnc.rb | 45 ++ .../vsphere/requests/compute/vm_destroy.rb | 23 + .../vsphere/requests/compute/vm_execute.rb | 47 ++ .../vsphere/requests/compute/vm_migrate.rb | 33 + .../vsphere/requests/compute/vm_power_off.rb | 39 + .../vsphere/requests/compute/vm_power_on.rb | 26 + lib/fog/vsphere/requests/compute/vm_reboot.rb | 31 + .../requests/compute/vm_reconfig_cpus.rb | 23 + .../requests/compute/vm_reconfig_hardware.rb | 24 + .../requests/compute/vm_reconfig_memory.rb | 23 + lib/fog/vsphere/version.rb | 5 + tests/compute_tests.rb | 53 ++ tests/helper.rb | 1 + tests/helpers/mock_helper.rb | 9 + tests/helpers/succeeds_helper.rb | 9 + tests/models/compute/server_tests.rb | 44 ++ tests/models/compute/servers_tests.rb | 15 + tests/requests/compute/current_time_tests.rb | 12 + tests/requests/compute/get_network_tests.rb | 50 ++ tests/requests/compute/list_clusters_tests.rb | 11 + .../compute/list_virtual_machines_tests.rb | 38 + .../compute/set_vm_customvalue_tests.rb | 20 + tests/requests/compute/vm_clone_tests.rb | 50 ++ tests/requests/compute/vm_config_vnc_tests.rb | 19 + tests/requests/compute/vm_destroy_tests.rb | 17 + tests/requests/compute/vm_migrate_tests.rb | 16 + tests/requests/compute/vm_power_off_tests.rb | 26 + tests/requests/compute/vm_power_on_tests.rb | 17 + tests/requests/compute/vm_reboot_tests.rb | 26 + .../compute/vm_reconfig_cpus_tests.rb | 19 + .../compute/vm_reconfig_hardware_tests.rb | 19 + .../compute/vm_reconfig_memory_tests.rb | 19 + 109 files changed, 5015 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTORS.md create mode 100644 Gemfile create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 Rakefile create mode 100644 fog-vsphere.gemspec create mode 100644 gemfiles/Gemfile.1.9.2+ create mode 100644 gemfiles/Gemfile.1.9.2- create mode 100644 lib/fog/vsphere.rb create mode 100644 lib/fog/vsphere/compute.rb create mode 100644 lib/fog/vsphere/models/compute/cluster.rb create mode 100644 lib/fog/vsphere/models/compute/clusters.rb create mode 100644 lib/fog/vsphere/models/compute/customfield.rb create mode 100644 lib/fog/vsphere/models/compute/customfields.rb create mode 100644 lib/fog/vsphere/models/compute/customvalue.rb create mode 100644 lib/fog/vsphere/models/compute/customvalues.rb create mode 100644 lib/fog/vsphere/models/compute/datacenter.rb create mode 100644 lib/fog/vsphere/models/compute/datacenters.rb create mode 100644 lib/fog/vsphere/models/compute/datastore.rb create mode 100644 lib/fog/vsphere/models/compute/datastores.rb create mode 100644 lib/fog/vsphere/models/compute/folder.rb create mode 100644 lib/fog/vsphere/models/compute/folders.rb create mode 100644 lib/fog/vsphere/models/compute/interface.rb create mode 100644 lib/fog/vsphere/models/compute/interfaces.rb create mode 100644 lib/fog/vsphere/models/compute/interfacetype.rb create mode 100644 lib/fog/vsphere/models/compute/interfacetypes.rb create mode 100644 lib/fog/vsphere/models/compute/network.rb create mode 100644 lib/fog/vsphere/models/compute/networks.rb create mode 100644 lib/fog/vsphere/models/compute/resource_pool.rb create mode 100644 lib/fog/vsphere/models/compute/resource_pools.rb create mode 100644 lib/fog/vsphere/models/compute/scsicontroller.rb create mode 100644 lib/fog/vsphere/models/compute/server.rb create mode 100644 lib/fog/vsphere/models/compute/servers.rb create mode 100644 lib/fog/vsphere/models/compute/servertype.rb create mode 100644 lib/fog/vsphere/models/compute/servertypes.rb create mode 100644 lib/fog/vsphere/models/compute/template.rb create mode 100644 lib/fog/vsphere/models/compute/templates.rb create mode 100644 lib/fog/vsphere/models/compute/volume.rb create mode 100644 lib/fog/vsphere/models/compute/volumes.rb create mode 100644 lib/fog/vsphere/requests/compute/cloudinit_to_customspec.rb create mode 100644 lib/fog/vsphere/requests/compute/create_folder.rb create mode 100644 lib/fog/vsphere/requests/compute/create_vm.rb create mode 100644 lib/fog/vsphere/requests/compute/current_time.rb create mode 100644 lib/fog/vsphere/requests/compute/get_cluster.rb create mode 100644 lib/fog/vsphere/requests/compute/get_compute_resource.rb create mode 100644 lib/fog/vsphere/requests/compute/get_datacenter.rb create mode 100644 lib/fog/vsphere/requests/compute/get_datastore.rb create mode 100644 lib/fog/vsphere/requests/compute/get_folder.rb create mode 100644 lib/fog/vsphere/requests/compute/get_interface_type.rb create mode 100644 lib/fog/vsphere/requests/compute/get_network.rb create mode 100644 lib/fog/vsphere/requests/compute/get_resource_pool.rb create mode 100644 lib/fog/vsphere/requests/compute/get_server_type.rb create mode 100644 lib/fog/vsphere/requests/compute/get_template.rb create mode 100644 lib/fog/vsphere/requests/compute/get_virtual_machine.rb create mode 100644 lib/fog/vsphere/requests/compute/get_vm_first_scsi_controller.rb create mode 100644 lib/fog/vsphere/requests/compute/list_clusters.rb create mode 100644 lib/fog/vsphere/requests/compute/list_compute_resources.rb create mode 100644 lib/fog/vsphere/requests/compute/list_customfields.rb create mode 100644 lib/fog/vsphere/requests/compute/list_datacenters.rb create mode 100644 lib/fog/vsphere/requests/compute/list_datastores.rb create mode 100644 lib/fog/vsphere/requests/compute/list_folders.rb create mode 100644 lib/fog/vsphere/requests/compute/list_interface_types.rb create mode 100644 lib/fog/vsphere/requests/compute/list_networks.rb create mode 100644 lib/fog/vsphere/requests/compute/list_resource_pools.rb create mode 100644 lib/fog/vsphere/requests/compute/list_server_types.rb create mode 100644 lib/fog/vsphere/requests/compute/list_templates.rb create mode 100644 lib/fog/vsphere/requests/compute/list_virtual_machines.rb create mode 100644 lib/fog/vsphere/requests/compute/list_vm_customvalues.rb create mode 100644 lib/fog/vsphere/requests/compute/list_vm_interfaces.rb create mode 100644 lib/fog/vsphere/requests/compute/list_vm_volumes.rb create mode 100644 lib/fog/vsphere/requests/compute/modify_vm_interface.rb create mode 100644 lib/fog/vsphere/requests/compute/modify_vm_volume.rb create mode 100644 lib/fog/vsphere/requests/compute/set_vm_customvalue.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_clone.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_config_vnc.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_destroy.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_execute.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_migrate.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_power_off.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_power_on.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_reboot.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_reconfig_cpus.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_reconfig_hardware.rb create mode 100644 lib/fog/vsphere/requests/compute/vm_reconfig_memory.rb create mode 100644 lib/fog/vsphere/version.rb create mode 100644 tests/compute_tests.rb create mode 100644 tests/helper.rb create mode 100644 tests/helpers/mock_helper.rb create mode 100644 tests/helpers/succeeds_helper.rb create mode 100644 tests/models/compute/server_tests.rb create mode 100644 tests/models/compute/servers_tests.rb create mode 100644 tests/requests/compute/current_time_tests.rb create mode 100644 tests/requests/compute/get_network_tests.rb create mode 100644 tests/requests/compute/list_clusters_tests.rb create mode 100644 tests/requests/compute/list_virtual_machines_tests.rb create mode 100644 tests/requests/compute/set_vm_customvalue_tests.rb create mode 100644 tests/requests/compute/vm_clone_tests.rb create mode 100644 tests/requests/compute/vm_config_vnc_tests.rb create mode 100644 tests/requests/compute/vm_destroy_tests.rb create mode 100644 tests/requests/compute/vm_migrate_tests.rb create mode 100644 tests/requests/compute/vm_power_off_tests.rb create mode 100644 tests/requests/compute/vm_power_on_tests.rb create mode 100644 tests/requests/compute/vm_reboot_tests.rb create mode 100644 tests/requests/compute/vm_reconfig_cpus_tests.rb create mode 100644 tests/requests/compute/vm_reconfig_hardware_tests.rb create mode 100644 tests/requests/compute/vm_reconfig_memory_tests.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a92eeeed --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +*.bundle +*.so +*.o +*.a +mkmf.log +gemfiles/*.lock \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..ee58d814 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: ruby +sudo: false +script: bundle exec rake test +matrix: + fast_finish: true + include: + - rvm: 1.8.7 + gemfile: gemfiles/Gemfile.1.9.2- + - rvm: 1.9.3 + gemfile: gemfiles/Gemfile.1.9.2+ + - rvm: 2.0.0 + gemfile: Gemfile + - rvm: 2.1.0 + gemfile: Gemfile + - rvm: 2.1.1 + gemfile: Gemfile + - rvm: 2.2.0 + gemfile: Gemfile + - rvm: jruby-18mode + gemfile: gemfiles/Gemfile.1.9.2- + - rvm: jruby-19mode + gemfile: gemfiles/Gemfile.1.9.2+ + - rvm: jruby-head + gemfile: Gemfile + allow_failures: + - rvm: jruby-head +notifications: + email: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8494aeb2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +## Getting Involved + +New contributors are always welcome, when it doubt please ask questions. We strive to be an open and welcoming community. Please be nice to one another. + +### Coding + +* Pick a task: +* Offer feedback on open [pull requests](https://github.com/fog/fog-vsphere/pulls). +* Review open [issues](https://github.com/fog/fog-vsphere/issues) for things to help on. +* [Create an issue](https://github.com/fog/fog-vsphere/issues/new) to start a discussion on additions or features. +* Fork the project, add your changes and tests to cover them in a topic branch. +* Commit your changes and rebase against `fog/fog-vsphere` to ensure everything is up to date. +* [Submit a pull request](https://github.com/fog/fog-vsphere/compare/) + +### Non-Coding + +* Offer feedback on open [issues](https://github.com/fog/fog-vsphere/issues). +* Organize or volunteer at events. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 00000000..9276019f --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,57 @@ +* Ahmed Elsabbahy +* Alan Sebastian +* Carl Caum +* Carlos Sanchez +* Chris Thompson +* Chris Thompson +* Cyrus Team +* Darren Foo +* Dominic Cleal +* Eric Stonfer +* Ewoud Kohl van Wijngaarden +* Francois Herbert +* Francois Herbert +* Ivan Nečas +* James Herdman +* Jeff McCune +* Justin Clayton +* Justin Pratt +* Justin Pratt +* Karan Misra +* Kelsey Hightower +* Kevin Menard +* Kevin Menard +* Lance Ivy +* Lukas Zapletal +* Marc Grimme +* Marc Grimme +* Martin Matuska +* Matt Darby +* Matthew Black +* Matthew Black +* Michael Moll +* Mick Pollard +* Ming Jin +* Nick Huanca +* Nick Huanca +* Nick Huanuca +* Ohad Levy +* Ohad Levy +* Oscar Elfving +* Paul Thornthwaite +* Paul Thornthwaite +* Rich Lane +* Samuel Keeley +* Shlomi Zadok +* Simon Josi +* Tejas Ravindra Mandke +* Timur Alperovich +* Wesley Beary +* Wesley Beary +* Xavier Fontrodona +* alan +* endzyme +* geemus +* karmab +* slivik +* tipt0e diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..196ab134 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in fog-vsphere.gemspec +gemspec diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..459d74a0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 [CONTRIBUTORS.md](CONTRIBUTORS.md) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..f1af7d9d --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Fog::Vsphere + +[![Gem Version](https://badge.fury.io/rb/fog-vsphere.svg)](http://badge.fury.io/rb/fog-vsphere) [![Build Status](https://travis-ci.org/fog/fog-vsphere.svg?branch=master)](https://travis-ci.org/fog/fog-vsphere) [![Dependency Status](https://gemnasium.com/fog/fog-vsphere.svg)](https://gemnasium.com/fog/fog-vsphere) [![Coverage Status](https://img.shields.io/coveralls/fog/fog-vsphere.svg)](https://coveralls.io/r/fog/fog-vsphere) [![Code Climate](https://codeclimate.com/github/fog/fog-vsphere.png)](https://codeclimate.com/github/fog/fog-vsphere) [![Stories in Ready](https://badge.waffle.io/fog/fog-vsphere.png?label=ready&title=Ready)](https://waffle.io/fog/fog-vsphere) + +TODO: Write a gem description + +## Installation + +Add this line to your application's Gemfile: + +gem 'fog-vsphere' + +And then execute: + +$ bundle + +Or install it yourself as: + +$ gem install fog-vsphere + +## Usage + +TODO: Write usage instructions here + +## Contributing + +1. Fork it ( https://github.com/fog/fog-vsphere/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..4e09ec38 --- /dev/null +++ b/Rakefile @@ -0,0 +1,15 @@ +require 'bundler/gem_tasks' +# require 'rake/testtask' + +# Rake::TestTask.new do |t| +# t.libs << 'spec/' +# t.test_files = Rake::FileList['spec/**/*_spec.rb'] +# t.verbose = true +# end + +mock = ENV['FOG_MOCK'] || 'true' +task :test do + sh("export FOG_MOCK=#{mock} && bundle exec shindont") +end + +task(:default => [:test]) diff --git a/fog-vsphere.gemspec b/fog-vsphere.gemspec new file mode 100644 index 00000000..065237b7 --- /dev/null +++ b/fog-vsphere.gemspec @@ -0,0 +1,30 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'fog/vsphere/version' + +Gem::Specification.new do |spec| + spec.name = 'fog-vsphere' + spec.version = Fog::Vsphere::VERSION + spec.authors = ['J.R. Garcia'] + spec.email = ['jrg@vmware.com'] + + spec.summary = "Module for the 'fog' gem to support VMware vSphere." + spec.description = 'This library can be used as a module for `fog` or as standalone provider to use the XENSERVER in applications.' + spec.homepage = 'https://github.com/fog/fog-vsphere' + spec.license = 'MIT' + + spec.files = `git ls-files -z`.split("\x0") + spec.test_files = spec.files.grep(%r{^tests\/}) + + spec.require_paths = ['lib'] + + spec.add_runtime_dependency 'fog-core', '~> 1.32' + spec.add_runtime_dependency 'rbvmomi', '~> 1.8' + + spec.add_development_dependency 'bundler', '~> 1.10' + spec.add_development_dependency 'pry', '~> 0.10' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'rubocop', '~> 0.34' + spec.add_development_dependency 'shindo', '~> 0.3' +end diff --git a/gemfiles/Gemfile.1.9.2+ b/gemfiles/Gemfile.1.9.2+ new file mode 100644 index 00000000..5e8e3f87 --- /dev/null +++ b/gemfiles/Gemfile.1.9.2+ @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'net-ssh', '~> 2.9' +gem 'rubocop', '~> 0.34' + +gemspec :path => '..' diff --git a/gemfiles/Gemfile.1.9.2- b/gemfiles/Gemfile.1.9.2- new file mode 100644 index 00000000..fba6e608 --- /dev/null +++ b/gemfiles/Gemfile.1.9.2- @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gem 'mime-types', '~> 1.25' +gem 'net-ssh', '~> 2.9' +gem 'nokogiri', '~> 1.5.11' +gem 'rainbow', '~> 1.99' +gem 'rest-client', '~> 1.6.8' +gem 'rubocop', '~> 0.5.0' + +gemspec :path => '..' diff --git a/lib/fog/vsphere.rb b/lib/fog/vsphere.rb new file mode 100644 index 00000000..cc247ce2 --- /dev/null +++ b/lib/fog/vsphere.rb @@ -0,0 +1,40 @@ +require 'fog/core' +require 'fog/vsphere/compute' + +module Fog + module Vsphere + extend Fog::Provider + + module Errors + class ServiceError < Fog::Errors::Error; end + class SecurityError < ServiceError; end + class NotFound < ServiceError; end + end + + service(:compute, 'Compute') + + # This helper was originally added as Fog.class_as_string and moved to core but only used here + def self.class_from_string classname, defaultpath="" + if classname and classname.is_a? String then + chain = classname.split("::") + klass = Kernel + chain.each do |klass_string| + klass = klass.const_get klass_string + end + if klass.is_a? Class then + klass + elsif defaultpath != nil then + class_from_string((defaultpath.split("::")+chain).join("::"), nil) + else + nil + end + elsif classname and classname.is_a? Class then + classname + else + nil + end + rescue NameError + defaultpath != nil ? class_from_string((defaultpath.split("::")+chain).join("::"), nil) : nil + end + end +end diff --git a/lib/fog/vsphere/compute.rb b/lib/fog/vsphere/compute.rb new file mode 100644 index 00000000..f0284573 --- /dev/null +++ b/lib/fog/vsphere/compute.rb @@ -0,0 +1,465 @@ +require 'digest/sha2' + +module Fog + module Compute + class Vsphere < Fog::Service + requires :vsphere_username, :vsphere_password, :vsphere_server + recognizes :vsphere_port, :vsphere_path, :vsphere_ns + recognizes :vsphere_rev, :vsphere_ssl, :vsphere_expected_pubkey_hash + recognizes :vsphere_debug + + model_path 'fog/vsphere/models/compute' + model :server + collection :servers + model :servertype + collection :servertypes + model :datacenter + collection :datacenters + model :interface + collection :interfaces + model :interfacetype + collection :interfacetypes + model :volume + collection :volumes + model :template + collection :templates + model :cluster + collection :clusters + model :resource_pool + collection :resource_pools + model :network + collection :networks + model :datastore + collection :datastores + model :folder + collection :folders + model :customvalue + collection :customvalues + model :customfield + collection :customfields + model :scsicontroller + + request_path 'fog/vsphere/requests/compute' + request :current_time + request :cloudinit_to_customspec + request :list_virtual_machines + request :vm_power_off + request :vm_power_on + request :vm_reboot + request :vm_clone + request :vm_destroy + request :vm_migrate + request :vm_execute + request :list_datacenters + request :get_datacenter + request :list_clusters + request :get_cluster + request :list_resource_pools + request :get_resource_pool + request :list_networks + request :get_network + request :list_datastores + request :get_datastore + request :list_compute_resources + request :get_compute_resource + request :list_templates + request :get_template + request :get_folder + request :list_folders + request :create_vm + request :list_vm_interfaces + request :modify_vm_interface + request :modify_vm_volume + request :list_vm_volumes + request :get_virtual_machine + request :vm_reconfig_hardware + request :vm_reconfig_memory + request :vm_reconfig_cpus + request :vm_config_vnc + request :create_folder + request :list_server_types + request :get_server_type + request :list_interface_types + request :get_interface_type + request :list_vm_customvalues + request :list_customfields + request :get_vm_first_scsi_controller + request :set_vm_customvalue + + module Shared + attr_reader :vsphere_is_vcenter + attr_reader :vsphere_rev + attr_reader :vsphere_server + attr_reader :vsphere_username + + protected + + ATTR_TO_PROP = { + :id => 'config.instanceUuid', + :name => 'name', + :uuid => 'config.uuid', + :template => 'config.template', + :parent => 'parent', + :hostname => 'summary.guest.hostName', + :operatingsystem => 'summary.guest.guestFullName', + :ipaddress => 'guest.ipAddress', + :power_state => 'runtime.powerState', + :connection_state => 'runtime.connectionState', + :hypervisor => 'runtime.host', + :tools_state => 'guest.toolsStatus', + :tools_version => 'guest.toolsVersionStatus', + :memory_mb => 'config.hardware.memoryMB', + :cpus => 'config.hardware.numCPU', + :corespersocket => 'config.hardware.numCoresPerSocket', + :overall_status => 'overallStatus', + :guest_id => 'config.guestId', + :hardware_version => 'config.version', + :cpuHotAddEnabled => 'config.cpuHotAddEnabled', + :memoryHotAddEnabled => 'config.memoryHotAddEnabled', + :firmware => 'config.firmware', + } + + def convert_vm_view_to_attr_hash(vms) + vms = @connection.serviceContent.propertyCollector.collectMultiple(vms,*ATTR_TO_PROP.values.uniq) + vms.map { |vm| props_to_attr_hash(*vm) } + end + + # Utility method to convert a VMware managed object into an attribute hash. + # This should only really be necessary for the real class. + # This method is expected to be called by the request methods + # in order to massage VMware Managed Object References into Attribute Hashes. + def convert_vm_mob_ref_to_attr_hash(vm_mob_ref) + return nil unless vm_mob_ref + + props = vm_mob_ref.collect!(*ATTR_TO_PROP.values.uniq) + props_to_attr_hash vm_mob_ref, props + end + + def props_to_attr_hash vm_mob_ref, props + # NOTE: Object.tap is in 1.8.7 and later. + # Here we create the hash object that this method returns, but first we need + # to add a few more attributes that require additional calls to the vSphere + # API. The hypervisor name and mac_addresses attributes may not be available + # so we need catch any exceptions thrown during lookup and set them to nil. + # + # The use of the "tap" method here is a convenience, it allows us to update the + # hash object without explicitly returning the hash at the end of the method. + Hash[ATTR_TO_PROP.map { |k,v| [k.to_s, props[v]] }].tap do |attrs| + attrs['id'] ||= vm_mob_ref._ref + attrs['mo_ref'] = vm_mob_ref._ref + # The name method "magically" appears after a VM is ready and + # finished cloning. + if attrs['hypervisor'].kind_of?(RbVmomi::VIM::HostSystem) + host = attrs['hypervisor'] + attrs['datacenter'] = Proc.new { parent_attribute(host.path, :datacenter)[1] rescue nil } + attrs['cluster'] = Proc.new { parent_attribute(host.path, :cluster)[1] rescue nil } + attrs['hypervisor'] = Proc.new { host.name rescue nil } + attrs['resource_pool'] = Proc.new {(vm_mob_ref.resourcePool || host.resourcePool).name rescue nil} + end + # This inline rescue catches any standard error. While a VM is + # cloning, a call to the macs method will throw and NoMethodError + attrs['mac_addresses'] = Proc.new {vm_mob_ref.macs rescue nil} + # Rescue nil to catch testing while vm_mob_ref isn't reaL?? + attrs['path'] = "/"+attrs['parent'].path.map(&:last).join('/') rescue nil + end + end + # returns the parent object based on a type + # provides both real RbVmomi object and its name. + # e.g. + #[Datacenter("datacenter-2"), "dc-name"] + def parent_attribute path, type + element = case type + when :datacenter + RbVmomi::VIM::Datacenter + when :cluster + RbVmomi::VIM::ClusterComputeResource + when :host + RbVmomi::VIM::HostSystem + else + raise "Unknown type" + end + path.select {|x| x[0].is_a? element}.flatten + rescue + nil + end + + # returns vmware managed obj id string + def managed_obj_id obj + obj.to_s.match(/\("([^"]+)"\)/)[1] + end + + def is_uuid?(id) + !(id =~ /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/).nil? + end + end + + class Mock + include Shared + + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = { + :servers => { + "5032c8a5-9c5e-ba7a-3804-832a03e16381" => { + "resource_pool" => "Resources", + "memory_mb" => 2196, + "mac_addresses" => { "Network adapter 1" => "00:50:56:a9:00:28" }, + "power_state" => "poweredOn", + "cpus" => 1, + "hostname" => "dhcp75-197.virt.bos.redhat.com", + "mo_ref" => "vm-562", + "connection_state" => "connected", + "overall_status" => "green", + "datacenter" => "Solutions", + "volumes" => + [{ + "id" => "6000C29c-a47d-4cd9-5249-c371de775f06", + "datastore" => "Storage1", + "mode" => "persistent", + "size" => 8388608, + "thin" => true, + "name" => "Hard disk 1", + "filename" => "[Storage1] rhel6-mfojtik/rhel6-mfojtik.vmdk", + "size_gb" => 8 + }], + "interfaces" => + [{"mac" => "00:50:56:a9:00:28", + "network" => "VM Network", + "name" => "Network adapter 1", + "status" => "ok", + "summary" => "VM Network", + }], + "hypervisor" => "gunab.puppetlabs.lan", + "guest_id" => "rhel6_64Guest", + "tools_state" => "toolsOk", + "cluster" => "Solutionscluster", + "name" => "rhel64", + "operatingsystem" => "Red Hat Enterprise Linux 6 (64-bit)", + "path" => "/Datacenters/Solutions/vm", + "uuid" => "4229f0e9-bfdc-d9a7-7bac-12070772e6dc", + "instance_uuid" => "5032c8a5-9c5e-ba7a-3804-832a03e16381", + "id" => "5032c8a5-9c5e-ba7a-3804-832a03e16381", + "tools_version" => "guestToolsUnmanaged", + "ipaddress" => "192.168.100.184", + "template" => false + }, + "502916a3-b42e-17c7-43ce-b3206e9524dc" => { + "resource_pool" => "Resources", + "memory_mb" => 512, + "power_state" => "poweredOn", + "mac_addresses" => { "Network adapter 1" => "00:50:56:a9:00:00" }, + "hostname" => nil, + "cpus" => 1, + "connection_state" => "connected", + "mo_ref" => "vm-621", + "overall_status" => "green", + "datacenter" => "Solutions", + "volumes" => + [{"thin" => false, + "size_gb" => 10, + "datastore" => "datastore1", + "filename" => "[datastore1] i-1342439683/i-1342439683.vmdk", + "size" => 10485762, + "name" => "Hard disk 1", + "mode" => "persistent", + "id" => "6000C29b-f364-d073-8316-8e98ac0a0eae" }], + "interfaces" => + [{ "summary" => "VM Network", + "mac" => "00:50:56:a9:00:00", + "status" => "ok", + "network" => "VM Network", + "name" => "Network adapter 1" }], + "hypervisor" => "gunab.puppetlabs.lan", + "guest_id" => nil, + "cluster" => "Solutionscluster", + "tools_state" => "toolsNotInstalled", + "name" => "i-1342439683", + "operatingsystem" => nil, + "path" => "/", + "tools_version" => "guestToolsNotInstalled", + "uuid" => "4229e0de-30cb-ceb2-21f9-4d8d8beabb52", + "instance_uuid" => "502916a3-b42e-17c7-43ce-b3206e9524dc", + "id" => "502916a3-b42e-17c7-43ce-b3206e9524dc", + "ipaddress" => nil, + "template" => false + }, + "5029c440-85ee-c2a1-e9dd-b63e39364603" => { + "resource_pool" => "Resources", + "memory_mb" => 2196, + "power_state" => "poweredOn", + "mac_addresses" => { "Network adapter 1" => "00:50:56:b2:00:af" }, + "hostname" => "centos56gm.localdomain", + "cpus" => 1, + "connection_state" => "connected", + "mo_ref" => "vm-715", + "overall_status" => "green", + "datacenter" => "Solutions", + "hypervisor" => "gunab.puppetlabs.lan", + "guest_id" => "rhel6_64Guest", + "cluster" => "Solutionscluster", + "tools_state" => "toolsOk", + "name" => "jefftest", + "operatingsystem" => "Red Hat Enterprise Linux 6 (64-bit)", + "path" => "/", + "tools_version" => "guestToolsUnmanaged", + "ipaddress" => "192.168.100.187", + "uuid" => "42329da7-e8ab-29ec-1892-d6a4a964912a", + "instance_uuid" => "5029c440-85ee-c2a1-e9dd-b63e39364603", + "id" => "5029c440-85ee-c2a1-e9dd-b63e39364603", + "template" => false + } + }, + :datacenters => { + "Solutions" => {:name => "Solutions", :status => "grey"} + }, + :clusters => + [{:id => "1d4d9a3f-e4e8-4c40-b7fc-263850068fa4", + :name => "Solutionscluster", + :num_host => "4", + :num_cpu_cores => "16", + :overall_status => "green", + :datacenter => "Solutions", + :klass => "RbVmomi::VIM::ComputeResource" + }, + {:id => "e4195973-102b-4096-bbd6-5429ff0b35c9", + :name => "Problemscluster", + :num_host => "4", + :num_cpu_cores => "32", + :overall_status => "green", + :datacenter => "Solutions", + :klass => "RbVmomi::VIM::ComputeResource" + }, + { + :klass => "RbVmomi::VIM::Folder", + :clusters => [{:id => "03616b8d-b707-41fd-b3b5-The first", + :name => "Problemscluster", + :num_host => "4", + :num_cpu_cores => "32", + :overall_status => "green", + :datacenter => "Solutions", + :klass => "RbVmomi::VIM::ComputeResource" + }, + {:id => "03616b8d-b707-41fd-b3b5-the Second", + :name => "Lastcluster", + :num_host => "8", + :num_cpu_cores => "32", + :overall_status => "green", + :datacenter => "Solutions", + :klass => "RbVmomi::VIM::ComputeResource"} + ] + } + ] + } + end + end + + def initialize(options={}) + require 'rbvmomi' + @vsphere_username = options[:vsphere_username] + @vsphere_password = 'REDACTED' + @vsphere_server = options[:vsphere_server] + @vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash] + @vsphere_is_vcenter = true + @vsphere_rev = '4.0' + end + + def data + self.class.data[@vsphere_username] + end + + def reset_data + self.class.data.delete(@vsphere_username) + end + end + + class Real + include Shared + + def initialize(options={}) + require 'rbvmomi' + @vsphere_username = options[:vsphere_username] + @vsphere_password = options[:vsphere_password] + @vsphere_server = options[:vsphere_server] + @vsphere_port = options[:vsphere_port] || 443 + @vsphere_path = options[:vsphere_path] || '/sdk' + @vsphere_ns = options[:vsphere_ns] || 'urn:vim25' + @vsphere_rev = options[:vsphere_rev] || '4.0' + @vsphere_ssl = options[:vsphere_ssl] || true + @vsphere_debug = options[:vsphere_debug] || false + @vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash] + @vsphere_must_reauthenticate = false + @vsphere_is_vcenter = nil + @connection = nil + connect + negotiate_revision(options[:vsphere_rev]) + authenticate + end + + def reload + connect + # Check if the negotiation was ever run + if @vsphere_is_vcenter.nil? + negotiate + end + authenticate + end + + private + def negotiate_revision(revision = nil) + # Negotiate the API revision + if not revision + rev = @connection.serviceContent.about.apiVersion + @connection.rev = [ rev, ENV['FOG_VSPHERE_REV'] || '4.1' ].min + end + + @vsphere_is_vcenter = @connection.serviceContent.about.apiType == "VirtualCenter" + @vsphere_rev = @connection.rev + end + + def connect + # This is a state variable to allow digest validation of the SSL cert + bad_cert = false + loop do + begin + @connection = RbVmomi::VIM.new :host => @vsphere_server, + :port => @vsphere_port, + :path => @vsphere_path, + :ns => @vsphere_ns, + :rev => @vsphere_rev, + :ssl => @vsphere_ssl, + :insecure => bad_cert, + :debug => @vsphere_debug + break + rescue OpenSSL::SSL::SSLError + raise if bad_cert + bad_cert = true + end + end + + if bad_cert then + validate_ssl_connection + end + end + + def authenticate + begin + @connection.serviceContent.sessionManager.Login :userName => @vsphere_username, + :password => @vsphere_password + rescue RbVmomi::VIM::InvalidLogin => e + raise Fog::Vsphere::Errors::ServiceError, e.message + end + end + + # Verify a SSL certificate based on the hashed public key + def validate_ssl_connection + pubkey = @connection.http.peer_cert.public_key + pubkey_hash = Digest::SHA2.hexdigest(pubkey.to_s) + expected_pubkey_hash = @vsphere_expected_pubkey_hash + if pubkey_hash != expected_pubkey_hash then + raise Fog::Vsphere::Errors::SecurityError, "The remote system presented a public key with hash #{pubkey_hash} but we're expecting a hash of #{expected_pubkey_hash || ''}. If you are sure the remote system is authentic set vsphere_expected_pubkey_hash: in ~/.fog" + end + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/cluster.rb b/lib/fog/vsphere/models/compute/cluster.rb new file mode 100644 index 00000000..f628bf0e --- /dev/null +++ b/lib/fog/vsphere/models/compute/cluster.rb @@ -0,0 +1,28 @@ +module Fog + module Compute + class Vsphere + class Cluster < Fog::Model + identity :id + + attribute :name + attribute :datacenter + attribute :num_host + attribute :num_cpu_cores + attribute :overall_status + attribute :full_path + + def resource_pools(filters = { }) + self.attributes[:resource_pools] ||= id.nil? ? [] : service.resource_pools({ + :service => service, + :cluster => name, + :datacenter => datacenter + }.merge(filters)) + end + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/clusters.rb b/lib/fog/vsphere/models/compute/clusters.rb new file mode 100644 index 00000000..440b47a6 --- /dev/null +++ b/lib/fog/vsphere/models/compute/clusters.rb @@ -0,0 +1,23 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/cluster' + +module Fog + module Compute + class Vsphere + class Clusters < Fog::Collection + model Fog::Compute::Vsphere::Cluster + attr_accessor :datacenter + + def all(filters = {}) + requires :datacenter + load service.list_clusters(filters.merge(:datacenter => datacenter)) + end + + def get(id) + requires :datacenter + new service.get_cluster(id, datacenter) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/customfield.rb b/lib/fog/vsphere/models/compute/customfield.rb new file mode 100644 index 00000000..83bde144 --- /dev/null +++ b/lib/fog/vsphere/models/compute/customfield.rb @@ -0,0 +1,16 @@ +module Fog + module Compute + class Vsphere + class Customfield < Fog::Model + identity :key + + attribute :name + attribute :type + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/customfields.rb b/lib/fog/vsphere/models/compute/customfields.rb new file mode 100644 index 00000000..f2341ead --- /dev/null +++ b/lib/fog/vsphere/models/compute/customfields.rb @@ -0,0 +1,24 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/customfield' + +module Fog + module Compute + class Vsphere + class Customfields < Fog::Collection + model Fog::Compute::Vsphere::Customfield + + attr_accessor :vm + + def all(filters = {}) + load service.list_customfields() + end + + def get(key) + load(service.list_customfields()).find do | cv | + cv.key == ((key.is_a? String) ? key.to_i : key) + end + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/customvalue.rb b/lib/fog/vsphere/models/compute/customvalue.rb new file mode 100644 index 00000000..399c0c44 --- /dev/null +++ b/lib/fog/vsphere/models/compute/customvalue.rb @@ -0,0 +1,14 @@ +module Fog + module Compute + class Vsphere + class Customvalue < Fog::Model + attribute :value + attribute :key + + def to_s + value + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/customvalues.rb b/lib/fog/vsphere/models/compute/customvalues.rb new file mode 100644 index 00000000..fbec7417 --- /dev/null +++ b/lib/fog/vsphere/models/compute/customvalues.rb @@ -0,0 +1,34 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/customvalue' + +module Fog + module Compute + class Vsphere + class Customvalues < Fog::Collection + model Fog::Compute::Vsphere::Customvalue + + attr_accessor :vm + + def all(filters = {}) + requires :vm + case vm + when Fog::Compute::Vsphere::Server + load service.list_vm_customvalues(vm.id) + else + raise 'customvalues should have vm' + end + end + + def get(key) + requires :vm + case vm + when Fog::Compute::Vsphere::Server + load service.list_vm_customvalues(vm.id) + else + raise 'customvalues should have vm' + end.find { | cv | cv.key == key } + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/datacenter.rb b/lib/fog/vsphere/models/compute/datacenter.rb new file mode 100644 index 00000000..26b7d6cb --- /dev/null +++ b/lib/fog/vsphere/models/compute/datacenter.rb @@ -0,0 +1,44 @@ +module Fog + module Compute + class Vsphere + class Datacenter < Fog::Model + identity :id + attribute :name + attribute :path + attribute :status + + def clusters filters = { } + service.clusters({ :datacenter => path.join("/") }.merge(filters)) + end + + def networks filters = { } + service.networks({ :datacenter => path.join("/") }.merge(filters)) + end + + def datastores filters = { } + service.datastores({ :datacenter => path.join("/") }.merge(filters)) + end + + def vm_folders filters = { } + service.folders({ :datacenter => path.join("/"), :type => :vm }.merge(filters)) + end + + def virtual_machines filters = {} + service.servers({ :datacenter => path.join("/") }.merge(filters)) + end + + def servertypes filters={} + service.servertypes({:datacenter => name }.merge(filters)) + end + + def customfields filters = {} + service.customfields({ :datacenter => path.join("/")}.merge(filters)) + end + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/datacenters.rb b/lib/fog/vsphere/models/compute/datacenters.rb new file mode 100644 index 00000000..c5812bc3 --- /dev/null +++ b/lib/fog/vsphere/models/compute/datacenters.rb @@ -0,0 +1,20 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/datacenter' + +module Fog + module Compute + class Vsphere + class Datacenters < Fog::Collection + model Fog::Compute::Vsphere::Datacenter + + def all(filters = {}) + load service.list_datacenters(filters) + end + + def get(name) + new service.get_datacenter(name) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/datastore.rb b/lib/fog/vsphere/models/compute/datastore.rb new file mode 100644 index 00000000..6baa2210 --- /dev/null +++ b/lib/fog/vsphere/models/compute/datastore.rb @@ -0,0 +1,21 @@ +module Fog + module Compute + class Vsphere + class Datastore < Fog::Model + identity :id + + attribute :name + attribute :datacenter + attribute :type + attribute :freespace + attribute :accessible # reachable by at least one hypervisor + attribute :capacity + attribute :uncommitted + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/datastores.rb b/lib/fog/vsphere/models/compute/datastores.rb new file mode 100644 index 00000000..67bbcd00 --- /dev/null +++ b/lib/fog/vsphere/models/compute/datastores.rb @@ -0,0 +1,22 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/datastore' + +module Fog + module Compute + class Vsphere + class Datastores < Fog::Collection + model Fog::Compute::Vsphere::Datastore + attr_accessor :datacenter + + def all(filters = {}) + load service.list_datastores(filters.merge(:datacenter => datacenter)) + end + + def get(id) + requires :datacenter + new service.get_datastore(id, datacenter) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/folder.rb b/lib/fog/vsphere/models/compute/folder.rb new file mode 100644 index 00000000..7dea57e8 --- /dev/null +++ b/lib/fog/vsphere/models/compute/folder.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class Vsphere + class Folder < Fog::Model + identity :id + + attribute :name + attribute :parent + attribute :datacenter + attribute :path + attribute :type + + def vms + return [] if type.to_s != 'vm' + service.servers(:folder => path, :datacenter => datacenter) + end + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/folders.rb b/lib/fog/vsphere/models/compute/folders.rb new file mode 100644 index 00000000..467bb78a --- /dev/null +++ b/lib/fog/vsphere/models/compute/folders.rb @@ -0,0 +1,24 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/folder' + +module Fog + module Compute + class Vsphere + class Folders < Fog::Collection + model Fog::Compute::Vsphere::Folder + attr_accessor :datacenter, :type, :path + + def all(filters = { }) + requires :datacenter + requires :type + load service.list_folders(filters.merge(:datacenter => datacenter, :type => type, :path => path)) + end + + def get(id) + requires :datacenter + new service.get_folder(id, datacenter, type) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/interface.rb b/lib/fog/vsphere/models/compute/interface.rb new file mode 100644 index 00000000..198501a5 --- /dev/null +++ b/lib/fog/vsphere/models/compute/interface.rb @@ -0,0 +1,91 @@ +module Fog + module Compute + class Vsphere + class Interface < Fog::Model + SAVE_MUTEX = Mutex.new + + identity :mac + alias_method :id, :mac + + attribute :network + attribute :name + attribute :status + attribute :summary + attribute :type + attribute :key + attribute :virtualswitch + attribute :server_id + + def initialize(attributes = {}) + # Assign server first to prevent race condition with persisted? + self.server_id = attributes.delete(:server_id) + + if attributes.key? :type then + if attributes[:type].is_a? String then + attributes[:type] = Fog::Vsphere.class_from_string(attributes[:type], "RbVmomi::VIM") + end + else + attributes[:type] = Fog::Vsphere.class_from_string("VirtualE1000", "RbVmomi::VIM") + end + + super defaults.merge(attributes) + end + + def to_s + name + end + + def server + requires :server_id + service.servers.get(server_id) + end + + def destroy + requires :server_id, :key, :type + + service.destroy_vm_interface(server_id, :key => key, :type => type) + end + + def save + raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted? + requires :server_id, :type, :network + + # Our approach of finding the newly created interface is rough. We assume that the :key value always increments + # and thus the highest :key value must correspond to the created interface. Since this has an inherent race + # condition we need to gate the saves. + SAVE_MUTEX.synchronize do + data = service.add_vm_interface(server_id, attributes) + + if data['task_state'] == 'success' + # We have to query vSphere to get the interface attributes since the task handle doesn't include that info. + created = server.interfaces.all.sort_by(&:key).last + + self.mac = created.mac + self.name = created.name + self.status = created.status + self.summary = created.summary + self.key = created.key + self.virtualswitch = created.virtualswitch + + true + else + false + end + end + end + + private + + def defaults + default_type=Fog.credentials[:default_nic_type] || RbVmomi::VIM::VirtualE1000 + { + :name=>"Network adapter", + :network=>"VM Network", + :summary=>"VM Network", + :type=> Fog::Vsphere.class_from_string(default_type, "RbVmomi::VIM"), + } + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/interfaces.rb b/lib/fog/vsphere/models/compute/interfaces.rb new file mode 100644 index 00000000..04d3dbf5 --- /dev/null +++ b/lib/fog/vsphere/models/compute/interfaces.rb @@ -0,0 +1,67 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/interface' + +module Fog + module Compute + class Vsphere + class Interfaces < Fog::Collection + model Fog::Compute::Vsphere::Interface + + attribute :server_id + + def all(filters = {}) + requires :server_id + + case server + when Fog::Compute::Vsphere::Server + load service.list_vm_interfaces(server.id) + when Fog::Compute::Vsphere::Template + load service.list_template_interfaces(server.id) + else + raise 'interfaces should have vm or template' + end + + self.each { |interface| interface.server_id = server.id } + self + end + + def get(id) + requires :server_id + + case server + when Fog::Compute::Vsphere::Server + interface = service.get_vm_interface(server.id, :key => id, :mac=> id, :name => id) + when Fog::Compute::Vsphere::Template + interface = service.get_template_interfaces(server.id, :key => id, :mac=> id, :name => id) + else + + raise 'interfaces should have vm or template' + end + + if interface + Fog::Compute::Vsphere::Interface.new(interface.merge(:server_id => server.id, :service => service)) + else + nil + end + end + + def new(attributes = {}) + if server_id + super({ :server_id => server_id }.merge(attributes)) + else + super + end + end + + def server + return nil if server_id.nil? + service.servers.get(server_id) + end + + def server=(new_server) + server_id = new_server.id + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/interfacetype.rb b/lib/fog/vsphere/models/compute/interfacetype.rb new file mode 100644 index 00000000..66f1b3fb --- /dev/null +++ b/lib/fog/vsphere/models/compute/interfacetype.rb @@ -0,0 +1,22 @@ +module Fog + module Compute + class Vsphere + class Interfacetype < Fog::Model + identity :id + +# attribute :class + attribute :name + attribute :datacenter + attribute :servertype + + def initialize(attributes={} ) + super attributes + end + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/interfacetypes.rb b/lib/fog/vsphere/models/compute/interfacetypes.rb new file mode 100644 index 00000000..a5365b45 --- /dev/null +++ b/lib/fog/vsphere/models/compute/interfacetypes.rb @@ -0,0 +1,35 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/interfacetype' + +module Fog + module Compute + class Vsphere + class Interfacetypes < Fog::Collection + model Fog::Compute::Vsphere::Interfacetype + attr_accessor :datacenter + attr_accessor :servertype + + def all(filters = { }) + requires :servertype + case servertype + when Fog::Compute::Vsphere::Servertype + load service.list_interface_types(filters.merge({ + :datacenter => datacenter, + :servertype => servertype.id + })) + else + raise 'interfacetypes should have a servertype' + end + end + + def get(id) + requires :servertype + requires :datacenter + new service.get_interface_type id, servertype, datacenter + rescue Fog::Compute::Vsphere::NotFound + nil + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/network.rb b/lib/fog/vsphere/models/compute/network.rb new file mode 100644 index 00000000..fcd7eca5 --- /dev/null +++ b/lib/fog/vsphere/models/compute/network.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class Vsphere + class Network < Fog::Model + identity :id + + attribute :name + attribute :datacenter + attribute :accessible # reachable by at least one hypervisor + attribute :virtualswitch + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/networks.rb b/lib/fog/vsphere/models/compute/networks.rb new file mode 100644 index 00000000..c727cb9d --- /dev/null +++ b/lib/fog/vsphere/models/compute/networks.rb @@ -0,0 +1,23 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/network' + +module Fog + module Compute + class Vsphere + class Networks < Fog::Collection + model Fog::Compute::Vsphere::Network + attr_accessor :datacenter + + def all(filters = {}) + f = { :datacenter => datacenter }.merge(filters) + load service.list_networks(f) + end + + def get(id) + requires :datacenter + new service.get_network(id, datacenter) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/resource_pool.rb b/lib/fog/vsphere/models/compute/resource_pool.rb new file mode 100644 index 00000000..d5f1b916 --- /dev/null +++ b/lib/fog/vsphere/models/compute/resource_pool.rb @@ -0,0 +1,19 @@ +module Fog + module Compute + class Vsphere + class ResourcePool < Fog::Model + identity :id + + attribute :name + attribute :cluster + attribute :datacenter + attribute :configured_memory_mb + attribute :overall_status + + def to_s + name + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/resource_pools.rb b/lib/fog/vsphere/models/compute/resource_pools.rb new file mode 100644 index 00000000..4407eba3 --- /dev/null +++ b/lib/fog/vsphere/models/compute/resource_pools.rb @@ -0,0 +1,23 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/resource_pool' + +module Fog + module Compute + class Vsphere + class ResourcePools < Fog::Collection + model Fog::Compute::Vsphere::ResourcePool + attr_accessor :datacenter, :cluster + + def all(filters = {}) + load service.list_resource_pools(filters.merge(:datacenter => datacenter, :cluster => cluster)) + end + + def get(id) + requires :datacenter + requires :cluster + new service.get_resource_pool(id, cluster, datacenter) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/scsicontroller.rb b/lib/fog/vsphere/models/compute/scsicontroller.rb new file mode 100644 index 00000000..6a7fed76 --- /dev/null +++ b/lib/fog/vsphere/models/compute/scsicontroller.rb @@ -0,0 +1,16 @@ +module Fog + module Compute + class Vsphere + class SCSIController < Fog::Model + attribute :shared_bus + attribute :type + attribute :unit_number + attribute :key + + def to_s + "#{type} ##{key}: shared: #{shared_bus}, unit_number: #{unit_number}" + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/server.rb b/lib/fog/vsphere/models/compute/server.rb new file mode 100644 index 00000000..559e3358 --- /dev/null +++ b/lib/fog/vsphere/models/compute/server.rb @@ -0,0 +1,296 @@ +require 'fog/compute/models/server' + +module Fog + module Compute + class Vsphere + class Server < Fog::Compute::Server + extend Fog::Deprecation + deprecate(:ipaddress, :public_ip_address) + + # This will be the instance uuid which is globally unique across + # a vSphere deployment. + identity :id + + # JJM REVISIT (Extend the model of a vmware server) + # SEE: http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.VirtualMachine.html + # (Take note of the See also section.) + # In particular: + # GuestInfo: information about the guest operating system + # VirtualMachineConfigInfo: Access to the VMX file and configuration + + attribute :name + # UUID may be the same from VM to VM if the user does not select (I copied it) + attribute :uuid + attribute :hostname + attribute :operatingsystem + attribute :public_ip_address, :aliases => 'ipaddress' + attribute :power_state, :aliases => 'power' + attribute :tools_state, :aliases => 'tools' + attribute :tools_version + attribute :mac_addresses, :aliases => 'macs' + attribute :hypervisor, :aliases => 'host' + attribute :connection_state + attribute :mo_ref + attribute :path + attribute :memory_mb + attribute :cpus + attribute :corespersocket + attribute :interfaces + attribute :volumes + attribute :customvalues + attribute :overall_status, :aliases => 'status' + attribute :cluster + attribute :datacenter + attribute :resource_pool + attribute :instance_uuid # move this --> id + attribute :guest_id + attribute :hardware_version + attribute :scsi_controller # this is the first scsi controller. Right now no more of them can be used. + attribute :cpuHotAddEnabled + attribute :memoryHotAddEnabled + attribute :firmware + + def initialize(attributes={} ) + super defaults.merge(attributes) + self.instance_uuid ||= id # TODO: remvoe instance_uuid as it can be replaced with simple id + initialize_interfaces + initialize_volumes + initialize_customvalues + initialize_scsi_controller + end + + # Lazy Loaded Attributes + [:datacenter, :cluster, :hypervisor, :resource_pool, :mac_addresses].each do |attr| + define_method attr do + attributes[attr] = attributes[attr].call if attributes[attr].is_a?(Proc) + attributes[attr] + end + end + # End Lazy Loaded Attributes + + def vm_reconfig_memory(options = {}) + requires :instance_uuid, :memory + service.vm_reconfig_memory('instance_uuid' => instance_uuid, 'memory' => memory_mb) + end + + def vm_reconfig_cpus(options = {}) + requires :instance_uuid, :cpus, :corespersocket + service.vm_reconfig_cpus('instance_uuid' => instance_uuid, 'cpus' => cpus, 'corespersocket' => corespersocket) + end + + def vm_reconfig_hardware(hardware_spec, options = {}) + requires :instance_uuid + service.vm_reconfig_hardware('instance_uuid' => instance_uuid, 'hardware_spec' => hardware_spec) + end + + def start(options = {}) + requires :instance_uuid + service.vm_power_on('instance_uuid' => instance_uuid) + end + + def stop(options = {}) + options = { :force => !tools_installed? || !tools_running? }.merge(options) + requires :instance_uuid + service.vm_power_off('instance_uuid' => instance_uuid, 'force' => options[:force]) + end + + def reboot(options = {}) + options = { :force => false }.merge(options) + requires :instance_uuid + service.vm_reboot('instance_uuid' => instance_uuid, 'force' => options[:force]) + end + + def destroy(options = {}) + requires :instance_uuid + if ready? + # need to turn it off before destroying + stop(options) + wait_for { !ready? } + end + service.vm_destroy('instance_uuid' => instance_uuid) + end + + def migrate(options = {}) + options = { :priority => 'defaultPriority' }.merge(options) + requires :instance_uuid + service.vm_migrate('instance_uuid' => instance_uuid, 'priority' => options[:priority]) + end + + # Clone from a server object + # + # ==== Parameters + # *<~Hash>: + # * 'name'<~String> - *REQUIRED* Name of the _new_ VirtualMachine + # * See more options in vm_clone request/compute/vm_clone.rb + # + def clone(options = {}) + requires :name, :datacenter, :path + + # Convert symbols to strings + req_options = options.reduce({}) { |hsh, (k,v)| hsh[k.to_s] = v; hsh } + + # Give our path to the request + req_options['template_path'] ="#{relative_path}/#{name}" + req_options['datacenter'] = "#{datacenter}" + + # Perform the actual clone + clone_results = service.vm_clone(req_options) + + # We need to assign the service, otherwise we can't reload the model + # Create the new VM model. TODO This only works when "wait=true" + new_vm = self.class.new(clone_results['new_vm'].merge(:service => self.service)) + + # We need to assign the collection otherwise we + # cannot reload the model. + new_vm.collection = self.collection + + # Return the new VM model. + new_vm + end + + def ready? + power_state == "poweredOn" + end + + def tools_installed? + tools_state != "toolsNotInstalled" + end + + def tools_running? + ["toolsOk","toolsOld"].include? tools_state + end + + # defines VNC attributes on the hypervisor + def config_vnc(options = {}) + requires :instance_uuid + service.vm_config_vnc(options.merge('instance_uuid' => instance_uuid)) + end + + # returns a hash of VNC attributes required for service + def vnc + requires :instance_uuid + service.vm_get_vnc(instance_uuid) + end + + def memory + memory_mb * 1024 * 1024 + end + + def sockets + cpus / corespersocket + end + + def mac + interfaces.first.mac unless interfaces.empty? + end + + def interfaces + attributes[:interfaces] ||= id.nil? ? [] : service.interfaces( :server_id => self.id ) + end + + def interface_ready? attrs + (attrs.is_a? Hash and attrs[:blocking]) or attrs.is_a? Fog::Compute::Vsphere::Interface + end + + def add_interface attrs + Fog::Logger.deprecation(".add_interface is deprecated. Call .interfaces.create instead.") + + interfaces.create(attrs) + end + + def update_interface attrs + wait_for { not ready? } if interface_ready? attrs + service.update_vm_interface(id, attrs) + end + + def destroy_interface attrs + Fog::Logger.deprecation(".destroy_vm_interface is deprecated. Call .interfaces.get(:key => ).destroy instead.") + + interfaces.get(attrs[:key] || attrs['key']).destroy + end + + def volumes + attributes[:volumes] ||= id.nil? ? [] : service.volumes(:server_id => self.id) + end + + def customvalues + attributes[:customvalues] ||= id.nil? ? [] : service.customvalues( :vm => self ) + end + + def scsi_controller + self.attributes[:scsi_controller] ||= service.get_vm_first_scsi_controller(id) + end + + def folder + return nil unless datacenter and path + attributes[:folder] ||= service.folders(:datacenter => datacenter, :type => :vm).get(path) + end + + def save + requires :name, :cluster, :datacenter + if persisted? + raise "update is not supported yet" + # service.update_vm(attributes) + else + self.id = service.create_vm(attributes) + end + reload + end + + def new? + id.nil? + end + + def reload + # reload does not re-read assoiciated attributes, so we clear it manually + [:interfaces, :volumes].each do |attr| + self.attributes.delete(attr) + end + super + end + + def relative_path + requires :path, :datacenter + + (path.split('/').reject {|e| e.empty?} - ["Datacenters", datacenter, "vm"]).join("/") + end + + private + + def defaults + { + :cpus => 1, +# :corespersocket => 1, + :memory_mb => 512, + :guest_id => 'otherGuest', + :path => '/' + } + end + + def initialize_interfaces + if attributes[:interfaces] and attributes[:interfaces].is_a?(Array) + self.attributes[:interfaces].map! { |nic| nic.is_a?(Hash) ? service.interfaces.new(nic) : nic } + end + end + + def initialize_volumes + if attributes[:volumes] and attributes[:volumes].is_a?(Array) + self.attributes[:volumes].map! { |vol| vol.is_a?(Hash) ? service.volumes.new(vol) : vol } + end + end + + def initialize_customvalues + if attributes[:customvalues] and attributes[:customvalues].is_a?(Array) + self.attributes[:customvalues].map { |cfield| cfield.is_a?(Hash) ? service.customvalue.new(cfield) : cfield} + end + end + + def initialize_scsi_controller + if attributes[:scsi_controller] and attributes[:scsi_controller].is_a?(Hash) + Fog::Compute::Vsphere::SCSIController.new(self.attributes[:scsi_controller]) + end + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/servers.rb b/lib/fog/vsphere/models/compute/servers.rb new file mode 100644 index 00000000..7c7b9f1a --- /dev/null +++ b/lib/fog/vsphere/models/compute/servers.rb @@ -0,0 +1,37 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/server' + +module Fog + module Compute + class Vsphere + class Servers < Fog::Collection + model Fog::Compute::Vsphere::Server + attr_accessor :datacenter + attr_accessor :network + attr_accessor :cluster + attr_accessor :resource_pool + attr_accessor :folder + + # 'folder' => '/Datacenters/vm/Jeff/Templates' will be MUCH faster. + # than simply listing everything. + def all(filters = { }) + f = { + :datacenter => datacenter, + :cluster => cluster, + :network => network, + :resource_pool => resource_pool, + :folder => folder + }.merge(filters) + + load service.list_virtual_machines(f) + end + + def get(id, datacenter = nil) + new service.get_virtual_machine id, datacenter + rescue Fog::Compute::Vsphere::NotFound + nil + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/servertype.rb b/lib/fog/vsphere/models/compute/servertype.rb new file mode 100644 index 00000000..6bc0665f --- /dev/null +++ b/lib/fog/vsphere/models/compute/servertype.rb @@ -0,0 +1,36 @@ +module Fog + module Compute + class Vsphere + class Servertype < Fog::Model + identity :id + + attribute :family + attribute :fullname + attribute :datacenter + attribute :interfacetypes + + def initialize(attributes={} ) + super defaults.merge(attributes) + end + + def to_s + id + end + + def interfacetypes filters={} + attributes[:interfacetypes] ||= service.interfacetypes({ :datacenter => datacenter, :servertype => self }.merge(filters)) + end + + private + + def defaults + { + :id=>"otherGuest64", + :family=>"otherGuestFamily", + :interfacetypes => nil, + } + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/servertypes.rb b/lib/fog/vsphere/models/compute/servertypes.rb new file mode 100644 index 00000000..d3f04536 --- /dev/null +++ b/lib/fog/vsphere/models/compute/servertypes.rb @@ -0,0 +1,24 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/servertype' + +module Fog + module Compute + class Vsphere + class Servertypes < Fog::Collection + model Fog::Compute::Vsphere::Servertype + attr_accessor :datacenter, :id, :fullname + + def all(filters = { }) + load service.list_server_types(filters.merge({:datacenter => datacenter})) + end + + def get(id) + requires :datacenter + new service.get_server_type(id, datacenter) + rescue Fog::Compute::Vsphere::NotFound + nil + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/template.rb b/lib/fog/vsphere/models/compute/template.rb new file mode 100644 index 00000000..76140a35 --- /dev/null +++ b/lib/fog/vsphere/models/compute/template.rb @@ -0,0 +1,11 @@ +module Fog + module Compute + class Vsphere + class Template < Fog::Model + identity :id + attribute :name + attribute :uuid + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/templates.rb b/lib/fog/vsphere/models/compute/templates.rb new file mode 100644 index 00000000..bc76c851 --- /dev/null +++ b/lib/fog/vsphere/models/compute/templates.rb @@ -0,0 +1,20 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/template' + +module Fog + module Compute + class Vsphere + class Templates < Fog::Collection + model Fog::Compute::Vsphere::Template + + def all(filters = {}) + load service.list_templates(filters) + end + + def get(id) + new service.get_template(id) + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/volume.rb b/lib/fog/vsphere/models/compute/volume.rb new file mode 100644 index 00000000..38d9801d --- /dev/null +++ b/lib/fog/vsphere/models/compute/volume.rb @@ -0,0 +1,99 @@ +module Fog + module Compute + class Vsphere + class Volume < Fog::Model + DISK_SIZE_TO_GB = 1048576 + identity :id + + attribute :datastore + attribute :mode + attribute :size + attribute :thin + attribute :eager_zero + attribute :name + attribute :filename + attribute :size_gb + attribute :key + attribute :unit_number + attribute :server_id + + def initialize(attributes={} ) + # Assign server first to prevent race condition with persisted? + self.server_id = attributes.delete(:server_id) + + super defaults.merge(attributes) + end + + def size_gb + attributes[:size_gb] ||= attributes[:size].to_i / DISK_SIZE_TO_GB if attributes[:size] + end + + def size_gb= s + attributes[:size] = s.to_i * DISK_SIZE_TO_GB if s + end + + def to_s + name + end + + def destroy + requires :server_id, :key, :unit_number + + service.destroy_vm_volume(self) + true + end + + def save + raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted? + requires :server_id, :size, :datastore + + if unit_number.nil? + used_unit_numbers = server.volumes.map { |volume| volume.unit_number } + max_unit_number = used_unit_numbers.max + + if max_unit_number > server.volumes.size + # If the max ID exceeds the number of volumes, there must be a hole in the range. Find a hole and use it. + self.unit_number = max_unit_number.times.to_a.find { |i| used_unit_numbers.exclude?(i) } + else + self.unit_number = max_unit_number + 1 + end + else + if server.volumes.any? { |volume| volume.unit_number == self.unit_number && volume.id != self.id } + raise "A volume already exists with that unit_number, so we can't save the new volume" + end + end + + data = service.add_vm_volume(self) + + if data['task_state'] == 'success' + # We have to query vSphere to get the volume attributes since the task handle doesn't include that info. + created = server.volumes.all.find { |volume| volume.unit_number == self.unit_number } + + self.id = created.id + self.key = created.key + self.filename = created.filename + + true + else + false + end + end + + def server + requires :server_id + service.servers.get(server_id) + end + + private + + def defaults + { + :thin => true, + :name => "Hard disk", + :mode => "persistent" + } + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/volumes.rb b/lib/fog/vsphere/models/compute/volumes.rb new file mode 100644 index 00000000..0aec5c7c --- /dev/null +++ b/lib/fog/vsphere/models/compute/volumes.rb @@ -0,0 +1,54 @@ +require 'fog/core/collection' +require 'fog/vsphere/models/compute/volume' + +module Fog + module Compute + class Vsphere + class Volumes < Fog::Collection + attribute :server_id + + model Fog::Compute::Vsphere::Volume + + def all(filters = {}) + requires :server_id + + case server + when Fog::Compute::Vsphere::Server + load service.list_vm_volumes(server.id) + when Fog::Compute::Vsphere::Template + load service.list_template_volumes(server.id) + else + raise 'volumes should have vm or template' + end + + self.each { |volume| volume.server_id = server.id } + self + end + + def get(id) + new service.get_volume(id) + end + + def new(attributes = {}) + if server_id + # Default to the root volume datastore if one is not configured. + datastore = ! attributes.key?(:datastore) && self.any? ? self.first.datastore : nil + + super({ :server_id => server_id, :datastore => datastore }.merge!(attributes)) + else + super + end + end + + def server + return nil if server_id.nil? + service.servers.get(server_id) + end + + def server=(new_server) + server_id = new_server.id + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/cloudinit_to_customspec.rb b/lib/fog/vsphere/requests/compute/cloudinit_to_customspec.rb new file mode 100644 index 00000000..b1e176ef --- /dev/null +++ b/lib/fog/vsphere/requests/compute/cloudinit_to_customspec.rb @@ -0,0 +1,65 @@ +module Fog + module Compute + class Vsphere + class Real + def cloudinit_to_customspec(user_data) + raise ArgumentError, "user_data can't be nil" if user_data.nil? + custom_spec = { 'customization_spec' => Hash.new } + user_data = YAML.load(user_data) + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html + # encryptionKey expects an array + # globalIPSettings expects a hash, REQUIRED + # identity expects an hash, REQUIRED + # nicSettingMap expects an array + # options expects an hash + + custom_spec['encryptionKey'] = user_data['encryptionKey'] if user_data.key?('encryptionKey') + custom_spec['globalIPSettings'] = user_data['globalIPSettings'] if user_data.key?('globalIPSettings') + custom_spec['identity'] = user_data['identity'] if user_data.key?('identity') + custom_spec['nicSettingMap'] = user_data['nicSettingMap'] if user_data.key?('nicSettingMap') + custom_spec['options'] = user_data['options'] if user_data.key?('options') + + # for backwards compatability + # hostname expects a string, REQUIRED + # netmask expects a string + # dns expects an array + # gateway expects an array + # domain expects a string, REQUIRED + # domainsuffixlist expects an array, REQUIRED + # timezone expects a string, for example Europe/Copenhagen, REQUIRED + custom_spec['hostname'] = user_data['hostname'] if user_data.key?('hostname') + custom_spec['ipsettings'] = { 'ip' => user_data['ip'] } if user_data.key?('ip') + custom_spec['ipsettings']['subnetMask'] = user_data['netmask'] if user_data.key?('netmask') + custom_spec['ipsettings']['dnsServerList'] = user_data['dns'] if user_data.key?('dns') + custom_spec['ipsettings']['gateway'] = user_data['gateway'] if user_data.key?('gateway') + custom_spec['domain'] = user_data['domain'] if user_data.key?('domain') + custom_spec['dnsSuffixList'] = user_data['domainsuffixlist'] if user_data.key?('domainsuffixlist') + custom_spec['time_zone'] = user_data['timezone'] if user_data.key?('timezone') + custom_spec + end + end + + class Mock + def cloudinit_to_customspec(user_data) + raise ArgumentError, "user_data can't be nil" if user_data.nil? + custom_spec = { 'customization_spec' => Hash.new } + user_data = YAML.load(user_data) + custom_spec['encryptionKey'] = user_data['encryptionKey'] if user_data.key?('encryptionKey') + custom_spec['globalIPSettings'] = user_data['globalIPSettings'] if user_data.key?('globalIPSettings') + custom_spec['identity'] = user_data['identity'] if user_data.key?('identity') + custom_spec['nicSettingMap'] = user_data['nicSettingMap'] if user_data.key?('nicSettingMap') + custom_spec['options'] = user_data['options'] if user_data.key?('options') + custom_spec['hostname'] = user_data['hostname'] if user_data.key?('hostname') + custom_spec['ipsettings'] = { 'ip' => user_data['ip'] } if user_data.key?('ip') + custom_spec['ipsettings']['subnetMask'] = user_data['netmask'] if user_data.key?('netmask') + custom_spec['ipsettings']['dnsServerList'] = user_data['dns'] if user_data.key?('dns') + custom_spec['ipsettings']['gateway'] = user_data['gateway'] if user_data.key?('gateway') + custom_spec['domain'] = user_data['domain'] if user_data.key?('domain') + custom_spec['dnsSuffixList'] = user_data['domainsuffixlist'] if user_data.key?('domainsuffixlist') + custom_spec['time_zone'] = user_data['timezone'] if user_data.key?('timezone') + custom_spec + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/create_folder.rb b/lib/fog/vsphere/requests/compute/create_folder.rb new file mode 100644 index 00000000..4baa5644 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/create_folder.rb @@ -0,0 +1,22 @@ +module Fog + module Compute + class Vsphere + class Real + def create_folder(datacenter, path, name) + #Path cannot be nil but it can be an empty string + raise ArgumentError, "Path cannot be nil" if path.nil? + + parent_folder = get_raw_vmfolder(path, datacenter) + begin + new_folder = parent_folder.CreateFolder(:name => name) + # output is cleaned up to return the new path + # new path will be path/name, example: "Production/Pool1" + new_folder.path.reject { |a| a.first.class == "Folder" }.map { |a| a.first.name }.join("/").sub(/^\/?Datacenters\/#{datacenter}\/vm\/?/, '') + rescue => e + raise e, "failed to create folder: #{e}" + end + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/create_vm.rb b/lib/fog/vsphere/requests/compute/create_vm.rb new file mode 100644 index 00000000..84eb989b --- /dev/null +++ b/lib/fog/vsphere/requests/compute/create_vm.rb @@ -0,0 +1,169 @@ +module Fog + module Compute + class Vsphere + class Real + def create_vm attributes = { } + # build up vm configuration + + vm_cfg = { + :name => attributes[:name], + :guestId => attributes[:guest_id], + :version => attributes[:hardware_version], + :files => { :vmPathName => vm_path_name(attributes) }, + :numCPUs => attributes[:cpus], + :numCoresPerSocket => attributes[:corespersocket], + :memoryMB => attributes[:memory_mb], + :deviceChange => device_change(attributes), + :extraConfig => extra_config(attributes), + } + vm_cfg[:cpuHotAddEnabled] = attributes[:cpuHotAddEnabled] if attributes.key?(:cpuHotAddEnabled) + vm_cfg[:memoryHotAddEnabled] = attributes[:memoryHotAddEnabled] if attributes.key?(:memoryHotAddEnabled) + vm_cfg[:firmware] = attributes[:firmware] if attributes.key?(:firmware) + resource_pool = if attributes[:resource_pool] + get_raw_resource_pool(attributes[:resource_pool], attributes[:cluster], attributes[:datacenter]) + else + get_raw_cluster(attributes[:cluster], attributes[:datacenter]).resourcePool + end + vmFolder = get_raw_vmfolder(attributes[:path], attributes[:datacenter]) + vm = vmFolder.CreateVM_Task(:config => vm_cfg, :pool => resource_pool).wait_for_completion + vm.config.instanceUuid + rescue => e + raise e, "failed to create vm: #{e}" + end + + private + + # this methods defines where the vm config files would be located, + # by default we prefer to keep it at the same place the (first) vmdk is located + def vm_path_name attributes + datastore = attributes[:volumes].first.datastore unless attributes[:volumes].empty? + datastore ||= 'datastore1' + "[#{datastore}]" + end + + def device_change attributes + devices = [] + if (nics = attributes[:interfaces]) + devices << nics.map { |nic| create_interface(nic, nics.index(nic), :add, attributes) } + end + + if (disks = attributes[:volumes]) + devices << create_controller(attributes[:scsi_controller]||attributes["scsi_controller"]||{}) + devices << disks.map { |disk| create_disk(disk, disks.index(disk)) } + end + devices.flatten + end + + def create_nic_backing nic, attributes + raw_network = get_raw_network(nic.network, attributes[:datacenter], if nic.virtualswitch then nic.virtualswitch end) + + if raw_network.kind_of? RbVmomi::VIM::DistributedVirtualPortgroup + RbVmomi::VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo( + :port => RbVmomi::VIM.DistributedVirtualSwitchPortConnection( + :portgroupKey => raw_network.key, + :switchUuid => raw_network.config.distributedVirtualSwitch.uuid + ) + ) + else + RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => nic.network) + end + end + + def create_interface nic, index = 0, operation = :add, attributes = {} + { + :operation => operation, + :device => nic.type.new( + :key => index, + :deviceInfo => + { + :label => nic.name, + :summary => nic.summary, + }, + :backing => create_nic_backing(nic, attributes), + :addressType => 'generated') + } + end + + def create_controller options=nil + options=if options + controller_default_options.merge(Hash[options.map{|k,v| [k.to_sym,v] }]) + else + controller_default_options + end + controller_class=if options[:type].is_a? String then + Fog::Vsphere.class_from_string options[:type], "RbVmomi::VIM" + else + options[:type] + end + { + :operation => options[:operation], + :device => controller_class.new({ + :key => options[:key], + :busNumber => options[:bus_id], + :sharedBus => controller_get_shared_from_options(options), + }) + } + end + + def controller_default_options + {:operation => "add", :type => RbVmomi::VIM.VirtualLsiLogicController.class, :key => 1000, :bus_id => 0, :shared => false } + end + + def controller_get_shared_from_options options + if (options.key? :shared and options[:shared]==false) or not options.key? :shared then + :noSharing + elsif options[:shared]==true then + :virtualSharing + elsif options[:shared].is_a? String + options[:shared] + else + :noSharing + end + end + + def create_disk disk, index = 0, operation = :add, controller_key = 1000 + if (index > 6) then + _index = index + 1 + else + _index = index + end + payload = { + :operation => operation, + :fileOperation => operation == :add ? :create : :destroy, + :device => RbVmomi::VIM.VirtualDisk( + :key => disk.key || _index, + :backing => RbVmomi::VIM.VirtualDiskFlatVer2BackingInfo( + :fileName => "[#{disk.datastore}]", + :diskMode => disk.mode.to_sym, + :thinProvisioned => disk.thin + ), + :controllerKey => controller_key, + :unitNumber => _index, + :capacityInKB => disk.size + ) + } + + if operation == :add && disk.thin == 'false' && disk.eager_zero == 'true' + payload[:device][:backing][:eagerlyScrub] = disk.eager_zero + end + + payload + end + + def extra_config attributes + [ + { + :key => 'bios.bootOrder', + :value => 'ethernet0' + } + ] + end + end + + class Mock + def create_vm attributes = { } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/current_time.rb b/lib/fog/vsphere/requests/compute/current_time.rb new file mode 100644 index 00000000..78bd4fb1 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/current_time.rb @@ -0,0 +1,18 @@ +module Fog + module Compute + class Vsphere + class Real + def current_time + current_time = @connection.serviceInstance.CurrentTime + { 'current_time' => current_time } + end + end + + class Mock + def current_time + { 'current_time' => Time.now.utc } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_cluster.rb b/lib/fog/vsphere/requests/compute/get_cluster.rb new file mode 100644 index 00000000..61050959 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_cluster.rb @@ -0,0 +1,25 @@ +module Fog + module Compute + class Vsphere + class Real + def get_cluster(name, datacenter_name) + cluster = get_raw_cluster(name, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless cluster + cluster_attributes(cluster, datacenter_name) + end + + protected + + def get_raw_cluster(name, datacenter_name) + dc = find_raw_datacenter(datacenter_name) + dc.find_compute_resource(name) + end + end + + class Mock + def get_cluster(id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_compute_resource.rb b/lib/fog/vsphere/requests/compute/get_compute_resource.rb new file mode 100644 index 00000000..be3119df --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_compute_resource.rb @@ -0,0 +1,41 @@ +module Fog + module Compute + class Vsphere + class Real + def get_compute_resource(name, datacenter_name) + compute_resource = get_raw_compute_resource(name, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless compute_resource + compute_resource_attributes(compute_resource, datacenter_name) + end + + protected + + def get_raw_compute_resource(name, datacenter_name) + find_raw_datacenter(datacenter_name).find_compute_resource(name) + end + end + + class Mock + def get_compute_resource(name, datacenter_name) + { + :id=>"domain-s7", + :name=>"fake-host", + :totalCpu=>33504, + :totalMemory=>154604142592, + :numCpuCores=>12, + :numCpuThreads=>24, + :effectiveCpu=>32247, + :effectiveMemory=>135733, + :numHosts=>1, + :numEffectiveHosts=>1, + :overallStatus=>"gray", + :overallCpuUsage=>15682, + :overallMemoryUsage=>132755, + :effective=>true, + :isSingleHost=>true + } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_datacenter.rb b/lib/fog/vsphere/requests/compute/get_datacenter.rb new file mode 100644 index 00000000..8c81f509 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_datacenter.rb @@ -0,0 +1,31 @@ +module Fog + module Compute + class Vsphere + class Real + def get_datacenter name + dc = find_raw_datacenter(name) + raise(Fog::Compute::Vsphere::NotFound) unless dc + {:name => dc.name, :status => dc.overallStatus, :path => raw_getpathmo(dc) } + end + + protected + + def find_raw_datacenter name + raw_datacenters.find {|d| d.name == name} || get_raw_datacenter(name) + end + + def get_raw_datacenter name + @connection.serviceInstance.find_datacenter(name) + end + end + + class Mock + def get_datacenter name + dc = self.data[:datacenters][name] + raise(Fog::Compute::Vsphere::NotFound) unless dc + dc + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_datastore.rb b/lib/fog/vsphere/requests/compute/get_datastore.rb new file mode 100644 index 00000000..8d8fe600 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_datastore.rb @@ -0,0 +1,30 @@ +module Fog + module Compute + class Vsphere + class Real + def get_datastore(name, datacenter_name) + datastore = get_raw_datastore(name, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless datastore + datastore_attributes(datastore, datacenter_name) + end + + protected + + def get_raw_datastore(name, datacenter_name) + dc = find_raw_datacenter(datacenter_name) + + @connection.serviceContent.viewManager.CreateContainerView({ + :container => dc.datastoreFolder, + :type => ["Datastore"], + :recursive => true + }).view.select{|ds| ds.name == name}.first + end + end + + class Mock + def get_datastore(name, datacenter_name) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_folder.rb b/lib/fog/vsphere/requests/compute/get_folder.rb new file mode 100644 index 00000000..7e1c57c0 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_folder.rb @@ -0,0 +1,74 @@ +module Fog + module Compute + class Vsphere + class Real + def get_folder(path, datacenter_name, type = nil) + type ||= 'vm' + + # Cycle through all types of folders. + case type + when 'vm', :vm + # if you're a vm then grab the VM. + folder = get_raw_vmfolder(path, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless folder + folder_attributes(folder, datacenter_name) + when 'network', :network + raise "not implemented" + when 'datastore', :datastore + raise "not implemented" + else + raise ArgumentError, "#{type} is unknown" + end + end + + protected + + def get_raw_vmfolder(path, datacenter_name) + # The required path syntax - 'topfolder/subfolder + + # Clean up path to be relative since we're providing datacenter name + dc = find_raw_datacenter(datacenter_name) + dc_root_folder = dc.vmFolder + # Filter the root path for this datacenter not to be used." + dc_root_folder_path=dc_root_folder.path.map { | id, name | name }.join("/") + paths = path.sub(/^\/?#{Regexp.quote(dc_root_folder_path)}\/?/, '').split('/') + + return dc_root_folder if paths.empty? + # Walk the tree resetting the folder pointer as we go + paths.reduce(dc_root_folder) do |last_returned_folder, sub_folder| + # JJM VIM::Folder#find appears to be quite efficient as it uses the + # searchIndex It certainly appears to be faster than + # VIM::Folder#inventory since that returns _all_ managed objects of + # a certain type _and_ their properties. + sub = last_returned_folder.find(sub_folder, RbVmomi::VIM::Folder) + raise ArgumentError, "Could not descend into #{sub_folder}. Please check your path. #{path}" unless sub + sub + end + end + + def folder_attributes(folder, datacenter_name) + { + :id => managed_obj_id(folder), + :name => folder.name, + :parent => folder.parent.name, + :datacenter => datacenter_name, + :type => folder_type(folder), + :path => "/"+folder.path.map(&:last).join('/'), + } + end + + def folder_type(folder) + types = folder.childType + return :vm if types.include?('VirtualMachine') + return :network if types.include?('Network') + return :datastore if types.include?('Datastore') + end + end + + class Mock + def get_folder(path, filters = { }) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_interface_type.rb b/lib/fog/vsphere/requests/compute/get_interface_type.rb new file mode 100644 index 00000000..27d3c866 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_interface_type.rb @@ -0,0 +1,15 @@ +module Fog + module Compute + class Vsphere + class Real + def get_interface_type(id, servertype, datacenter, filter={}) + interfacetype=list_interface_types(filters={:id => id, + :datacenter => datacenter, + :servertype => servertype.id }).first + raise(Fog::Compute::Vsphere::NotFound) unless interfacetype + interfacetype + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_network.rb b/lib/fog/vsphere/requests/compute/get_network.rb new file mode 100644 index 00000000..a1cb7532 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_network.rb @@ -0,0 +1,59 @@ +module Fog + module Compute + class Vsphere + class Real + def get_network(name, datacenter_name) + network = get_raw_network(name, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless network + network_attributes(network, datacenter_name) + end + + protected + + def get_raw_network(name, datacenter_name, distributedswitch=nil) + finder = choose_finder(name, distributedswitch) + networks = get_all_raw_networks(datacenter_name) + networks.find { |n| finder.call(n) } + end + end + + module Shared + + protected + + def get_all_raw_networks(datacenter_name) + dc = find_raw_datacenter(datacenter_name) + @connection.serviceContent.viewManager. + CreateContainerView({ + :container => dc.networkFolder, + :type => ["Network"], + :recursive => true + }).view + end + + def choose_finder(name, distributedswitch) + case distributedswitch + when String + # only the one will do + Proc.new { |n| (n.name == name) && + (n.class.to_s == "DistributedVirtualPortgroup") && + (n.config.distributedVirtualSwitch.name == distributedswitch) + } + when :dvs + # the first distributed virtual switch will do - selected by network - gives control to vsphere + Proc.new { |n| (n.name == name) && (n.class.to_s == "DistributedVirtualPortgroup") } + else + # the first matching network will do, seems like the non-distributed networks come first + Proc.new { |n| (n.name == name) } + end + end + + end + + class Mock + def get_network(id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_resource_pool.rb b/lib/fog/vsphere/requests/compute/get_resource_pool.rb new file mode 100644 index 00000000..483a2b85 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_resource_pool.rb @@ -0,0 +1,26 @@ +module Fog + module Compute + class Vsphere + class Real + def get_resource_pool(name, cluster_name, datacenter_name) + resource_pool = get_raw_resource_pool(name, cluster_name, datacenter_name) + raise(Fog::Compute::Vsphere::NotFound) unless resource_pool + resource_pool_attributes(resource_pool, cluster_name, datacenter_name) + end + + protected + + def get_raw_resource_pool(name, cluster_name, datacenter_name) + dc = find_raw_datacenter(datacenter_name) + cluster = dc.find_compute_resource(cluster_name) + name.nil? ? cluster.resourcePool : cluster.resourcePool.traverse(name) + end + end + + class Mock + def get_resource_pool(name, cluster_name, datacenter_name) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_server_type.rb b/lib/fog/vsphere/requests/compute/get_server_type.rb new file mode 100644 index 00000000..8335baa2 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_server_type.rb @@ -0,0 +1,32 @@ +module Fog + module Compute + class Vsphere + class Real + def get_server_type(id, datacenter, filter={}) + server_type=get_raw_server_type(id, datacenter) + raise(Fog::Compute::Vsphere::NotFound) unless server_type + server_type_attributes(server_type, datacenter) + end + + protected + + def get_raw_server_type(id, datacenter, filter={}) + types=raw_server_types(datacenter) + raise(Fog::Compute::Vsphere::NotFound) unless types + types=types.select{ | servertype | servertype.id == id }.first + raise(Fog::Compute::Vsphere::NotFound) unless types + types + end + end + class Mock + def get_server_type(id) + {:id=>"rhel6Guest", + :name=>"rhel6Guest", + :family=>"linuxGuest", + :fullname=>"Red Hat Enterprise Linux 6 (32-Bit)", + :datacenter=>"Solutions"} + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_template.rb b/lib/fog/vsphere/requests/compute/get_template.rb new file mode 100644 index 00000000..94565e8b --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_template.rb @@ -0,0 +1,16 @@ +module Fog + module Compute + class Vsphere + class Real + def get_template(id, datacenter_name = nil) + convert_vm_mob_ref_to_attr_hash(get_vm_ref(id, datacenter_name)) + end + end + + class Mock + def get_template(id, datacenter_name = nil) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_virtual_machine.rb b/lib/fog/vsphere/requests/compute/get_virtual_machine.rb new file mode 100644 index 00000000..ded4a7b6 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_virtual_machine.rb @@ -0,0 +1,57 @@ +module Fog + module Compute + class Vsphere + class Real + def get_virtual_machine(id, datacenter_name = nil) + # The larger the VM list the longer it will take if not searching based on UUID. + convert_vm_mob_ref_to_attr_hash(get_vm_ref(id, datacenter_name)) + end + + protected + + def get_vm_ref(id, dc = nil) + raw_datacenter = find_raw_datacenter(dc) if dc + vm = case is_uuid?(id) + # UUID based + when true + params = {:uuid => id, :vmSearch => true, :instanceUuid => true} + params[:datacenter] = raw_datacenter if dc + @connection.searchIndex.FindByUuid(params) + else + # try to find based on VM name + if dc + get_vm_by_name(id, dc) + else + raw_datacenters.map { |d| get_vm_by_name(id, d["name"])}.compact.first + end + end + vm ? vm : raise(Fog::Compute::Vsphere::NotFound, "#{id} was not found") + end + + def get_vm_by_name(name, dc) + vms = raw_list_all_virtual_machines(dc) + + if name.include?('/') + folder = File.dirname(name) + basename = File.basename(name) + vms.keep_if { |v| v["name"] == basename && v.parent.pretty_path.include?(folder) }.first + else + vms.keep_if { |v| v["name"] == name }.first + end + end + end + + class Mock + def get_virtual_machine(id, datacenter_name = nil) + if is_uuid?(id) + vm = list_virtual_machines({ 'instance_uuid' => id, 'datacenter' => datacenter_name }).first + else + # try to find based on VM name. May need to handle the path of the VM + vm = list_virtual_machines({ 'name' => id, 'datacenter' => datacenter_name }).first + end + vm ? vm : raise(Fog::Compute::Vsphere::NotFound, "#{id} was not found") + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/get_vm_first_scsi_controller.rb b/lib/fog/vsphere/requests/compute/get_vm_first_scsi_controller.rb new file mode 100644 index 00000000..0b2d80e3 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_vm_first_scsi_controller.rb @@ -0,0 +1,26 @@ + +module Fog + module Compute + class Vsphere + class Real + def get_vm_first_scsi_controller(vm_id) + Fog::Compute::Vsphere::SCSIController.new(get_vm_first_scsi_controller_raw(vm_id)) + end + + def get_vm_first_scsi_controller_raw(vm_id) + ctrl=get_vm_ref(vm_id).config.hardware.device.grep(RbVmomi::VIM::VirtualSCSIController).select{ | ctrl | ctrl.key == 1000 }.first + { + :type => ctrl.class.to_s, + :shared_bus => ctrl.sharedBus.to_s, + :unit_number => ctrl.scsiCtlrUnitNumber, + :key => ctrl.key, + } + end + end + class Mock + def get_vm_first_scsi_controller(vm_id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_clusters.rb b/lib/fog/vsphere/requests/compute/list_clusters.rb new file mode 100644 index 00000000..3add8262 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_clusters.rb @@ -0,0 +1,72 @@ +module Fog + module Compute + class Vsphere + class Real + def list_clusters(filters = { }) + datacenter_name = filters[:datacenter] + + raw_clusters(datacenter_name).map do |cluster| + cluster_attributes(cluster, datacenter_name) + end + end + + def raw_clusters(datacenter) + folder ||= find_raw_datacenter(datacenter).hostFolder + @raw_clusters = get_raw_clusters_from_folder(folder) + end + + protected + + def get_raw_clusters_from_folder(folder) + folder.childEntity.map do |child_entity| + if child_entity.is_a? RbVmomi::VIM::ComputeResource + child_entity + elsif child_entity.is_a? RbVmomi::VIM::Folder + get_raw_clusters_from_folder(child_entity) + end + end.flatten + end + + def cluster_attributes cluster, datacenter_name + { + :id => managed_obj_id(cluster), + :name => cluster.name, + :full_path => cluster_path(cluster, datacenter_name), + :num_host => cluster.summary.numHosts, + :num_cpu_cores => cluster.summary.numCpuCores, + :overall_status => cluster.summary.overallStatus, + :datacenter => datacenter_name || parent_attribute(cluster.path, :datacenter)[1], + } + end + + def cluster_path(cluster, datacenter_name) + datacenter = find_raw_datacenter(datacenter_name) + cluster.pretty_path.gsub(/(#{datacenter_name}|#{datacenter.hostFolder.name})\//,'') + end + end + + class Mock + def list_clusters(filters = { }) + raw_clusters.map do |cluster| + cluster + end + end + + def raw_clusters + folder = self.data[:clusters] + @raw_clusters = get_raw_clusters_from_folder(folder) + end + + def get_raw_clusters_from_folder(folder) + folder.map do |child| + if child[:klass] == "RbVmomi::VIM::ComputeResource" + child + elsif child[:klass] == "RbVmomi::VIM::Folder" + get_raw_clusters_from_folder(child[:clusters]) + end + end.flatten + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_compute_resources.rb b/lib/fog/vsphere/requests/compute/list_compute_resources.rb new file mode 100644 index 00000000..572ebc0a --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_compute_resources.rb @@ -0,0 +1,92 @@ +module Fog + module Compute + class Vsphere + class Real + def list_compute_resources(filters = { }) + datacenter_name = filters[:datacenter] + # default to show all compute_resources + only_active = filters[:effective] || false + compute_resources = raw_compute_resources datacenter_name + + compute_resources.map do |compute_resource| + summary = compute_resource.summary + next if only_active and summary.numEffectiveHosts == 0 + compute_resource_attributes(compute_resource, datacenter_name) + end.compact + end + + def raw_compute_resources(datacenter_name) + find_raw_datacenter(datacenter_name).find_compute_resource('').children + end + + protected + + def compute_resource_attributes compute_resource, datacenter + overall_usage = compute_resource.host.inject({:overallCpuUsage=>0, :overallMemoryUsage=>0}) do |sum, host| + { + :overallCpuUsage => sum[:overallCpuUsage]+(host.summary.quickStats.overallCpuUsage || 0), + :overallMemoryUsage=> sum[:overallMemoryUsage]+(host.summary.quickStats.overallMemoryUsage || 0) + } + end + { + :id => managed_obj_id(compute_resource), + :name => compute_resource.name, + :totalCpu => compute_resource.summary.totalCpu, + :totalMemory => compute_resource.summary.totalMemory, + :numCpuCores => compute_resource.summary.numCpuCores, + :numCpuThreads => compute_resource.summary.numCpuThreads, + :effectiveCpu => compute_resource.summary.effectiveCpu, + :effectiveMemory => compute_resource.summary.effectiveMemory, + :numHosts => compute_resource.summary.numHosts, + :numEffectiveHosts => compute_resource.summary.numEffectiveHosts, + :overallStatus => compute_resource.summary.overallStatus, + :overallCpuUsage => overall_usage[:overallCpuUsage], + :overallMemoryUsage => overall_usage[:overallMemoryUsage], + :effective => compute_resource.summary.numEffectiveHosts > 0, + :isSingleHost => compute_resource.summary.numHosts == 1 + } + end + + end + class Mock + def list_compute_resources(filters = { }) + [ + { + :id=>"domain-s7", + :name=>"fake-host", + :totalCpu=>33504, + :totalMemory=>154604142592, + :numCpuCores=>12, + :numCpuThreads=>24, + :effectiveCpu=>32247, + :effectiveMemory=>135733, + :numHosts=>1, + :numEffectiveHosts=>1, + :overallStatus=>"gray", + :overallCpuUsage=>15682, + :overallMemoryUsage=>132755, + :effective=>true, + :isSingleHost=>true + }, { + :id=>"domain-s74", + :name=>"fake-cluster", + :totalCpu=>41484, + :totalMemory=>51525996544, + :numCpuCores=>12, + :numCpuThreads=>24, + :effectiveCpu=>37796, + :effectiveMemory=>45115, + :numHosts=>2, + :numEffectiveHosts=>2, + :overallStatus=>"gray", + :overallCpuUsage=>584, + :overallMemoryUsage=>26422, + :effective=>true, + :isSingleHost=>false + } + ] + end + end + end + end +end \ No newline at end of file diff --git a/lib/fog/vsphere/requests/compute/list_customfields.rb b/lib/fog/vsphere/requests/compute/list_customfields.rb new file mode 100644 index 00000000..1ea35e47 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_customfields.rb @@ -0,0 +1,21 @@ +module Fog + module Compute + class Vsphere + class Real + def list_customfields() + @connection.serviceContent.customFieldsManager.field.map do |customfield| + { + :key => customfield.key.to_i, + :name => customfield.name, + :type => customfield.type + } + end + end + end + class Mock + def list_vm_customfields() + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_datacenters.rb b/lib/fog/vsphere/requests/compute/list_datacenters.rb new file mode 100644 index 00000000..8ec3a741 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_datacenters.rb @@ -0,0 +1,53 @@ +module Fog + module Compute + class Vsphere + class Real + def list_datacenters filters = {} + raw_datacenters.map do |dc| + { + :id => managed_obj_id(dc), + :name => dc.name, + :path => raw_getpathmo(dc), + :status => dc.overallStatus + } + end + end + + protected + + def raw_getpathmo mo + if mo.parent == nil or mo.parent.name == @connection.rootFolder.name then + [ mo.name ] + else + [ raw_getpathmo(mo.parent), mo.name ].flatten + end + end + + def raw_datacenters folder=nil + folder ||= @connection.rootFolder + @raw_datacenters ||= get_raw_datacenters_from_folder folder + end + + def get_raw_datacenters_from_folder folder=nil + folder.childEntity.map do | childE | + if childE.is_a? RbVmomi::VIM::Datacenter + childE + elsif childE.is_a? RbVmomi::VIM::Folder + get_raw_datacenters_from_folder childE + end + end.flatten + end + + def find_datacenters name=nil + name ? [find_raw_datacenter(name)] : raw_datacenters + end + end + + class Mock + def list_datacenters filters = {} + [ {:name => "Solutions", :status => "grey"}, {:name => "Solutions2", :status => "green" }] + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_datastores.rb b/lib/fog/vsphere/requests/compute/list_datastores.rb new file mode 100644 index 00000000..037ffde3 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_datastores.rb @@ -0,0 +1,40 @@ +module Fog + module Compute + class Vsphere + class Real + def list_datastores(filters = { }) + datacenter_name = filters[:datacenter] + # default to show all datastores + only_active = filters[:accessible] || false + raw_datastores(datacenter_name).map do |datastore| + next if only_active and !datastore.summary.accessible + datastore_attributes(datastore, datacenter_name) + end.compact + end + + def raw_datastores(datacenter_name) + find_raw_datacenter(datacenter_name).datastore + end + protected + + def datastore_attributes datastore, datacenter + { + :id => managed_obj_id(datastore), + :name => datastore.name, + :accessible => datastore.summary.accessible, + :type => datastore.summary.type, + :freespace => datastore.summary.freeSpace, + :capacity => datastore.summary.capacity, + :uncommitted => datastore.summary.uncommitted, + :datacenter => datacenter, + } + end + end + class Mock + def list_datastores(datacenter_name) + [] + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_folders.rb b/lib/fog/vsphere/requests/compute/list_folders.rb new file mode 100644 index 00000000..3f340311 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_folders.rb @@ -0,0 +1,44 @@ +module Fog + module Compute + class Vsphere + class Real + # Grabs all sub folders within a given path folder. + # + # ==== Parameters + # * filters<~Hash>: + # * :datacenter<~String> - *REQUIRED* Your datacenter where you're + # looking for folders. Example: 'my-datacenter-name' (passed if you + # are using the models/collections) + # eg: vspconn.datacenters.first.vm_folders('mypath') + # * :path<~String> - Your path where you're looking for + # more folders, if return = none you will get an error. If you don't + # define it will look in the main datacenter folder for any folders + # in that datacenter. + # + # Example Usage Testing Only: + # vspconn = Fog::Compute[:vsphere] + # mydc = vspconn.datacenters.first + # folders = mydc.vm_folders + # + def list_folders(filters = { }) + path = filters[:path] || filters['path'] || '' + datacenter_name = filters[:datacenter] + get_raw_vmfolders(path, datacenter_name).map do |folder| + folder_attributes(folder, datacenter_name) + end + end + + protected + + def get_raw_vmfolders(path, datacenter_name) + folder = get_raw_vmfolder(path, datacenter_name) + child_folders(folder).flatten.compact + end + + def child_folders folder + [folder, folder.childEntity.grep(RbVmomi::VIM::Folder).map(&method(:child_folders)).flatten] + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_interface_types.rb b/lib/fog/vsphere/requests/compute/list_interface_types.rb new file mode 100644 index 00000000..561fc2fb --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_interface_types.rb @@ -0,0 +1,25 @@ +module Fog + module Compute + class Vsphere + class Real + def list_interface_types(filters={}) + datacenter_name = filters[:datacenter] + servertype_name = filters[:servertype] + get_raw_server_type(servertype_name, datacenter_name)[:supportedEthernetCard].map do | nictype | + next if filters.key?(:id) and filters[:id] != nictype + interface_type_attributes(nictype, servertype_name, datacenter_name) + end.compact + end + + def interface_type_attributes(nic, servertype, datacenter) + { + :id => nic, + :name => nic, + :datacenter => datacenter, + :servertype => servertype + } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_networks.rb b/lib/fog/vsphere/requests/compute/list_networks.rb new file mode 100644 index 00000000..b2aee8ac --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_networks.rb @@ -0,0 +1,38 @@ +module Fog + module Compute + class Vsphere + class Real + def list_networks(filters = { }) + datacenter_name = filters[:datacenter] + # default to show all networks + only_active = filters[:accessible] || false + raw_networks(datacenter_name).map do |network| + next if only_active and !network.summary.accessible + network_attributes(network, datacenter_name) + end.compact + end + + def raw_networks(datacenter_name) + find_raw_datacenter(datacenter_name).network + end + + protected + + def network_attributes network, datacenter + { + :id => managed_obj_id(network), + :name => network.name, + :accessible => network.summary.accessible, + :datacenter => datacenter, + :virtualswitch => network.class.name == "DistributedVirtualPortgroup" ? network.config.distributedVirtualSwitch.name : nil + } + end + end + class Mock + def list_networks(datacenter_name) + [] + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_resource_pools.rb b/lib/fog/vsphere/requests/compute/list_resource_pools.rb new file mode 100644 index 00000000..aedc4f46 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_resource_pools.rb @@ -0,0 +1,38 @@ +module Fog + module Compute + class Vsphere + class Real + def list_resource_pools(filters = { }) + datacenter_name = filters[:datacenter] + cluster_name = filters[:cluster] + cluster = get_raw_cluster(cluster_name, datacenter_name) + list_raw_resource_pools(cluster).map do |resource_pool| + resource_pool_attributes(resource_pool, cluster_name, datacenter_name) + end + end + + protected + + # root ResourcePool + Children if they exists + def list_raw_resource_pools(cluster) + [cluster.resourcePool, cluster.resourcePool.resourcePool].flatten + end + + def resource_pool_attributes resource_pool, cluster, datacenter + { + :id => managed_obj_id(resource_pool), + :name => resource_pool.name, + :configured_memory_mb => resource_pool.summary.configuredMemoryMB, + :overall_status => resource_pool.overallStatus, + :cluster => cluster, + :datacenter => datacenter + } + end + end + class Mock + def list_resource_pools(filters = { }) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_server_types.rb b/lib/fog/vsphere/requests/compute/list_server_types.rb new file mode 100644 index 00000000..d2215770 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_server_types.rb @@ -0,0 +1,54 @@ +module Fog + module Compute + class Vsphere + class Real + def list_server_types(filters={}) + datacenter_name = filters[:datacenter] + servertypes=raw_server_types(datacenter_name) + if servertypes + servertypes.map do | servertype | + server_type_attributes(servertype, datacenter_name) + end.compact + else + nil + end + #select{ | guestdesc | guestdesc.select{ | k, v | filter.has_key?(k) and filter[k] == v }==filter } + end + + def raw_server_types(datacenter_name, filter={}) + datacenter=find_raw_datacenter(datacenter_name) + environmentBrowser=datacenter.hostFolder.childEntity.grep(RbVmomi::VIM::ComputeResource).first.environmentBrowser + if environmentBrowser + environmentBrowser.QueryConfigOption[:guestOSDescriptor] + end + end + + protected + + def server_type_attributes(servertype, datacenter) + { + :id => servertype.id, + :name => servertype.id, + :family => servertype.family, + :fullname => servertype.fullName, + :datacenter => datacenter, + } + end + end + class Mock + def list_server_types(datacenter_name) + [{:id=>"rhel6Guest", + :name=>"rhel6Guest", + :family=>"linuxGuest", + :fullname=>"Red Hat Enterprise Linux 6 (32-Bit)", + :datacenter=>"Solutions"}, + {:id=>"rhel5_64Guest", + :name=>"rhel5_64Guest", + :family=>"linuxGuest", + :fullname=>"Red Hat Enterprise Linux 5 (64-Bit)", + :datacenter=>"Solutions"}] + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_templates.rb b/lib/fog/vsphere/requests/compute/list_templates.rb new file mode 100644 index 00000000..52011aa6 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_templates.rb @@ -0,0 +1,48 @@ +module Fog + module Compute + class Vsphere + class Real + def list_templates(options = { }) + options[:folder] ||= options['folder'] + if options[:folder] then + list_all_templates_in_folder(options[:folder], options[:datacenter]) + else + list_all_templates(options) + end + end + + private + + def list_all_templates_in_folder(path, datacenter_name) + folder = get_raw_vmfolder(path, datacenter_name) + + vms = folder.children.grep(RbVmomi::VIM::VirtualMachine) + # remove all virtual machines that are not template + vms.delete_if { |v| v.config.nil? or not v.config.template } + + vms.map(&method(:convert_vm_mob_ref_to_attr_hash)) + end + + def list_all_templates(options = {}) + datacenters = find_datacenters(options[:datacenter]) + + vms = datacenters.map do |dc| + @connection.serviceContent.viewManager.CreateContainerView({ + :container => dc.vmFolder, + :type => ["VirtualMachine"], + :recursive => true + }).view + end.flatten + # remove all virtual machines that are not templates + vms.delete_if { |v| v.config.nil? or not v.config.template } + + vms.map(&method(:convert_vm_mob_ref_to_attr_hash)) + end + end + class Mock + def list_templates(filters = { }) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_virtual_machines.rb b/lib/fog/vsphere/requests/compute/list_virtual_machines.rb new file mode 100644 index 00000000..3510f095 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_virtual_machines.rb @@ -0,0 +1,80 @@ +module Fog + module Compute + class Vsphere + class Real + def list_virtual_machines(options = { }) + # Listing all VM's can be quite slow and expensive. Try and optimize + # based on the available options we have. These conditions are in + # ascending order of time to complete for large deployments. + + options[:folder] ||= options['folder'] + if options['instance_uuid'] then + [get_virtual_machine(options['instance_uuid'])] + elsif options[:folder] && options[:datacenter] then + list_all_virtual_machines_in_folder(options[:folder], options[:datacenter]) + else + list_all_virtual_machines(options) + end + end + + + private + + def list_all_virtual_machines_in_folder(path, datacenter_name) + folder = get_raw_vmfolder(path, datacenter_name) + + vms = folder.children.grep(RbVmomi::VIM::VirtualMachine) + # remove all template based virtual machines + vms.delete_if { |v| v.config.nil? or v.config.template } + vms.map(&method(:convert_vm_mob_ref_to_attr_hash)) + end + + def list_all_virtual_machines(options = { }) + raw_vms = raw_list_all_virtual_machines(options[:datacenter]) + vms = convert_vm_view_to_attr_hash(raw_vms) + + # remove all template based virtual machines + vms.delete_if { |v| v['template'] } + vms + end + + def raw_list_all_virtual_machines(datacenter_name = nil) + ## Moved this to its own function since trying to get a list of all virtual machines + ## to parse for a find function took way too long. The raw list returned will make it + ## much faster to interact for some functions. + datacenters = find_datacenters(datacenter_name) + datacenters.map do |dc| + @connection.serviceContent.viewManager.CreateContainerView({ + :container => dc.vmFolder, + :type => ["VirtualMachine"], + :recursive => true + }).view + end.flatten + end + def get_folder_path(folder, root = nil) + if (not folder.methods.include?('parent')) or (folder == root) + return + end + "#{get_folder_path(folder.parent)}/#{folder.name}" + end + end + + class Mock + def get_folder_path(folder, root = nil) + nil + end + + def list_virtual_machines(options = { }) + if options['instance_uuid'].nil? and options['mo_ref'].nil? + self.data[:servers].values + elsif !options['instance_uuid'].nil? + server = self.data[:servers][options['instance_uuid']] + server.nil? ? [] : [server] + else + self.data[:servers].values.select{|vm| vm['mo_ref'] == options['mo_ref']} + end + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_vm_customvalues.rb b/lib/fog/vsphere/requests/compute/list_vm_customvalues.rb new file mode 100644 index 00000000..ba2078f3 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_vm_customvalues.rb @@ -0,0 +1,20 @@ +module Fog + module Compute + class Vsphere + class Real + def list_vm_customvalues(vm_id) + get_vm_ref(vm_id).summary.customValue.map do |customvalue| + { + :key => customvalue.key.to_i, + :value => customvalue.value, + } + end + end + end + class Mock + def list_vm_customfields(vm_id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_vm_interfaces.rb b/lib/fog/vsphere/requests/compute/list_vm_interfaces.rb new file mode 100644 index 00000000..3b1e45dd --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_vm_interfaces.rb @@ -0,0 +1,63 @@ +module Fog + module Compute + class Vsphere + class Real + # => VirtualE1000( + #addressType: "assigned", + #backing: VirtualEthernetCardNetworkBackingInfo( + # deviceName: "VM Network", + # dynamicProperty: [], + # network: Network("network-163"), + # useAutoDetect: false + #), + #connectable: VirtualDeviceConnectInfo( + # allowGuestControl: true, + # connected: true, + # dynamicProperty: [], + # startConnected: true, + # status: "ok" + #), + #controllerKey: 100, + #deviceInfo: Description( + # dynamicProperty: [], + # label: "Network adapter 1", + # summary: "VM Network" + #), + #dynamicProperty: [], + #key: 4000, + #macAddress: "00:50:56:a9:00:28", + #unitNumber: 7, + # + def list_vm_interfaces(vm_id, datacenter = nil) + get_vm_ref(vm_id, datacenter).config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).map do |nic| + { + :name => nic.deviceInfo.label, + :mac => nic.macAddress, + :network => nic.backing.respond_to?("network") ? nic.backing.network.name : nic.backing.port.portgroupKey, + :status => nic.connectable.status, + :summary => nic.deviceInfo.summary, + :type => nic.class, + :key => nic.key, + } + end + end + + def get_vm_interface(vm_id, options={}) + raise ArgumentError, "instance id is a required parameter" unless vm_id + if options.is_a? Fog::Compute::Vsphere::Interface + options + else + raise ArgumentError, "Either key or name is a required parameter. options: #{options}" unless options.key? :key or options.key? :mac or options.key? :name + list_vm_interfaces(vm_id).find do | nic | + (options.key? :key and nic[:key]==options[:key].to_i) or (options.key? :mac and nic[:mac]==options[:mac]) or (options.key? :name and nic[:name]==options[:name]) + end + end + end + end + class Mock + def list_vm_interfaces(vm_id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_vm_volumes.rb b/lib/fog/vsphere/requests/compute/list_vm_volumes.rb new file mode 100644 index 00000000..e59b22e7 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_vm_volumes.rb @@ -0,0 +1,52 @@ +module Fog + module Compute + class Vsphere + class Real + # [VirtualDisk( + # backing: VirtualDiskFlatVer2BackingInfo( + # contentId: "a172d19487e878e17d6b16ff2505d7eb", + # datastore: Datastore("datastore-162"), + # diskMode: "persistent", + # dynamicProperty: [], + # fileName: "[Storage1] rhel6-mfojtik/rhel6-mfojtik.vmdk", + # split: false, + # thinProvisioned: true, + # uuid: "6000C29c-a47d-4cd9-5249-c371de775f06", + # writeThrough: false + # ), + # capacityInKB: 8388608, + # controllerKey: 1000, + # deviceInfo: Description( + # dynamicProperty: [], + # label: "Hard disk 1", + # summary: "8,388,608 KB" + # ), + # dynamicProperty: [], + # key: 2001, + # shares: SharesInfo( dynamicProperty: [], level: "normal", shares: 1000 ), + # unitNumber: 1 + #)] + + def list_vm_volumes(vm_id) + get_vm_ref(vm_id).disks.map do |vol| + { + :id => vol.backing.uuid, + :thin => (vol.backing.thinProvisioned rescue(nil)), + :mode => vol.backing.diskMode, + :filename => vol.backing.fileName, + :datastore => (vol.backing.datastore.name rescue(nil)), + :size => vol.capacityInKB, + :name => vol.deviceInfo.label, + :key => vol.key, + :unit_number => vol.unitNumber + } + end + end + end + class Mock + def list_vm_volumes(vm_id) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/modify_vm_interface.rb b/lib/fog/vsphere/requests/compute/modify_vm_interface.rb new file mode 100644 index 00000000..c6744a16 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/modify_vm_interface.rb @@ -0,0 +1,59 @@ +module Fog + module Compute + class Vsphere + class Real + def add_vm_interface(vmid, options = {}) + raise ArgumentError, "instance id is a required parameter" unless vmid + + interface = get_interface_from_options(vmid, options.merge(:server_id => vmid)) + vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, 0, :add, options)]}) + end + + def destroy_vm_interface(vmid, options = {}) + raise ArgumentError, "instance id is a required parameter" unless vmid + + interface = get_interface_from_options(vmid, options.merge(:server_id => vmid)) + vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, interface.key, :remove)]}) + end + + def update_vm_interface(vmid, options = {}) + raise ArgumentError, "instance id is a required parameter" unless vmid + + interface = get_interface_from_options(vmid, options.merge(:server_id => vmid)) + vm_reconfig_hardware('instance_uuid' => vmid, 'hardware_spec' => {'deviceChange'=>[create_interface(interface, interface.key, :edit)]}) + end + + private + def get_interface_from_options(vmid, options) + if options and options[:interface] + options[:interface] + + elsif options[:key] and options[:key]>0 + oldattributes = get_vm_interface(vmid, options) + Fog::Compute::Vsphere::Interface.new(oldattributes.merge(options)) + + elsif options[:type] and options[:network] + Fog::Compute::Vsphere::Interface.new options + + else + raise ArgumentError, "interface is a required parameter or pass options with type and network" + end + end + end + + class Mock + def add_vm_interface(vmid, options = {}) + raise ArgumentError, "instance id is a required parameter" unless vmid + raise ArgumentError, "interface is a required parameter" unless options and options[:interface] + true + end + + def destroy_vm_interface(vmid, options = {}) + raise ArgumentError, "instance id is a required parameter" unless vmid + raise ArgumentError, "interface is a required parameter" unless options and options[:interface] + true + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/modify_vm_volume.rb b/lib/fog/vsphere/requests/compute/modify_vm_volume.rb new file mode 100644 index 00000000..dce86d1c --- /dev/null +++ b/lib/fog/vsphere/requests/compute/modify_vm_volume.rb @@ -0,0 +1,25 @@ +module Fog + module Compute + class Vsphere + class Real + def add_vm_volume(volume) + vm_reconfig_hardware('instance_uuid' => volume.server_id, 'hardware_spec' => {'deviceChange'=>[create_disk(volume, volume.unit_number, :add)]}) + end + + def destroy_vm_volume(volume) + vm_reconfig_hardware('instance_uuid' => volume.server_id, 'hardware_spec' => {'deviceChange'=>[create_disk(volume, volume.unit_number, :remove)]}) + end + end + + class Mock + def add_vm_volume(volume) + true + end + + def destroy_vm_volume(volume) + true + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/set_vm_customvalue.rb b/lib/fog/vsphere/requests/compute/set_vm_customvalue.rb new file mode 100644 index 00000000..6239ee64 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/set_vm_customvalue.rb @@ -0,0 +1,17 @@ +module Fog + module Compute + class Vsphere + class Real + def set_vm_customvalue(vm_id, key, value) + vm_ref = get_vm_ref(vm_id) + vm_ref.setCustomValue(:key => key, :value => value) + end + end + class Mock + def set_vm_customvalue(vm_id, key, value) + nil + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_clone.rb b/lib/fog/vsphere/requests/compute/vm_clone.rb new file mode 100644 index 00000000..4426f248 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_clone.rb @@ -0,0 +1,727 @@ +module Fog + module Compute + class Vsphere + module Shared + private + def vm_clone_check_options(options) + default_options = { + 'force' => false, + 'linked_clone' => false, + 'nic_type' => 'VirtualE1000', + } + options = default_options.merge(options) + # Backwards compat for "path" option + options["template_path"] ||= options["path"] + options["path"] ||= options["template_path"] + required_options = %w{ datacenter template_path name } + required_options.each do |param| + raise ArgumentError, "#{required_options.join(', ')} are required" unless options.key? param + end + raise Fog::Compute::Vsphere::NotFound, "Datacenter #{options["datacenter"]} Doesn't Exist!" unless get_datacenter(options["datacenter"]) + raise Fog::Compute::Vsphere::NotFound, "Template #{options["template_path"]} Doesn't Exist!" unless get_virtual_machine(options["template_path"], options["datacenter"]) + options + end + end + + class Real + include Shared + + # Clones a VM from a template or existing machine on your vSphere + # Server. + # + # ==== Parameters + # * options<~Hash>: + # * 'datacenter'<~String> - *REQUIRED* Datacenter name your cloning + # in. Make sure this datacenter exists, should if you're using + # the clone function in server.rb model. + # * 'template_path'<~String> - *REQUIRED* The path to the machine you + # want to clone FROM. Relative to Datacenter (Example: + # "FolderNameHere/VMNameHere") + # * 'name'<~String> - *REQUIRED* The VMName of the Destination + # * 'dest_folder'<~String> - Destination Folder of where 'name' will + # be placed on your cluster. Relative Path to Datacenter E.G. + # "FolderPlaceHere/anotherSub Folder/onemore" + # * 'power_on'<~Boolean> - Whether to power on machine after clone. + # Defaults to true. + # * 'wait'<~Boolean> - Whether the method should wait for the virtual + # machine to finish cloning before returning information from + # vSphere. Broken right now as you cannot return a model of a serer + # that isn't finished cloning. Defaults to True + # * 'resource_pool'<~Array> - The resource pool on your datacenter + # cluster you want to use. Only works with clusters within same + # same datacenter as where you're cloning from. Datacenter grabbed + # from template_path option. + # Example: ['cluster_name_here','resource_pool_name_here'] + # * 'datastore'<~String> - The datastore you'd like to use. + # (datacenterObj.datastoreFolder.find('name') in API) + # * 'transform'<~String> - Not documented - see http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.RelocateSpec.html + # * 'numCPUs'<~Integer> - the number of Virtual CPUs of the Destination VM + # * 'memoryMB'<~Integer> - the size of memory of the Destination VM in MB + # * customization_spec<~Hash>: Options are marked as required if you + # use this customization_spec. + # As defined https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html + # * encryptionKey <~array of bytes> Used to encrypt/decrypt password + # * globalIPSettings expects a hash, REQUIRED + # * identity expects a hash, REQUIRED - either LinuxPrep, Sysprep or SysprepText + # * nicSettingMap expects an array + # * options expects a hash + # * All options can be parsed using a yaml template with cloudinit_to_customspec.rb + # + # OLD Values still supported: + # This only support cloning and setting DHCP on the first interface + # * 'domain'<~String> - *REQUIRED* This is put into + # /etc/resolve.conf (we hope) + # * 'hostname'<~String> - Hostname of the Guest Os - default is + # options['name'] + # * 'hw_utc_clock'<~Boolean> - *REQUIRED* Is hardware clock UTC? + # Default true + # * 'time_zone'<~String> - *REQUIRED* Only valid linux options + # are valid - example: 'America/Denver' + # * 'interfaces' <~Array> - interfaces object to apply to + # the template when cloning: overrides the + # network_label, network_adapter_device_key and nic_type attributes + # * 'volumes' <~Array> - volumes object to apply to + # the template when cloning: this allows to resize the + # existing disks as well as add or remove them. The + # resizing is applied only when the size is bigger then the + # in size in the template + def vm_clone(options = {}) + # Option handling + options = vm_clone_check_options(options) + + # Added for people still using options['path'] + template_path = options['path'] || options['template_path'] + + # Options['template_path']<~String> + # Added for people still using options['path'] + template_path = options['path'] || options['template_path'] + # Now find the template itself using the efficient find method + vm_mob_ref = get_vm_ref(template_path, options['datacenter']) + + # Options['dest_folder']<~String> + # Grab the destination folder object if it exists else use cloned mach + dest_folder_path = options.fetch('dest_folder','/') # default to root path ({dc_name}/vm/) + dest_folder = get_raw_vmfolder(dest_folder_path, options['datacenter']) + + # Options['resource_pool']<~Array> + # Now find _a_ resource pool to use for the clone if one is not specified + if ( options.key?('resource_pool') && options['resource_pool'].is_a?(Array) && options['resource_pool'].length == 2 ) + cluster_name = options['resource_pool'][0] + pool_name = options['resource_pool'][1] + resource_pool = get_raw_resource_pool(pool_name, cluster_name, options['datacenter']) + elsif ( vm_mob_ref.resourcePool == nil ) + # If the template is really a template then there is no associated resource pool, + # so we need to find one using the template's parent host or cluster + esx_host = vm_mob_ref.collect!('runtime.host')['runtime.host'] + # The parent of the ESX host itself is a ComputeResource which has a resourcePool + resource_pool = esx_host.parent.resourcePool + end + # If the vm given did return a valid resource pool, default to using it for the clone. + # Even if specific pools aren't implemented in this environment, we will still get back + # at least the cluster or host we can pass on to the clone task + # This catches if resource_pool option is set but comes back nil and if resourcePool is + # already set. + resource_pool ||= vm_mob_ref.resourcePool.nil? ? esx_host.parent.resourcePool : vm_mob_ref.resourcePool + + # Options['datastore']<~String> + # Grab the datastore object if option is set + datastore_obj = get_raw_datastore(options['datastore'], options['datacenter']) if options.key?('datastore') + # confirm nil if nil or option is not set + datastore_obj ||= nil + virtual_machine_config_spec = RbVmomi::VIM::VirtualMachineConfigSpec() + + device_change = [] + # fully futured interfaces api: replace the current nics + # with the new based on the specification + if (options.key?('interfaces') ) + if options.key?('network_label') + raise ArgumentError, "interfaces option can't be specified together with network_label" + end + device_change.concat(modify_template_nics_specs(template_path, options['interfaces'], options['datacenter'])) + elsif options.key?('network_label') + device_change << modify_template_nics_simple_spec(options['network_label'], options['nic_type'], options['network_adapter_device_key'], options['datacenter']) + end + if disks = options['volumes'] + device_change.concat(modify_template_volumes_specs(vm_mob_ref, options['volumes'])) + end + virtual_machine_config_spec.deviceChange = device_change if device_change.any? + # Options['numCPUs'] or Options['memoryMB'] + # Build up the specification for Hardware, for more details see ____________ + # https://github.com/rlane/rbvmomi/blob/master/test/test_serialization.rb + # http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.ConfigSpec.html + # FIXME: pad this out with the rest of the useful things in VirtualMachineConfigSpec + virtual_machine_config_spec.numCPUs = options['numCPUs'] if ( options.key?('numCPUs') ) + virtual_machine_config_spec.memoryMB = options['memoryMB'] if ( options.key?('memoryMB') ) + virtual_machine_config_spec.cpuHotAddEnabled = options['cpuHotAddEnabled'] if ( options.key?('cpuHotAddEnabled') ) + virtual_machine_config_spec.memoryHotAddEnabled = options['memoryHotAddEnabled'] if ( options.key?('memoryHotAddEnabled') ) + virtual_machine_config_spec.firmware = options['firmware'] if ( options.key?('firmware') ) + # Options['customization_spec'] + # OLD Options still supported + # * domain <~String> - *REQUIRED* - Sets the server's domain for customization + # * dnsSuffixList <~Array> - Optional - Sets the dns search paths in resolv - Example: ["dev.example.com", "example.com"] + # * time_zone <~String> - Required - Only valid linux options are valid - example: 'America/Denver' + # * ipsettings <~Hash> - Optional - If not set defaults to dhcp + # * ip <~String> - *REQUIRED* Sets the ip address of the VM - Example: 10.0.0.10 + # * dnsServerList <~Array> - Optional - Sets the nameservers in resolv - Example: ["10.0.0.2", "10.0.0.3"] + # * gateway <~Array> - Optional - Sets the gateway for the interface - Example: ["10.0.0.1"] + # * subnetMask <~String> - *REQUIRED* - Set the netmask of the interface - Example: "255.255.255.0" + # For other ip settings options see http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.customization.IPSettings.html + # + # Implement complete customization spec as per https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html + # * encryptionKey <~Array> - Optional, encryption key used to encypt any encrypted passwords + # https://pubs.vmware.com/vsphere-51/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GlobalIPSettings.html + # * globalIPSettings <~Hash> - REQUIRED + # * dnsServerList <~Array> - Optional, list of dns servers - Example: ["10.0.0.2", "10.0.0.3"] + # * dnsSuffixList <~Array> - Optional, List of name resolution suffixes - Example: ["dev.example.com", "example.com"] + # * identity <~Hash> - REQUIRED, Network identity and settings, similar to Microsoft's Sysprep tool. This is a Sysprep, LinuxPrep, or SysprepText object + # * Sysprep <~Hash> - Optional, representation of a Windows sysprep.inf answer file. + # * guiRunOnce: <~Hash> -Optional, representation of the sysprep GuiRunOnce key + # * commandList: <~Array> - REQUIRED, list of commands to run at first user logon, after guest customization. - Example: ["c:\sysprep\runaftersysprep.cmd", "c:\sysprep\installpuppet.ps1"] + # * guiUnattended: <~Hash> - REQUIRED, representation of the sysprep GuiUnattended key + # * autoLogin: boolean - REQUIRED, Flag to determine whether or not the machine automatically logs on as Administrator. + # * autoLogonCount: int - REQUIRED, specifies the number of times the machine should automatically log on as Administrator + # * password: <~Hash> - REQUIRED, new administrator password for the machine + # * plainText: boolean - REQUIRED, specify whether or not the password is in plain text, rather than encrypted + # * value: <~String> - REQUIRED, password string + # * timeZone: <~int> - REQUIRED, (see here for values https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx) + # * identification: <~Hash> - REQUIRED, representation of the sysprep Identification key + # * domainAdmin: <~String> - Optional, domain user account used for authentication if the virtual machine is joining a domain + # * domainAdminPassword: <~Hash> - Optional, password for the domain user account used for authentication + # * plainText: boolean - REQUIRED, specify whether or not the password is in plain text, rather than encrypted + # * value: <~String> - REQUIRED, password string + # * joinDomain: <~String> - Optional, The domain that the virtual machine should join. If this value is supplied, then domainAdmin and domainAdminPassword must also be supplied + # * joinWorkgroup: <~String> - Optional, The workgroup that the virtual machine should join. + # * licenseFilePrintData: <~Hash> - Optional, representation of the sysprep LicenseFilePrintData key + # * autoMode: <~String> - REQUIRED, Server licensing mode. Two strings are supported: 'perSeat' or 'perServer' + # * autoUsers: <~Int> - Optional, This key is valid only if AutoMode = PerServer. The integer value indicates the number of client licenses + # * userData: <~Hash> - REQUIRED, representation of the sysprep UserData key + # * computerName: <~String> - REQUIRED, The computer name of the (Windows) virtual machine. Will be truncates to 15 characters + # * fullName: <~String> - REQUIRED, User's full name + # * orgName: <~String> - REQUIRED, User's organization + # * productId: <~String> - REQUIRED, serial number for os, ignored if using volume licensed instance + # * LinuxPrep: <~Hash> - Optional, contains machine-wide settings (note the uppercase P) + # * domain: <~String> - REQUIRED, The fully qualified domain name. + # * hostName: <~String> - REQUIRED, the network host name + # * hwClockUTC: <~Boolean> - Optional, Specifies whether the hardware clock is in UTC or local time + # * timeZone: <~String> - Optional, Case sensistive timezone, valid values can be found at https://pubs.vmware.com/vsphere-51/topic/com.vmware.wssdk.apiref.doc/timezone.html + # * SysprepText: <~Hash> - Optional, alternate way to specify the sysprep.inf answer file. + # * value: <~String> - REQUIRED, Text for the sysprep.inf answer file. + # * nicSettingMap: <~Array> - Optional, IP settings that are specific to a particular virtual network adapter + # * Each item in array: + # * adapter: <~Hash> - REQUIRED, IP settings for the associated virtual network adapter + # * dnsDomain: <~String> - Optional, DNS domain suffix for adapter + # * dnsServerList: <~Array> - Optional, list of dns server ip addresses - Example: ["10.0.0.2", "10.0.0.3"] + # * gateway: <~Array> - Optional, list of gateways - Example: ["10.0.0.2", "10.0.0.3"] + # * ip: <~String> - Optional, but required if static IP + # * ipV6Spec: <~Hash> - Optional, IPv^ settings + # * ipAddress: <~String> - Optional, but required if setting static IP + # * gateway: <~Array> - Optional, list of ipv6 gateways + # * netBIOS: <~String> - Optional, NetBIOS settings, if supplied must be one of: disableNetBIOS','enableNetBIOS','enableNetBIOSViaDhcp' + # * primaryWINS: <~String> - Optional, IP address of primary WINS server + # * secondaryWINS: <~String> - Optional, IP address of secondary WINS server + # * subnetMask: <~String> - Optional, subnet mask for adapter + # * macAddress: <~String> - Optional, MAC address of adapter being customized. This cannot be set by the client + # * options: <~Hash> Optional operations, currently only win options have any value + # * changeSID: <~Boolean> - REQUIRED, The customization process should modify the machine's security identifier + # * deleteAccounts: <~Boolean> - REQUIRED, If deleteAccounts is true, then all user accounts are removed from the system + # * reboot: <~String> - Optional, (defaults to reboot), Action to be taken after running sysprep, must be one of: 'noreboot', 'reboot', 'shutdown' + # + if ( options.key?('customization_spec') ) + custom_spec = options['customization_spec'] + + # backwards compatablity + if custom_spec.key?('domain') + # doing this means the old options quash any new ones passed as well... might not be the best way to do it? + # any 'old' options overwrite the following: + # - custom_spec['identity']['LinuxPrep'] + # - custom_spec['globalIPSettings['['dnsServerList'] + # - custom_spec['globalIPSettings']['dnsSuffixList'] + # - custom_spec['nicSettingMap'][0]['adapter']['ip'] + # - custom_spec['nicSettingMap'][0]['adapter']['gateway'] + # - custom_spec['nicSettingMap'][0]['adapter']['subnetMask'] + # - custom_spec['nicSettingMap'][0]['adapter']['dnsDomain'] + # - custom_spec['nicSettingMap'][0]['adapter']['dnsServerList'] + # + # we can assume old parameters being passed + cust_hostname = custom_spec['hostname'] || options['name'] + custom_spec['identity'] = Hash.new unless custom_spec.key?('identity') + custom_spec['identity']['LinuxPrep'] = {"domain" => custom_spec['domain'], "hostName" => cust_hostname, "timeZone" => custom_spec['time_zone']} + + if custom_spec.key?('ipsettings') + custom_spec['globalIPSettings']=Hash.new unless custom_spec.key?('globalIPSettings') + custom_spec['globalIPSettings']['dnsServerList'] = custom_spec['ipsettings']['dnsServerList'] if custom_spec['ipsettings'].key?('dnsServerList') + custom_spec['globalIPSettings']['dnsSuffixList'] = custom_spec['dnsSuffixList'] || [custom_spec['domain']] if ( custom_spec['dnsSuffixList'] || custom_spec['domain']) + end + + if (custom_spec['ipsettings'].key?('ip') or custom_spec['ipsettings'].key?('gateway') or custom_spec['ipsettings'].key?('subnetMask') or custom_spec['ipsettings'].key?('domain') or custom_spec['ipsettings'].key?('dnsServerList')) + if custom_spec['ipsettings'].key?('ip') + raise ArgumentError, "subnetMask is required for static ip" unless custom_spec["ipsettings"].key?("subnetMask") + end + custom_spec['nicSettingMap']=Array.new unless custom_spec.key?('nicSettingMap') + custom_spec['nicSettingMap'][0]=Hash.new unless custom_spec['nicSettingMap'].length > 0 + custom_spec['nicSettingMap'][0]['adapter']=Hash.new unless custom_spec['nicSettingMap'][0].key?('adapter') + custom_spec['nicSettingMap'][0]['adapter']['ip'] = custom_spec['ipsettings']['ip'] if custom_spec['ipsettings'].key?('ip') + custom_spec['nicSettingMap'][0]['adapter']['gateway'] = custom_spec['ipsettings']['gateway'] if custom_spec['ipsettings'].key?('gateway') + custom_spec['nicSettingMap'][0]['adapter']['subnetMask'] = custom_spec['ipsettings']['subnetMask'] if custom_spec['ipsettings'].key?('subnetMask') + custom_spec['nicSettingMap'][0]['adapter']['dnsDomain'] = custom_spec['ipsettings']['domain'] if custom_spec['ipsettings'].key?('domain') + custom_spec['nicSettingMap'][0]['adapter']['dnsServerList'] = custom_spec['ipsettings']['dnsServerList'] if custom_spec['ipsettings'].key?('dnsServerList') + end + end + ### End of backwards compatability + + ## requirements check here ## + raise ArgumentError, "globalIPSettings are required when using Customization Spec" unless custom_spec.key?('globalIPSettings') + raise ArgumentError, "identity is required when using Customization Spec" unless custom_spec.key?('identity') + + # encryptionKey + custom_encryptionKey = custom_spec['encryptionKey'] if custom_spec.key?('encryptionKey') + custom_encryptionKey ||= nil + + # globalIPSettings + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GlobalIPSettings.html + custom_globalIPSettings = RbVmomi::VIM::CustomizationGlobalIPSettings.new() + custom_globalIPSettings.dnsServerList = custom_spec['globalIPSettings']['dnsServerList'] if custom_spec['globalIPSettings'].key?("dnsServerList") + custom_globalIPSettings.dnsSuffixList = custom_spec['globalIPSettings']['dnsSuffixList'] if custom_spec['globalIPSettings'].key?("dnsSuffixList") + + # identity + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IdentitySettings.html + # Accepts the 3 supported CustomizationIdentitySettings Types: + # 1. CustomizationLinuxPrep (LinuxPrep) - note the uppercase P + # 2. CustomizationSysprep (Sysprep) + # 3. CustomizationSysprepText (SysprepText) + # At least one of these is required + # + identity = custom_spec['identity'] + if identity.key?("LinuxPrep") + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.LinuxPrep.html + # Fields: + # * domain: string **REQUIRED** + # * hostName: string (CustomizationName) **REQUIRED** Will use options['name'] if not provided. + # * hwClockUTC: boolean + # * timeZone: string (https://pubs.vmware.com/vsphere-55/topic/com.vmware.wssdk.apiref.doc/timezone.html) + raise ArgumentError, "domain is required when using LinuxPrep identity" unless identity['LinuxPrep'].key?('domain') + custom_identity = RbVmomi::VIM::CustomizationLinuxPrep(:domain => identity['LinuxPrep']['domain']) + cust_hostname = RbVmomi::VIM::CustomizationFixedName(:name => identity['LinuxPrep']['hostName']) if identity['LinuxPrep'].key?('hostName') + cust_hostname ||= RbVmomi::VIM::CustomizationFixedName(:name => options['name']) + custom_identity.hostName = cust_hostname + custom_identity.hwClockUTC = identity['LinuxPrep']['hwClockUTC'] if identity['LinuxPrep'].key?('hwClockUTC') + custom_identity.timeZone = identity['LinuxPrep']['timeZone'] if identity['LinuxPrep'].key?('timeZone') + elsif identity.key?("Sysprep") + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Sysprep.html + # Fields: + # * guiRunOnce: CustomizationGuiRunOnce + # * guiUnattended: CustomizationGuiUnattended **REQUIRED** + # * identification: CustomizationIdentification **REQUIRED** + # * licenseFilePrintData: CustomizationLicenseFilePrintData + # * userData: CustomizationUserData **REQUIRED** + # + raise ArgumentError, "guiUnattended is required when using Sysprep identity" unless identity['Sysprep'].key?('guiUnattended') + raise ArgumentError, "identification is required when using Sysprep identity" unless identity['Sysprep'].key?('identification') + raise ArgumentError, "userData is required when using Sysprep identity" unless identity['Sysprep'].key?('userData') + if identity['Sysprep']['guiRunOnce'] + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GuiRunOnce.html + # Fields: + # * commandList: array of string **REQUIRED*** + # + raise ArgumentError, "commandList is required when using Sysprep identity and guiRunOnce" unless identity['Sysprep']['guiRunOnce'].key?('commandList') + cust_guirunonce = RbVmomi::VIM.CustomizationGuiRunOnce( :commandList => identity['Sysprep']['guiRunOnce']['commandList'] ) + end + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.GuiUnattended.html + # Fields: + # * autoLogin: boolean **REQUIRED** + # * autoLogonCount: int **REQUIRED** + # * timeZone: int (see here for values https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx) **REQUIRED** + # * password: CustomizationPassword + raise ArgumentError, "guiUnattended->autoLogon is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('autoLogon') + raise ArgumentError, "guiUnattended->autoLogonCount is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('autoLogonCount') + raise ArgumentError, "guiUnattended->timeZone is required when using Sysprep identity" unless identity['Sysprep']['guiUnattended'].key?('timeZone') + custom_guiUnattended = RbVmomi::VIM.CustomizationGuiUnattended( + :autoLogon => identity['Sysprep']['guiUnattended']['autoLogon'], + :autoLogonCount => identity['Sysprep']['guiUnattended']['autoLogonCount'], + :timeZone => identity['Sysprep']['guiUnattended']['timeZone'] + ) + if identity['Sysprep']['guiUnattended']['password'] + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Password.html + # Fields: + # * plainText: boolean **REQUIRED** + # * value: string **REQUIRED** + raise ArgumentError, "guiUnattended->password->plainText is required when using Sysprep identity and guiUnattended -> password" unless identity['Sysprep']['guiUnattended']['password'].key?('plainText') + raise ArgumentError, "guiUnattended->password->value is required when using Sysprep identity and guiUnattended -> password" unless identity['Sysprep']['guiUnattended']['password'].key?('value') + custom_guiUnattended.password = RbVmomi::VIM.CustomizationPassword( + :plainText => identity['Sysprep']['guiUnattended']['password']['plainText'], + :value => identity['Sysprep']['guiUnattended']['password']['value'] + ) + end + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Identification.html + # Fields: + # * domainAdmin: string + # * domainAdminPassword: CustomizationPassword + # * joinDomain: string *If supplied domainAdmin and domainAdminPassword must be set + # * joinWorkgroup: string *If supplied, joinDomain, domainAdmin and domainAdminPassword will be ignored + custom_identification = RbVmomi::VIM.CustomizationIdentification() + if identity['Sysprep']['identification'].key?('joinWorkgroup') + custom_identification.joinWorkgroup = identity['Sysprep']['identification']['joinWorkgroup'] + elsif identity['Sysprep']['identification'].key?('joinDomain') + raise ArgumentError, "identification->domainAdmin is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification'].key?('domainAdmin') + raise ArgumentError, "identification->domainAdminPassword is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification'].key?('domainAdmin') + raise ArgumentError, "identification->domainAdminPassword->plainText is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification']['domainAdminPassword'].key?('plainText') + raise ArgumentError, "identification->domainAdminPassword->value is required when using Sysprep identity and identification -> joinDomain" unless identity['Sysprep']['identification']['domainAdminPassword'].key?('value') + custom_identification.joinDomain = identity['Sysprep']['identification']['joinDomain'] + custom_identification.domainAdmin = identity['Sysprep']['identification']['domainAdmin'] + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Password.html + # Fields: + # * plainText: boolean **REQUIRED** + # * value: string **REQUIRED** + custom_identification.domainAdminPassword = RbVmomi::VIM.CustomizationPassword( + :plainText => identity['Sysprep']['identification']['domainAdminPassword']['plainText'], + :value => identity['Sysprep']['identification']['domainAdminPassword']['value'] + ) + else + raise ArgumentError, "No valid Indentification found, valid values are 'joinWorkgroup' and 'joinDomain'" + end + if identity['Sysprep'].key?('licenseFilePrintData') + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.LicenseFilePrintData.html + # Fields: + # * autoMode: string (CustomizationLicenseDataMode) ** REQUIRED **, valid strings are: 'perSeat' or 'perServer' + # * autoUsers: int (valid only if AutoMode = PerServer) + raise ArgumentError, "licenseFilePrintData->autoMode is required when using Sysprep identity and licenseFilePrintData" unless identity['Sysprep']['licenseFilePrintData'].key?('autoMode') + raise ArgumentError, "Unsupported autoMode, supported modes are : 'perSeat' or 'perServer'" unless ['perSeat', 'perServer'].include? identity['Sysprep']['licenseFilePrintData']['autoMode'] + custom_licenseFilePrintData = RbVmomi::VIM.CustomizationLicenseFilePrintData( + :autoMode => RbVmomi::VIM.CustomizationLicenseDataMode(identity['Sysprep']['licenseFilePrintData']['autoMode']) + ) + if identity['Sysprep']['licenseFilePrintData'].key?('autoUsers') + custom_licenseFilePrintData.autoUsers = identity['Sysprep']['licenseFilePrintData']['autoUsers'] if identity['Sysprep']['licenseFilePrintData']['autoMode'] == "PerServer" + end + end + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.UserData.html + # Fields: + # * computerName: string (CustomizationFixedName) **REQUIRED** + # * fullName: string **REQUIRED** + # * orgName: string **REQUIRED** + # * productID: string **REQUIRED** + raise ArgumentError, "userData->computerName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('computerName') + raise ArgumentError, "userData->fullName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('fullName') + raise ArgumentError, "userData->orgName is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('orgName') + raise ArgumentError, "userData->productId is required when using Sysprep identity" unless identity['Sysprep']['userData'].key?('productId') + custom_userData = RbVmomi::VIM.CustomizationUserData( + :fullName => identity['Sysprep']['userData']['fullName'], + :orgName => identity['Sysprep']['userData']['orgName'], + :productId => identity['Sysprep']['userData']['productId'], + :computerName => RbVmomi::VIM.CustomizationFixedName(:name => identity['Sysprep']['userData']['computerName']) + ) + + custom_identity = RbVmomi::VIM::CustomizationSysprep( + :guiUnattended => custom_guiUnattended, + :identification => custom_identification, + :userData => custom_userData + ) + custom_identity.guiRunOnce = cust_guirunonce if defined?(cust_guirunonce) + custom_identity.licenseFilePrintData = custom_licenseFilePrintData if defined?(custom_licenseFilePrintData) + elsif identity.key?("SysprepText") + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.SysprepText.html + # Fields: + # * value: string **REQUIRED** + raise ArgumentError, "SysprepText -> value is required when using SysprepText identity" unless identity['SysprepText'].key?('value') + custom_identity = RbVmomi::VIM::CustomizationSysprepText(:value => identity['SysprepText']['value']) + else + raise ArgumentError, "At least one of the following valid identities must be supplied: LinuxPrep, Sysprep, SysprepText" + end + + if custom_spec.key?("nicSettingMap") + # custom_spec['nicSettingMap'] is an array of adapater mappings: + # custom_spec['nicSettingMap'][0]['macAddress'] + # custom_spec['nicSettingMap'][0]['adapter']['ip'] + #https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.AdapterMapping.html + # Fields: + # * adapter: CustomizationIPSettings **REQUIRED** + # * macAddress: string + raise ArgumentError, "At least one nicSettingMap is required when using nicSettingMap" unless custom_spec['nicSettingMap'].length > 0 + raise ArgumentError, "Adapter is required when using nicSettingMap" unless custom_spec['nicSettingMap'][0].key?('adapter') + + custom_nicSettingMap = [] + # need to go through array here for each apapter + custom_spec['nicSettingMap'].each do | nic | + # https://pubs.vmware.com/vsphere-55/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.customization.IPSettings.html + # Fields: + # * dnsDomain: string + # * gateway: array of string + # * ip: CustomizationIpGenerator (string) **REQUIRED IF Assigning Static IP*** + # * ipV6Spec: CustomizationIPSettingsIpV6AddressSpec + # * netBIOS: CustomizationNetBIOSMode (string) + # * primaryWINS: string + # * secondaryWINS: string + # * subnetMask: string - Required if assigning static IP + if nic['adapter'].key?('ip') + raise ArgumentError, "SubnetMask is required when assigning static IP when using nicSettingMap -> Adapter" unless nic['adapter'].key?('subnetMask') + custom_ip = RbVmomi::VIM.CustomizationFixedIp(:ipAddress => nic['adapter']['ip']) + else + custom_ip = RbVmomi::VIM::CustomizationDhcpIpGenerator.new() + end + custom_adapter = RbVmomi::VIM.CustomizationIPSettings(:ip => custom_ip) + custom_adapter.dnsDomain = nic['adapter']['dnsDomain'] if nic['adapter'].key?('dnsDomain') + custom_adapter.dnsServerList = nic['adapter']['dnsServerList'] if nic['adapter'].key?('dnsServerList') + custom_adapter.gateway = nic['adapter']['gateway'] if nic['adapter'].key?('gateway') + if nic['adapter'].key?('ipV6Spec') + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IPSettings.IpV6AddressSpec.html + # Fields: + # * gateway: array of string + # * ip: CustomizationIpV6Generator[] **Required if setting static IP ** + if nic['adapter']['ipV6Spec'].key?('ipAddress') + raise ArgumentError, "SubnetMask is required when assigning static IPv6 when using nicSettingMap -> Adapter -> ipV6Spec" unless nic['adapter']['ipV6Spec'].key?('subnetMask') + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.FixedIpV6.html + # * ipAddress: string **REQUIRED** + # * subnetMask: int **REQUIRED** + custom_ipv6 = RbVmomi::VIM.CustomizationFixedIpV6( + :ipAddress => nic['adapter']['ipV6Spec']['ipAddress'], + :subnetMask => nic['adapter']['ipV6Spec']['subnetMask'] + ) + else + custom_ipv6 = RbVmomi::VIM::CustomizationDhcpIpV6Generator.new() + end + custom_ipv6Spec = RbVmomi::VIM.CustomizationIPSettingsIpV6AddressSpec(:ip => custom_ipv6) + custom_ipv6Spec.gateway = nic['adapter']['ipV6Spec']['gateway'] if nic['adapter']['ipV6Spec'].key?('gateway') + custom_adapter.ipV6Spec = custom_ipv6Spec + end + if nic['adapter'].key?('netBIOS') + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.IPSettings.NetBIOSMode.html + # Fields: + # netBIOS: string matching: 'disableNetBIOS','enableNetBIOS' or 'enableNetBIOSViaDhcp' ** REQUIRED ** + # + raise ArgumentError, "Unsupported NetBIOSMode, supported modes are : 'disableNetBIOS','enableNetBIOS' or 'enableNetBIOSViaDhcp'" unless ['disableNetBIOS','enableNetBIOS','enableNetBIOSViaDhcp'].include? nic['adapter']['netBIOS'] + custom_adapter.netBIOS = RbVmomi::VIM.CustomizationNetBIOSMode(nic['adapter']['netBIOS']) + end + custom_adapter.primaryWINS = nic['adapter']['primaryWINS'] if nic['adapter'].key?('primaryWINS') + custom_adapter.secondaryWINS = nic['adapter']['secondaryWINS'] if nic['adapter'].key?('secondaryWINS') + custom_adapter.subnetMask = nic['adapter']['subnetMask'] if nic['adapter'].key?('subnetMask') + + custom_adapter_mapping = RbVmomi::VIM::CustomizationAdapterMapping(:adapter => custom_adapter) + custom_adapter_mapping.macAddress = nic['macAddress'] if nic.key?('macAddress') + + # build the adapters array, creates it if not already created, otherwise appends to it + custom_nicSettingMap << custom_adapter_mapping + end + end + custom_nicSettingMap = nil if custom_nicSettingMap.length < 1 + + if custom_spec.key?("options") + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Options.html + # this currently doesn't have any Linux options, just windows + # Fields: + # * changeSID: boolean **REQUIRED** + # * deleteAccounts: boolean **REQUIRED** **note deleteAccounts is deprecated as of VI API 2.5 so can be ignored + # * reboot: CustomizationSysprepRebootOption: (string) one of following 'noreboot', reboot' or 'shutdown' (defaults to reboot) + raise ArgumentError, "changeSID id required when using Windows Options" unless custom_spec['options'].key?('changeSID') + raise ArgumentError, "deleteAccounts id required when using Windows Options" unless custom_spec['options'].key?('deleteAccounts') + custom_options = RbVmomi::VIM::CustomizationWinOptions( + :changeSID => custom_spec['options']['changeSID'], + :deleteAccounts => custom_spec['options']['deleteAccounts'] + ) + if custom_spec['options'].key?('reboot') + raise ArgumentError, "Unsupported reboot option, supported options are : 'noreboot', 'reboot' or 'shutdown'" unless ['noreboot','reboot','shutdown'].include? custom_spec['options']['reboot'] + custom_options.reboot = RBVmomi::VIM.CustomizationSysprepRebootOption(custom_spec['options']['reboot']) + end + end + custom_options ||=nil + + # https://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.customization.Specification.html + customization_spec = RbVmomi::VIM::CustomizationSpec( + :globalIPSettings => custom_globalIPSettings, + :identity => custom_identity + ) + customization_spec.encryptionKey = custom_encryptionKey if defined?(custom_encryptionKey) + customization_spec.nicSettingMap = custom_nicSettingMap if defined?(custom_nicSettingMap) + customization_spec.options = custom_options if defined?(custom_options) + + end + customization_spec ||= nil + + relocation_spec=nil + if ( options['linked_clone'] ) + # cribbed heavily from the rbvmomi clone_vm.rb + # this chunk of code reconfigures the disk of the clone source to be read only, + # and then creates a delta disk on top of that, this is required by the API in order to create + # linked clondes + disks = vm_mob_ref.config.hardware.device.select do |vm_device| + vm_device.class == RbVmomi::VIM::VirtualDisk + end + disks.select{|vm_device| vm_device.backing.parent == nil}.each do |disk| + disk_spec = { + :deviceChange => [ + { + :operation => :remove, + :device => disk + }, + { + :operation => :add, + :fileOperation => :create, + :device => disk.dup.tap{|disk_backing| + disk_backing.backing = disk_backing.backing.dup; + disk_backing.backing.fileName = "[#{disk.backing.datastore.name}]"; + disk_backing.backing.parent = disk.backing + } + } + ] + } + vm_mob_ref.ReconfigVM_Task(:spec => disk_spec).wait_for_completion + end + # Next, create a Relocation Spec instance + relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => datastore_obj, + :pool => resource_pool, + :diskMoveType => :moveChildMostDiskBacking) + else + relocation_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(:datastore => datastore_obj, + :pool => resource_pool, + :transform => options['transform'] || 'sparse') + end + # And the clone specification + clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => relocation_spec, + :config => virtual_machine_config_spec, + :customization => customization_spec, + :powerOn => options.key?('power_on') ? options['power_on'] : true, + :template => false) + + # Perform the actual Clone Task + task = vm_mob_ref.CloneVM_Task(:folder => dest_folder, + :name => options['name'], + :spec => clone_spec) + # Waiting for the VM to complete allows us to get the VirtulMachine + # object of the new machine when it's done. It is HIGHLY recommended + # to set 'wait' => true if your app wants to wait. Otherwise, you're + # going to have to reload the server model over and over which + # generates a lot of time consuming API calls to vmware. + if options.fetch('wait', true) then + # REVISIT: It would be awesome to call a block passed to this + # request to notify the application how far along in the process we + # are. I'm thinking of updating a progress bar, etc... + new_vm = task.wait_for_completion + else + tries = 0 + new_vm = begin + # Try and find the new VM (folder.find is quite efficient) + dest_folder.find(options['name'], RbVmomi::VIM::VirtualMachine) or raise Fog::Vsphere::Errors::NotFound + rescue Fog::Vsphere::Errors::NotFound + tries += 1 + if tries <= 10 then + sleep 15 + retry + end + nil + end + end + + # Return hash + { + 'vm_ref' => new_vm ? new_vm._ref : nil, + 'new_vm' => new_vm ? convert_vm_mob_ref_to_attr_hash(new_vm) : nil, + 'task_ref' => task._ref + } + end + + # Build up the network config spec for simple case: + # simple case: apply just the network_label, nic_type and network_adapter_device_key + def modify_template_nics_simple_spec(network_label, nic_type, network_adapter_device_key, datacenter) + config_spec_operation = RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit') + # Get the portgroup and handle it from there. + network = get_raw_network(network_label, datacenter) + if ( network.kind_of? RbVmomi::VIM::DistributedVirtualPortgroup) + # Create the NIC backing for the distributed virtual portgroup + nic_backing_info = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo( + :port => RbVmomi::VIM::DistributedVirtualSwitchPortConnection( + :portgroupKey => network.key, + :switchUuid => network.config.distributedVirtualSwitch.uuid + ) + ) + else + # Otherwise it's a non distributed port group + nic_backing_info = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(:deviceName => network_label) + end + connectable = RbVmomi::VIM::VirtualDeviceConnectInfo( + :allowGuestControl => true, + :connected => true, + :startConnected => true) + device = RbVmomi::VIM.public_send "#{nic_type}", + :backing => nic_backing_info, + :deviceInfo => RbVmomi::VIM::Description(:label => "Network adapter 1", :summary => network_label), + :key => network_adapter_device_key, + :connectable => connectable + device_spec = RbVmomi::VIM::VirtualDeviceConfigSpec( + :operation => config_spec_operation, + :device => device) + return device_spec + end + + + def modify_template_nics_specs(template_path, new_nics, datacenter) + template_nics = list_vm_interfaces(template_path, datacenter).map do |old_attributes| + Fog::Compute::Vsphere::Interface.new(old_attributes) + end + specs = [] + + template_nics.each do |interface| + specs << create_interface(interface, interface.key, :remove, :datacenter => datacenter) + end + + new_nics.each do |interface| + specs << create_interface(interface, 0, :add, :datacenter => datacenter) + end + + return specs + end + + def modify_template_volumes_specs(vm_mob_ref, volumes) + template_volumes = vm_mob_ref.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) + modified_volumes = volumes.take(template_volumes.size) + new_volumes = volumes.drop(template_volumes.size) + + specs = [] + template_volumes.zip(modified_volumes).each do |template_volume, new_volume| + if new_volume + # updated the attribtues on the existing volume + # it's not allowed to reduce the size of the volume when cloning + if new_volume.size > template_volume.capacityInKB + template_volume.capacityInKB = new_volume.size + end + template_volume.backing.diskMode = new_volume.mode + template_volume.backing.thinProvisioned = new_volume.thin + specs << { :operation => :edit, :device => template_volume } + else + specs << { :operation => :remove, + :fileOperation => :destroy, + :device => template_volume } + end + end + specs.concat(new_volumes.map { |volume| create_disk(volume, volumes.index(volume)) }) + return specs + end + end + + class Mock + include Shared + def vm_clone(options = {}) + # Option handling TODO Needs better method of checking + options = vm_clone_check_options(options) + notfound = lambda { raise Fog::Compute::Vsphere::NotFound, "Could not find VM template" } + template = list_virtual_machines.find(notfound) do |vm| + vm['name'] == options['template_path'].split("/")[-1] + end + + # generate a random id + id = [8,4,4,4,12].map{|i| Fog::Mock.random_hex(i)}.join("-") + new_vm = template.clone.merge({ + "name" => options['name'], + "id" => id, + "instance_uuid" => id, + "path" => "/Datacenters/#{options['datacenter']}/#{options['dest_folder'] ? options['dest_folder']+"/" : ""}#{options['name']}" + }) + self.data[:servers][id] = new_vm + + { + 'vm_ref' => "vm-#{Fog::Mock.random_numbers(3)}", + 'new_vm' => new_vm, + 'task_ref' => "task-#{Fog::Mock.random_numbers(4)}", + } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_config_vnc.rb b/lib/fog/vsphere/requests/compute/vm_config_vnc.rb new file mode 100644 index 00000000..ddfa0eab --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_config_vnc.rb @@ -0,0 +1,45 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_config_vnc(options = { }) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + task = vm_mob_ref.ReconfigVM_Task(:spec => { + :extraConfig => [ + { :key => 'RemoteDisplay.vnc.enabled', :value => options[:enabled] ? 'true' : 'false' }, + { :key => 'RemoteDisplay.vnc.password', :value => options[:password].to_s }, + { :key => 'RemoteDisplay.vnc.port', :value => options[:port].to_s || '5910' } + ] + }) + task.wait_for_completion + { 'task_state' => task.info.state } + end + + # return a hash of VNC attributes required to view the console + def vm_get_vnc uuid + search_filter = { :uuid => uuid, 'vmSearch' => true, 'instanceUuid' => true } + vm = @connection.searchIndex.FindAllByUuid(search_filter).first + Hash[vm.config.extraConfig.map do |config| + if config.key =~ /^RemoteDisplay\.vnc\.(\w+)$/ + [$1.to_sym, config.value] + end + end.compact] + end + end + + class Mock + def vm_config_vnc(options = { }) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => 'success' } + end + + def vm_get_vnc uuid + {:password => 'secret', :port => '5900', :enabled => 'true'} + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_destroy.rb b/lib/fog/vsphere/requests/compute/vm_destroy.rb new file mode 100644 index 00000000..200889e0 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_destroy.rb @@ -0,0 +1,23 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_destroy(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + vm_mob_ref = get_vm_ref(options['instance_uuid']) + task = vm_mob_ref.Destroy_Task + task.wait_for_completion + { 'task_state' => task.info.state } + end + end + + class Mock + def vm_destroy(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => 'success' } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_execute.rb b/lib/fog/vsphere/requests/compute/vm_execute.rb new file mode 100644 index 00000000..5ab2ad3a --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_execute.rb @@ -0,0 +1,47 @@ +module Fog + module Compute + class Vsphere + class Real + # NOTE: you must be using vsphere_rev 5.0 or greater to use this functionality + # e.g. Fog::Compute.new(provider: "vsphere", vsphere_rev: "5.5", etc) + # * options<~Hash>: + # * 'instance_uuid'<~String> - *REQUIRED* the instance uuid you would like to operate on + # * 'command'<~String> *REQUIRED* the command to execute + # * 'args'<~String> arguments to pass the command + # * 'working_dir'<~String> path to the working directory + # * 'user'<~String> *REQUIRED* the ssh username you would like to login as + # * 'password'<~String> *REQUIRED* the ssh password for the user you would like to log in as + def vm_execute(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + raise ArgumentError, "command is a required parameter" unless options.key? 'command' + raise ArgumentError, "user is a required parameter" unless options.key? 'user' + raise ArgumentError, "password is a required parameter" unless options.key? 'password' + + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + + auth = RbVmomi::VIM::NamePasswordAuthentication(:interactiveSession => false, + :username => options['user'], + :password => options['password']) + + spec = RbVmomi::VIM::GuestProgramSpec(:programPath => options['command'], + :arguments => options['args'], + :workingDirectory => options['working_dir']) + + gom = @connection.serviceContent.guestOperationsManager + gom.processManager.StartProgramInGuest(:vm => vm_mob_ref, :auth => auth, :spec => spec) + end + end + + class Mock + def vm_execute(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + raise ArgumentError, "command is a required parameter" unless options.key? 'command' + raise ArgumentError, "user is a required parameter" unless options.key? 'user' + raise ArgumentError, "password is a required parameter" unless options.key? 'password' + return 12345 + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_migrate.rb b/lib/fog/vsphere/requests/compute/vm_migrate.rb new file mode 100644 index 00000000..c4a422a5 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_migrate.rb @@ -0,0 +1,33 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_migrate(options = {}) + #priority is the only required option, and it has a sane default option. + priority = options['priority'].nil? ? 'defaultPriority' : options["priority"] + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + # Find the VM Object + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + + unless vm_mob_ref.kind_of? RbVmomi::VIM::VirtualMachine + raise Fog::Vsphere::Errors::NotFound, + "Could not find VirtualMachine with instance uuid #{options['instance_uuid']}" + end + task = vm_mob_ref.MigrateVM_Task(:pool => options['pool'], :host => options['host'], :priority => "#{priority}", :state => options['state'] ) + task.wait_for_completion + { 'task_state' => task.info.state } + end + end + + class Mock + def vm_migrate(options = {}) + priority = options['priority'].nil? ? 'defaultPriority' : options["priority"] + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => 'success' } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_power_off.rb b/lib/fog/vsphere/requests/compute/vm_power_off.rb new file mode 100644 index 00000000..fc954d35 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_power_off.rb @@ -0,0 +1,39 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_power_off(options = {}) + options = { 'force' => false }.merge(options) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + + if options['force'] then + task = vm_mob_ref.PowerOffVM_Task + task.wait_for_completion + { 'task_state' => task.info.result, 'power_off_type' => 'cut_power' } + else + vm_mob_ref.ShutdownGuest + { + 'task_state' => "running", + 'power_off_type' => 'shutdown_guest', + } + end + end + end + + class Mock + def vm_power_off(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + vm = get_virtual_machine(options['instance_uuid']) + vm["power_state"] = "poweredOff" + { + 'task_state' => "running", + 'power_off_type' => options['force'] ? 'cut_power' : 'shutdown_guest', + } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_power_on.rb b/lib/fog/vsphere/requests/compute/vm_power_on.rb new file mode 100644 index 00000000..40059cb6 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_power_on.rb @@ -0,0 +1,26 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_power_on(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + + task = vm_mob_ref.PowerOnVM_Task + task.wait_for_completion + # 'success', 'running', 'queued', 'error' + { 'task_state' => task.info.state } + end + end + + class Mock + def vm_power_on(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => 'success' } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_reboot.rb b/lib/fog/vsphere/requests/compute/vm_reboot.rb new file mode 100644 index 00000000..7d396821 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_reboot.rb @@ -0,0 +1,31 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_reboot(options = {}) + options = { 'force' => false }.merge(options) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + + search_filter = { :uuid => options['instance_uuid'], 'vmSearch' => true, 'instanceUuid' => true } + vm_mob_ref = @connection.searchIndex.FindAllByUuid(search_filter).first + + if options['force'] then + task = vm_mob_ref.ResetVM_Task + task.wait_for_completion + { 'task_state' => task.info.result, 'reboot_type' => 'reset_power' } + else + vm_mob_ref.RebootGuest + { 'task_state' => "running", 'reboot_type' => 'reboot_guest' } + end + end + end + + class Mock + def vm_reboot(options = {}) + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => "running", 'reboot_type' => options['force'] ? 'reset_power' : 'reboot_guest' } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_reconfig_cpus.rb b/lib/fog/vsphere/requests/compute/vm_reconfig_cpus.rb new file mode 100644 index 00000000..ffff6bb0 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_reconfig_cpus.rb @@ -0,0 +1,23 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_reconfig_cpus(options = {}) + raise ArgumentError, "cpus is a required parameter" unless options.key? 'cpus' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + hardware_spec={'numCPUs' => options['cpus'], 'numCoresPerSocket' => options['corespersocket']} + vm_reconfig_hardware('instance_uuid' => options['instance_uuid'], 'hardware_spec' => hardware_spec ) + end + end + + class Mock + def vm_reconfig_cpus(options = {}) + raise ArgumentError, "cpus is a required parameter" unless options.key? 'cpus' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + hardware_spec={'numCPUs' => options['cpus'], 'numCoresPerSocket' => options['corespersocket']} + vm_reconfig_hardware('instance_uuid' => options['instance_uuid'], 'hardware_spec' => hardware_spec ) + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_reconfig_hardware.rb b/lib/fog/vsphere/requests/compute/vm_reconfig_hardware.rb new file mode 100644 index 00000000..a491b9c0 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_reconfig_hardware.rb @@ -0,0 +1,24 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_reconfig_hardware(options = {}) + raise ArgumentError, "hardware_spec is a required parameter" unless options.key? 'hardware_spec' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + vm_mob_ref = get_vm_ref(options['instance_uuid']) + task = vm_mob_ref.ReconfigVM_Task(:spec => RbVmomi::VIM.VirtualMachineConfigSpec(options['hardware_spec'])) + task.wait_for_completion + { 'task_state' => task.info.state } + end + end + + class Mock + def vm_reconfig_hardware(options = {}) + raise ArgumentError, "hardware_spec is a required parameter" unless options.key? 'hardware_spec' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + { 'task_state' => 'success' } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_reconfig_memory.rb b/lib/fog/vsphere/requests/compute/vm_reconfig_memory.rb new file mode 100644 index 00000000..fef32841 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/vm_reconfig_memory.rb @@ -0,0 +1,23 @@ +module Fog + module Compute + class Vsphere + class Real + def vm_reconfig_memory(options = {}) + raise ArgumentError, "memory is a required parameter" unless options.key? 'memory' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + hardware_spec={'memoryMB' => options['memory']} + vm_reconfig_hardware('instance_uuid' => options['instance_uuid'], 'hardware_spec' => hardware_spec ) + end + end + + class Mock + def vm_reconfig_memory(options = {}) + raise ArgumentError, "memory is a required parameter" unless options.key? 'memory' + raise ArgumentError, "instance_uuid is a required parameter" unless options.key? 'instance_uuid' + hardware_spec={'memoryMB' => options['memory']} + vm_reconfig_hardware('instance_uuid' => options['instance_uuid'], 'hardware_spec' => hardware_spec ) + end + end + end + end +end diff --git a/lib/fog/vsphere/version.rb b/lib/fog/vsphere/version.rb new file mode 100644 index 00000000..9d76b9e1 --- /dev/null +++ b/lib/fog/vsphere/version.rb @@ -0,0 +1,5 @@ +module Fog + module Vsphere + VERSION = "0.1.0" + end +end diff --git a/tests/compute_tests.rb b/tests/compute_tests.rb new file mode 100644 index 00000000..70d62743 --- /dev/null +++ b/tests/compute_tests.rb @@ -0,0 +1,53 @@ +Shindo.tests('Fog::Compute[:vsphere]', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + tests("| convert_vm_mob_ref_to_attr_hash") do + # Mock the RbVmomi::VIM::ManagedObject class + class MockManagedObject + attr_reader :parent, :_ref + + def initialize + @parent = @_ref = 'vm-123' + end + + def collect! *pathSet + { '_ref' => 'vm-123', 'name' => 'fakevm' } + end + end + + fake_vm_mob_ref = MockManagedObject.new + + tests("When converting an incomplete vm object") do + test("it should return a Hash") do + compute.send(:convert_vm_mob_ref_to_attr_hash, fake_vm_mob_ref).kind_of? Hash + end + tests("The converted Hash should") do + attr_hash = compute.send(:convert_vm_mob_ref_to_attr_hash, fake_vm_mob_ref) + test("have a name") { attr_hash['name'] == 'fakevm' } + test("have a mo_ref") {attr_hash['mo_ref'] == 'vm-123' } + test("have an id") { attr_hash['id'] == 'vm-123' } + test("not have a instance_uuid") { attr_hash['instance_uuid'].nil? } + end + end + + tests("When passed a nil object") do + attr_hash = compute.send :convert_vm_mob_ref_to_attr_hash, nil + test("it should return a nil object") do + attr_hash.nil? + end + end + end + + tests("Compute attributes") do + %w{ vsphere_is_vcenter vsphere_rev vsphere_username vsphere_server }.each do |attr| + test("it should respond to #{attr}") { compute.respond_to? attr } + end + end + + tests("Compute collections") do + %w{ servers }.each do |collection| + test("it should respond to #{collection}") { compute.respond_to? collection } + end + end +end diff --git a/tests/helper.rb b/tests/helper.rb new file mode 100644 index 00000000..8e60fb21 --- /dev/null +++ b/tests/helper.rb @@ -0,0 +1 @@ +require File.expand_path('../../lib/fog/vsphere', __FILE__) diff --git a/tests/helpers/mock_helper.rb b/tests/helpers/mock_helper.rb new file mode 100644 index 00000000..6c4a18cd --- /dev/null +++ b/tests/helpers/mock_helper.rb @@ -0,0 +1,9 @@ +Fog.mock! if ENV['FOG_MOCK'] == 'true' + +if Fog.mock? + Fog.credentials = { + :vsphere_server => 'fake_vsphere_server', + :vsphere_username => 'fake_vsphere_username', + :vsphere_password => 'fake_vsphere_password' + }.merge(Fog.credentials) +end diff --git a/tests/helpers/succeeds_helper.rb b/tests/helpers/succeeds_helper.rb new file mode 100644 index 00000000..b54589e3 --- /dev/null +++ b/tests/helpers/succeeds_helper.rb @@ -0,0 +1,9 @@ +module Shindo + class Tests + def succeeds + test('succeeds') do + !!instance_eval(&Proc.new) + end + end + end +end diff --git a/tests/models/compute/server_tests.rb b/tests/models/compute/server_tests.rb new file mode 100644 index 00000000..4f692f75 --- /dev/null +++ b/tests/models/compute/server_tests.rb @@ -0,0 +1,44 @@ +Shindo.tests('Fog::Compute[:vsphere] | server model', ['vsphere']) do + + servers = Fog::Compute[:vsphere].servers + server = servers.last + + tests('The server model should') do + tests('have the action') do + test('reload') { server.respond_to? 'reload' } + %w{ stop start destroy reboot }.each do |action| + test(action) { server.respond_to? action } + test("#{action} returns successfully") { server.send(action.to_sym) ? true : false } + end + end + tests('have attributes') do + model_attribute_hash = server.attributes + attributes = [ :id, + :instance_uuid, + :uuid, + :power_state, + :tools_state, + :mo_ref, + :tools_version, + :hostname, + :mac_addresses, + :operatingsystem, + :connection_state, + :hypervisor, + :name, + :public_ip_address] + tests("The server model should respond to") do + attributes.each do |attribute| + test("#{attribute}") { server.respond_to? attribute } + end + end + tests("The attributes hash should have key") do + attributes.each do |attribute| + test("#{attribute}") { model_attribute_hash.key? attribute } + end + end + end + test('be a kind of Fog::Compute::Vsphere::Server') { server.kind_of? Fog::Compute::Vsphere::Server } + end + +end diff --git a/tests/models/compute/servers_tests.rb b/tests/models/compute/servers_tests.rb new file mode 100644 index 00000000..c0e004a0 --- /dev/null +++ b/tests/models/compute/servers_tests.rb @@ -0,0 +1,15 @@ +Shindo.tests('Fog::Compute[:vsphere] | servers collection', ['vsphere']) do + + servers = Fog::Compute[:vsphere].servers + + tests('The servers collection') do + test('should not be empty') { not servers.empty? } + test('should be a kind of Fog::Compute::Vsphere::Servers') { servers.kind_of? Fog::Compute::Vsphere::Servers } + tests('should be able to reload itself').succeeds { servers.reload } + tests('should be able to get a model') do + tests('by managed object reference').succeeds { servers.get 'vm-715' } + tests('by instance uuid').succeeds { servers.get '5032c8a5-9c5e-ba7a-3804-832a03e16381' } + end + end + +end diff --git a/tests/requests/compute/current_time_tests.rb b/tests/requests/compute/current_time_tests.rb new file mode 100644 index 00000000..a19755da --- /dev/null +++ b/tests/requests/compute/current_time_tests.rb @@ -0,0 +1,12 @@ +Shindo.tests('Fog::Compute[:vsphere] | current_time request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + tests('The response should') do + response = compute.current_time + test('be a kind of Hash') { response.kind_of? Hash } + test('have a current_time key') { response.key? 'current_time' } + test('have a current_time key with a Time value') { response['current_time'].kind_of? Time } + end + +end diff --git a/tests/requests/compute/get_network_tests.rb b/tests/requests/compute/get_network_tests.rb new file mode 100644 index 00000000..795b0bc7 --- /dev/null +++ b/tests/requests/compute/get_network_tests.rb @@ -0,0 +1,50 @@ +require 'ostruct' + +Shindo.tests('Fog::Compute[:vsphere] | get_network request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + + class DistributedVirtualPortgroup + attr_accessor :name, :dvs_name + + def initialize attrs + @name = attrs.fetch(:name) + @dvs_name = attrs.fetch(:dvs_name) + end + + def config + OpenStruct.new( :distributedVirtualSwitch => OpenStruct.new(:name => dvs_name)) + end + end + + fake_networks = [OpenStruct.new(:name => 'non-dvs'), + DistributedVirtualPortgroup.new( :name => 'web1', :dvs_name => 'dvs5'), + DistributedVirtualPortgroup.new( :name => 'web1', :dvs_name => 'dvs11'), + DistributedVirtualPortgroup.new( :name => 'other', :dvs_name => 'other'), + ] + + + tests('#choose_finder should') do + test('choose the network based on network name and dvs name'){ + finder = compute.send(:choose_finder, 'web1', 'dvs11') + found_network = fake_networks.find{ |n| finder.call(n) } + found_network.name == 'web1' && found_network.dvs_name == 'dvs11' + } + test('choose the network based on network name and any dvs'){ + finder = compute.send(:choose_finder, 'web1', :dvs) + found_network = fake_networks.find{ |n| finder.call(n) } + found_network.name == 'web1' && found_network.dvs_name == 'dvs5' + } + test('choose the network based on network name only'){ + finder = compute.send(:choose_finder, 'other', nil) + found_network = fake_networks.find{ |n| finder.call(n) } + found_network.name == 'other' && found_network.dvs_name == 'other' + } + test('choose the network based on network name only for non-dvs'){ + finder = compute.send(:choose_finder, 'non-dvs', nil) + found_network = fake_networks.find{ |n| finder.call(n) } + found_network.name == 'non-dvs' && found_network.class.name.to_s == 'OpenStruct' + } + end +end diff --git a/tests/requests/compute/list_clusters_tests.rb b/tests/requests/compute/list_clusters_tests.rb new file mode 100644 index 00000000..cf1e959e --- /dev/null +++ b/tests/requests/compute/list_clusters_tests.rb @@ -0,0 +1,11 @@ +Shindo.tests('Fog::Compute[:vsphere] | list_clusters request', ['vsphere']) do + tests("When listing all clusters") do + + response = Fog::Compute[:vsphere].list_clusters + test("Clusters extracted from folders... ") {response.length == 4} + + tests("The response data format ...") do + test("be a kind of Hash") { response.kind_of? Array } + end + end +end \ No newline at end of file diff --git a/tests/requests/compute/list_virtual_machines_tests.rb b/tests/requests/compute/list_virtual_machines_tests.rb new file mode 100644 index 00000000..6f572a79 --- /dev/null +++ b/tests/requests/compute/list_virtual_machines_tests.rb @@ -0,0 +1,38 @@ +Shindo.tests('Fog::Compute[:vsphere] | list_virtual_machines request', ['vsphere']) do + + tests("When listing all machines") do + + response = Fog::Compute[:vsphere].list_virtual_machines + + tests("The response data format ...") do + test("be a kind of Hash") { response.kind_of? Array } + end + end + + tests("When providing an instance_uuid") do + + # pending unless Fog.mock? + + tests("that does exist") do + uuid = "5029c440-85ee-c2a1-e9dd-b63e39364603" + response = Fog::Compute[:vsphere].list_virtual_machines({'instance_uuid' => uuid}) + + tests("The response should") do + test("contain one vm") { response.length == 1 } + test("contain that is an attribute hash") { response[0].kind_of? Hash } + test("find jefftest") { response.first['name'] == 'jefftest' } + end + end + + tests("that does not exist or is a template") do + %w{ does-not-exist-and-is-not-a-uuid 50323f93-6835-1178-8b8f-9e2109890e1a }.each do |uuid| + response = Fog::Compute[:vsphere].list_virtual_machines({'instance_uuid' => uuid}) + + tests("The response should") do + test("be empty") { response.empty? } + end + end + end + end + +end diff --git a/tests/requests/compute/set_vm_customvalue_tests.rb b/tests/requests/compute/set_vm_customvalue_tests.rb new file mode 100644 index 00000000..7adcebe3 --- /dev/null +++ b/tests/requests/compute/set_vm_customvalue_tests.rb @@ -0,0 +1,20 @@ +Shindo.tests('Fog::Compute[:vsphere] | set_vm_customvalue request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + instance_uuid = '50137835-88a1-436e-768e-9b2677076e67' + custom_key = nil + custom_value = nil + + tests('The response should') do + response = compute.set_vm_customvalue(instance_uuid, custom_key, custom_value) + test('be nil') { response.nil? } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.set_vm_customvalue } + raises(ArgumentError, 'raises ArgumentError when custom_key option is missing') { compute.set_vm_customvalue(instance_uuid) } + raises(ArgumentError, 'raises ArgumentError when custom_value option is missing') { compute.set_vm_customvalue(instance_uuid, custom_key) } + end + +end diff --git a/tests/requests/compute/vm_clone_tests.rb b/tests/requests/compute/vm_clone_tests.rb new file mode 100644 index 00000000..5d8abc5d --- /dev/null +++ b/tests/requests/compute/vm_clone_tests.rb @@ -0,0 +1,50 @@ +Shindo.tests("Fog::Compute[:vsphere] | vm_clone request", 'vsphere') do + # require 'guid' + compute = Fog::Compute[:vsphere] + response = nil + response_linked = nil + + template = "rhel64" + datacenter = "Solutions" + + tests("Standard Clone | The return value should") do + servers_size = compute.servers.size + response = compute.vm_clone('datacenter' => datacenter, 'template_path' => template, 'name' => 'cloning_vm', 'wait' => true) + test("be a kind of Hash") { response.kind_of? Hash } + %w{ vm_ref new_vm task_ref }.each do |key| + test("have a #{key} key") { response.key? key } + end + test("creates a new server") { compute.servers.size == servers_size+1 } + test("new server name is set") { compute.get_virtual_machine(response['new_vm']['id'])['name'] == 'cloning_vm' } + end + + tests("Standard Clone setting ram and cpu | The return value should") do + servers_size = compute.servers.size + response = compute.vm_clone('datacenter' => datacenter, 'template_path' => template, 'name' => 'cloning_vm', 'memoryMB' => '8192', 'numCPUs' => '8', 'wait' => true) + test("be a kind of Hash") { response.kind_of? Hash } + %w{ vm_ref new_vm task_ref }.each do |key| + test("have a #{key} key") { response.key? key } + end + test("creates a new server") { compute.servers.size == servers_size+1 } + test("new server name is set") { compute.get_virtual_machine(response['new_vm']['id'])['name'] == 'cloning_vm' } + end + + tests("Linked Clone | The return value should") do + servers_size = compute.servers.size + response = compute.vm_clone('datacenter' => datacenter, 'template_path' => template, 'name' => 'cloning_vm_linked', 'wait' => 1, 'linked_clone' => true) + test("be a kind of Hash") { response.kind_of? Hash } + %w{ vm_ref new_vm task_ref }.each do |key| + test("have a #{key} key") { response.key? key } + end + test("creates a new server") { compute.servers.size == servers_size+1 } + test("new server name is set") { compute.get_virtual_machine(response['new_vm']['id'])['name'] == 'cloning_vm_linked' } + end + + tests("When invalid input is presented") do + raises(ArgumentError, 'it should raise ArgumentError') { compute.vm_clone(:foo => 1) } + raises(Fog::Compute::Vsphere::NotFound, 'it should raise Fog::Compute::Vsphere::NotFound when the UUID is not a string') do + pending # require 'guid' + compute.vm_clone('instance_uuid' => Guid.from_s(template), 'name' => 'jefftestfoo') + end + end +end diff --git a/tests/requests/compute/vm_config_vnc_tests.rb b/tests/requests/compute/vm_config_vnc_tests.rb new file mode 100644 index 00000000..cb126cd1 --- /dev/null +++ b/tests/requests/compute/vm_config_vnc_tests.rb @@ -0,0 +1,19 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_config_vnc request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + reconfig_target = '50137835-88a1-436e-768e-9b2677076e67' + vnc_spec = {:port => '5900', :password => 'ssaaa', :enabled => 'true'} + + tests('The response should') do + response = compute.vm_config_vnc('instance_uuid' => reconfig_target).merge(vnc_spec) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('VNC attrs response should') do + response = compute.vm_get_vnc(reconfig_target) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a port key') { response.key? :port } + end +end diff --git a/tests/requests/compute/vm_destroy_tests.rb b/tests/requests/compute/vm_destroy_tests.rb new file mode 100644 index 00000000..1b4a5d66 --- /dev/null +++ b/tests/requests/compute/vm_destroy_tests.rb @@ -0,0 +1,17 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_destroy request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + booted_vm = '5032c8a5-9c5e-ba7a-3804-832a03e16381' + + tests('The response should') do + response = compute.vm_destroy('instance_uuid' => booted_vm) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + + end + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_destroy } + end + +end diff --git a/tests/requests/compute/vm_migrate_tests.rb b/tests/requests/compute/vm_migrate_tests.rb new file mode 100644 index 00000000..74f425fc --- /dev/null +++ b/tests/requests/compute/vm_migrate_tests.rb @@ -0,0 +1,16 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_migrate request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + powered_on_vm = '50137835-88a1-436e-768e-9b2677076e67' + + tests('The response should') do + response = compute.vm_migrate('instance_uuid' => powered_on_vm) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_migrate } + end +end diff --git a/tests/requests/compute/vm_power_off_tests.rb b/tests/requests/compute/vm_power_off_tests.rb new file mode 100644 index 00000000..f961bb3a --- /dev/null +++ b/tests/requests/compute/vm_power_off_tests.rb @@ -0,0 +1,26 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_power_off request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + powered_on_vm = '5032c8a5-9c5e-ba7a-3804-832a03e16381' + + tests('The response should') do + response = compute.vm_power_off('instance_uuid' => powered_on_vm) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + test('should have a power_off_type key') { response.key? 'power_off_type' } + end + + # When forcing the shutdown, we expect the result to be + { true => 'cut_power', false => 'shutdown_guest'}.each do |force, expected| + tests("When 'force' => #{force}") do + response = compute.vm_power_off('instance_uuid' => powered_on_vm, 'force' => force) + test('should retur power_off_type of #{expected}') { response['power_off_type'] == expected } + end + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_power_off } + end + +end diff --git a/tests/requests/compute/vm_power_on_tests.rb b/tests/requests/compute/vm_power_on_tests.rb new file mode 100644 index 00000000..1ee007fe --- /dev/null +++ b/tests/requests/compute/vm_power_on_tests.rb @@ -0,0 +1,17 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_power_on request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + powered_off_vm = nil + + tests('The response should') do + response = compute.vm_power_on('instance_uuid' => powered_off_vm) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_power_on } + end + +end diff --git a/tests/requests/compute/vm_reboot_tests.rb b/tests/requests/compute/vm_reboot_tests.rb new file mode 100644 index 00000000..24189183 --- /dev/null +++ b/tests/requests/compute/vm_reboot_tests.rb @@ -0,0 +1,26 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_reboot request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + powered_on_vm = '5032c8a5-9c5e-ba7a-3804-832a03e16381' + + tests('The response should') do + response = compute.vm_reboot('instance_uuid' => powered_on_vm) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + test('should have a reboot_type key') { response.key? 'reboot_type' } + end + + # When forcing the shutdown, we expect the result to be + { true => 'reset_power', false => 'reboot_guest'}.each do |force, expected| + tests("When force => #{force}") do + response = compute.vm_reboot('instance_uuid' => powered_on_vm, 'force' => force) + test("should return reboot_type of #{expected}") { response['reboot_type'] == expected } + end + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_reboot } + end + +end diff --git a/tests/requests/compute/vm_reconfig_cpus_tests.rb b/tests/requests/compute/vm_reconfig_cpus_tests.rb new file mode 100644 index 00000000..e0eeadb6 --- /dev/null +++ b/tests/requests/compute/vm_reconfig_cpus_tests.rb @@ -0,0 +1,19 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_reconfig_cpus request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + reconfig_target = '50137835-88a1-436e-768e-9b2677076e67' + reconfig_spec = 2 + + tests('The response should') do + response = compute.vm_reconfig_cpus('instance_uuid' => reconfig_target, 'cpus' => reconfig_spec) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_reconfig_cpus('cpus' => reconfig_spec) } + raises(ArgumentError, 'raises ArgumentError when cpus option is missing') { compute.vm_reconfig_cpus('instance_uuid' => reconfig_target) } + end + +end diff --git a/tests/requests/compute/vm_reconfig_hardware_tests.rb b/tests/requests/compute/vm_reconfig_hardware_tests.rb new file mode 100644 index 00000000..db73b3e5 --- /dev/null +++ b/tests/requests/compute/vm_reconfig_hardware_tests.rb @@ -0,0 +1,19 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_reconfig_hardware request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + reconfig_target = '50137835-88a1-436e-768e-9b2677076e67' + reconfig_spec = {'guestId' => 'rhel5_64Guest'} + + tests('The response should') do + response = compute.vm_reconfig_hardware('instance_uuid' => reconfig_target, 'hardware_spec' => reconfig_spec) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_reconfig_hardware('hardware_spec' => reconfig_spec) } + raises(ArgumentError, 'raises ArgumentError when hardware_spec option is missing') { compute.vm_reconfig_hardware('instance_uuid' => reconfig_target) } + end + +end diff --git a/tests/requests/compute/vm_reconfig_memory_tests.rb b/tests/requests/compute/vm_reconfig_memory_tests.rb new file mode 100644 index 00000000..64aa2694 --- /dev/null +++ b/tests/requests/compute/vm_reconfig_memory_tests.rb @@ -0,0 +1,19 @@ +Shindo.tests('Fog::Compute[:vsphere] | vm_reconfig_memory request', ['vsphere']) do + + compute = Fog::Compute[:vsphere] + + reconfig_target = '50137835-88a1-436e-768e-9b2677076e67' + reconfig_spec = 4096 + + tests('The response should') do + response = compute.vm_reconfig_memory('instance_uuid' => reconfig_target, 'memory' => reconfig_spec) + test('be a kind of Hash') { response.kind_of? Hash } + test('should have a task_state key') { response.key? 'task_state' } + end + + tests('The expected options') do + raises(ArgumentError, 'raises ArgumentError when instance_uuid option is missing') { compute.vm_reconfig_memory('memory' => reconfig_spec) } + raises(ArgumentError, 'raises ArgumentError when memory option is missing') { compute.vm_reconfig_memory('instance_uuid' => reconfig_target) } + end + +end