From: Martyn Taylor <mtaylor(a)redhat.com>
rebase
---
.../admin/hardware_profiles_controller.rb | 14 ++++-
.../controllers/resources/instances_controller.rb | 32 +++++++++-
.../hardware_profiles/_matching_profiles.xml.haml | 7 ++
.../instances/_can_perform_state_change.xml.haml | 7 ++
.../views/templates/xml/_hardware_profile.xml.haml | 6 ++
.../xml/_hardware_profile_property.xml.haml | 9 +++
src/config/routes.rb | 5 +-
src/features/api.feature | 68 ++++++++++++++++++++
src/features/step_definitions/api_steps.rb | 65 +++++++++++++++++++
src/spec/factories/cloud_account.rb | 1 +
.../services/data_service_active_record_spec.rb | 16 ++---
11 files changed, 217 insertions(+), 13 deletions(-)
create mode 100644 src/app/views/admin/hardware_profiles/_matching_profiles.xml.haml
create mode 100644 src/app/views/resources/instances/_can_perform_state_change.xml.haml
create mode 100644 src/app/views/templates/xml/_hardware_profile.xml.haml
create mode 100644 src/app/views/templates/xml/_hardware_profile_property.xml.haml
create mode 100644 src/features/api.feature
create mode 100644 src/features/step_definitions/api_steps.rb
diff --git a/src/app/controllers/admin/hardware_profiles_controller.rb
b/src/app/controllers/admin/hardware_profiles_controller.rb
index dea2cac..4ce9ccb 100644
--- a/src/app/controllers/admin/hardware_profiles_controller.rb
+++ b/src/app/controllers/admin/hardware_profiles_controller.rb
@@ -1,5 +1,5 @@
class Admin::HardwareProfilesController < ApplicationController
- before_filter :require_user
+ before_filter :require_user, :except => :matching_profiles
before_filter :set_params_and_header, :only => [:index, :show]
before_filter :load_hardware_profiles, :only => [:index, :show]
before_filter :load_hardware_profile, :only => [:show]
@@ -98,6 +98,18 @@ class Admin::HardwareProfilesController < ApplicationController
redirect_to admin_hardware_profiles_path
end
+ def matching_profiles
+ begin
+ hwp = HardwareProfile.find(params[:id])
+ @matches = HardwareProfile.matching_hwps(hwp)
+ render :partial => 'matching_profiles.xml'
+ rescue ActiveRecord::RecordNotFound
+ head :not_found
+ rescue Exception => e
+ head :internal_server_error
+ end
+ end
+
private
def setup_new_hardware_profile
if params[:hardware_profile]
diff --git a/src/app/controllers/resources/instances_controller.rb
b/src/app/controllers/resources/instances_controller.rb
index 9d2b5c8..bf5e021 100644
--- a/src/app/controllers/resources/instances_controller.rb
+++ b/src/app/controllers/resources/instances_controller.rb
@@ -1,5 +1,5 @@
class Resources::InstancesController < ApplicationController
- before_filter :require_user
+ before_filter :require_user, :except => [:can_start, :can_create]
before_filter :load_instance, :only => [:show, :remove_failed, :key, :stop]
before_filter :set_view_vars, :only => [:show, :index]
@@ -130,6 +130,36 @@ class Resources::InstancesController < ApplicationController
redirect_to resources_instances_path
end
+ def can_create
+ begin
+ cloud_account = CloudAccount.find(params[:cloud_account_id])
+ @instance = Instance.find(params[:instance_id])
+ @action_request = "can_create"
+ @value = Quota.can_create_instance?(@instance, cloud_account)
+ render :partial => 'can_perform_state_change.xml'
+ rescue ActiveRecord::RecordNotFound
+ head :not_found
+ rescue Exception
+ head :internal_server_error
+ end
+ end
+
+ def can_start
+ begin
+ cloud_account = CloudAccount.find(params[:cloud_account_id])
+ @instance = Instance.find(params[:instance_id])
+ @action_request = "can_start"
+ @value = Quota.can_start_instance?(@instance, cloud_account)
+ render :partial => 'can_perform_state_change.xml'
+ rescue ActiveRecord::RecordNotFound => e
+ puts e.inspect
+ head :not_found
+ rescue Exception => e
+ puts e.inspect
+ head :internal_server_error
+ end
+ end
+
private
def load_instance
diff --git a/src/app/views/admin/hardware_profiles/_matching_profiles.xml.haml
b/src/app/views/admin/hardware_profiles/_matching_profiles.xml.haml
new file mode 100644
index 0000000..4555898
--- /dev/null
+++ b/src/app/views/admin/hardware_profiles/_matching_profiles.xml.haml
@@ -0,0 +1,7 @@
+%matching_hardware_profiles<>
+ - @matches.each do |match|
+ %matching_hardware_profile<>
+ = render :partial => 'templates/xml/hardware_profile.xml', :locals =>
{ :profile => match[:hardware_profile] }
+ %constraints<>
+ - [:memory, :cpu, :storage, :architecture].each do |property|
+ = render :partial => 'templates/xml/hardware_profile_property.xml',
:locals => { :property => match[property] }
\ No newline at end of file
diff --git a/src/app/views/resources/instances/_can_perform_state_change.xml.haml
b/src/app/views/resources/instances/_can_perform_state_change.xml.haml
new file mode 100644
index 0000000..e20a232
--- /dev/null
+++ b/src/app/views/resources/instances/_can_perform_state_change.xml.haml
@@ -0,0 +1,7 @@
+%result<
+ %action_request<
+ =@action_request
+ %instance_id<
+ =(a)instance.id
+ %value<
+ =@value
\ No newline at end of file
diff --git a/src/app/views/templates/xml/_hardware_profile.xml.haml
b/src/app/views/templates/xml/_hardware_profile.xml.haml
new file mode 100644
index 0000000..1a8324f
--- /dev/null
+++ b/src/app/views/templates/xml/_hardware_profile.xml.haml
@@ -0,0 +1,6 @@
+!!! XML
+%hardware_profile{ :href => admin_hardware_profile_path(profile.id), :id =>
profile.id }<>
+ %name<>
+ = profile.name
+ - [profile.memory, profile.cpu, profile.architecture, profile.storage].each do |prop|
+ = render :partial => 'templates/xml/hardware_profile_property.xml',
:locals => { :property => prop }
\ No newline at end of file
diff --git a/src/app/views/templates/xml/_hardware_profile_property.xml.haml
b/src/app/views/templates/xml/_hardware_profile_property.xml.haml
new file mode 100644
index 0000000..bb0fa70
--- /dev/null
+++ b/src/app/views/templates/xml/_hardware_profile_property.xml.haml
@@ -0,0 +1,9 @@
+!!! XML
+- if property.kind == "range"
+ %property{ :kind => property.kind, :name => property.name, :unit =>
property.unit, :value => property.value, :first => property.range_first, :last =>
property.range_last }<>
+- else
+ %property{ :kind => property.kind, :name => property.name, :unit =>
property.unit, :value => property.value }<>
+ - if property.kind == "enum"
+ %enum<>
+ - property.property_enum_entries.each do |e|
+ %entry{ :value => e.value }<>
\ No newline at end of file
diff --git a/src/config/routes.rb b/src/config/routes.rb
index 0aa281b..d920838 100644
--- a/src/config/routes.rb
+++ b/src/config/routes.rb
@@ -35,9 +35,12 @@ ActionController::Routing::Routes.draw do |map|
map.namespace 'resources' do |r|
r.resources :pools, :collection => { :multi_destroy => :delete }
r.resources :deployments
- r.resources :instances, :collection => {:start => :get, :stop => :get,
:select_template => :get, :remove_failed => :get}, :member => {:key => :get}
+ r.resources :instances, :collection => {:start => :get, :stop => :get,
:select_template => :get, :remove_failed => :get, :can_start => :get, :can_create
=> :get }, :member => {:key => :get}
end
+ map.can_start_instance
'/resources/instances/:instance_id/can_start/:cloud_account_id', :controller =>
'resources/instances', :action => 'can_start', :conditions => {
:method => :get }
+ map.can_create_instance
'/resources/instances/:instance_id/can_create/:cloud_account_id', :controller
=> 'resources/instances', :action => 'can_create', :conditions =>
{ :method => :get }
+
map.namespace 'image_factory' do |r|
r.resources :assemblies
r.resources :deployables, :collection => { :multi_destroy => :delete }
diff --git a/src/features/api.feature b/src/features/api.feature
new file mode 100644
index 0000000..64cc183
--- /dev/null
+++ b/src/features/api.feature
@@ -0,0 +1,68 @@
+Feature: User authentication
+ In order to automatically manage my Cloud Engine environment
+ As a client
+ I must be able to access a Cloud Engine APU+I
+
+ Background:
+ Given I am an authorised user
+ And I am logged in
+
+ Scenario: Get matching profiles via API
+ Given I am an authorised user
+ Given there are the following provider hardware profiles:
+ | name | memory | cpu |storage | architecture |
+ | m1-small | 1740 | 1 | 250 | i386 |
+ | m1-medium | 4096 | 4 | 850 | x86_64 |
+ Given there are the following aggregator hardware profiles:
+ | name | memory | cpu |storage | architecture |
+ | agg-medium | 4096 | 4 | 850 | x86_64 |
+ When a client requests matching hardware profiles for "agg-medium"
+ Then the root element should be "matching_hardware_profiles"
+ And there should exist the following xpath:
"/matching_hardware_profiles/matching_hardware_profile/hardware_profile/name"
+ And this path should have the value "m1-medium"
+ And there should exist the following xpath:
"/matching_hardware_profiles/matching_hardware_profile/hardware_profile/property"
+ And this path should contain the following elements:
+ | element | kind | name | unit | value |
+ | property | fixed | memory | MB | 4096 |
+ | property | fixed | cpu | count | 4 |
+ | property | fixed | architecture | label | x86_64 |
+ | property | fixed | storage | GB | 850 |
+ And there should exist the following xpath:
"/matching_hardware_profiles/matching_hardware_profile/constraints/property"
+ And this path should contain the following elements:
+ | element | kind | name | unit | value |
+ | property | fixed | memory | MB | 4096 |
+ | property | fixed | cpu | count | 4 |
+ | property | fixed | storage | GB | 850 |
+ | property | fixed | architecture | label | x86_64 |
+
+ Scenario: Check can start instance
+ Given there is a user "testuser"
+ And there is a provider named "testprovider"
+ And there is a provider account named "testaccount"
+ And there are the following instances:
+ | name | external_key | state | public_addresses | private_addresses |
+ | mock | ext_mock | stopped | mock.public.address | mock.private.address |
+ And user "testuser" owns instance "mock"
+ When a client requests "can_start" for instance "mock" for cloud
account "testaccount"
+ Then the root element should be "result"
+ And there should exist the following xpath: "/result/action_request"
+ And this path should have the value "can_start"
+ And there should exist the following xpath: "/result/instance_id"
+ And there should exist the following xpath: "/result/value"
+ And this path should have the value "true"
+
+ Scenario: Check can create instance
+ Given there is a user "testuser"
+ And there is a provider named "testprovider"
+ And there is a provider account named "testaccount"
+ And there are the following instances:
+ | name | external_key | state | public_addresses | private_addresses |
+ | mock | ext_mock | stopped | mock.public.address | mock.private.address |
+ And user "testuser" owns instance "mock"
+ When a client requests "can_create" for instance "mock" for cloud
account "testaccount"
+ Then the root element should be "result"
+ And there should exist the following xpath: "/result/action_request"
+ And this path should have the value "can_create"
+ And there should exist the following xpath: "/result/instance_id"
+ And there should exist the following xpath: "/result/value"
+ And this path should have the value "true"
\ No newline at end of file
diff --git a/src/features/step_definitions/api_steps.rb
b/src/features/step_definitions/api_steps.rb
new file mode 100644
index 0000000..8b1f868
--- /dev/null
+++ b/src/features/step_definitions/api_steps.rb
@@ -0,0 +1,65 @@
+Then /^I should see xml element "([^"]*)"$/ do |element|
+ Then %{I should see "#{element}"}
+end
+
+Then /^I should see xml element "([^"]*)" with the following properties:$/
do |element, properties|
+ Then %{I should see xml element "#{element}"}
+ Then %{I should see the following: "#{properties}"}
+end
+
+When /^a client requests matching hardware profiles for "([^"]*)"$/ do
|name|
+ uri = url_for :action => 'matching_profiles', :controller =>
'admin/hardware_profiles', :id => HardwareProfile.find_by_name(name)
+ send_xml_get(uri)
+end
+
+Then /^the root element should be "([^"]*)"$/ do |element|
+ @last_element = @xml_response.xpath('/'+element).first
+ @last_element.should_not be_nil
+ @last_element.name.should == element
+end
+
+Then /^there should exist the following xpath: "([^"]*)"$/ do |xpath|
+ @path = xpath
+ @last_element = @xml_response.xpath(xpath).first
+ @last_element.should_not be_nil
+end
+
+Then /^this path should have the value "([^"]*)"$/ do |value|
+ @last_element.content.should == value
+end
+
+Given /^user "([^"]*)" owns instance "([^"]*)"$/ do |user,
instance|
+ user = User.find_by_login(user)
+ instance = Instance.find_by_name(instance)
+ instance.owner = user
+ instance.save!
+end
+
+When /^a client requests "([^"]*)" for instance "([^"]*)"
for cloud account "([^"]*)"$/ do |action, instance, cloud_account|
+ instance = Instance.find_by_name(instance)
+ cloud_account = CloudAccount.find_by_label(cloud_account)
+ uri = url_for :action => action, :controller => 'resources/instances',
:instance_id => instance.id, :cloud_account_id => cloud_account.id
+ send_xml_get(uri)
+end
+
+Then /^this path should contain the following elements:$/ do |table|
+ table.hashes.each do |hash|
+ @last_element.name.should == hash['element']
+ attr = @last_element.attributes
+ attr['kind'].value.should == hash['kind']
+ attr['name'].value.should == hash['name']
+ attr['unit'].value.should == hash['unit']
+ attr['value'].value.should == hash['value']
+ #TODO: Fix this, NOKOGIRI is outputting text elements containing only \n, between
property elements.
+ begin
+ @last_element = @last_element.next.next
+ rescue Exception => e
+ end
+ end
+end
+
+def send_xml_get(uri)
+ header 'Accept', 'application/xml'
+ get uri
+ @xml_response = Nokogiri::XML(response.body)
+end
diff --git a/src/spec/factories/cloud_account.rb b/src/spec/factories/cloud_account.rb
index 0b9c0a7..6e301c8 100644
--- a/src/spec/factories/cloud_account.rb
+++ b/src/spec/factories/cloud_account.rb
@@ -6,6 +6,7 @@ Factory.define :cloud_account do |f|
f.x509_cert_priv "x509 private key"
f.x509_cert_pub "x509 public key"
f.association :provider
+ f.association :quota
end
Factory.define :mock_cloud_account, :parent => :cloud_account do |f|
diff --git a/src/spec/services/data_service_active_record_spec.rb
b/src/spec/services/data_service_active_record_spec.rb
index b443fa9..c96024f 100644
--- a/src/spec/services/data_service_active_record_spec.rb
+++ b/src/spec/services/data_service_active_record_spec.rb
@@ -11,12 +11,9 @@ describe DataServiceActiveRecord do
data = [[25, 10], [40, 20], [20, 20]]
free = 0
for i in 0..2
- cloud_account = Factory.build(:cloud_account, :provider => provider, :username
=> "username" + i.to_s)
- cloud_account.stub!(:valid_credentials?).and_return(true)
- cloud_account.save!
-
quota = Factory(:quota, :maximum_total_instances => data[i][0], :total_instances
=> data[i][1])
- cloud_account.quota_id = quota.id
+ cloud_account = Factory.build(:cloud_account, :provider => provider, :username
=> "username" + i.to_s, :quota => quota)
+ cloud_account.stub!(:valid_credentials?).and_return(true)
cloud_account.save!
free += (data[i][0] - data[i][1])
@@ -36,16 +33,15 @@ describe DataServiceActiveRecord do
provider.stub!(:connect).and_return(client)
provider.save!
- cloud_account = Factory.build(:cloud_account, :provider => provider)
- cloud_account.stub!(:valid_credentials?).and_return(true)
- cloud_account.save!
-
quota = Factory(:quota,
:maximum_running_instances => 40,
:maximum_total_instances => 50,
:running_instances => 20,
:total_instances => 20)
- cloud_account.quota_id = quota.id
+
+ cloud_account = Factory.build(:cloud_account, :provider => provider, :quota =>
quota)
+ cloud_account.stub!(:valid_credentials?).and_return(true)
+ cloud_account.save!
data_point = DataServiceActiveRecord.quota_usage(cloud_account,
Quota::RESOURCE_RUNNING_INSTANCES)
data_point.should == DataServiceActiveRecord::QuotaUsagePoint.new(20, 40)
--
1.7.3.4