Merge pull request #3862 from tlawrence/master

[VCLOUD DIRECTOR] Enhanced catalogue & vApp Template functionality
This commit is contained in:
Wesley Beary 2016-03-17 09:23:29 -05:00
commit 81ddf2f661
21 changed files with 559 additions and 39 deletions

View File

@ -82,6 +82,12 @@ namespace :test do
task :vcloud_director do
sh("export FOG_MOCK=#{mock} && bundle exec shindont tests/vcloud_director")
end
task :vcloud_director_specs do
puts "Running vCloud Minitest Suite"
Rake::TestTask.new do |t|
Dir.glob('./spec/vcloud_director/**/*_spec.rb').each { |file| require file}
end
end
end
desc 'Run mocked tests for a specific provider'

View File

@ -78,7 +78,7 @@ Gem::Specification.new do |s|
s.add_development_dependency("docker-api", ">= 1.13.6")
s.add_development_dependency("fission")
s.add_development_dependency("mime-types")
s.add_development_dependency("mime-types", "<=2.99.1")
s.add_development_dependency("minitest")
s.add_development_dependency("minitest-stub-const")
s.add_development_dependency("opennebula")

View File

@ -12,8 +12,11 @@ organizations
-> disks -> disk
-> tags -> tag
-> power_on
vapp_templates -> vms -> vm
networks -> network
catalogs -> catalog -> catalog_items -> catalog_item -> instantiate_vapp
-> vapp_template -> vms -> vm
medias -> media
```

View File

@ -55,6 +55,10 @@ module Fog
model :organization
collection :organizations
model :catalog_item
collection :vapp_templates
model :vapp_template
collection :template_vms
model :template_vm
collection :catalog_items
model :custom_field
collection :custom_fields
@ -159,6 +163,8 @@ module Fog
request :get_supported_versions
request :get_task
request :get_task_list
request :get_template_vm
request :get_template_vms
request :get_thumbnail
request :get_users_from_query
request :get_vapp

View File

@ -50,12 +50,21 @@ module Fog
}
}
end
def build_source_template(xml)
xml.Source(:href => @configuration[:Source])
end
def build_source_items(xml)
vms = @configuration[:source_vms]
vms.each do |vm|
xml.SourcedItem {
xml.Source(:name =>vm[:name], :href => vm[:href])
xml.VmGeneralParams {
xml.Name vm[:name]
xml.Description vm[:Description] if vm[:Description]
xml.NeedsCustomization if vm[:NeedsCustomization]
} if vm[:name]
xml.InstantiationParams {
if vm[:networks]
xml.NetworkConnectionSection(:href => "#{vm[:href]}/networkConnectionSection/", :type => "application/vnd.vmware.vcloud.networkConnectionSection+xml", 'xmlns:ovf' => "http://schemas.dmtf.org/ovf/envelope/1", "ovf:required" => "false") {

View File

@ -0,0 +1,30 @@
require 'fog/vcloud_director/generators/compute/compose_common'
module Fog
module Generators
module Compute
module VcloudDirector
# @see http://pubs.vmware.com/vcd-51/topic/com.vmware.vcloud.api.reference.doc_51/doc/types/VAppType.html
class InstantiateVappTemplateParams
attr_reader :options
include ComposeCommon
def generate_xml
Nokogiri::XML::Builder.new do |xml|
xml.InstantiateVAppTemplateParams((vapp_attrs)) {
build_vapp_instantiation_params(xml)
build_source_template(xml)
build_source_items(xml)
}
end.to_xml
end
end
end
end
end
end

View File

@ -12,6 +12,13 @@ module Fog
attribute :description, :aliases => :Description
attribute :vapp_template_id
def vapp_template
requires :id
service.vapp_templates.get(self.vapp_template_id)
end
def instantiate(vapp_name, options={})
response = service.instantiate_vapp_template(vapp_name, vapp_template_id, options)
service.process_task(response.body[:Tasks][:Task])

View File

@ -0,0 +1,52 @@
require 'fog/core/model'
require 'fog/vcloud_director/models/compute/vm_customization'
module Fog
module Compute
class VcloudDirector
class TemplateVm < Model
identity :id
attribute :vapp_template_id
attribute :vapp_template_name
attribute :name
attribute :type
attribute :href
def reload
#Parent vapp_name & id are nil on a template_vm. Adding them from the collection parent
self.vapp_template_id = collection.vapp_template.id
self.vapp_template_name = collection.vapp_template.name
end
def tags
requires :id
service.tags(:vm => self)
end
def customization
requires :id
data = service.get_vm_customization(id).body
service.vm_customizations.new(data)
end
def network
requires :id
data = service.get_vm_network(id).body
service.vm_networks.new(data)
end
def disks
requires :id
service.disks(:vm => self)
end
def vapp_template
service.vapp_templates.get(vapp_template_id)
end
end
end
end
end

View File

@ -0,0 +1,41 @@
require 'fog/core/collection'
require 'fog/vcloud_director/models/compute/vm'
module Fog
module Compute
class VcloudDirector
class TemplateVms < Collection
include Fog::VcloudDirector::Query
model Fog::Compute::VcloudDirector::TemplateVm
attribute :vapp_template
def get_single_vm(vm_id)
item = service.get_template_vm(vm_id).body
return nil unless item
new(item[:vm])
end
def query_type
"vm"
end
private
def get_by_id(item_id)
item = item_list.find{ |vm| vm[:id] == item_id }
item
end
def item_list
data = service.get_template_vms(vapp_template.id).body # vapp.id
items = data[:vms]
items
end
end
end
end
end

View File

@ -0,0 +1,26 @@
require 'fog/core/model'
module Fog
module Compute
class VcloudDirector
class VappTemplate < Model
identity :id
attribute :name
attribute :type
attribute :href
attribute :description, :aliases => :Description
attribute :status
attribute :lease_settings, :aliases => :LeaseSettingsSection
attribute :network_section, :aliases => :"ovf:NetworkSection", :squash => :"ovf:Network"
attribute :network_config, :aliases => :NetworkConfigSection, :squash => :NetworkConfig
attribute :owner, :aliases => :Owner, :squash => :User
def vms
requires :id
service.template_vms(:vapp_template => self)
end
end
end
end
end

View File

@ -0,0 +1,40 @@
require 'fog/core/collection'
require 'fog/vcloud_director/models/compute/vapp'
module Fog
module Compute
class VcloudDirector
class VappTemplates < Collection
include Fog::VcloudDirector::Query
model Fog::Compute::VcloudDirector::VappTemplate
attribute :vdc
def query_type
"vAppTemplate"
end
private
def get_by_id(item_id)
item = service.get_vapp_template(item_id).body
%w(:Link).each {|key_to_delete| item.delete(key_to_delete) }
service.add_id_from_href!(item)
item[:Description] ||= ""
item
end
def item_list
data = service.get_vdc(vdc.id).body
return [] if data[:ResourceEntities].empty?
resource_entities = data[:ResourceEntities][:ResourceEntity]
items = resource_entities.select { |link| link[:type] == "application/vnd.vmware.vcloud.vAppTemplate+xml" }
items.each{|item| service.add_id_from_href!(item) }
items
end
end
end
end
end

View File

@ -30,6 +30,11 @@ module Fog
service.vapps(:vdc => self)
end
def vapp_templates
requires :id
service.vapp_templates(:vdc => self)
end
def networks
requires :available_networks
service.networks(:vdc => self)

View File

@ -0,0 +1,74 @@
module Fog
module Compute
class VcloudDirector
class Real
require 'fog/vcloud_director/parsers/compute/vm'
# Retrieve a vApp or VM.
#
# @note This should probably be deprecated.
#
# @param [String] id Object identifier of the vApp or VM.
# @return [Excon::Response]
# * body<~Hash>:
#
# @see #get_vapp
def get_vm(id)
request(
:expects => 200,
:idempotent => true,
:method => 'GET',
:parser => Fog::Parsers::Compute::VcloudDirector::Vm.new,
:path => "vAppTemplate/#{id}"
)
end
end
class Mock
def get_vm(id)
vapp = get_vapp(id).body
vm = parse_vapp_to_vm(vapp)
body = {:type => vapp[:type], :vm => vm}
Excon::Response.new(
:status => 200,
:headers => {'Content-Type' => "#{body[:type]};version=#{api_version}"},
:body => body
)
end
# Mock equivalent of Fog::Parsers::Compute::VcloudDirector::Vm
def parse_vapp_to_vm(vapp)
parser = Fog::Parsers::Compute::VcloudDirector::Vm.new
vm = vapp.select {|k| [:href, :name, :status, :type].include? k}
network = vapp[:NetworkConnectionSection]
vm.merge({
:id => vapp[:href].split('/').last,
:status => parser.human_status(vapp[:status]),
:ip_address => network[:NetworkConnection][:IpAddress],
:description => vapp[:Description],
:cpu => get_hardware(vapp, 3),
:memory => get_hardware(vapp, 4),
:disks => get_disks(vapp),
:links => [vapp[:GuestCustomizationSection][:Link]],
})
end
def get_hardware(vapp, resource_type)
hardware = vapp[:"ovf:VirtualHardwareSection"][:"ovf:Item"]
item = hardware.find {|h| h[:"rasd:ResourceType"].to_i == resource_type}
if item and item.key? :"rasd:VirtualQuantity"
item[:"rasd:VirtualQuantity"].to_i
else
nil
end
end
def get_disks(vapp)
hardware = vapp[:"ovf:VirtualHardwareSection"][:"ovf:Item"]
disks = hardware.select {|h| h[:"rasd:ResourceType"].to_i == 17}
disks.map {|d| {d[:"rasd:ElementName"] => d[:"rasd:HostResource"][:ns12_capacity].to_i}}
end
end
end
end
end

View File

@ -0,0 +1,41 @@
module Fog
module Compute
class VcloudDirector
class Real
require 'fog/vcloud_director/parsers/compute/vms'
# Retrieve a vApp or VM.
#
# @note This should probably be deprecated.
#
# @param [String] id Object identifier of the vApp or VM.
# @return [Excon::Response]
# * body<~Hash>:
#
# @see #get_vapp
def get_template_vms(id)
request(
:expects => 200,
:idempotent => true,
:method => 'GET',
:parser => Fog::Parsers::Compute::VcloudDirector::Vms.new,
:path => "vAppTemplate/#{id}"
)
end
end
class Mock
def get_vms(id)
vapptemplate = get_vapp(id).body
parser = Fog::Parsers::Compute::VcloudDirector::Vms.new
vms = vapp[:Children][:Vm].map {|child| parse_vapp_to_vm(child) }
body = {:type => vapp[:type], :vms => vms}
Excon::Response.new(
:status => 200,
:headers => {'Content-Type' => "#{body[:type]};version=#{api_version}"},
:body => body
)
end
end
end
end
end

View File

@ -135,7 +135,7 @@ module Fog
{:type => "application/vnd.vmware.vcloud.vApp+xml",
:name => vapp[:name],
:href => make_href("vApp/#{vapp_id}")}
end
end
body[:AvailableNetworks][:Network] =
data[:networks].map do |id, network|

View File

@ -1,7 +1,10 @@
require 'builder'
module Fog
module Compute
class VcloudDirector
class Real
require 'fog/vcloud_director/generators/compute/instantiate_vapp_template_params'
# Create a vApp from a vApp template.
#
# The response includes a Task element. You can monitor the task to to
@ -41,43 +44,33 @@ module Fog
options
end
def generate_instantiate_vapp_template_request(options ={})
xml = Builder::XmlMarkup.new
xml.InstantiateVAppTemplateParams(xmlns.merge!(:name => options[:vapp_name], :"xml:lang" => "en")) {
xml.Description(options[:description])
xml.InstantiationParams {
# This options are fully ignored
if options[:network_uri]
xml.NetworkConfigSection {
xml.tag!("ovf:Info"){ "Configuration parameters for logical networks" }
xml.NetworkConfig("networkName" => options[:network_name]) {
xml.Configuration {
xml.ParentNetwork(:href => options[:network_uri])
xml.FenceMode("bridged")
}
}
}
end
}
# The template
xml.Source(:href => options[:template_uri])
# Use of sourceItems for configuring VM's during instantiation.
# NOTE: Name and storage profile configuration supported so far.
# http://pubs.vmware.com/vca/index.jsp?topic=%2Fcom.vmware.vcloud.api.doc_56%2FGUID-BF9B790D-512E-4EA1-99E8-6826D4B8E6DC.html
(options[:vms_config] || []).each do |vm_config|
next unless vm_config[:href]
xml.SourcedItem {
xml.Source(:href => vm_config[:href])
xml.VmGeneralParams{
xml.Name(vm_config[:name]) if vm_config[:name]
}
if storage_href = vm_config[:storage_profile_href]
xml.StorageProfile(:href => storage_href)
end
}
end
xml.AllEULAsAccepted("true")
}
def generate_instantiate_vapp_template_request(options ={})
#overriding some params so they work with new standardised generator
options[:InstantiationParams] =
{
:NetworkConfig =>
[{
:networkName => options[:network_name],
:networkHref => options[:network_uri],
:fenceMode => 'bridged'
}]
} unless options[:InstantiationParams]
options[:name] = options.delete(:vapp_name) if options[:vapp_name]
options[:Description] = options.delete(:description) unless options[:Description]
if options[:vms_config] then
options[:source_vms] = options.delete(:vms_config)
options[:source_vms].each_with_index {|vm, i|options[:source_vms][i][:StorageProfileHref] = options[:source_vms][i].delete(:storage_profile_href) }
end
options[:Source] = options.delete(:template_uri) if options[:template_uri]
Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(options).generate_xml
end
def xmlns

View File

@ -0,0 +1,68 @@
require './spec/vcloud_director/spec_helper.rb'
require 'minitest/autorun'
require 'nokogiri'
require './lib/fog/vcloud_director/generators/compute/instantiate_vapp_template_params.rb'
describe Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams do
let(:xml) do
params = {
:name => 'VAPP_NAME',
:Description => 'MY VAPP',
:InstantiationParams => {
:NetworkConfig => [
{
:networkName => 'NETWORK',
:networkHref => 'http://vcloud/api/network/123456789',
:fenceMode => 'bridged'
}
]
},
:Source => 'http://vcloud/vapp_template/1234',
:source_vms => [
{
:name => 'VM1',
:href => 'http://vcloud/api/vm/12345',
:StorageProfileHref => 'http://vcloud/storage/123456789'
},
{
:name => 'VM2',
:href => 'http://vcloud/api/vm/12345',
:StorageProfileHref => 'http://vcloud/storage/123456789'
}
]
}
output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(params).generate_xml
Nokogiri::XML(output)
end
it "Generates InstantiateVAppTemplateParams" do
xml.xpath('//InstantiateVAppTemplateParams').must_be_instance_of Nokogiri::XML::NodeSet
end
it "Has a valid Network" do
node = xml.xpath('//xmlns:NetworkConfigSection')
xml.xpath("//xmlns:NetworkConfig")[0].attr('networkName').must_equal "NETWORK"
xml.xpath('//xmlns:ParentNetwork')[0].attr('href').must_equal 'http://vcloud/api/network/123456789'
end
it "Has valid source VAPP info" do
node = xml.xpath('//xmlns:Source[@href="http://vcloud/vapp_template/1234"]')
node.length.must_equal 1
end
it "Has valid source VM info" do
xml.xpath('//xmlns:StorageProfile[@href="http://vcloud/storage/123456789"]').length.must_equal 2
end
it "Allows New VM Parameters" do
nodes = xml.xpath('//xmlns:VmGeneralParams')
nodes.length.must_equal 2
end
end

View File

@ -0,0 +1,78 @@
require './spec/vcloud_director/spec_helper.rb'
require 'minitest/autorun'
require './lib/fog/vcloud_director/requests/compute/instantiate_vapp_template.rb'
describe Fog::Compute::VcloudDirector::Real do
before do
Fog.unmock!
end
let(:xml) do
service = Fog::Compute::VcloudDirector.new(
{
:vcloud_director_host => 'vcloud-director-host',
:vcloud_director_password => 'vcloud_director_password',
:vcloud_director_username => 'vcd_user@vcd_org_name',
}
)
params = {
:description => 'MY VAPP',
:vdc_uri => 'http://vcloud/api/vdc/123456789',
:network_uri => 'http://vcloud/api/network/123456789',
:template_uri => 'http://vcloud/api/vapptemplate/123456789',
:vapp_name => 'http://vcloud/api/vapp/123456789',
:network_name => 'NETWORK',
:vms_config => [
{
:name => 'VM1',
:href => 'http://vcloud/api/vm/12345',
:storage_profile_href => 'http://vcloud/storage/123456789'
},
{
:name => 'VM2',
:href => 'http://vcloud/api/vm/12345',
:storage_profile_href => 'http://vcloud/storage/123456789'
}
]
}
Nokogiri::XML(service.send(:generate_instantiate_vapp_template_request,params))
end
it "Generates InstantiateVAppTemplateParams" do
xml.xpath('//InstantiateVAppTemplateParams').must_be_instance_of Nokogiri::XML::NodeSet
end
it "Has a valid Network" do
node = xml.xpath('//xmlns:NetworkConfigSection')
xml.xpath("//xmlns:NetworkConfig")[0].attr('networkName').must_equal "NETWORK"
xml.xpath('//xmlns:ParentNetwork')[0].attr('href').must_equal 'http://vcloud/api/network/123456789'
end
it "Has valid source VAPP info" do
node = xml.xpath('//xmlns:Source[@href="http://vcloud/api/vapptemplate/123456789"]')
node.length.must_equal 1
end
it "Has valid source VM info" do
xml.xpath('//xmlns:Source[@name="VM1"]').length.must_equal 1
xml.xpath('//xmlns:StorageProfile[@href="http://vcloud/storage/123456789"]').length.must_equal 2
end
after do
Fog.mock!
end
end

View File

@ -0,0 +1,11 @@
if ENV["FOG_MOCK"] == "true"
Fog.mock!
end
if Fog.mock?
Fog.credentials = {
:vcloud_director_host => 'vcloud-director-host',
:vcloud_director_password => 'vcloud_director_password',
:vcloud_director_username => 'vcd_user@vcd_org_name',
}.merge(Fog.credentials)
end

View File

@ -14,6 +14,7 @@ Shindo.tests("Compute::VcloudDirector | catalog_items", ['vclouddirector', 'all'
tests("#name").returns(String){ catalog_item.name.class }
tests("#href").returns(String){ catalog_item.href.class }
tests("#type").returns("application/vnd.vmware.vcloud.catalogItem+xml"){ catalog_item.type }
tests("#vapp_template").returns(VappTemplate){ catalog_item.vapp_template.class }
end
tests("Compute::VcloudDirector | catalog_item", ['lazy load attrs']) do

View File

@ -0,0 +1,29 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
Shindo.tests("Compute::VcloudDirector | vapp_templates", ['vclouddirector', 'all']) do
# unless there is atleast one vapp we cannot run these tests
pending if vdc.vapp_templates.empty?
vapp_templates = vdc.vapp_templates
vapp = vapp_templates.first
tests("Compute::VcloudDirector | vapp_template") do
tests("#id").returns(String){ vapp.id.class }
tests("#name").returns(String){ vapp.name.class }
tests("#href").returns(String){ vapp.href.class }
tests("#type").returns("application/vnd.vmware.vcloud.vAppTemplate+xml"){ vapp.type }
end
tests("Compute::VcloudDirector | vapp_template vms") do
tests("#vms").returns(Fog::Compute::VcloudDirector::TemplateVms) { vapp.vms.class }
pending if Fog.mock?
vm = vapp.vms[0]
tests("#name").returns(String){ vm.name.class }
tests("#type").returns("application/vnd.vmware.vcloud.vm+xml"){ vm.type }
end
end