From: marios <[email protected]>
Signed-off-by: marios <[email protected]> --- tests/Rakefile | 28 ++--- tests/deltacloud/base_api_test.rb | 111 ++++++++++++++++ tests/deltacloud/buckets_test.rb | 187 ++++++++++++++++++++++++++++ tests/deltacloud/config.yaml | 18 +++ tests/deltacloud/test_setup.rb | 119 ++++++++++++++++++ 5 files changed, 443 insertions(+), 20 deletions(-) create mode 100644 tests/deltacloud/base_api_test.rb create mode 100644 tests/deltacloud/buckets_test.rb create mode 100644 tests/deltacloud/config.yaml create mode 100644 tests/deltacloud/hardware_profiles_test.rb create mode 100644 tests/deltacloud/images_test.rb create mode 100644 tests/deltacloud/instance_states_test.rb create mode 100644 tests/deltacloud/instances_test.rb create mode 100644 tests/deltacloud/keys_test.rb create mode 100644 tests/deltacloud/realms_test.rb create mode 100644 tests/deltacloud/storage_snapshots_test.rb create mode 100644 tests/deltacloud/storage_volumes_test.rb create mode 100644 tests/deltacloud/test_setup.rb diff --git a/tests/Rakefile b/tests/Rakefile index dfdb209..0475ae5 100644 --- a/tests/Rakefile +++ b/tests/Rakefile @@ -19,28 +19,16 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require 'rake' -require 'cucumber' -require 'cucumber/rake/task' +require 'rake/testtask' +require 'deltacloud/test_setup.rb' -DRIVER=ENV['API_DRIVER'] || 'mock' +namespace :test do -Cucumber::Rake::Task.new(:features) do |t| - t.cucumber_opts = "../tests/#{DRIVER} --format html --out ../tests/tmp/cucumber_#{DRIVER}.html" - t.rcov = false -end - -Cucumber::Rake::Task.new(:cucumber) do |t| - t.cucumber_opts = "../tests/#{DRIVER} --format pretty" - t.rcov = false -end + file_list = Rake::FileList.new(deltacloud_test_file_names) + Rake::TestTask.new(:deltacloud) do |t| + t.test_files = file_list << "deltacloud/base_api_test.rb" + t.options = "-v" + end -Cucumber::Rake::Task.new(:rcov) do |t| - t.cucumber_opts = "../tests/#{DRIVER} --format pretty" - t.rcov = true - t.rcov_opts << %[-o "../tests/tmp/coverage_#{DRIVER}"] end -Cucumber::Rake::Task.new(:junit) do |t| - t.cucumber_opts = "../tests/#{DRIVER} --format junit --out #{File.join(File.dirname(__FILE__), "tmp", "junit_#{DRIVER}")}" -end diff --git a/tests/deltacloud/base_api_test.rb b/tests/deltacloud/base_api_test.rb new file mode 100644 index 0000000..2035b8f --- /dev/null +++ b/tests/deltacloud/base_api_test.rb @@ -0,0 +1,111 @@ +$:.unshift File.join(File.dirname(__FILE__), '..') +require "deltacloud/test_setup.rb" + +describe "Deltacloud API Entry Point" do + + it 'return status 200 OK when accessing API entrypoint' do + res = get + res.code.must_equal 200 + end + + it 'advertise the current driver in API entrypoint' do + res = get({:accept => :xml}) + driver = xml_response(res).root[:driver] + driver.wont_be_nil + DRIVERS.include?(driver).must_equal true + end + + it 'advertise the current API version in API entrypoint' do + res = get({:accept => :xml}) + version = xml_response(res).root[:version] + version.wont_be_nil + version.must_equal API_VERSION + end + + it 'advertise the current API version in HTTP headers' do + res = get + res.headers[:server].must_equal "Apache-Deltacloud/#{API_VERSION}" + end + + it 'must include the ETag in HTTP headers' do + res = get + res.headers[:etag].wont_be_nil + end + + it 'advertise collections in API entrypoint' do + res = get({:accept => :xml}) + xml_response(res).xpath('//api/link').wont_be_empty + end + + it 'include the :href and :rel attribute for each collection in API entrypoint' do + xml_response(get({:accept => :xml})).xpath("//api/link").each do |collection| + collection[:href].wont_be_nil + collection[:rel].wont_be_nil + end + end + + it 'uses the absolute URI in the :href attribute for each collection in API entrypoint' do + xml_response(get({:accept => :xml})).xpath("//api/link").each do |collection| + collection[:href].must_match /^http/ + end + end + + it 'advertise features for some collections in API entrypoint' do + xml_doc = xml_response(get({:accept => :xml})) + xml_doc.xpath("//api/link/feature").wont_be_empty + end + + it 'advertise the name of the feature for some collections in API entrypoint' do + xml_response(get({:accept => :xml})).xpath("//api/link/feature").each do |feature| + feature[:name].wont_be_nil + end + end + + it 'must change the media type from XML to JSON using Accept headers' do + res = get({:accept => :json}) + res.headers[:content_type].must_equal 'application/json' + end + + it 'must change the media type to JSON using the "?format" parameter in URL' do + res = get({}, "?format=json") + res.headers[:content_type].must_equal 'application/json' + end + + it 'must change the driver when using X-Deltacloud-Driver HTTP header' do + res = xml_response(get({'X-Deltacloud-Driver'=> 'ec2', :accept=> :xml})) + res.root[:driver].must_equal 'ec2' + res = xml_response(get({'X-Deltacloud-Driver'=> 'mock', :accept=> :xml})) + res.root[:driver].must_equal 'mock' + end + + it 'must change the features when driver is swapped using HTTP headers' do + res = xml_response(get({'X-Deltacloud-Driver'=> 'ec2', :accept=> :xml})) + # The 'user_name' feature is not supported currently for the EC2 driver + (res/'api/link/feature').map { |f| f[:name] }.wont_include 'user_name' + res = xml_response(get({'X-Deltacloud-Driver'=> 'mock', :accept=> :xml})) + # But it's supported in Mock driver + (res/'api/link/feature').map { |f| f[:name] }.must_include 'user_name' + end + + it 'must re-validate the driver credentials when using "?force_auth" parameter in URL' do + proc {get({ :params => {:force_auth => '1'} })}.must_raise RestClient::Request::Unauthorized + res = get({ "X-Deltacloud-Driver"=>"mock", :params=>{:force_auth => '1'}, :Authorization=>"Basic #{Base64.encode64('mockuser:mockpassword')}" }) + res.code.must_equal 200 + end + + it 'must change the API PROVIDER using the /api;provider matrix parameter in URI' do + res = xml_response(get({}, ';provider=test1')) + res.root[:provider].wont_be_nil + res.root[:provider].must_equal 'test1' + res = xml_response(get({}, ';provider=test2')) + res.root[:provider].must_equal 'test2' + end + + it 'must change the API DRIVER using the /api;driver matrix parameter in URI' do + res = xml_response(get({}, ';driver=ec2')) + res.root[:driver].must_equal 'ec2' + res = xml_response(get({}, ';driver=mock')) + res.root[:driver].must_equal 'mock' + end + +end diff --git a/tests/deltacloud/buckets_test.rb b/tests/deltacloud/buckets_test.rb new file mode 100644 index 0000000..3a525ed --- /dev/null +++ b/tests/deltacloud/buckets_test.rb @@ -0,0 +1,187 @@ +$:.unshift File.join(File.dirname(__FILE__), '..') +require "deltacloud/test_setup.rb" + + +require 'ruby-debug' +BUCKETS = "/buckets" + +#make sure we have at least one bucket and blob to test +bucket, blob = create_a_bucket_and_blob + +features_hash = discover_features + +describe 'Deltacloud API buckets collection' do + +MiniTest::Unit.after_tests{ + #finally delete the bucket/blob we created for the tests: + delete_bucket_and_blob(bucket, blob) +} + + it 'must advertise the buckets collection in API entrypoint' do + res = xml_response(get) + (res/'api/link[@rel=buckets]').wont_be_empty + end + + it 'must require authentication to access the "bucket" collection' do + proc { get({},BUCKETS) }.must_raise RestClient::Request::Unauthorized + end + + it 'should respond with HTTP_OK when accessing the :buckets collection with authentication' do + res = get({}, BUCKETS, true) + res.code.must_equal 200 + end + + it 'should be possible to create bucket with POST /api/buckets and delete it with DELETE /api/buckets/:id' do + bucket_name = random_name + #create bucket + res = post({:name=>bucket_name}, BUCKETS, {}, true) + #check response + res.code.must_equal 201 + xml_res = xml_response(res) + xml_res.xpath("//bucket/name").text.must_equal bucket_name + xml_res.xpath("//bucket").size.must_equal 1 + xml_res.xpath("//bucket")[0][:id].must_equal bucket_name + #GET bucket + res = get({}, BUCKETS+"/"+bucket_name, true) + res.code.must_equal 200 + #DELETE bucket + res = delete({}, BUCKETS+"/"+bucket_name) + res.code.must_equal 204 + end + + it 'should be possible to specify location for POST /api/buckets if bucket_location feature' do + skip("No bucket_location feature specified for driver #{API_DRIVER} running at #{API_URL}... skipping test") unless features_hash["buckets"].include?("bucket_location") + bucket_name = random_name +# res = post({:name=>bucket_name, :bucket_location=> + end + + + it 'should support the JSON media type' do + res = get({:accept=>:json}, BUCKETS, true) + res.code.must_equal 200 + res.headers[:content_type].must_equal 'application/json' + assert_silent {JSON.parse(res)} + end + + it 'must include the ETag in HTTP headers' do + res = get({}, BUCKETS, true) + res.headers[:etag].wont_be_nil + end + + it 'must have the "buckets" element on top level' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + xml_res.root.name.must_equal 'buckets' + end + + it 'must have some "bucket" elements inside "buckets"' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').wont_be_empty + end + + it 'must provide the :id attribute for each bucket in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + r[:id].wont_be_nil + end + end + + it 'must include the :href attribute for each "bucket" element in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + r[:href].wont_be_nil + end + end + + it 'must use the absolute URL in each :href attribute' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + r[:href].must_match /^http/ + end + end + + it 'must have the URL ending with the :id of the bucket' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + r[:href].must_match /#{r[:id]}$/ + end + end + + it 'must have the "name" element defined for each bucket in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + (r/'name').wont_be_nil + (r/'name').wont_be_empty + end + end + + it 'must have the "size" element defined for each bucket in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + (r/'size').wont_be_nil + (r/'size').wont_be_empty + end + end + + it 'must return 200 OK when following the URL in bucket element' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + bucket_res = RestClient.get(r[:href], {:Authorization=>BASIC_AUTH}) + bucket_res.code.must_equal 200 + end + end + + it 'must have the "name" element for the bucket and it should match with the one in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true)) + (bucket_xml/'name').wont_be_empty + (bucket_xml/'name').first.text.must_equal((r/'name').first.text) + end + end + + it 'all "blob" elements for the bucket should match the ones in collection' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true)) + (bucket_xml/'bucket/blob').each do |b| + b[:id].wont_be_nil + b[:href].wont_be_nil + b[:href].must_match /^http/ + b[:href].must_match /#{r[:id]}\/#{b[:id]}$/ + end + end + end + + it 'must allow to get all blobs details and the details should be set correctly' do + xml_res = xml_response(get({:accept=>:xml}, BUCKETS, true)) + (xml_res/'buckets/bucket').each do |r| + bucket_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}", true)) + (bucket_xml/'bucket/blob').each do |b| + blob_xml = xml_response(get({:accept=>:xml}, BUCKETS+"/#{r[:id]}/#{b[:id]}", true)) + blob_xml.root.name.must_equal 'blob' + blob_xml.root[:id].must_equal b[:id] + (blob_xml/'bucket').wont_be_empty + (blob_xml/'bucket').size.must_equal 1 + (blob_xml/'bucket').first.text.wont_be_nil + (blob_xml/'bucket').first.text.must_equal r[:id] + (blob_xml/'content_length').wont_be_empty + (blob_xml/'content_length').size.must_equal 1 + (blob_xml/'content_length').first.text.must_match /^(\d+)$/ + (blob_xml/'content_type').wont_be_empty + (blob_xml/'content_type').size.must_equal 1 + (blob_xml/'content_type').first.text.wont_be_nil + (blob_xml/'last_modified').wont_be_empty + (blob_xml/'last_modified').size.must_equal 1 + (blob_xml/'last_modified').first.text.wont_be_empty + (blob_xml/'content').wont_be_empty + (blob_xml/'content').size.must_equal 1 + (blob_xml/'content').first[:rel].wont_be_nil + (blob_xml/'content').first[:rel].must_equal 'blob_content' + (blob_xml/'content').first[:href].wont_be_nil + (blob_xml/'content').first[:href].must_match /^http/ + (blob_xml/'content').first[:href].must_match /\/content$/ + end + end + end + +end diff --git a/tests/deltacloud/config.yaml b/tests/deltacloud/config.yaml new file mode 100644 index 0000000..e94a29e --- /dev/null +++ b/tests/deltacloud/config.yaml @@ -0,0 +1,18 @@ +--- +api_url: "http://localhost:3001/api" +#MOCK DRIVER CONFIG: +mock: + user: "mockuser" + password: "mockpassword" +#EC2 DRIVER CONFIG: +ec2: + user: #your EC2 KEY HERE + password: #your EC2 SECRET_KEY HERE + bucket_locations: + #location constraint : provider + "EU" : "eu-west-1" + "sa-east-1" : "sa-east-1" + "us-west-1" : "us-west-1" + "us-west-2" : "us-west-2" + "ap-southeast-1" : "ap-southeast-1" + "ap-northeast-1" : "ap-northeast-1" diff --git a/tests/deltacloud/hardware_profiles_test.rb b/tests/deltacloud/hardware_profiles_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/images_test.rb b/tests/deltacloud/images_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/instance_states_test.rb b/tests/deltacloud/instance_states_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/instances_test.rb b/tests/deltacloud/instances_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/keys_test.rb b/tests/deltacloud/keys_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/realms_test.rb b/tests/deltacloud/realms_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/storage_snapshots_test.rb b/tests/deltacloud/storage_snapshots_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/storage_volumes_test.rb b/tests/deltacloud/storage_volumes_test.rb new file mode 100644 index 0000000..e69de29 diff --git a/tests/deltacloud/test_setup.rb b/tests/deltacloud/test_setup.rb new file mode 100644 index 0000000..0c7fe6b --- /dev/null +++ b/tests/deltacloud/test_setup.rb @@ -0,0 +1,119 @@ +require 'rubygems' +require 'minitest/autorun' +require 'rest_client' +require 'nokogiri' +require 'json' +require 'base64' +require 'yaml' + +#SETUP +$:.unshift File.join(File.dirname(__FILE__), '..') +CONFIG = YAML.load(File.open("deltacloud/config.yaml")) +API_URL = CONFIG["api_url"] +API_DRIVER = RestClient.get(API_URL) do |response, request, result| + Nokogiri::XML(response).root[:driver] +end +raise Exception.new("Can't find config for driver: #{API_DRIVER} currently running at #{API_URL} in config.yaml file") unless CONFIG[API_DRIVER] and CONFIG[API_DRIVER]["user"] and CONFIG[API_DRIVER]["password"] +USER = CONFIG[API_DRIVER]["user"] +PASSWORD= CONFIG[API_DRIVER]["password"] +BASIC_AUTH="Basic #{Base64.encode64(USER+":"+PASSWORD)}" + +DRIVERS = RestClient.get(API_URL+"/drivers") do |response, request, result| + Nokogiri::XML(response).xpath("//driver/name").inject([]){|res, c| res << c.text.downcase; res} +end +API_VERSION = Nokogiri::XML(RestClient.get API_URL).root[:version] +#SETUP + +def xml_response(xml) + Nokogiri::XML(xml) +end + +def json_response(json) + JSON.parse(json) +end + +def get(params={}, path="", authenticate = false) + if authenticate + params.merge!({:Authorization=>BASIC_AUTH}) + end + RestClient.get API_URL+path, params +end + +def post(post_body = "", path= "", params={}, authenticate = false) + if authenticate + params.merge!({:Authorization=>BASIC_AUTH}) + end + RestClient.post API_URL+path, post_body, params +end + +def delete(params={}, path = "", authenticate = true) + if authenticate + params.merge!({:Authorization=>BASIC_AUTH}) + end + RestClient.delete API_URL+path, params +end + +def options(params={}, path="", authenticate = false) + if authenticate + params.merge!({:Authorization=>BASIC_AUTH}) + end + RestClient.options API_URL+path, params +end + +#the TEST_FILES hash and deltacloud_test_file_names method +#which follows is used in the Rakefile for the rake test:deltacloud task +TEST_FILES = { :images => "images_test.rb", + :realms => "realms_test.rb", + :hardware_profiles => "hardware_profiles_test.rb", + :instance_states => "instance_states_test.rb", + :instances => "instances_test.rb", + :keys => "keys_test.rb", + :firewalls => "firewalls_test.rb", + :addresses => "addresses_test.rb", + :load_balancers => "load_balancers_test.rb", + :storage_volumes => "storage_volumes_test.rb", + :storage_snapshots => "storage_snapshots_test.rb", + :buckets => "buckets_test.rb" + } +#gets the list of collections from the server running at API_URL and translates those into file names accoring to TEST_FILES +def deltacloud_test_file_names + driver_collections = xml_response(RestClient.get API_URL, {:accept=>:xml}).xpath("//api/link").inject([]){|res, current| res<<current[:rel].to_sym ;res} + driver_collections.inject([]){|res, current| res << "deltacloud/#{TEST_FILES[current]}" if TEST_FILES[current] ;res} +end + +def create_a_bucket_and_blob + #random bucket and blob name - make sure starts with letter: + bucket_name = random_name + blob_name = random_name + #create bucket: +# res = RestClient.post "#{API_URL}/buckets", {:name=>bucket_name}, {:Authorization=>BASIC_AUTH} + res = post({:name=>bucket_name}, "/buckets", {}, true) + raise Exception.new("unable to create bucket with name #{bucket_name} for bucket_test.rb") unless res.code == 201 + #create blob: + res = RestClient.put "#{API_URL}/buckets/#{bucket_name}/#{blob_name}", "This is the test blob content", {:Authorization=>BASIC_AUTH, :content_type=>"text/plain", "X-Deltacloud-Blobmeta-Version"=>"1.0", "X-Deltacloud-Blobmeta-Author"=>"herpyderp"} + raise Exception.new("unable to create blob with name #{blob_name} for bucket_test.rb") unless res.code == 200 + return [bucket_name, blob_name] +end + +def random_name + name = rand(36**10).to_s(36) + name.insert(0, "apitest") +end + +def delete_bucket_and_blob(bucket, blob) +# res = RestClient.delete "#{API_URL}/buckets/#{bucket}/#{blob}", {:Authorization=>BASIC_AUTH} + res = delete({}, "/buckets/#{bucket}/#{blob}") + raise Exception.new("unable to delete blob with name #{blob} for bucket_test.rb") unless res.code == 204 +# res = RestClient.delete "#{API_URL}/buckets/#{bucket}", {:Authorization=>BASIC_AUTH} + res = delete({}, "/buckets/#{bucket}") + raise Exception.new("unable to delete bucket with name #{bucket} for bucket_test.rb") unless res.code == 204 +end + +def discover_features + res = xml_response(get) + features_hash = res.xpath("//api/link").inject({}) do |result, collection| + result.merge!({collection[:rel] => []}) + collection.children.inject([]){|features, current_child| result[collection[:rel]] << current_child[:name] if current_child.name == "feature"} + result + end +end -- 1.7.6.5
