Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

function test of mysql recover using cucumber.

Change-Id: Iad1d98abe5e9ddb66aa5d0833a0178e170069ca7
  • Loading branch information...
commit 6208043296bd780a9df2150098b4ce7c449224af 1 parent d972c64
Andrew Liu authored
View
4 Rakefile
@@ -0,0 +1,4 @@
+desc "Run integration tests."
+task "tests" do |t|
+ system "cd tests; bundle exec rake tests"
+end
View
12 tests/Gemfile
@@ -0,0 +1,12 @@
+source "http://rubygems.org"
+
+gem "json"
+gem "httpclient"
+gem "rubyzip"
+gem "rake", "0.8.7"
+gem "rspec", "1.3.0"
+gem "cucumber", "0.9.0"
+gem "gherkin", "2.2.4"
+gem "curb"
+gem "nokogiri"
+gem "nats"
View
46 tests/Gemfile.lock
@@ -0,0 +1,46 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ builder (2.1.2)
+ cucumber (0.9.0)
+ builder (~> 2.1.2)
+ diff-lcs (~> 1.1.2)
+ gherkin (~> 2.2.2)
+ json (~> 1.4.6)
+ term-ansicolor (~> 1.0.5)
+ curb (0.7.15)
+ daemons (1.1.3)
+ diff-lcs (1.1.2)
+ eventmachine (0.12.10)
+ gherkin (2.2.4)
+ json (~> 1.4.6)
+ term-ansicolor (~> 1.0.5)
+ trollop (~> 1.16.2)
+ httpclient (2.2.0.2)
+ json (1.4.6)
+ json_pure (1.5.1)
+ nats (0.4.10)
+ daemons (>= 1.1.0)
+ eventmachine (>= 0.12.10)
+ json_pure (>= 1.5.1)
+ nokogiri (1.4.4)
+ rake (0.8.7)
+ rspec (1.3.0)
+ rubyzip (0.9.4)
+ term-ansicolor (1.0.5)
+ trollop (1.16.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ cucumber (= 0.9.0)
+ curb
+ gherkin (= 2.2.4)
+ httpclient
+ json
+ nats
+ nokogiri
+ rake (= 0.8.7)
+ rspec (= 1.3.0)
+ rubyzip
View
1  tests/README
@@ -0,0 +1 @@
+Before run integration test of services repo. Make sure all components are running properly by running `bin/vcap status` at vcap repo.
View
8 tests/Rakefile
@@ -0,0 +1,8 @@
+require 'rubygems'
+require 'bundler/setup'
+Bundler.require(:default, :test)
+
+# Integration test
+task :tests do
+ sh('bundle exec cucumber')
+end
View
8 tests/apps/service_test_app/Gemfile
@@ -0,0 +1,8 @@
+source :rubygems
+
+gem "thin"
+gem "sinatra"
+gem "datamapper"
+gem "yajl-ruby"
+gem "dm-mysql-adapter"
+gem "dm-sqlite-adapter"
View
82 tests/apps/service_test_app/Gemfile.lock
@@ -0,0 +1,82 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ addressable (2.2.5)
+ bcrypt-ruby (2.1.4)
+ daemons (1.1.3)
+ data_objects (0.10.3)
+ addressable (~> 2.1)
+ datamapper (1.1.0)
+ dm-aggregates (= 1.1.0)
+ dm-constraints (= 1.1.0)
+ dm-core (= 1.1.0)
+ dm-migrations (= 1.1.0)
+ dm-serializer (= 1.1.0)
+ dm-timestamps (= 1.1.0)
+ dm-transactions (= 1.1.0)
+ dm-types (= 1.1.0)
+ dm-validations (= 1.1.0)
+ dm-aggregates (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-constraints (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-core (1.1.0)
+ addressable (~> 2.2.4)
+ dm-do-adapter (1.1.0)
+ data_objects (~> 0.10.2)
+ dm-core (~> 1.1.0)
+ dm-migrations (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-mysql-adapter (1.1.0)
+ dm-do-adapter (~> 1.1.0)
+ do_mysql (~> 0.10.2)
+ dm-serializer (1.1.0)
+ dm-core (~> 1.1.0)
+ fastercsv (~> 1.5.4)
+ json (~> 1.4.6)
+ dm-sqlite-adapter (1.1.0)
+ dm-do-adapter (~> 1.1.0)
+ do_sqlite3 (~> 0.10.2)
+ dm-timestamps (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-transactions (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-types (1.1.0)
+ bcrypt-ruby (~> 2.1.4)
+ dm-core (~> 1.1.0)
+ fastercsv (~> 1.5.4)
+ json (~> 1.4.6)
+ stringex (~> 1.2.0)
+ uuidtools (~> 2.1.2)
+ dm-validations (1.1.0)
+ dm-core (~> 1.1.0)
+ do_mysql (0.10.3)
+ data_objects (= 0.10.3)
+ do_sqlite3 (0.10.3)
+ data_objects (= 0.10.3)
+ eventmachine (0.12.10)
+ fastercsv (1.5.4)
+ json (1.4.6)
+ rack (1.2.2)
+ sinatra (1.2.1)
+ rack (~> 1.1)
+ tilt (>= 1.2.2, < 2.0)
+ stringex (1.2.1)
+ thin (1.2.11)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.2.2)
+ uuidtools (2.1.2)
+ yajl-ruby (0.8.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ datamapper
+ dm-mysql-adapter
+ dm-sqlite-adapter
+ sinatra
+ thin
+ yajl-ruby
View
111 tests/apps/service_test_app/server.rb
@@ -0,0 +1,111 @@
+#!/usr/bin/env ruby
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
+require "rubygems"
+require "bundler/setup"
+require 'sinatra'
+require 'thin'
+require 'datamapper'
+require 'logger'
+require 'yajl'
+
+# VCAP environment
+port = ENV['VMC_APP_PORT']
+port ||= 8082
+
+class TestApp < Sinatra::Base
+ set :public, File.join(File.dirname(__FILE__) , '/static')
+ set :views, File.join(File.dirname(__FILE__) , '/template')
+
+ class User
+ include DataMapper::Resource
+ property :id, Serial, :key => true
+ property :name, String, :required => true
+ end
+
+ def initialize(opts)
+ super
+ @opts = opts
+ @logger = Logger.new(STDOUT, 'daily')
+ @logger.level = Logger::DEBUG
+ @db= false
+ if @opts[:mysql]
+ @db= true
+ DataMapper.setup(:default, @opts[:mysql])
+ DataMapper::auto_upgrade!
+ end
+ end
+
+ not_found do
+ halt 404
+ end
+
+ error do
+ @logger.error("Error: #{env['sinatra.erro']}")
+ halt 500
+ end
+
+ get '/' do
+ 'It works.'
+ end
+
+ before '/user/*' do
+ if not @db
+ halt 500, "database not enabled."
+ end
+ end
+
+ get '/user/:id' do
+ @logger.debug("Get user #{params[:id]}")
+ user = User.get(params[:id])
+ if user
+ user.name
+ else
+ halt 404
+ end
+ end
+
+ get '/user' do
+ users = User.all
+ res = ""
+ users.each do |user|
+ name = user.name
+ res += "#{name}\n"
+ end
+ res
+ end
+
+ post '/user' do
+ request.body.rewind
+ name = request.body.read
+ @logger.debug("Create a user #{name}")
+ user = User.new
+ user.name = name
+ if not user.save
+ @logger.error("Can't save to db:#{user.errors.pretty_inspect}")
+ halt 500
+ else
+ redirect ("/user/#{user.id}")
+ end
+ end
+
+end
+
+config = {}
+svcs = ENV['VMC_SERVICES']
+if svcs
+ # override db config if VMC_SERVICE atmos service is supplied.
+ svcs = Yajl::Parser.parse(svcs)
+ svcs.each do |svc|
+ if svc["name"] =~ /^mysql/
+ opts = svc["options"]
+ user,passwd,host,db,db_port = %w(user password hostname name port).map {|key|
+ opts[key]}
+ conn_string="mysql://#{user}:#{passwd}@#{host}:#{db_port}/#{db}"
+ config[:mysql] = conn_string
+ end
+ end
+end
+
+puts "Config: #{config.inspect}"
+instance = TestApp.new(config)
+Thin::Server.start('0.0.0.0', port , instance)
View
2  tests/config/function_test.yml
@@ -0,0 +1,2 @@
+---
+tmp_dir: /tmp
View
22 tests/features/services_admin.feature
@@ -0,0 +1,22 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+
+Feature: Service admin tasks
+ As service admin or operator
+ I want to perform admin tasks on serivces
+
+ @creates_service_test_app @provisions_service
+ Scenario: Recover a mysql instance
+ Given I have registered and logged in
+ Given I deploy a service demo application using the "mysql" service
+ When I add 3 user records to service demo application
+ Then I should have the same 3 user records on demo application
+ When I backup "mysql" service
+ When I shutdown "mysql" node
+ When I delete the service from "mysql" node
+ When I delete the service from the local database of "mysql" node
+ When I start "mysql" node
+ Then I should not able to read 3 user records on demo application
+ When I recover "mysql" service
+ # Restart application in case application have cache
+ When I restart the application
+ Then I should have the same 3 user records on demo application
View
531 tests/features/step_definitions/appcloud_steps.rb
@@ -0,0 +1,531 @@
+#
+# The test automation based on Cucumber uses the steps defined and implemented here to
+# facilitate the handling of the various scenarios that make up the feature set of
+# AppCloud.
+#
+# Author:: A.B.Srinivasan
+# Copyright:: Copyright (c) 2010 VMware Inc.
+
+#World(AppCloudHelper)
+
+World do
+ AppCloudHelper.instance
+end
+
+## User management
+
+# Register
+Given /^I am a new user to AppCloud$/ do
+ pending "new user registration is temporarily disabled in the bvts"
+ AppCloudHelper.instance.create_user
+end
+
+When /^I register$/ do
+ @user = AppCloudHelper.instance.register
+end
+
+Then /^I should be able to login to AppCloud\.$/ do
+ @user.should_not == nil
+ AppCloudHelper.instance.login
+end
+
+# Login
+Given /^I am registered$/ do
+ user = AppCloudHelper.instance.get_registered_user
+ if (user == nil)
+ user = AppCloudHelper.instance.create_user
+ AppCloudHelper.instance.register
+ end
+ user.should_not == nil
+end
+
+When /^I login$/ do
+ @token = AppCloudHelper.instance.login
+end
+
+Then /^I should get an authentication token that I need to use with all subsequent AppCloud requests$/ do
+ @token.should_not == nil
+end
+
+# Re-login
+Given /^I have logged in$/ do
+
+ user = AppCloudHelper.instance.get_registered_user
+ if (user == nil)
+ user = AppCloudHelper.instance.create_user
+ AppCloudHelper.instance.register
+ end
+ user.should_not == nil
+
+ @first_login_token = AppCloudHelper.instance.get_login_token
+ @first_login_token.should_not == nil
+end
+
+Then /^I should get a new authentication token that I need to use for all subsequent AppCloud requests$/ do
+ @token.should_not == nil
+ @token.should_not == @first_login_token
+end
+
+## Application CRUD operations
+
+Given /^I have registered and logged in$/ do
+ user = AppCloudHelper.instance.get_registered_user
+ if user == nil
+ user = AppCloudHelper.instance.create_user
+ AppCloudHelper.instance.register
+ end
+ user.should_not == nil
+
+ @token = AppCloudHelper.instance.get_login_token
+ if @token == nil
+ @token = AppCloudHelper.instance.login
+ end
+ @token.should_not == nil
+end
+
+# Create
+When /^I create a simple application$/ do
+ @app = create_app SIMPLE_APP, @token
+end
+
+Then /^I should have my application on AppCloud$/ do
+ @app.should_not == nil
+end
+
+Then /^it should not be started$/ do
+ status = get_app_status @app, @token
+ status.should_not == nil
+ status['state'].should_not == 'STARTED'
+end
+
+# Read (Query status)
+Given /^I have my simple application on AppCloud$/ do
+ @app = create_app SIMPLE_APP, @token
+end
+
+When /^I query status of my application$/ do
+ @status = get_app_status @app, @token
+end
+
+Then /^I should get the state of my application$/ do
+ @status.should_not == nil
+end
+
+# Delete
+When /^I delete my application$/ do
+ delete_app @app, @token
+end
+
+Then /^it should not be on AppCloud$/ do
+ status = get_app_status @app, @token
+ status.should == nil
+end
+
+# Upload
+When /^I upload my application$/ do
+ upload_app @app, @token
+end
+
+## Application availability control
+# Start
+When /^I start my application$/ do
+ start_app @app, @token
+end
+
+Then /^it should be started$/ do
+ status = get_app_status @app, @token
+ status.should_not == nil
+ status['state'].should == 'STARTED'
+ expected_health = 1.0
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+Then /^it should be available for use$/ do
+ contents = get_app_contents @app
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from VCAP/
+ contents.close
+end
+
+# Stop
+When /^I stop my application$/ do
+ stop_app @app, @token
+end
+
+Then /^it should be stopped$/ do
+ status = get_app_status @app, @token
+ status.should_not == nil
+ status['state'].should == 'STOPPED'
+ expected_health = 0.0
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+Then /^it should not be available for use$/ do
+ contents = get_app_contents @app
+ contents.should_not == nil
+ contents.response_code.should == 404
+ contents.close
+end
+
+# List apps
+Given /^I have deployed a simple application$/ do
+ @app = create_app SIMPLE_APP, @token
+ upload_app @app, @token
+ start_app @app, @token
+ expected_health = 1.0
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+Given /^I have built a simple Erlang application$/ do
+ # Try to find an appropriate Erlang
+ erlang_path = '/var/vcap/runtimes/erlang-R14B02/bin'
+ unless File.exists?(erlang_path)
+ pending "Not running Erlang test because the Erlang runtime is not installed"
+ else
+ Dir.chdir("#{@testapps_dir}/#{SIMPLE_ERLANG_APP}")
+ make_prefix = "PATH=#{erlang_path}:$PATH"
+ rel_build_result = `#{make_prefix} make relclean rel`
+ raise "Erlang application build failed: #{rel_build_result}" if $? != 0
+ end
+end
+
+Given /^I have deployed a simple Erlang application$/ do
+ @app = create_app SIMPLE_ERLANG_APP, @token
+ upload_app @app, @token, "rel/mochiweb_test"
+ start_app @app, @token
+ expected_health = 1.0
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+Given /^I have deployed a tiny Java application$/ do
+ @java_app = create_app TINY_JAVA_APP, @token
+ upload_app @java_app, @token
+ start_app @java_app, @token
+ expected_health = 1.0
+ health = poll_until_done @java_app, expected_health, @token
+ health.should == expected_health
+end
+
+When /^I list my applications$/ do
+ @app_list = list_apps @token
+ @app_list.should_not == nil
+end
+
+Then /^I should get status on the simple app as well as the tiny Java application$/ do
+ simple_app = get_app_info @app_list, @app
+ tiny_java_app = get_app_info @app_list, @java_app
+ simple_app.should_not == nil
+ tiny_java_app.should_not == nil
+end
+
+# Get app files
+When /^I list files associated with my application$/ do
+ @instance = '0'
+ path = '/'
+ @response = get_app_files @app, @instance, path, @token
+end
+
+Then /^I should get a list of directories and files associated with my application on AppCloud$/ do
+ @response.status.should == 200
+ @response.content.should_not == nil
+end
+
+Then /^I should be able to retrieve any of the listed files$/ do
+ @instance = '0'
+ path = '/app'
+ response = get_app_files @app, @instance, path, @token
+ response.status.should == 200
+end
+
+# Get instances info
+Given /^I have (\d+) instances of a simple application$/ do |arg1|
+ @instances = set_app_instances @app, arg1.to_i, @token
+end
+
+When /^I get instance information for my application$/ do
+ @instances_info = get_instances_info @app, @token
+end
+
+Then /^I should get status on all instances of my application$/ do
+ @instances_info.should_not == nil
+ @instances_info['instances'].length.should == @instances
+end
+
+# Get crash info
+Given /^I that my application has a crash$/ do
+ @instance = '0'
+ path = '/run.pid'
+ response = get_app_files @app, @instance, path, @token
+ response.status.should == 200
+ pid = response.content.chomp
+ # This call causes the app to crash
+ begin
+ contents = get_app_contents @app, "crash/#{pid}"
+ contents.close
+ rescue
+ end
+end
+
+When /^I get crash information for my application$/ do
+ @crash_info = get_app_crashes @app, @token
+end
+
+Then /^I should be able to get the time of the crash from that information$/ do
+ @crash_info.should_not == nil
+ Time.at(@crash_info['crashes'][0]['since']).should_not == nil
+end
+
+Then /^I should be able to get a list of files associated with my application on AppCloud$/ do
+ @instance = '0'
+ path = '/'
+ @response = get_app_files @app, @instance, path, @token
+ @response.status.should == 200
+ @response.content.should_not == nil
+end
+
+# Crash info for a broken (persistently broken) app
+Given /^I have deployed a broken application$/ do
+ @app = create_app BROKEN_APP, @token
+ upload_app @app, @token
+ start_app @app, @token
+ sleep 3
+end
+
+# Resource use
+When /^I get resource usage for my application$/ do
+ @app_stats = get_app_stats @app, @token
+end
+
+Then /^I should get information representing my application\'s resource use\.$/ do
+ @app_stats.should_not == nil
+ stats = @app_stats.to_a[0][1]['stats']
+ stats.should_not == nil
+ appname = get_app_name @app
+ stats['name'].should == appname
+
+ timeout = 6 # Because monitor sweeps are 5 secs..
+ sleep_time = 0.5
+
+ while stats['usage'] == nil && timeout > 0
+ sleep sleep_time
+ timeout -= sleep_time
+ @app_stats = get_app_stats @app, @token
+ stats = @app_stats.to_a[0][1]['stats']
+ end
+
+ stats['usage'].should_not == nil
+end
+
+# Update app instance count
+When /^I increase the instance count of my application by (\d+)$/ do |arg1|
+ instances_info = get_instances_info @app, @token
+ instances_info.should_not == nil
+ set_app_instances @app, instances_info['instances'].length + arg1.to_i, @token
+end
+
+Then /^I should have (\d+) instances of my application$/ do |arg1|
+ instances_info = get_instances_info @app, @token
+ instances_info.should_not == nil
+ instances_info['instances'].length.should == arg1.to_i
+end
+
+When /^I decrease the instance count of my application by (\d+)$/ do |arg1|
+ instances_info = get_instances_info @app, @token
+ instances_info.should_not == nil
+ set_app_instances @app, instances_info['instances'].length - arg1.to_i, @token
+end
+
+# Map & unmap application URIs
+When /^I add a url to my application$/ do
+ app_info = get_app_status @app, @token
+ app_info.should_not == nil
+ uris = app_info['uris']
+ @original_uri = uris[0]
+ appname = get_app_name @app
+ @new_uri = create_uri "#{appname}-1"
+ add_app_uri @app, @new_uri, @token
+end
+
+# Map & unmap application URIs
+When /^I add a url that differs only by case$/ do
+ # While odd, this is allowed for a single user. It should fail
+ # for similar urls, both in terms of the same case and mixed
+ # case across users. These tests aren't setup for
+ # cross user testing at the moment. For a single user we might
+ # merge these urls on the backend, but we don't for the moment,
+ # hence the 'pending' status below.
+ pending "the expected behavior of this test is under discussion"
+ app_info = get_app_status @app, @token
+ app_info.should_not == nil
+ uris = app_info['uris']
+ uris.length.should == 1
+ @original_uri = uris[0]
+ appname = get_app_name @app
+ @new_uri = create_uri "#{appname.swapcase}"
+ @new_uri.should_not == @original_uri
+ add_app_uri @app, @new_uri, @token
+end
+
+Then /^I should have (\d+) urls associated with my application$/ do |arg1|
+ app_info = get_app_status @app, @token
+ app_info.should_not == nil
+ uris = app_info['uris']
+ uris.length.should == arg1.to_i
+end
+
+Then /^I should be able to access the application through the original url\.$/ do
+ contents = get_uri_contents @original_uri
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from VCAP/
+ contents.close
+end
+
+Then /^I should be able to access the application through the new url\.$/ do
+ # Time dependent, so sleep for a small amount.
+ sleep 0.25
+
+ contents = get_uri_contents @new_uri
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from VCAP/
+ contents.close
+end
+
+Given /^I have my application associated with '(\d+)' urls$/ do |arg1|
+ app_info = get_app_status @app, @token
+ app_info.should_not == nil
+ uris = app_info['uris']
+ @remaining_uri = uris[0]
+ appname = get_app_name @app
+ @uri_to_be_removed = appname << "-1"
+ @uri_to_be_removed = create_uri @uri_to_be_removed
+ add_app_uri @app, @uri_to_be_removed, @token
+end
+
+When /^I remove one of the urls associated with my application$/ do
+ remove_app_uri @app, @uri_to_be_removed, @token
+end
+
+Then /^I should be able to access the application through the remaining url\.$/ do
+ contents = get_uri_contents @remaining_uri
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from VCAP/
+ contents.close
+end
+
+Then /^I should be not be able to access the application through the removed url\.$/ do
+ # Time dependent, so sleep for a small amount.
+ sleep 0.25
+
+ contents = get_uri_contents @uri_to_be_removed
+ contents.should_not == nil
+ contents.response_code.should == 404
+ contents.close
+end
+
+When /^I remove the original url associated with my application$/ do
+ remove_app_uri @app, @original_uri, @token
+end
+
+Then /^I should be not be able to access the application through the original url\.$/ do
+ contents = get_uri_contents @original_uri
+ contents.should_not == nil
+ contents.response_code.should == 404
+ contents.close
+end
+
+# Modify application contents
+When /^I upload a modified simple application to AppCloud$/ do
+ modify_and_upload_app @app, @token
+end
+
+When /^I update my application on AppCloud$/ do
+ @response = poll_until_update_app_done @app, @token
+end
+
+Then /^my update should succeed$/ do
+ @response.should == 'SUCCEEDED'
+end
+
+Then /^I should be able to access the updated version of my application$/ do
+ contents = get_app_contents @app
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from modified VCAP/
+ contents.close
+end
+
+Then /^I should be able to access the original version of my application$/ do
+ pending
+ contents = get_app_contents @app
+ contents.should_not == nil
+ contents.body_str.should_not == nil
+ contents.body_str.should =~ /Hello from VCAP/
+ contents.close
+end
+
+# Simple Sinatra CRUD application that uses MySQL
+Given /^I deploy my simple application that is backed by the MySql database service on AppCloud$/ do
+ @app = create_app SIMPLE_DB_APP, @token
+ @service = provision_db_service @token
+ attach_provisioned_service @app, @service, @token
+ upload_app @app, @token
+ start_app @app, @token
+ expected_health = 1.0
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+When /^I add a record to my application$/ do
+ @desc = "Description"
+ @id = "tester1"
+ data_hash = { :id => @id, :desc => @desc}
+ uri = get_uri @app, "users"
+ post_record uri, data_hash
+end
+
+Then /^I should be able to retrieve the record that was added$/ do
+ user_id = @id
+ uri = get_uri @app, "users/#{user_id}"
+ contents = get_uri_contents uri
+ contents.should_not == nil
+ user_hash = parse_json contents.body_str
+ user_hash['id'].should == user_id
+ user_hash['desc'].should == @desc
+ contents.close
+end
+
+Then /^be able to update the record$/ do
+ updated_desc = "Updated description"
+ data_hash = { :id => @id, :desc => updated_desc}
+ uri = get_uri @app, "users/#{@id}"
+ put_record uri, data_hash
+
+ uri = get_uri @app, "users/#{@id}"
+ contents = get_uri_contents uri
+ contents.should_not == nil
+ user_hash = parse_json contents.body_str
+ user_hash['id'].should == @id
+ user_hash['desc'].should == updated_desc
+ contents.close
+end
+
+Then /^be able to delete the record$/ do
+ uri = get_uri @app, "users/#{@id}"
+ contents = get_uri_contents uri
+ contents.should_not == nil
+ contents.close
+ delete_record uri
+ contents = get_uri_contents uri
+ contents.should_not == nil
+ contents.response_code.should == 404
+ contents.close
+end
+
View
122 tests/features/step_definitions/service_admin_steps.rb
@@ -0,0 +1,122 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+require 'httpclient'
+
+Given /^I deploy a service demo application using the "([^""]*)" service$/ do |service|
+ expected_health = 1.0
+ @app = SERVICE_TEST_APP
+ @app_detail = create_app(@app, @token)
+ case service
+ when "mysql"
+ @service_detail = provision_mysql_service(@token)
+ debug "service_detail #{@service_detail}"
+ else
+ fail "Unknown service #{service}"
+ end
+ upload_app @app, @token
+ bind_service_to_app @app_detail, @service_detail, @token
+ start_app @app, @token
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+When /^I backup "([^""]*)" service$/ do |service|
+ case service
+ when "mysql"
+ mysql_backup @service_detail
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+When /^I add (\d+) user records to service demo application/ do |records|
+ @user_records ||= {}
+ 1.upto records.to_i do |i|
+ uri = get_uri @app, "user"
+ uri = "http://" + uri
+ debug "URI: #{uri}"
+ username = "username#{i}"
+ response = HTTPClient.post uri, username
+ debug "response of create user: #{response.content}"
+ response.status.should == 302
+ location = response.headers['Location']
+ location =~ /user\/(\d+)/
+ id = $1
+ @user_records[id] = username
+ end
+end
+
+
+
+When /^I shutdown "([^""]*)" node$/ do |service|
+ if valid_services.include? service
+ shutdown_service_node service
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+When /^I delete the service from the local database of "([^""]*)" node$/ do |service|
+ case service
+ when "mysql"
+ mysql_drop_service_from_db
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+When /^I restart the application$/ do
+ expected_health = 1.0
+ stop_app @app, @token
+ start_app @app, @token
+ health = poll_until_done @app, expected_health, @token
+ health.should == expected_health
+end
+
+When /^I delete the service from "([^""]*)" node$/ do |service|
+ case service
+ when "mysql"
+ mysql_drop_service
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+When /^I start "([^""]*)" node$/ do |service|
+ if valid_services.include? service
+ start_service_node service
+ # Wait until node is ready.
+ sleep 10
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+When /^I recover "([^""]*)" service$/ do |service|
+ case service
+ when "mysql"
+ mysql_recover
+ else
+ fail "Unknown service #{service}. Valide services:#{valid_services}"
+ end
+end
+
+Then /^I should not able to read (\d+) user records on demo application$/ do |records|
+ @user_records.size.should == records.to_i
+ @user_records.keys.each do |id|
+ uri = get_uri @app, "user/#{id}"
+ response = HTTPClient.get "http://"+uri
+ debug "Response of get user: #{response.content}"
+ response.status.should == 500
+ end
+end
+
+Then /^I should have the same (\d+) user records on demo application$/ do |records|
+ @user_records.size.should == records.to_i
+ @user_records.keys.each do |id|
+ uri = get_uri @app, "user/#{id}"
+ response = HTTPClient.get "http://"+uri
+ debug "Response of get user: #{response.content}"
+ response.status.should == 200
+ response.content.should == @user_records[id]
+ end
+end
View
696 tests/features/support/env.rb
@@ -0,0 +1,696 @@
+
+require 'rubygems'
+require 'bundler'
+Bundler.setup
+
+$:.unshift(File.join(File.dirname(__FILE__), '../../lib/client/lib'))
+$:.unshift(File.dirname(__FILE__))
+require 'mysql_helper'
+require 'json/pure'
+require 'singleton'
+require 'spec'
+require 'vmc_base'
+require 'curb'
+require 'pp'
+
+# The integration test automation based on Cucumber uses the AppCloudHelper as a driver that takes care of
+# of all interactions with AppCloud through the VCAP::BaseClient intermediary.
+#
+# Author:: A.B.Srinivasan
+# Copyright:: Copyright (c) 2010 VMware Inc.
+
+TEST_AUTOMATION_USER_ID = "tester@vcap.example.com"
+TEST_AUTOMATION_PASSWORD = "tester"
+SIMPLE_APP = "simple_app"
+REDIS_LB_APP = "redis_lb_app"
+ENV_TEST_APP = "env_test_app"
+TINY_JAVA_APP = "tiny_java_app"
+SIMPLE_DB_APP = "simple_db_app"
+BROKEN_APP = "broken_app"
+RAILS3_APP = "rails3_app"
+JPA_APP = "jpa_app"
+HIBERNATE_APP = "hibernate_app"
+DBRAILS_APP = "dbrails_app"
+DBRAILS_BROKEN_APP = "dbrails_broken_app"
+GRAILS_APP = "grails_app"
+ROO_APP = "roo_app"
+SIMPLE_ERLANG_APP = "mochiweb_test"
+SERVICE_TEST_APP = "service_test_app"
+
+After do
+ # This used to delete the entire user, but that now require admin privs
+ # so it was removed.
+ AppCloudHelper.instance.cleanup
+end
+
+After("@creates_simple_app") do
+ AppCloudHelper.instance.delete_app_internal SIMPLE_APP
+end
+
+After("@creates_tiny_java_app") do
+ AppCloudHelper.instance.delete_app_internal TINY_JAVA_APP
+end
+
+After("@creates_simple_db_app") do
+ AppCloudHelper.instance.delete_app_internal SIMPLE_DB_APP
+end
+
+After("@creates_service_test_app") do
+ AppCloudHelper.instance.delete_app_internal SERVICE_TEST_APP
+end
+
+After("@creates_redis_lb_app") do
+ AppCloudHelper.instance.delete_app_internal REDIS_LB_APP
+end
+
+
+After("@creates_env_test_app") do
+ AppCloudHelper.instance.delete_app_internal ENV_TEST_APP
+end
+
+After("@creates_broken_app") do
+ AppCloudHelper.instance.delete_app_internal BROKEN_APP
+end
+
+After("@creates_rails3_app") do
+ AppCloudHelper.instance.delete_app_internal RAILS3_APP
+end
+
+After("@creates_jpa_app") do
+ AppCloudHelper.instance.delete_app_internal JPA_APP
+end
+
+After("@creates_hibernate_app") do
+ AppCloudHelper.instance.delete_app_internal HIBERNATE_APP
+end
+
+After("@creates_dbrails_app") do
+ AppCloudHelper.instance.delete_app_internal DBRAILS_APP
+end
+
+After("@creates_dbrails_broken_app") do
+ AppCloudHelper.instance.delete_app_internal DBRAILS_BROKEN_APP
+end
+
+After("@creates_grails_app") do
+ AppCloudHelper.instance.delete_app_internal GRAILS_APP
+end
+
+After("@creates_roo_app") do
+ AppCloudHelper.instance.delete_app_internal ROO_APP
+end
+
+After("@creates_mochiweb_app") do
+ AppCloudHelper.instance.delete_app_internal SIMPLE_ERLANG_APP
+end
+
+After("@provisions_service") do
+ AppCloudHelper.instance.unprovision_service
+end
+
+at_exit do
+ AppCloudHelper.instance.cleanup
+end
+
+['TERM', 'INT'].each { |s| trap(s) { AppCloudHelper.instance.cleanup; Process.exit! } }
+
+class AppCloudHelper
+ include Singleton, MysqlServiceHelper
+
+ def initialize
+ @last_registered_user, @last_login_token = nil
+ # Go through router endpoint for CloudController
+ @target = ENV['VCAP_BVT_TARGET'] || 'vcap.me'
+ @registered_user = ENV['VCAP_BVT_USER']
+ @registered_user_passwd = ENV['VCAP_BVT_USER_PASSWD']
+ @base_uri = "http://api.#{@target}"
+ @droplets_uri = "#{@base_uri}/apps"
+ @resources_uri = "#{@base_uri}/resources"
+ @services_uri = "#{@base_uri}/services"
+ @configuration_uri = "#{@base_uri}/services/v1/configurations"
+ @binding_uri = "#{@base_uri}/services/v1/bindings"
+ @suggest_url = @target
+
+ puts "\n** VCAP_BVT_TARGET = '#{@target}' (set environment variable to override) **"
+ puts "** Running as user: '#{test_user}' (set environment variables VCAP_BVT_USER / VCAP_BVT_USER_PASSWD to override) **"
+ puts "** VCAP CloudController = '#{@base_uri}' **\n\n"
+
+ # Namespacing allows multiple tests to run in parallel.
+ # Deprecated, along with the load-test tasks.
+ # puts "** To run multiple tests in parallel, set environment variable VCAP_BVT_NS **"
+ @namespace = ENV['VCAP_BVT_NS'] || ''
+ puts "** Using namespace: '#{@namespace}' **\n\n" unless @namespace.empty?
+
+ config_file = File.join(File.dirname(__FILE__), 'testconfig.yml')
+ begin
+ @config = File.open(config_file) do |f|
+ YAML.load(f)
+ end
+ rescue => e
+ puts "Could not read configuration file: #{e}"
+ exit
+ end
+
+ load_mysql_config()
+ @testapps_dir = File.join(File.dirname(__FILE__), '../../apps')
+ @client = VMC::BaseClient.new()
+
+ # Make sure we cleanup if we had a failed run..
+ # Fake out the login and registration piece..
+ begin
+ login
+ @last_registered_user = test_user
+ rescue
+ end
+ cleanup
+ end
+
+ def cleanup
+ delete_app_internal(SIMPLE_APP)
+ delete_app_internal(TINY_JAVA_APP)
+ delete_app_internal(REDIS_LB_APP)
+ delete_app_internal(ENV_TEST_APP)
+ delete_app_internal(SIMPLE_DB_APP)
+ delete_app_internal(BROKEN_APP)
+ delete_app_internal(RAILS3_APP)
+ delete_app_internal(JPA_APP)
+ delete_app_internal(HIBERNATE_APP)
+ delete_app_internal(DBRAILS_APP)
+ delete_app_internal(DBRAILS_BROKEN_APP)
+ delete_app_internal(GRAILS_APP)
+ delete_app_internal(ROO_APP)
+ delete_app_internal(SERVICE_TEST_APP)
+ # This used to delete the entire user, but that now require admin privs
+ # so it was removed, as we the delete_user method. See the git
+ # history if it needs to be revived.
+ end
+
+ def create_uri name
+ "#{name}.#{@suggest_url}"
+ end
+
+ def create_user
+ @registered_user || "#{@namespace}#{TEST_AUTOMATION_USER_ID}"
+ end
+
+ def create_passwd
+ @registered_user_passwd || TEST_AUTOMATION_PASSWORD
+ end
+
+ alias :test_user :create_user
+ alias :test_passwd :create_passwd
+
+ def get_registered_user
+ @last_registered_user
+ end
+
+ def register
+ unless @registered_user
+ @client.register_internal(@base_uri, test_user, test_passwd)
+ end
+ @last_registered_user = test_user
+ end
+
+ def login
+ token = @client.login_internal(@base_uri, test_user, test_passwd)
+ # TBD - ABS: This is a hack around the 1 sec granularity of our token time stamp
+ sleep(1)
+ @last_login_token = token
+ end
+
+ def get_login_token
+ @last_login_token
+ end
+
+ def list_apps token
+ @client.get_apps_internal(@droplets_uri, auth_hdr(token))
+ end
+
+ def get_app_info app_list, app
+ if app_list.empty?
+ return
+ end
+ appname = get_app_name app
+ app_list.each { |d|
+ if d['name'] == appname
+ return d
+ end
+ }
+ end
+
+ def create_app app, token, instances=1
+ appname = get_app_name app
+ delete_app app, token
+ @app = app
+ url = create_uri appname
+ manifest = {
+ :name => "#{appname}",
+ :staging => {
+ :model => @config[app]['framework'],
+ :stack => @config[app]['startup']
+ },
+ :resources=> {
+ :memory => @config[app]['memory'] || 64
+ },
+ :uris => [url],
+ :instances => "#{instances}",
+ }
+ response = @client.create_app_internal @droplets_uri, manifest, auth_json_hdr(token)
+ response.should_not == nil
+ if response.status == 200
+ return parse_json(response.content)
+ else
+ puts "Creation of app #{appname} failed. Http status #{response.status}. Content: #{response.content}"
+ return
+ end
+ end
+
+ def get_app_name app
+ # It seems _ is not welcomed in hostname
+ "#{@namespace}my_test_app_#{app}".gsub("_", "-")
+ end
+
+ def upload_app app, token, subdir = nil
+ Dir.chdir("#{@testapps_dir}/#{app}" + (if subdir then "/#{subdir}" else "" end))
+ opt_war_file = nil
+ if Dir.glob('*.war').first
+ opt_war_file = Dir.glob('*.war').first
+ end
+ appname = get_app_name app
+ @client.upload_app_bits @resources_uri, @droplets_uri, appname, auth_hdr(token), opt_war_file
+ end
+
+ def get_app_status app, token
+ appname = get_app_name app
+ response = @client.get_app_internal(@droplets_uri, appname, auth_hdr(token))
+ if (response.status == 200)
+ JSON.parse(response.content)
+ end
+ end
+
+ def delete_app_internal app
+ token = get_login_token
+ if app != nil && token != nil
+ delete_app app, token
+ end
+ end
+
+ def delete_app app, token
+ appname = get_app_name app
+ response = @client.delete_app_internal(@droplets_uri, appname, [], auth_hdr(token))
+ @app = nil
+ response
+ end
+
+ def start_app app, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ if app_manifest == nil
+ raise "Application #{appname} does not exist, app needs to be created."
+ end
+
+ if (app_manifest['state'] == 'STARTED')
+ return
+ end
+
+ app_manifest['state'] = 'STARTED'
+ response = @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ raise "Problem starting application #{appname}." if response.status != 200
+ end
+
+ def poll_until_done app, expected_health, token
+ secs_til_timeout = @config['timeout_secs']
+ health = nil
+ sleep_time = 0.5
+ while secs_til_timeout > 0 && health != expected_health
+ sleep sleep_time
+ secs_til_timeout = secs_til_timeout - sleep_time
+ status = get_app_status app, token
+ runningInstances = status['runningInstances'] || 0
+ health = runningInstances/status['instances'].to_f
+ # to mark? Not sure why this change, but breaks simple stop tests
+ #health = runningInstances == 0 ? status['instances'].to_f : runningInstances.to_f
+ end
+ health
+ end
+
+ def stop_app app, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ if app_manifest == nil
+ raise "Application #{appname} does not exist."
+ end
+
+ if (app_manifest['state'] == 'STOPPED')
+ return
+ end
+
+ app_manifest['state'] = 'STOPPED'
+ @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ end
+
+ def get_app_files app, instance, path, token
+ appname = get_app_name app
+ @client.get_app_files_internal @droplets_uri, appname, instance, path, auth_hdr(token)
+ end
+
+ def get_instances_info app, token
+ appname = get_app_name app
+ instances_info = @client.get_app_instances_internal @droplets_uri, appname, auth_hdr(token)
+ instances_info
+ end
+
+ def set_app_instances app, new_instance_count, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ if app_manifest == nil
+ raise "App #{appname} needs to be deployed on AppCloud before being able to increment its instance count"
+ end
+
+ instances = app_manifest['instances']
+ health = app_manifest['health']
+ if (instances == new_instance_count)
+ return
+ end
+ app_manifest['instances'] = new_instance_count
+
+
+ response = @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ raise "Problem setting instance count for application #{appname}." if response.status != 200
+ expected_health = 1.0
+ poll_until_done app, expected_health, token
+ new_instance_count
+ end
+
+ def get_app_crashes app, token
+ appname = get_app_name app
+ response = @client.get_app_crashes_internal @droplets_uri, appname, auth_hdr(token)
+
+ crash_info = JSON.parse(response.content) if (response.status == 200)
+ end
+
+ def get_app_stats app, token
+ appname = get_app_name app
+ response = @client.get_app_stats_internal(@droplets_uri, appname, auth_hdr(token))
+ if (response.status == 200)
+ JSON.parse(response.content)
+ end
+ end
+
+ def add_app_uri app, uri, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ if app_manifest == nil
+ raise "Application #{appname} does not exist, app needs to be created."
+ end
+
+ app_manifest['uris'] << uri
+ response = @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ raise "Problem adding uri #{uri} to application #{appname}." if response.status != 200
+ expected_health = 1.0
+ poll_until_done app, expected_health, token
+ end
+
+ def remove_app_uri app, uri, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ if app_manifest == nil
+ raise "Application #{appname} does not exist, app needs to be created."
+ end
+
+ if app_manifest['uris'].delete(uri) == nil
+ raise "Application #{appname} is not associated with #{uri} to be removed"
+ end
+ response = @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ raise "Problem removing uri #{uri} from application #{appname}." if response.status != 200
+ expected_health = 1.0
+ poll_until_done app, expected_health, token
+ end
+
+ def modify_and_upload_app app,token
+ Dir.chdir("#{@testapps_dir}/modified_#{app}")
+ appname = get_app_name app
+ @client.upload_app_bits @resources_uri, @droplets_uri, appname, auth_hdr(token), nil
+ end
+
+ def modify_and_upload_bad_app app,token
+ appname = get_app_name app
+ Dir.chdir("#{@testapps_dir}/#{BROKEN_APP}")
+ @client.upload_app_bits @resources_uri, @droplets_uri, appname, auth_hdr(token), nil
+ end
+
+ def poll_until_update_app_done app, token
+ appname = get_app_name app
+ @client.update_app_internal @droplets_uri, appname, auth_hdr(token)
+ update_state = nil
+ secs_til_timeout = @config['timeout_secs']
+ while secs_til_timeout > 0 && update_state != 'SUCCEEDED' && update_state != 'CANARY_FAILED'
+ sleep 1
+ secs_til_timeout = secs_til_timeout - 1
+ response = @client.get_update_app_status @droplets_uri, appname, auth_hdr(token)
+ update_info = JSON.parse(response.content)
+ update_state = update_info['state']
+ end
+ update_state
+ end
+
+ def get_services token
+ response = HTTPClient.get "#{@base_uri}/info/services", nil, auth_hdr(token)
+ services = JSON.parse(response.content)
+ services
+ end
+
+ def get_frameworks token
+ response = HTTPClient.get "#{@base_uri}/info", nil, auth_hdr(token)
+ frameworks = JSON.parse(response.content)
+ frameworks['frameworks']
+ end
+
+ def provision_db_service token
+ name = "#{@namespace}#{@app || 'simple_db_app'}"
+ service_manifest = {
+ :type=>"database",
+ :vendor=>"mysql",
+ :tier=>"free",
+ :version=>"5.1.45",
+ :name=>name,
+ :options=>{"size"=>"256MiB"}}
+ @client.add_service_internal @services_uri, service_manifest, auth_hdr(token)
+ #puts "Provisioned service #{service_manifest}"
+ service_manifest
+ end
+
+ def provision_redis_service token
+ service_manifest = {
+ :type=>"key-value",
+ :vendor=>"redis",
+ :tier=>"free",
+ :version=>"5.1.45",
+ :name=>"#{@namespace}redis_lb_app-service",
+ }
+ @client.add_service_internal @services_uri, service_manifest, auth_hdr(token)
+ #puts "Provisioned service #{service_manifest}"
+ service_manifest
+ end
+
+ def provision_redis_service_named token, name
+ service_manifest = {
+ :type=>"key-value",
+ :vendor=>"redis",
+ :tier=>"free",
+ :version=>"5.1.45",
+ :name=>redis_name(name),
+ }
+ @client.add_service_internal @services_uri, service_manifest, auth_hdr(token)
+ #puts "Provisioned service #{service_manifest}"
+ service_manifest
+ end
+
+ def redis_name name
+ "#{@namespace}redis_#{name}"
+ end
+
+ def aurora_name name
+ "#{@namespace}aurora_#{name}"
+ end
+
+
+ def mozyatmos_name name
+ "#{@namespace}mozyatmos_#{name}"
+ end
+
+ def provision_aurora_service_named token, name
+ service_manifest = {
+ :type=>"database",
+ :vendor=>"aurora",
+ :tier=>"std",
+ :name=>aurora_name(name),
+ }
+ @client.add_service_internal @services_uri, service_manifest, auth_hdr(token)
+ #puts "Provisioned service #{service_manifest}"
+ service_manifest
+ end
+
+ def provision_mozyatmos_service_named token, name
+ service_manifest = {
+ :type=>"blob",
+ :vendor=>"mozyatmos",
+ :tier=>"std",
+ :name=>mozyatmos_name(name),
+ }
+ @client.add_service_internal @services_uri, service_manifest, auth_hdr(token)
+ #puts "Provisioned service #{service_manifest}"
+ service_manifest
+ end
+
+
+ def attach_provisioned_service app, service_manifest, token
+ appname = get_app_name app
+ app_manifest = get_app_status app, token
+ provisioned_service = app_manifest['services']
+ provisioned_service = [] unless provisioned_service
+ svc_name = service_manifest[:name]
+ provisioned_service << svc_name
+ app_manifest['services'] = provisioned_service
+ response = @client.update_app_state_internal @droplets_uri, appname, app_manifest, auth_hdr(token)
+ raise "Problem attaching service #{svc_name} to application #{appname}." if response.status != 200
+ end
+
+ def delete_services services, token
+ #puts "Deleting services #{services}"
+ response = @client.delete_services_internal(@services_uri, services, auth_hdr(token))
+ response
+ end
+
+ def get_uri app, relative_path=nil
+ appname = get_app_name app
+ uri = "#{appname}.#{@suggest_url}"
+ if relative_path != nil
+ uri << "/#{relative_path}"
+ end
+ uri
+ end
+
+ def get_app_contents app, relative_path=nil
+
+ uri = get_uri app, relative_path
+ get_uri_contents uri
+ end
+
+ def get_uri_contents uri
+ easy = Curl::Easy.new
+ easy.url = uri
+ easy.http_get
+ easy
+ end
+
+ def post_record uri, data_hash
+ easy = Curl::Easy.new
+ easy.url = uri
+ easy.http_post(data_hash.to_json)
+ easy.close
+ end
+
+ def put_record uri, data_hash
+ easy = Curl::Easy.new
+ easy.url = uri
+ easy.http_put(data_hash.to_json)
+ easy.close
+ end
+
+ def delete_record uri
+ easy = Curl::Easy.new
+ easy.url = uri
+ easy.http_delete
+ easy.close
+ end
+
+ def parse_json payload
+ JSON.parse(payload)
+ end
+
+ def auth_hdr token
+ {
+ 'AUTHORIZATION' => "#{token}",
+ }
+ end
+
+ def auth_json_hdr token
+ {
+ 'AUTHORIZATION' => "#{token}",
+ 'content-type' => "application/json",
+ }
+ end
+
+ def bind_service_to_app(app, service, token)
+ app_name = app["name"]
+ service_name = service["service_id"]
+ app["services"] ||= []
+ app["services"] << @service_alias
+ res = @client.update_app_state_internal(@droplets_uri, app_name, app, auth_json_hdr(token))
+ debug "binding result: #{res.content}"
+ res.status.should == 200
+ res.content
+ end
+
+ def unprovision_service(service = nil)
+ service = @service_detail if service.nil?
+ return if service.nil?
+ res = @client.unprovision_service_internal(@configuration_uri, service['service_id'], auth_json_hdr(@token))
+ res.status.should == 200
+ @service_detail = nil
+ end
+
+ def valid_services
+ %w(mysql mongodb redis rabbitmq)
+ end
+
+ def shutdown_service_node(service)
+ pid = service_node_pid(service)
+ %x[kill -9 #{pid}]
+ end
+
+ def service_node_pid(service)
+ node_process = service + "_node"
+ pid = %x[ps -ef|grep ruby|grep #{node_process}|grep -v grep|awk '{print $2}']
+ return pid
+ end
+
+ def start_service_node(service)
+ service_node_pid(service).should == ""
+ start_script = File.expand_path("../../../../#{service}/bin/#{service}_node", __FILE__)
+ gem_file = File.expand_path("../../../../#{service}/Gemfile", __FILE__)
+ debug "start script for #{service}:#{start_script}"
+ # FIXME Bundler.with_clean_env issue
+ Bundler.with_clean_env do
+ pid = spawn({"BUNDLE_GEMFILE"=>gem_file}, "#{start_script} >/tmp/vcap-run/#{service}_node.log 2>&1")
+ Process.detach(pid)
+ end
+ # check
+ service_node_pid(service).should_not == nil
+ end
+
+ def debug(msg)
+ if ENV['SERVICE_TEST']
+ puts "D: " + msg
+ end
+ end
+
+ # Parse the port of given service gateway. Only works if service gw running on the same host with test code.
+ def service_gateway_port(service)
+ gw_name = "#{service}_gateway"
+ pid = %x[ps -ef|grep 'ruby'|grep -v grep|grep '#{gw_name}'|awk '{ print $2}']
+ pid.strip!
+ output = %x[netstat -apn 2>/dev/null|grep -v grep|grep #{pid}| grep -v ESTABLISHED| awk '{print $4}']
+ ip_ports = output.split("\n")
+ debug "all ports: #{ip_ports}"
+ ip_ports.each do |i|
+ # GW return 400 for a request not using json as content-type
+ res= %x[curl -i #{i} 2>/dev/null|head -n 1|grep 400]
+ debug "Result of curl: #{res}"
+ if not res.empty?
+ return i
+ end
+ end
+ end
+end
View
112 tests/features/support/mysql_helper.rb
@@ -0,0 +1,112 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+require 'fileutils'
+require 'uri'
+require 'httpclient'
+
+module MysqlServiceHelper
+
+ def load_mysql_config
+ mysql_node_file = File.expand_path("../../../../mysql/config/mysql_node.yml", __FILE__)
+ mysql_gateway_file = File.expand_path("../../../../mysql/config/mysql_gateway.yml", __FILE__)
+ mysql_backup_file = File.expand_path("../../../../mysql/config/mysql_backup.yml", __FILE__)
+ begin
+ @config[:mysql_config] = File.open(mysql_node_file) do |f|
+ YAML.load(f)
+ end
+ @config[:mysql_config][:backup_config] = File.open(mysql_backup_file) do |f1|
+ YAML.load(f1)
+ end
+ @config[:mysql_config][:gateway_config] = File.open(mysql_gateway_file) do |f2|
+ YAML.load(f2)
+ end
+ rescue => e
+ raise "Could not read mysql node configuration file: #{e}"
+ end
+ end
+
+ def mysql_backup(service)
+ name = service["service_id"]
+ name.should_not == nil
+ folder = mysql_backup_folder(name)
+ FileUtils.rm_rf(folder)
+ File.exist?(folder).should == false
+ mysql_backup_bin = File.expand_path("../../../../mysql/bin/mysql_backup", __FILE__)
+ gem_file = File.expand_path("../../../../mysql/Gemfile", __FILE__)
+ # FIXME Bunlder.with_clean_env seems to have bug: https://github.com/carlhuda/bundler/issues/1133 Use explicit BUNDKE_GEMFILE as workaround
+ cmd = "export BUNDLE_GEMFILE=#{gem_file};#{mysql_backup_bin}"
+ Bundler.with_clean_env do
+ result = %x[#{cmd}]
+ debug "backup result: #{result}"
+ end
+ File.exist?(folder).should == true
+ end
+
+ def mysql_backup_folder(name)
+ nfs_path = @config[:mysql_config][:backup_config]["backup_path"]
+ path_prefix = 'backups'
+ full_path = File.join(nfs_path, path_prefix, "mysql", name[0,2], name[2,2], name[4,2], name)
+ end
+
+ def provision_mysql_service(token)
+ name = "mysql-service-test"
+ @service_alias = name
+ prov_request = {
+ :name => name,
+ :label=> 'mysql-5.1',
+ :plan=> 'free',
+ }
+ res = @client.provision_service_internal(@configuration_uri, prov_request, auth_json_hdr(token))
+ debug "Provision response: #{res.content}"
+ res.status.should == 200
+ service = parse_json(res.content)
+ service
+ end
+
+ def mysql_drop_service
+ name = @service_detail['service_id']
+ mysql_bin = @config[:mysql_config]["mysql_bin"]
+ host, port, user, pass = %w(host port user pass).map{|k| @config[:mysql_config]["mysql"][k]}
+ cmd ="echo 'drop database #{name}'| #{mysql_bin} -u#{user} -p#{pass} -h#{host} -P#{port}"
+ result = %x[#{cmd}]
+ end
+
+ def mysql_drop_service_from_db
+ name = @service_detail['service_id']
+ sqlite_bin = 'sqlite3'
+ file_uri = @config[:mysql_config]["local_db"]
+ uri = URI.parse(file_uri)
+ path = uri.path
+ cmd = "echo 'delete from vcap_services_mysql_node_provisioned_services where name = \"#{name}\";'|#{sqlite_bin} #{path}"
+ debug "drop local db cmd:#{cmd}"
+ result = %x[#{cmd}]
+ debug "drop local db cmd result:#{result}"
+ end
+
+ def mysql_recover
+ name = @service_detail['service_id']
+ ip_port = service_gateway_port("mysql")
+ debug "ip_port of gw: #{ip_port}"
+ recover_url = "http://#{ip_port}/service/internal/v1/recover"
+ token = @config[:mysql_config][:gateway_config]["token"]
+ hdr = {
+ 'X-VCAP-Service-Token' => token,
+ 'content-type' => "application/json",
+ }
+ # Find a sub directory in backup_folder
+ path = mysql_backup_folder(name)
+ subdir = nil
+ Dir.foreach(path) do |sub|
+ subdir = sub if not sub =~ /\./
+ end
+ subdir.should_not == nil
+ req = {
+ :instance_id => name,
+ :backup_path => File.join(path, subdir)
+ }
+ debug ("url: #{recover_url}. Header: #{hdr.inspect}. Body: #{req.inspect}")
+ res = HTTPClient.post(recover_url, req.to_json, hdr)
+ debug "Return of recover: #{res.content}"
+ res.status.should == 200
+ end
+
+end
View
72 tests/features/support/testconfig.yml
@@ -0,0 +1,72 @@
+---
+# Timeout value after we which we give up on AppCloud operations that are long running.
+# Within this value, we poll for completion status of the operations.
+timeout_secs: 60
+
+simple_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby foo.rb"
+
+service_test_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby server.rb"
+
+tiny_java_app:
+ framework: "spring_web/1.0"
+ startup: "thin start"
+
+simple_db_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby simple_db_app.rb"
+
+redis_lb_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby redis_lb.rb"
+
+env_test_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby env_test.rb"
+
+broken_app:
+ framework: "http://b20nine.com/unknown"
+ startup: "ruby broken.rb"
+
+rails3_app:
+ framework: "rails/1.0"
+ startup: "thin start"
+ memory: 256
+
+jpa_app:
+ framework: "spring_web/1.0"
+ startup: "thin start"
+ memory: 256
+
+hibernate_app:
+ framework: "spring_web/1.0"
+ startup: "thin start"
+ memory: 256
+
+dbrails_app:
+ framework: "rails/1.0"
+ startup: "thin start"
+ memory: 256
+
+dbrails_broken_app:
+ framework: "rails/1.0"
+ startup: "thin start"
+ memory: 256
+
+grails_app:
+ framework: "grails/1.0"
+ startup: "thin start"
+ memory: 512
+
+roo_app:
+ framework: "spring_web/1.0"
+ startup: "thin start"
+ memory: 256
+
+mochiweb_test:
+ framework: "otp_rebar"
+ startup: erlangR14B02
+ memory: 64
View
216 tests/lib/client/lib/vmc_base.rb
@@ -0,0 +1,216 @@
+require 'digest/sha1'
+require 'fileutils'
+require 'tempfile'
+require 'tmpdir'
+
+# self contained
+$:.unshift File.expand_path('../../vendor/gems/json/lib', __FILE__)
+$:.unshift File.expand_path('../../vendor/gems/httpclient/lib', __FILE__)
+$:.unshift File.expand_path('../../vendor/gems/rubyzip2/lib', __FILE__)
+
+require 'json/pure'
+require 'httpclient'
+require 'zip/zipfilesystem'
+
+# This class captures the common interactions with AppCloud that can be shared by different
+# clients. Clients that use this class are the VMC CLI and the integration test automation.
+# TBD - ABS: This is currently a minimal extraction of methods to tease out
+# the interactive aspects of the VMC CLI from the AppCloud API calls.
+# Update service related API to new version.
+
+module VMC; end
+
+class VMC::BaseClient
+
+ def register_internal(base_uri, email, password, auth_hdr = {})
+ response = HTTPClient.post("#{base_uri}/users", {:email => email, :password => password}.to_json, auth_hdr)
+ raise(JSON.parse(response.content)['description'] || 'registration failed') if response.status != 200 && response.status != 204
+ end
+
+ def login_internal(base_uri, email, password)
+ response = HTTPClient.post "#{base_uri}/users/#{email}/tokens", {:password => password}.to_json
+ raise "login failed" if response.status != 200
+ token = JSON.parse(response.content)['token']
+ end
+
+ def change_passwd_internal(base_uri, user_info, auth_hdr)
+ email = user_info['email']
+ response = HTTPClient.put("#{base_uri}/users/#{email}", user_info.to_json, auth_hdr)
+ raise(JSON.parse(response.content)['description'] || 'password change failed') if response.status != 200 && response.status != 204
+ end
+
+ def get_user_internal(base_uri, email, auth_hdr)
+ response = HTTPClient.get "#{base_uri}/users/#{email}", nil, auth_hdr
+ end
+
+ def delete_user_internal(base_uri, email, auth_hdr)
+ response = HTTPClient.delete "#{base_uri}/users/#{email}", auth_hdr
+ end
+
+ def get_apps_internal(droplets_uri, auth_hdr)
+ response = HTTPClient.get droplets_uri, nil, auth_hdr
+ raise "(#{response.status}) can not contact server" if (response.status > 500 || response.status == 404)
+ raise "Access Denied, please login or register" if response.status == 403
+ droplets_full = JSON.parse(response.content)
+ rescue => e
+ error "Problem executing list command, #{e}"
+ end
+
+ def create_app_internal(droplets_uri, app_manifest, auth_hdr)
+ response = HTTPClient.post droplets_uri, app_manifest.to_json, auth_hdr
+ # Auto redirection
+ if response.status == 302
+ location = response.header["Location"][0]
+ res = HTTPClient.get location, nil, auth_hdr
+ return res
+ else
+ return nil
+ end
+ end
+
+ def get_app_internal(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.get "#{droplets_uri}/#{appname}", nil, auth_hdr
+ end
+
+ def delete_app_internal(droplets_uri, appname, services_to_delete, auth_hdr)
+ HTTPClient.delete "#{droplets_uri}/#{appname}", auth_hdr
+ services_to_delete.each { |service_name|
+ HTTPClient.delete "#{services_uri}/#{service_name}", auth_hdr
+ }
+ end
+
+ def upload_app_bits(resources_uri, droplets_uri, appname, auth_hdr, opt_war_file, provisioned_db = false)
+# puts "[#{resources_uri}]:[#{droplets_uri}]:[#{appname}]:[#{auth_hdr}]:[#{opt_war_file}]:[#{provisioned_db}]"
+ explode_dir = "#{Dir.tmpdir}/.vcap_#{appname}_files"
+ FileUtils.rm_rf(explode_dir) # Make sure we didn't have anything left over..
+
+ # Stage the app appropriately and do the appropriate fingerprinting, etc.
+ if opt_war_file
+ Zip::ZipFile.foreach(opt_war_file) { |zentry|
+ epath = "#{explode_dir}/#{zentry}"
+ FileUtils.mkdir_p(File.dirname(epath)) unless (File.exists?(File.dirname(epath)))
+ zentry.extract("#{explode_dir}/#{zentry}")
+ }
+ else
+ FileUtils.cp_r('.', explode_dir)
+ end
+
+ # Send the resource list to the cloud controller, the response will tell us what it already has..
+ fingerprints = []
+ resource_files = Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH)
+ resource_files.each { |filename|
+ fingerprints << { :size => File.size(filename),
+ :sha1 => Digest::SHA1.file(filename).hexdigest,
+ :fn => filename # TODO(dlc) probably should not send over the wire
+ } unless (File.directory?(filename) || !File.exists?(filename))
+ }
+
+ # Send resource fingerprints to the cloud controller
+ response = HTTPClient.post resources_uri, fingerprints.to_json, auth_hdr
+ appcloud_resources = nil
+ if response.status == 200
+ appcloud_resources = JSON.parse(response.content)
+ # we will use the exploded version of the files here to whip through and delete what we
+ # will have appcloud fill in for us.
+ appcloud_resources.each { |resource| FileUtils.rm_f resource['fn'] }
+ end
+
+ # Perform Packing of the upload bits here.
+ upload_file = "#{Dir.tmpdir}/#{appname}.zip"
+ FileUtils.rm_f(upload_file)
+ exclude = ['..', '*~', '#*#', '*.log']
+ exclude << '*.sqlite3' if provisioned_db
+ Zip::ZipFile::open(upload_file, true) { |zf|
+ Dir.glob("#{explode_dir}/**/*", File::FNM_DOTMATCH).each { |f|
+ process = true
+ exclude.each { |e| process = false if File.fnmatch(e, File.basename(f)) }
+ zf.add(f.sub("#{explode_dir}/",''), f) if (process && File.exists?(f))
+ }
+ }
+
+ upload_size = File.size(upload_file);
+
+ upload_data = {:application => File.new(upload_file, 'rb'), :_method => 'put'}
+ if appcloud_resources
+ # Need to adjust filenames sans the explode_dir prefix
+ appcloud_resources.each { |ar| ar['fn'].sub!("#{explode_dir}/", '') }
+ upload_data[:resources] = appcloud_resources.to_json
+ end
+
+ response = HTTPClient.post "#{droplets_uri}/#{appname}/application", upload_data, auth_hdr
+ raise "Problem uploading application bits" if response.status != 200
+ upload_size
+
+ ensure
+ # Cleanup if we created an exploded directory.
+ FileUtils.rm_f(upload_file)
+ FileUtils.rm_rf(explode_dir)
+ end
+
+ def update_app_state_internal droplets_uri, appname, appinfo, auth_hdr
+ hdrs = auth_hdr.merge({'content-type' => 'application/json'})
+ response = HTTPClient.put "#{droplets_uri}/#{appname}", appinfo.to_json, hdrs
+ end
+
+ def get_app_instances_internal(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.get "#{droplets_uri}/#{appname}/instances", nil, auth_hdr
+ instances_info = JSON.parse(response.content)
+ end
+
+ def get_app_files_internal(droplets_uri, appname, instance, path, auth_hdr)
+ cc_url = "#{droplets_uri}/#{appname}/instances/#{instance}/files/#{path}"
+ cc_url.gsub!('files//', 'files/')
+ response = HTTPClient.get cc_url, nil, auth_hdr
+ end
+
+ def get_app_crashes_internal(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.get "#{droplets_uri}/#{appname}/crashes", nil, auth_hdr
+ end
+
+ def get_app_stats_internal(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.get "#{droplets_uri}/#{appname}/stats", nil, auth_hdr
+ end
+
+ def update_app_internal(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.put "#{droplets_uri}/#{appname}/update", '', auth_hdr
+ end
+
+ def get_update_app_status(droplets_uri, appname, auth_hdr)
+ response = HTTPClient.get "#{droplets_uri}/#{appname}/update", nil, auth_hdr
+ raise "Problem updating application" if response.status != 200
+ response
+ end
+
+ def provision_service_internal(config_uri, request, auth_hdr)
+ response = HTTPClient.post config_uri, request.to_json, auth_hdr
+ end
+
+ def bind_service_internal(binding_uri, request, auth_hdr)
+ response = HTTPClient.post binding_uri, request.to_json, auth_hdr
+ end
+
+ def unprovision_service_internal(config_uri, service_id, auth_hdr)
+ uri = config_uri + '/' + service_id
+ response = HTTPClient.delete uri, auth_hdr
+ end
+
+ def add_service_internal(services_uri, service_manifest, auth_hdr)
+ response = HTTPClient.post services_uri, service_manifest.to_json, auth_hdr
+ end
+
+ def remove_service_internal(services_uri, service_id, auth_hdr)
+ HTTPClient.delete "#{services_uri}/#{service_id}", auth_hdr
+ end
+
+ def delete_services_internal(services_uri, services, auth_hdr)
+ services.each do |service_name|
+ HTTPClient.delete "#{services_uri}/#{service_name}", auth_hdr
+ end
+ end
+
+ def error(msg)
+ STDERR.puts(msg)
+ end
+end
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.