From: David Lutterkort lutter@redhat.com
--- client/bin/deltacloudc | 1 - client/lib/dcloud/hardware_profile.rb | 85 +++++++++++++++++++++++++++++ client/lib/dcloud/instance.rb | 30 ++++++++++ client/lib/deltacloud.rb | 49 ++++++++++++++++- client/specs/fixtures/instances/inst0.yml | 17 ++++++ client/specs/fixtures/instances/inst1.yml | 2 + client/specs/fixtures/instances/inst2.yml | 2 + client/specs/hardware_profiles_spec.rb | 71 ++++++++++++++++++++++++ client/specs/instances_spec.rb | 46 +++++++++++++--- 9 files changed, 291 insertions(+), 12 deletions(-) create mode 100644 client/lib/dcloud/hardware_profile.rb create mode 100644 client/specs/fixtures/instances/inst0.yml create mode 100644 client/specs/hardware_profiles_spec.rb
diff --git a/client/bin/deltacloudc b/client/bin/deltacloudc index d2a7936..246c5b1 100755 --- a/client/bin/deltacloudc +++ b/client/bin/deltacloudc @@ -70,7 +70,6 @@ client = DeltaCloud.new(url.user, url.password, api_url, { :verbose => options[: collections = client.entry_points.keys
# Exclude collection which don't have methods in client library yet -collections.delete(:hardware_profiles) collections.delete(:instance_states)
# If list parameter passed print out available collection diff --git a/client/lib/dcloud/hardware_profile.rb b/client/lib/dcloud/hardware_profile.rb new file mode 100644 index 0000000..d6c4aa0 --- /dev/null +++ b/client/lib/dcloud/hardware_profile.rb @@ -0,0 +1,85 @@ +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +require 'dcloud/base_model' + +module DCloud + class HardwareProfile < BaseModel + + class Property + attr_reader :name, :unit, :value + + def initialize(xml, name) + @name = name + p = REXML::XPath.first(xml, "property[@name = '#{name}']") + if p + @value = p.attributes['value'] + @unit = p.attributes['unit'] + end + end + + def present? + ! @value.nil? + end + + def to_s + v = @value || "---" + u = @unit || "" + u = "" if ["label", "count"].include?(u) + "#{v} #{u}" + end + end + + class FloatProperty < Property + def initialize(xml, name) + super(xml, name) + @value = @value.to_f if @value + end + end + + xml_tag_name :hardware_profile + + attribute :memory + attribute :storage + attribute :architecture + + def initialize(client, uri, xml=nil) + super( client, uri, xml ) + end + + def load_payload(xml=nil) + super(xml) + unless xml.nil? + @memory = FloatProperty.new(xml, 'memory') + @storage = FloatProperty.new(xml, 'storage') + @architecture = Property.new(xml, 'architecture') + end + end + + def to_plain + sprintf("%-15s | %-6s | %10s | %10s ", id[0, 15], + architecture.to_s[0,6], memory.to_s[0,10], storage.to_s[0,10]) + end + + private + def property_value(xml, name) + p = REXML::XPath.first(xml, "property[@name = '#{name}']") + p ? p.attributes['value'] : "" + end + end +end diff --git a/client/lib/dcloud/instance.rb b/client/lib/dcloud/instance.rb index e3455aa..00636c6 100644 --- a/client/lib/dcloud/instance.rb +++ b/client/lib/dcloud/instance.rb @@ -19,6 +19,33 @@ require 'dcloud/base_model'
module DCloud + + class InstanceProfile + attr_reader :hardware_profile, :id + + def initialize(client, xml) + @hardware_profile = HardwareProfile.new(client, xml.attributes['href']) + @properties = {} + @id = xml.text("id") + xml.get_elements('property').each do |prop| + @properties[prop.attributes['name'].to_sym] = { + :value => prop.attributes['value'], + :unit => prop.attributes['unit'], + :kind => prop.attributes['kind'].to_sym + } + end + end + + def [](prop) + p = @properties[prop] + p ? p[:value] : nil + end + + def property(prop) + @properties[prop] + end + end + class Instance < BaseModel
xml_tag_name :instance @@ -33,6 +60,7 @@ module DCloud attribute :flavor attribute :realm attribute :action_urls + attribute :instance_profile
def initialize(client, uri, xml=nil) @action_urls = {} @@ -100,6 +128,8 @@ module DCloud realm_uri = xml.get_elements( 'realm' )[0].attributes['href'] @realm = Realm.new( @client, realm_uri ) end + instance_profile = xml.get_elements( 'hardware-profile' ).first + @instance_profile = InstanceProfile.new( @client, instance_profile ) @state = xml.text( 'state' ) @actions = [] xml.get_elements( 'actions/link' ).each do |link| diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb index 87c60fa..dbbd89b 100644 --- a/client/lib/deltacloud.rb +++ b/client/lib/deltacloud.rb @@ -19,6 +19,7 @@ require 'rest_client' require 'rexml/document' require 'logger' require 'dcloud/flavor' +require 'dcloud/hardware_profile' require 'dcloud/realm' require 'dcloud/image' require 'dcloud/instance' @@ -108,6 +109,34 @@ class DeltaCloud nil end
+ def hardware_profiles(opts={}) + hardware_profiles = [] + request(entry_points[:hardware_profiles], :get, opts) do |response| + doc = REXML::Document.new( response ) + doc.get_elements( 'hardware-profiles/hardware-profile' ).each do |hwp| + uri = hwp.attributes['href'] + hardware_profiles << DCloud::HardwareProfile.new( self, uri, hwp ) + end + end + hardware_profiles + end + + def hardware_profile(id) + request( entry_points[:hardware_profiles], :get, {:id=>id } ) do |response| + doc = REXML::Document.new( response ) + doc.get_elements( '/hardware-profile' ).each do |hwp| + uri = hwp.attributes['href'] + return DCloud::HardwareProfile.new( self, uri, hwp ) + end + end + end + + def fetch_hardware_profile(uri) + xml = fetch_resource( :hardware_profile, uri ) + return DCloud::HardwareProfile.new( self, uri, xml ) if xml + nil + end + def fetch_resource(type, uri) request( uri ) do |response| doc = REXML::Document.new( response ) @@ -262,16 +291,32 @@ class DeltaCloud nil end
+ # Create a new instance, using image +image_id+. Possible optiosn are + # + # name - a user-defined name for the instance + # realm - a specific realm for placement of the instance + # hardware_profile - either a string giving the name of the + # hardware profile or a hash. The hash must have an + # entry +id+, giving the id of the hardware profile, + # and may contain additional names of properties, + # e.g. 'storage', to override entries in the + # hardware profile def create_instance(image_id, opts={}) name = opts[:name] realm_id = opts[:realm] - flavor_id = opts[:flavor]
params = {} ( params[:realm_id] = realm_id ) if realm_id - ( params[:flavor_id] = flavor_id ) if flavor_id ( params[:name] = name ) if name
+ if opts[:hardware_profile].is_a?(String) + params[:hwp_id] = opts[:hardware_profile] + elsif opts[:hardware_profile].is_a?(Hash) + opts[:hardware_profile].each do |k,v| + params[:"hwp_#{k}"] = v + end + end + params[:image_id] = image_id request( entry_points[:instances], :post, {}, params ) do |response| doc = REXML::Document.new( response ) diff --git a/client/specs/fixtures/instances/inst0.yml b/client/specs/fixtures/instances/inst0.yml new file mode 100644 index 0000000..c1880b7 --- /dev/null +++ b/client/specs/fixtures/instances/inst0.yml @@ -0,0 +1,17 @@ +--- +:realm_id: us +:public_addresses: +- img1.inst0.public.com +:state: RUNNING +:name: "Mock Instance With Profile Change" +:private_addresses: +- img1.inst0.private.com +:image_id: img1 +:flavor_id: m1-large +:instance_profile: !ruby/object:InstanceProfile + id: m1-large + memory: "12288" +:owner_id: mockuser +:actions: +- :reboot +- :stop diff --git a/client/specs/fixtures/instances/inst1.yml b/client/specs/fixtures/instances/inst1.yml index 26a6e3b..aa3f86e 100644 --- a/client/specs/fixtures/instances/inst1.yml +++ b/client/specs/fixtures/instances/inst1.yml @@ -6,3 +6,5 @@ :private_addresses: [ img3.inst1.private.com ] :flavor_id: m1-small :realm_id: us +:instance_profile: !ruby/object:InstanceProfile + id: m1-small diff --git a/client/specs/fixtures/instances/inst2.yml b/client/specs/fixtures/instances/inst2.yml index ee1de37..69d4720 100644 --- a/client/specs/fixtures/instances/inst2.yml +++ b/client/specs/fixtures/instances/inst2.yml @@ -6,3 +6,5 @@ :private_addresses: [ img1.inst2.private.com ] :flavor_id: m1-small :realm_id: us +:instance_profile: !ruby/object:InstanceProfile + id: m1-large diff --git a/client/specs/hardware_profiles_spec.rb b/client/specs/hardware_profiles_spec.rb new file mode 100644 index 0000000..5a59aa1 --- /dev/null +++ b/client/specs/hardware_profiles_spec.rb @@ -0,0 +1,71 @@ +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +require 'specs/spec_helper' + +def prop_check(prop, value_class) + if prop.present? + prop.value.should_not be_nil + prop.value.should be_a(value_class) + end +end + +describe "hardware_profiles" do + + it_should_behave_like "all resources" + + it "should allow retrieval of all hardware profiles" do + DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| + hardware_profiles = client.hardware_profiles + hardware_profiles.should_not be_empty + hardware_profiles.each do |hwp| + hwp.uri.should_not be_nil + hwp.uri.should be_a(String) + prop_check(hwp.architecture, String) + prop_check(hwp.storage, Float) + prop_check(hwp.memory, Float) + end + end + end + + it "should allow filtering of hardware_profiles by architecture" do + DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| + hardware_profiles = client.hardware_profiles( :architecture=>'i386' ) + hardware_profiles.should_not be_empty + hardware_profiles.size.should eql( 2 ) + hardware_profiles.first.architecture.value.should eql( 'i386' ) + end + end + + it "should allow fetching a hardware_profile by id" do + DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| + hwp = client.hardware_profile( 'm1-small' ) + hwp.should_not be_nil + hwp.id.should eql( 'm1-small' ) + end + end + + it "should allow fetching a hardware_profile by URI" do + DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| + hwp = client.fetch_hardware_profile( API_URL + '/hardware_profiles/m1-small' ) + hwp.should_not be_nil + hwp.id.should eql( 'm1-small' ) + end + end + +end diff --git a/client/specs/instances_spec.rb b/client/specs/instances_spec.rb index d5a7816..8aaf79f 100644 --- a/client/specs/instances_spec.rb +++ b/client/specs/instances_spec.rb @@ -35,6 +35,8 @@ describe "instances" do instance.image.should be_a( DCloud::Image ) instance.flavor.should_not be_nil instance.flavor.should be_a( DCloud::Flavor ) + instance.instance_profile.should_not be_nil + instance.instance_profile.should be_a( DCloud::InstanceProfile ) instance.state.should_not be_nil instance.state.should be_a( String ) instance.public_addresses.should_not be_nil @@ -60,18 +62,23 @@ describe "instances" do
it "should allow retrieval of a single instance" do DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| - instance = client.instance( "inst1" ) + instance = client.instance( "inst0" ) instance.should_not be_nil instance.name.should_not be_nil - instance.name.should eql( 'MockUserInstance' ) + instance.name.should eql( 'Mock Instance With Profile Change' ) instance.uri.should_not be_nil instance.uri.should be_a( String ) instance.owner_id.should eql( "mockuser" ) - instance.public_addresses.first.should eql( "img3.inst1.public.com" ) + instance.public_addresses.first.should eql( "img1.inst0.public.com" ) instance.image.should_not be_nil - instance.image.uri.should eql( API_URL + "/images/img3" ) + instance.image.uri.should eql( API_URL + "/images/img1" ) instance.flavor.should_not be_nil - instance.flavor.uri.should eql( API_URL + "/flavors/m1-small" ) + instance.flavor.uri.should eql( API_URL + "/flavors/m1-large" ) + instance.instance_profile.should_not be_nil + instance.instance_profile.hardware_profile.should_not be_nil + instance.instance_profile.hardware_profile.uri.should eql( API_URL + "/hardware_profiles/m1-large" ) + instance.instance_profile[:memory].should eql( "12288" ) + instance.instance_profile[:storage].should be_nil instance.state.should eql( "RUNNING" ) instance.actions.should_not be_nil end @@ -86,6 +93,7 @@ describe "instances" do instance.name.should eql( 'TestInstance' ) instance.image.id.should eql( 'img1' ) instance.flavor.id.should eql( 'm1-large' ) + instance.instance_profile.id.should eql( 'm1-large' ) instance.realm.id.should eql( 'us' ) end end @@ -98,30 +106,50 @@ describe "instances" do instance.id.should match( /inst[0-9]+/ ) instance.image.id.should eql( 'img1' ) instance.flavor.id.should eql( 'm1-large' ) + instance.instance_profile.id.should eql( 'm1-large' ) instance.realm.id.should eql( 'eu' ) end end
- it "should allow creation of new instances with specific flavor" do + it "should allow creation of new instances with specific hardware profile" do DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| - instance = client.create_instance( 'img1', :flavor=>'m1-xlarge' ) + instance = client.create_instance( 'img1', + :hardware_profile=>'m1-xlarge' ) instance.should_not be_nil instance.uri.should match( %r{#{API_URL}/instances/inst[0-9]+} ) instance.id.should match( /inst[0-9]+/ ) instance.image.id.should eql( 'img1' ) instance.flavor.id.should eql( 'm1-xlarge' ) + instance.instance_profile.id.should eql( 'm1-xlarge' ) instance.realm.id.should eql( 'us' ) end end
- it "should allow creation of new instances with specific realm and flavor" do + it "should allow creation of new instances with specific hardware profile overriding memory" do DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| - instance = client.create_instance( 'img1', :realm=>'eu', :flavor=>'m1-xlarge' ) + hwp = { :id => 'm1-xlarge', :memory => 32768 } + instance = client.create_instance( 'img1', :hardware_profile=> hwp ) instance.should_not be_nil instance.uri.should match( %r{#{API_URL}/instances/inst[0-9]+} ) instance.id.should match( /inst[0-9]+/ ) instance.image.id.should eql( 'img1' ) instance.flavor.id.should eql( 'm1-xlarge' ) + instance.instance_profile.id.should eql( 'm1-xlarge' ) + instance.instance_profile[:memory].should eql( "32768" ) + instance.realm.id.should eql( 'us' ) + end + end + + it "should allow creation of new instances with specific realm and hardware profile" do + DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client| + instance = client.create_instance( 'img1', :realm=>'eu', + :hardware_profile=>'m1-xlarge' ) + instance.should_not be_nil + instance.uri.should match( %r{#{API_URL}/instances/inst[0-9]+} ) + instance.id.should match( /inst[0-9]+/ ) + instance.image.id.should eql( 'img1' ) + instance.flavor.id.should eql( 'm1-xlarge' ) + instance.instance_profile.id.should eql( 'm1-xlarge' ) instance.realm.id.should eql( 'eu' ) end end