diff --git a/atmos/Gemfile b/atmos/Gemfile new file mode 100644 index 00000000..eda7689a --- /dev/null +++ b/atmos/Gemfile @@ -0,0 +1,22 @@ +source :rubygems + +gem "eventmachine" +gem "em-http-request" +gem "ruby-hmac" +gem "uuidtools" +gem "datamapper", ">= 0.10.2" +gem "do_sqlite3" +gem "dm-sqlite-adapter" +gem "sinatra" +gem "thin" +gem "xml-simple" + +gem 'vcap_common', :path => '../../common' +gem 'vcap_logging', '>=0.1.3', :require => ['vcap/logging'] + +group :test do + gem "rake" + gem "rspec" + gem "rcov" + gem "ci_reporter" +end diff --git a/atmos/Gemfile.lock b/atmos/Gemfile.lock new file mode 100644 index 00000000..e0006161 --- /dev/null +++ b/atmos/Gemfile.lock @@ -0,0 +1,129 @@ +PATH + remote: ../../common + specs: + vcap_common (0.99) + eventmachine (~> 0.12.10) + logging (>= 1.5.0) + nats + posix-spawn + thin + yajl-ruby + +GEM + remote: http://rubygems.org/ + specs: + addressable (2.2.4) + bcrypt-ruby (2.1.4) + builder (3.0.0) + ci_reporter (1.6.4) + builder (>= 2.1.2) + daemons (1.1.2) + 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) + diff-lcs (1.1.2) + 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-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_sqlite3 (0.10.3) + data_objects (= 0.10.3) + em-http-request (0.3.0) + addressable (>= 2.0.0) + escape_utils + eventmachine (>= 0.12.9) + escape_utils (0.2.3) + eventmachine (0.12.10) + fastercsv (1.5.4) + json (1.4.6) + json_pure (1.5.1) + little-plugger (1.1.2) + logging (1.5.0) + little-plugger (>= 1.1.2) + nats (0.4.10) + daemons (>= 1.1.0) + eventmachine (>= 0.12.10) + json_pure (>= 1.5.1) + posix-spawn (0.3.6) + rack (1.2.2) + rake (0.8.7) + rcov (0.9.9) + rspec (2.5.0) + rspec-core (~> 2.5.0) + rspec-expectations (~> 2.5.0) + rspec-mocks (~> 2.5.0) + rspec-core (2.5.1) + rspec-expectations (2.5.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.5.0) + ruby-hmac (0.4.0) + sinatra (1.2.1) + rack (~> 1.1) + tilt (< 2.0, >= 1.2.2) + 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) + vcap_logging (0.1.3) + xml-simple (1.0.15) + yajl-ruby (0.8.3) + +PLATFORMS + ruby + +DEPENDENCIES + ci_reporter + datamapper (>= 0.10.2) + dm-sqlite-adapter + do_sqlite3 + em-http-request + eventmachine + rake + rcov + rspec + ruby-hmac + sinatra + thin + uuidtools + vcap_common! + vcap_logging (>= 0.1.3) + xml-simple diff --git a/atmos/Rakefile b/atmos/Rakefile new file mode 100644 index 00000000..07ac2e80 --- /dev/null +++ b/atmos/Rakefile @@ -0,0 +1,39 @@ +require 'rake' + +desc "Run specs" +task "spec" => ["bundler:install:test", "test:spec"] + +desc "Run specs using RCov" +task "spec:rcov" => ["bundler:install:test", "test:spec:rcov"] + +namespace "bundler" do + desc "Install gems" + task "install" do + sh("bundle install") + end + + desc "Install gems for test" + task "install:test" do + sh("bundle install --without development production") + end + + desc "Install gems for production" + task "install:production" do + sh("bundle install --without development test") + end + + desc "Install gems for development" + task "install:development" do + sh("bundle install --without test production") + end +end + +namespace "test" do + task "spec" do |t| + sh("cd spec && ../../base/bin/nats-util start && rake spec && ../../base/bin/nats-util stop") + end + + task "spec:rcov" do |t| + sh("cd spec && rake spec:rcov") + end +end diff --git a/atmos/bin/atmos_gateway b/atmos/bin/atmos_gateway new file mode 100755 index 00000000..fe0dc5d8 --- /dev/null +++ b/atmos/bin/atmos_gateway @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby +# -*- mode: ruby -*- +# +# Copyright (c) 2009-2011 VMware, Inc. + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) + +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', 'base', 'lib') +require 'base/gateway' + +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') +require 'atmos_service/provisioner' +require 'atmos_service/atmos_helper' + +class VCAP::Services::Atmos::Gateway < VCAP::Services::Base::Gateway + + def provisioner_class + VCAP::Services::Atmos::Provisioner + end + + def default_config_file + File.join(File.dirname(__FILE__), '..', 'config', 'atmos_gateway.yml') + end + + def additional_options + {:atmos => @config[:atmos]} + end +end + +VCAP::Services::Atmos::Gateway.new.start + + diff --git a/atmos/config/atmos_gateway.yml b/atmos/config/atmos_gateway.yml new file mode 100644 index 00000000..32b97225 --- /dev/null +++ b/atmos/config/atmos_gateway.yml @@ -0,0 +1,29 @@ +--- +#cloud_controller_uri: api.vcap.me +service: + name: atmos + version: "1.4" + description: 'Atmos object store' + plans: ['free'] + tags: ['atmos', 'atmos-1.4', 'object store'] +ip_route: localhost +#proxy: +# host: proxy +# port: 8080 +# keepalive: true +index: 0 +token: "0xdeadbeef" +mbus: nats://localhost:4222 +logging: + level: debug +pid: /var/vcap/sys/run/atmos_service.pid + +# atmos configuration +# Not configured by default as it would require revealing +# credentials to a non-local resource. +atmos: + host: "127.0.0.1" + port: "443" + tenant: "tenant" + tenantadmin: "admin" + tenantpasswd: "password" diff --git a/atmos/lib/atmos_service/atmos_error.rb b/atmos/lib/atmos_service/atmos_error.rb new file mode 100644 index 00000000..bf9efeab --- /dev/null +++ b/atmos/lib/atmos_service/atmos_error.rb @@ -0,0 +1,17 @@ +# Copyright (c) 2009-2011 VMware, Inc. +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..', 'base', 'lib') + +require "base/service_error" + +module VCAP + module Services + module Atmos + class AtmosError < VCAP::Services::Base::Error::ServiceError + ATMOS_BACKEND_ERROR_CREATE_SUBTENAT = [31801, HTTP_INTERNAL, 'Atmos create subtenant error. Atmos error code: %s.'] + ATMOS_BACKEND_ERROR_DELETE_SUBTENAT = [31802, HTTP_INTERNAL, 'Atmos delete subtenant error. Atmos error code: %s.'] + ATMOS_BACKEND_ERROR_CREATE_USER = [31803, HTTP_INTERNAL, 'Atmos create user error. Atmos error code: %s.'] + ATMOS_BACKEND_ERROR_DELETE_USER = [31804, HTTP_INTERNAL, 'Atmos delete user error. Atmos error code: %s.'] + end + end + end +end diff --git a/atmos/lib/atmos_service/atmos_helper.rb b/atmos/lib/atmos_service/atmos_helper.rb new file mode 100644 index 00000000..2f03495b --- /dev/null +++ b/atmos/lib/atmos_service/atmos_helper.rb @@ -0,0 +1,143 @@ +# Copyright (c) 2009-2011 VMware, Inc. +require 'xmlsimple' +require 'net/http' +require 'atmos_error' + +class VCAP::Services::Atmos::Helper + + include VCAP::Services::Atmos + + def initialize(atmos_config, logger) + @logger = logger + + @host = atmos_config[:host] + @tenant = atmos_config[:tenant] + @tenantadmin = atmos_config[:tenantadmin] + @tenantpasswd = atmos_config[:tenantpasswd] + @port = atmos_config[:port] + end + + + def create_subtenant(name) + uri = URI.parse("http://#{@host}/sysmgmt/tenants/#{@tenant}/subtenants") + + headers = { 'x-atmos-authsource' => 'local', + 'x-atmos-tenantadmin' => @tenantadmin, + 'x-atmos-tenantadminpassword' => @tenantpasswd, + 'x-atmos-authtype' => 'password', + 'x-atmos-subtenantname' => name + } + req = Net::HTTP::Post.new(uri.request_uri) + + req["accept"] = '*/*' + headers.keys.each {|f| req.add_field(f, headers[f])} + + @logger.debug "create subtenant #{name} uri: #{uri.inspect} req: #{req.inspect} hdrs: #{headers.inspect}" + + http = Net::HTTP.new(uri.host, @port.to_i) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + res = http.request(req) + + # Response Body is like: + # + # 2e705c1a08e5412fb7231055d56733e0 + + if res.code == "201" + id = XmlSimple.xml_in(res.body, { 'KeyAttr' => 'subtenantId' }) + return id + end + raise AtmosError.new(AtmosError::ATMOS_BACKEND_ERROR_CREATE_SUBTENAT, res.code) + end + + def delete_subtenant(name) + uri = URI.parse("http://#{@host}/sysmgmt/tenants/#{@tenant}/subtenants/#{name}") + + headers = { 'x-atmos-tenantadmin' => @tenantadmin, + 'x-atmos-tenantadminpassword' => @tenantpasswd, + 'x-atmos-authtype' => 'password' + } + req = Net::HTTP::Delete.new(uri.request_uri) + + req["accept"] = '*/*' + headers.keys.each {|f| req.add_field(f, headers[f])} + + @logger.debug "delete subtenant #{name}" + + http = Net::HTTP.new(uri.host, @port.to_i) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + res = http.request(req) + + # Response Body is like: + # + # true + + raise AtmosError.new(AtmosError::ATMOS_BACKEND_ERROR_DELETE_SUBTENAT, res.code) unless res.code == "200" + true + end + + def create_user(username, subtenant_name) + uri = URI.parse("http://#{@host}/sysmgmt/tenants/#{@tenant}/subtenants/#{subtenant_name}/uids") + + headers = { 'x-atmos-tenantadmin' => @tenantadmin, + 'x-atmos-tenantadminpassword' => @tenantpasswd, + 'x-atmos-authtype' => 'password', + 'x-atmos-uid' => username + } + + req = Net::HTTP::Post.new(uri.request_uri) + req["accept"] = '*/*' + headers.keys.each {|f| req.add_field(f, headers[f])} + + @logger.debug "create user #{username} under subtenant #{subtenant_name} in tenant #{@tenant}" + @logger.debug uri + + http = Net::HTTP.new(uri.host, @port.to_i) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + res = http.request(req) + + # Response Body is like: + # + # jzpKHy5ee28jMpTiiqkmTa5vdu8= + + if res.code == "200" + shared_secret = XmlSimple.xml_in(res.body, { 'KeyAttr' => 'sharedSecret' }) + return shared_secret + end + raise AtmosError.new(AtmosError::ATMOS_BACKEND_ERROR_CREATE_USER, res.code) + end + + def delete_user(username, subtenant_name) + uri = URI.parse("http://#{@host}/sysmgmt/tenants/#{@tenant}/subtenants/#{subtenant_name}/uids/#{username}") + + headers = { 'x-atmos-tenantadmin' => @tenantadmin, + 'x-atmos-tenantadminpassword' => @tenantpasswd, + 'x-atmos-authtype' => 'password' + } + + req = Net::HTTP::Delete.new(uri.request_uri) + req["accept"] = '*/*' + headers.keys.each {|f| req.add_field(f, headers[f])} + + @logger.debug "delete user #{username} under subtenant #{subtenant_name} in tenant #{@tenant}" + @logger.debug uri + + http = Net::HTTP.new(uri.host, @port.to_i) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + res = http.request(req) + + # Response Body: + # + # true + + raise AtmosError.new(AtmosError::ATMOS_BACKEND_ERROR_DELETE_USER, res.code) unless res.code == "200" + true + end +end diff --git a/atmos/lib/atmos_service/common.rb b/atmos/lib/atmos_service/common.rb new file mode 100644 index 00000000..7728d65c --- /dev/null +++ b/atmos/lib/atmos_service/common.rb @@ -0,0 +1,12 @@ +# Copyright (c) 2009-2011 VMware, Inc. +module VCAP + module Services + module Atmos + module Common + def service_name + "AtmosaaS" + end + end + end + end +end diff --git a/atmos/lib/atmos_service/provisioner.rb b/atmos/lib/atmos_service/provisioner.rb new file mode 100644 index 00000000..29b6a61f --- /dev/null +++ b/atmos/lib/atmos_service/provisioner.rb @@ -0,0 +1,154 @@ +# Copyright (c) 2009-2011 VMware, Inc. +$:.unshift File.join(File.dirname(__FILE__), '.') + +require "base/provisioner" +require "atmos_service/common" +require "uuidtools" +require "atmos_helper" +require "atmos_error" + +class VCAP::Services::Atmos::Provisioner < VCAP::Services::Base::Provisioner + + include VCAP::Services::Atmos::Common + include VCAP::Services::Atmos + + ATMOS_CONFIG_FILE = File.expand_path("../../../config/atmos_gateway.yml", __FILE__) + + def to_s + "VCAP::Services::Atmos::Provisioner instance: #{@atmos_config.inspect}" + end + + def get_atmos_config + config_file = YAML.load_file(ATMOS_CONFIG_FILE) + config = VCAP.symbolize_keys(config_file) + config[:atmos] + end + + def initialize(options) + super(options) + @atmos_config = options[:additional_options][:atmos] || get_atmos_config + @logger.debug "atmos_config: #{@atmos_config.inspect}" + + @host = @atmos_config[:host] + @port = @atmos_config[:port] + + @atmos_helper = VCAP::Services::Atmos::Helper.new(@atmos_config, @logger) + end + + def provision_service(request, prov_handle=nil, &blk) + @logger.debug("[#{service_description}] Attempting to provision instance (request=#{request.extract})") + begin + st_name = UUIDTools::UUID.random_create.to_s + st_id = @atmos_helper.create_subtenant(st_name) + + # should we create subtenant admin rather than uid here? + token = UUIDTools::UUID.random_create.to_s + shared_secret = @atmos_helper.create_user(token, st_name) + + svc = { + :data => {:subtenant_name => st_name, :subtenant_id => st_id, :host => @host}, + :service_id => st_name, + :credentials => {:host => @host, :port => @port, :token => token, + :shared_secret => shared_secret, :subtenant_id => st_id} + } + @logger.debug("Service provisioned: #{svc.inspect}") + @prov_svcs[svc[:service_id]] = svc + blk.call(success(svc)) + rescue => e + # roll back work + @logger.warn 'provision error, trying to roll back if necessary' + begin + @atmos_helper.delete_subtenant(st_name) if st_id + rescue => e1 + @logger.info 'roll back error' + end + if e.instance_of? AtmosError + blk.call(failure(e)) + else + @logger.warn(e) + blk.call(internal_fail) + end + end + end + + def unprovision_service(instance_id, &blk) + @logger.debug("[#{service_description}] Attempting to unprovision instance (instance id=#{instance_id}") + begin + success = @atmos_helper.delete_subtenant(instance_id) + if success + bindings = find_all_bindings(instance_id) + @logger.debug("unprovision service: #{instance_id} ") + @prov_svcs.delete(instance_id) + bindings.each do |b| + @logger.debug("delete binded user: #{b[:service_id]} ") + @prov_svcs.delete(b[:service_id]) + end + end + blk.call(success()) + rescue => e + if e.instance_of? AtmosError + blk.call(failure(e)) + else + @logger.warn(e) + blk.call(internal_fail) + end + end + end + + def bind_instance(instance_id, binding_options, bind_handle=nil, &blk) + @logger.debug("attempting to bind service: #{instance_id}") + if instance_id.nil? + @logger.warn("#{instance_id} is null!") + blk.call(internal_fail) + end + + begin + svc = @prov_svcs[instance_id] + raise "#{instance_id} not found!" if svc.nil? + @logger.debug("svc[data]: #{svc[:data]}") + + token = UUIDTools::UUID.random_create.to_s + shared_secret = @atmos_helper.create_user(token, instance_id) + + res = { + :service_id => token, + :configuration => svc[:data], + :credentials => {:host => @host, :port => @port, :token => token, + :shared_secret => shared_secret, :subtenant_id => svc[:data][:subtenant_id]} + } + @logger.debug("binded: #{res.inspect}") + @prov_svcs[res[:service_id]] = res + blk.call(success(res)) + rescue => e + if e.instance_of? AtmosError + blk.call(failure(e)) + else + @logger.warn(e) + blk.call(internal_fail) + end + end + end + + def unbind_instance(instance_id, handle_id, binding_options, &blk) + @logger.debug("attempting to unbind service: #{instance_id}") + begin + raise "instance_id cannot be nil" if instance_id.nil? + svc = @prov_svcs[handle_id] + raise "#{handle_id} not found!" if svc.nil? + + configuration = (svc[:configuration].nil?) ? svc[:data] : svc[:configuration] + @logger.debug("svc[configuration]: #{configuration}") + success = @atmos_helper.delete_user(handle_id, instance_id) + @prov_svcs.delete(handle_id) if success + blk.call(success()) + rescue => e + if e.instance_of? AtmosError + blk.call(failure(e)) + else + @logger.warn(e) + blk.call(internal_fail) + end + end + end + +end diff --git a/atmos/spec/Rakefile b/atmos/spec/Rakefile new file mode 100644 index 00000000..79753f79 --- /dev/null +++ b/atmos/spec/Rakefile @@ -0,0 +1,41 @@ +require 'rake' +require 'tempfile' + +require 'rubygems' +require 'bundler/setup' +Bundler.require(:default, :test) + +require 'rspec' +require 'rspec/core/rake_task' +require 'ci/reporter/rake/rspec' + +coverage_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_coverage")) +reports_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_reports")) +dump_file = File.join(Dir.tmpdir, "atmos.rcov") +ignore_pattern = 'spec,[.]bundle,[/]gems[/]' + +ENV['CI_REPORTS'] = reports_dir + +desc "Run specs using RCov" +task "spec:rcov" => ["ci:setup:rspec", "spec:rcov_internal", "convert_rcov_to_clover"] + +RSpec::Core::RakeTask.new do |t| + t.pattern = "**/*_spec.rb" + t.rspec_opts = ["--format", "documentation", "--colour"] +end + +desc "Run specs using RCov (internal, use spec:rcov instead)" +RSpec::Core::RakeTask.new("spec:rcov_internal") do |t| + sh("rm -f #{dump_file}") + t.pattern = "**/*_spec.rb" + t.rspec_opts = ["--format", "progress", "--colour"] + t.rcov = true + t.rcov_opts = ['--aggregate', dump_file, '--exclude', ignore_pattern, '--output', coverage_dir] +end + +task "convert_rcov_to_clover" do |t| + analyzer = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "tests", "common", "rcov_analyzer.rb")) + clover_output = File.join(coverage_dir, "clover.xml") + sh("ruby #{analyzer} #{dump_file} #{ignore_pattern} > #{clover_output}") + sh("rm -f #{dump_file}") +end diff --git a/atmos/spec/atmos_gateway_spec.rb b/atmos/spec/atmos_gateway_spec.rb new file mode 100644 index 00000000..4962a511 --- /dev/null +++ b/atmos/spec/atmos_gateway_spec.rb @@ -0,0 +1,194 @@ +# Copyright (c) 2009-2011 VMware, Inc. +$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') +$LOAD_PATH.unshift(File.expand_path("../../../base/lib", __FILE__)) + +require File.dirname(__FILE__) + '/spec_helper' +require "atmos_service/provisioner" +require "atmos_service/atmos_helper" +require "uuidtools" + +require "atmos_rest_client" + +include VCAP::Services::Atmos + +describe VCAP::Services::Atmos::Provisioner do + + before :all do + @run_tests = check_provisioner_config + @config = get_provisioner_config + @logger = @config[:logger] + @logger.debug @config + + @atmos_helper = Helper.new(@config[:additional_options][:atmos], @logger) + end + + it "should successfully new VCAP::Services::Atmos::Provisioner instance" do + EM.run do + @sg = Provisioner.new(@config) + @logger.debug @sg + @sg.should_not be_nil + EM.stop + end + end + + describe "provision_bind_unbind" do + before :all do + @subtenant_name_p = UUIDTools::UUID.random_create.to_s + @subtenant_name_p1 = UUIDTools::UUID.random_create.to_s + @token = UUIDTools::UUID.random_create.to_s + end + + it "should successfully create atmos subtenant" do + subtenant_id = @atmos_helper.create_subtenant(@subtenant_name_p) + subtenant_id.should_not be_nil + end + + it "should successfully create token under a subtenant" do + shared_secret = @atmos_helper.create_user(@token, @subtenant_name_p) + @logger.debug "token: " + @token + ", shared_secret: " + shared_secret + shared_secret.should_not be_nil + end + + it "should successfully delete token under a subtenant" do + success = @atmos_helper.delete_user(@token, @subtenant_name_p) + success.should == true + end + + it "should successfully create object after bind" do + subtenant_id = @atmos_helper.create_subtenant(@subtenant_name_p1) + subtenant_id.should_not be_nil + shared_secret = @atmos_helper.create_user(@token, @subtenant_name_p1) + @logger.debug "token: " + @token + ", shared_secret: " + shared_secret + shared_secret.should_not be_nil + host = @config[:additional_options][:atmos][:host] + port = @config[:additional_options][:atmos][:port] + + opts = { + :url => "http://" + host + ":" + port, + :sid => subtenant_id, + :uid => @token, + :key => shared_secret, + } + client = AtmosClient.new(opts) + obj = UUIDTools::UUID.random_create.to_s + res = client.create_obj(obj) + res.should_not == Net::HTTPForbidden + + id = res['location'] + @logger.debug "object: " + obj + " created at: #{id}" + res = client.get_obj(id) + res.should_not == Net::HTTPForbidden + + @logger.debug "response of reading object: #{res.body}" + obj_same = obj == res.body + obj_same.should == true + + res = client.delete_obj(id) + res.should_not == Net::HTTPForbidden + @logger.debug "response of deleting file: #{res}" + end + + after :all do + if @run_tests + @atmos_helper.delete_subtenant(@subtenant_name_p) + @atmos_helper.delete_subtenant(@subtenant_name_p1) + end + end + end + + describe "multi-tenancy" do + before :all do + @subtenant_name1 = UUIDTools::UUID.random_create.to_s + @subtenant_name2 = UUIDTools::UUID.random_create.to_s + @token = UUIDTools::UUID.random_create.to_s + end + + it "should isolate between different subtenants" do + subtenant_id1 = @atmos_helper.create_subtenant(@subtenant_name1) + subtenant_id2 = @atmos_helper.create_subtenant(@subtenant_name2) + subtenant_id1.should_not be_nil + subtenant_id2.should_not be_nil + + shared_secret1 = @atmos_helper.create_user(@token, @subtenant_name1) + shared_secret2 = @atmos_helper.create_user(@token, @subtenant_name2) + shared_secret1.should_not be_nil + shared_secret2.should_not be_nil + + host = @config[:additional_options][:atmos][:host] + port = @config[:additional_options][:atmos][:port] + + opts = { + :url => "http://" + host + ":" + port, + :sid => subtenant_id1, + :uid => @token, + :key => shared_secret2, + } + client = AtmosClient.new(opts) + res = client.create_obj("obj") + @logger.debug res.to_s + same_class = res == Net::HTTPForbidden || res['location'].nil? + same_class.should == true + + opts = { + :url => "http://" + host + ":" + port, + :sid => subtenant_id2, + :uid => @token, + :key => shared_secret1, + } + client = AtmosClient.new(opts) + res = client.create_obj("obj") + @logger.debug res.to_s + same_class = res == Net::HTTPForbidden || res['location'].nil? + same_class.should == true + end + + after :all do + if @run_tests + @atmos_helper.delete_subtenant(@subtenant_name1) + @atmos_helper.delete_subtenant(@subtenant_name2) + end + end + end + + describe "null credential" do + before :all do + @subtenant_name = UUIDTools::UUID.random_create.to_s + end + + it "should prevent null credential from login" do + subtenant_id = @atmos_helper.create_subtenant(@subtenant_name) + subtenant_id.should_not be_nil + host = @config[:additional_options][:atmos][:host] + port = @config[:additional_options][:atmos][:port] + + opts = { + :url => "http://" + host + ":" + port, + :sid => subtenant_id, + :uid => "", + :key => "", + } + client = AtmosClient.new(opts) + res = client.create_obj("obj") + @logger.debug res.to_s + same_class = res == Net::HTTPForbidden || res['location'].nil? + same_class.should == true + end + + after :all do + @atmos_helper.delete_subtenant(@subtenant_name) if @run_tests + end + end + + describe "unprovision" do + before :all do + @subtenant_name_up = UUIDTools::UUID.random_create.to_s + @atmos_helper.create_subtenant(@subtenant_name_up) if @run_tests + end + + it "should successfully delete atmos subtenant" do + @logger.debug "subtenant_name: " + @subtenant_name_up + success = @atmos_helper.delete_subtenant(@subtenant_name_up) + success.should == true + end + end +end diff --git a/atmos/spec/atmos_rest_client.rb b/atmos/spec/atmos_rest_client.rb new file mode 100644 index 00000000..d419fbc6 --- /dev/null +++ b/atmos/spec/atmos_rest_client.rb @@ -0,0 +1,115 @@ +# Copyright (c) 2009-2011 VMware, Inc. +require 'net/http' +require 'net/https' +require 'uri' +require 'time' +require 'openssl' +require 'base64' + +# A very simple Atmos client which focus on basic file operations +# using namespace method. +class AtmosClient + include Digest + include OpenSSL + + REQUIRED_OPTS = %w(url sid uid key) + HEADERS = { + :date => 'date', + :emc_date => 'x-emc-date', + :sign => 'x-emc-signature', + :uid => 'x-emc-uid', + :type => 'content-type', + :extent => 'Extent', + } + def initialize(opts) + @opts = opts + # http server + url = URI.parse(@opts[:url]) + @http = Net::HTTP.new(url.host, url.port) + @http.use_ssl = true if (url.port == 443 || url.port == 10080) + @http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + def get_obj_ns(path='') + path = '/rest/namespace/'+ path + req = Net::HTTP::Get.new(path) + send_request(req) + end + + def get_obj(id) + return unless id + req = Net::HTTP::Get.new(id) + send_request(req) + end + + def create_obj_ns(path='', content=nil) + path = '/rest/namespace/'+ path + req = Net::HTTP::Post.new(path) + req.body = content + send_request(req) + end + + def create_obj(content=nil) + req = Net::HTTP::Post.new('/rest/objects') + req.body = content + send_request(req) + end + + def delete_obj_ns(path='') + path = '/rest/namespace/'+ path + req = Net::HTTP::Delete.new(path) + send_request(req) + end + + def delete_obj(id) + return unless id + req = Net::HTTP::Delete.new(id) + send_request(req) + end + +protected + + def send_request(req) + prepare_request(req) + res = @http.start do |http| + http.request(req) + end + return res + end + + def prepare_request(request) + t = Time.now.httpdate + request[HEADERS[:emc_date]] = t + request[HEADERS[:date]] = t + request[HEADERS[:type]] = 'application/octet-stream' + request[HEADERS[:uid]] = "#{@opts[:sid]}/#{@opts[:uid]}" + request[HEADERS[:sign]] = gen_auth_header(request) + end + + def gen_auth_header(request) + hash_string = "#{request.method}\n"+ + "#{request[HEADERS[:type]]}\n" + + "#{request[HEADERS[:extent]]}\n"+ + "#{request[HEADERS[:date]]}\n"+ + "#{request.path.downcase}\n" + + custom_args = {} + request.each_header do |key, value| + if key =~ /^x-emc-/ + custom_args[key] = value + end + end + custom_args = custom_args.sort() + custom_headers = "" + custom_args.each do |key, value| + custom_headers += key + ":" + value.lstrip.rstrip + "\n" + end + + custom_headers = custom_headers.chomp() + hash_string += custom_headers + digest = HMAC.digest(OpenSSL::Digest.new('sha1'), + Base64.decode64(@opts[:key]), hash_string) + Base64.encode64(digest.to_s).chomp + end +end + diff --git a/atmos/spec/spec_helper.rb b/atmos/spec/spec_helper.rb new file mode 100644 index 00000000..e859c7b1 --- /dev/null +++ b/atmos/spec/spec_helper.rb @@ -0,0 +1,88 @@ +# Copyright (c) 2009-2011 VMware, Inc. +$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') +$LOAD_PATH.unshift(File.expand_path("../../../", __FILE__)) + +require "rubygems" +require "rspec" + +HTTP_PORT = 9865 + +def symbolize_keys(hash) + if hash.is_a? Hash + new_hash = {} + hash.each do |k, v| + new_hash[k.to_sym] = symbolize_keys(v) + end + new_hash + else + hash + end +end + +def parse_property(hash, key, type, options = {}) + obj = hash[key] + if obj.nil? + raise "Missing required option: #{key}" unless options[:optional] + nil + elsif type == Range + raise "Invalid Range object: #{obj}" unless obj.kind_of?(Hash) + first, last = obj["first"], obj["last"] + raise "Invalid Range object: #{obj}" unless first.kind_of?(Integer) and last.kind_of?(Integer) + Range.new(first, last) + else + raise "Invalid #{type} object: #{obj}" unless obj.kind_of?(type) + obj + end +end + +# Atmos configuration. The atmos service we test against is a shared, +# off-box instace, so we don't want to bake the credentials into the +# config file. To avoid having to update the config file manually +# prior to testing, we load the atmos config from the environment. +def check_provisioner_config + vars = ["VCAP_ATMOS_HOST", "VCAP_ATMOS_TENANT", "VCAP_ATMOS_TENANT_ADMIN", "VCAP_ATMOS_TENANT_PASSWD"] + vars.each do |e| + if ENV[e].nil? + pending "Disabling atmos tests. Set the following environment variables to run them: #{vars.inspect}" + return false + end + end + true +end + +def get_provisioner_config() + config_file = File.join(File.dirname(__FILE__), "../config/atmos_gateway.yml") + config = YAML.load_file(config_file) + config = symbolize_keys(config) + options = { + :logger => Logger.new(parse_property(config, "log_file", String, :optional => true) || STDOUT, "daily"), + # Following options are for Provisioner + :version => config[:service][:version], + :local_ip => 'localhost', + # Following options are for AsynchronousServiceGateway + :service => config[:service], + :token => config[:token], + :cloud_controller => config[:cloud_controller], + # Following options are for Thin + :host => 'localhost', + :port => HTTP_PORT, + :additional_options => {:atmos => { + :host => ENV['VCAP_ATMOS_HOST'], + :port => ENV['VCAP_ATMOS_PORT'] || "443", + :tenant => ENV['VCAP_ATMOS_TENANT'], + :tenantadmin => ENV['VCAP_ATMOS_TENANT_ADMIN'], + :tenantpasswd => ENV['VCAP_ATMOS_TENANT_PASSWD'] + }} + } + + options[:logger].level = Logger::DEBUG + options +end + +def start_server(opts) + sp = Provisioner.new(@opts).start() + opts = opts.merge({:provisioner => sp}) + sg = VCAP::Services::AsynchronousServiceGateway.new(opts) + Thin::Server.start(opts[:host], opts[:port], sg) +end + diff --git a/atmos/vendor/cache/addressable-2.2.4.gem b/atmos/vendor/cache/addressable-2.2.4.gem new file mode 100644 index 00000000..9a6215d5 Binary files /dev/null and b/atmos/vendor/cache/addressable-2.2.4.gem differ diff --git a/atmos/vendor/cache/bcrypt-ruby-2.1.4.gem b/atmos/vendor/cache/bcrypt-ruby-2.1.4.gem new file mode 100644 index 00000000..cba34df3 Binary files /dev/null and b/atmos/vendor/cache/bcrypt-ruby-2.1.4.gem differ diff --git a/atmos/vendor/cache/builder-3.0.0.gem b/atmos/vendor/cache/builder-3.0.0.gem new file mode 100644 index 00000000..a2c6d160 Binary files /dev/null and b/atmos/vendor/cache/builder-3.0.0.gem differ diff --git a/atmos/vendor/cache/ci_reporter-1.6.4.gem b/atmos/vendor/cache/ci_reporter-1.6.4.gem new file mode 100644 index 00000000..d8a1cac0 Binary files /dev/null and b/atmos/vendor/cache/ci_reporter-1.6.4.gem differ diff --git a/atmos/vendor/cache/daemons-1.1.2.gem b/atmos/vendor/cache/daemons-1.1.2.gem new file mode 100644 index 00000000..9fd917df Binary files /dev/null and b/atmos/vendor/cache/daemons-1.1.2.gem differ diff --git a/atmos/vendor/cache/data_objects-0.10.3.gem b/atmos/vendor/cache/data_objects-0.10.3.gem new file mode 100644 index 00000000..fb0b4b92 Binary files /dev/null and b/atmos/vendor/cache/data_objects-0.10.3.gem differ diff --git a/atmos/vendor/cache/datamapper-1.1.0.gem b/atmos/vendor/cache/datamapper-1.1.0.gem new file mode 100644 index 00000000..2f83dea1 Binary files /dev/null and b/atmos/vendor/cache/datamapper-1.1.0.gem differ diff --git a/atmos/vendor/cache/diff-lcs-1.1.2.gem b/atmos/vendor/cache/diff-lcs-1.1.2.gem new file mode 100644 index 00000000..aa0be73b Binary files /dev/null and b/atmos/vendor/cache/diff-lcs-1.1.2.gem differ diff --git a/atmos/vendor/cache/dm-aggregates-1.1.0.gem b/atmos/vendor/cache/dm-aggregates-1.1.0.gem new file mode 100644 index 00000000..bdec7df0 Binary files /dev/null and b/atmos/vendor/cache/dm-aggregates-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-constraints-1.1.0.gem b/atmos/vendor/cache/dm-constraints-1.1.0.gem new file mode 100644 index 00000000..be01d0ff Binary files /dev/null and b/atmos/vendor/cache/dm-constraints-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-core-1.1.0.gem b/atmos/vendor/cache/dm-core-1.1.0.gem new file mode 100644 index 00000000..33f27852 Binary files /dev/null and b/atmos/vendor/cache/dm-core-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-do-adapter-1.1.0.gem b/atmos/vendor/cache/dm-do-adapter-1.1.0.gem new file mode 100644 index 00000000..f0b2f2dc Binary files /dev/null and b/atmos/vendor/cache/dm-do-adapter-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-migrations-1.1.0.gem b/atmos/vendor/cache/dm-migrations-1.1.0.gem new file mode 100644 index 00000000..04ac505d Binary files /dev/null and b/atmos/vendor/cache/dm-migrations-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-serializer-1.1.0.gem b/atmos/vendor/cache/dm-serializer-1.1.0.gem new file mode 100644 index 00000000..4a861974 Binary files /dev/null and b/atmos/vendor/cache/dm-serializer-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-sqlite-adapter-1.1.0.gem b/atmos/vendor/cache/dm-sqlite-adapter-1.1.0.gem new file mode 100644 index 00000000..da919e4c Binary files /dev/null and b/atmos/vendor/cache/dm-sqlite-adapter-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-timestamps-1.1.0.gem b/atmos/vendor/cache/dm-timestamps-1.1.0.gem new file mode 100644 index 00000000..48c51cca Binary files /dev/null and b/atmos/vendor/cache/dm-timestamps-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-transactions-1.1.0.gem b/atmos/vendor/cache/dm-transactions-1.1.0.gem new file mode 100644 index 00000000..538f2a30 Binary files /dev/null and b/atmos/vendor/cache/dm-transactions-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-types-1.1.0.gem b/atmos/vendor/cache/dm-types-1.1.0.gem new file mode 100644 index 00000000..713e5cad Binary files /dev/null and b/atmos/vendor/cache/dm-types-1.1.0.gem differ diff --git a/atmos/vendor/cache/dm-validations-1.1.0.gem b/atmos/vendor/cache/dm-validations-1.1.0.gem new file mode 100644 index 00000000..d50113ff Binary files /dev/null and b/atmos/vendor/cache/dm-validations-1.1.0.gem differ diff --git a/atmos/vendor/cache/do_sqlite3-0.10.3.gem b/atmos/vendor/cache/do_sqlite3-0.10.3.gem new file mode 100644 index 00000000..482ed131 Binary files /dev/null and b/atmos/vendor/cache/do_sqlite3-0.10.3.gem differ diff --git a/atmos/vendor/cache/em-http-request-0.3.0.gem b/atmos/vendor/cache/em-http-request-0.3.0.gem new file mode 100644 index 00000000..e1e30403 Binary files /dev/null and b/atmos/vendor/cache/em-http-request-0.3.0.gem differ diff --git a/atmos/vendor/cache/escape_utils-0.2.3.gem b/atmos/vendor/cache/escape_utils-0.2.3.gem new file mode 100644 index 00000000..586014cb Binary files /dev/null and b/atmos/vendor/cache/escape_utils-0.2.3.gem differ diff --git a/atmos/vendor/cache/eventmachine-0.12.10.gem b/atmos/vendor/cache/eventmachine-0.12.10.gem new file mode 100644 index 00000000..aa54c34a Binary files /dev/null and b/atmos/vendor/cache/eventmachine-0.12.10.gem differ diff --git a/atmos/vendor/cache/fastercsv-1.5.4.gem b/atmos/vendor/cache/fastercsv-1.5.4.gem new file mode 100644 index 00000000..ff247ecd Binary files /dev/null and b/atmos/vendor/cache/fastercsv-1.5.4.gem differ diff --git a/atmos/vendor/cache/json-1.4.6.gem b/atmos/vendor/cache/json-1.4.6.gem new file mode 100644 index 00000000..53e04e6e Binary files /dev/null and b/atmos/vendor/cache/json-1.4.6.gem differ diff --git a/atmos/vendor/cache/json_pure-1.5.1.gem b/atmos/vendor/cache/json_pure-1.5.1.gem new file mode 100644 index 00000000..04ac32bd Binary files /dev/null and b/atmos/vendor/cache/json_pure-1.5.1.gem differ diff --git a/atmos/vendor/cache/little-plugger-1.1.2.gem b/atmos/vendor/cache/little-plugger-1.1.2.gem new file mode 100644 index 00000000..ec25ec0b Binary files /dev/null and b/atmos/vendor/cache/little-plugger-1.1.2.gem differ diff --git a/atmos/vendor/cache/logging-1.5.0.gem b/atmos/vendor/cache/logging-1.5.0.gem new file mode 100644 index 00000000..9d434f53 Binary files /dev/null and b/atmos/vendor/cache/logging-1.5.0.gem differ diff --git a/atmos/vendor/cache/nats-0.4.10.gem b/atmos/vendor/cache/nats-0.4.10.gem new file mode 100644 index 00000000..7bc1af7e Binary files /dev/null and b/atmos/vendor/cache/nats-0.4.10.gem differ diff --git a/atmos/vendor/cache/posix-spawn-0.3.6.gem b/atmos/vendor/cache/posix-spawn-0.3.6.gem new file mode 100644 index 00000000..1156f3a3 Binary files /dev/null and b/atmos/vendor/cache/posix-spawn-0.3.6.gem differ diff --git a/atmos/vendor/cache/rack-1.2.2.gem b/atmos/vendor/cache/rack-1.2.2.gem new file mode 100644 index 00000000..4158b135 Binary files /dev/null and b/atmos/vendor/cache/rack-1.2.2.gem differ diff --git a/atmos/vendor/cache/rake-0.8.7.gem b/atmos/vendor/cache/rake-0.8.7.gem new file mode 100644 index 00000000..0740cec7 Binary files /dev/null and b/atmos/vendor/cache/rake-0.8.7.gem differ diff --git a/atmos/vendor/cache/rcov-0.9.9.gem b/atmos/vendor/cache/rcov-0.9.9.gem new file mode 100644 index 00000000..acb0cbc1 Binary files /dev/null and b/atmos/vendor/cache/rcov-0.9.9.gem differ diff --git a/atmos/vendor/cache/rspec-2.5.0.gem b/atmos/vendor/cache/rspec-2.5.0.gem new file mode 100644 index 00000000..89e4b6c7 Binary files /dev/null and b/atmos/vendor/cache/rspec-2.5.0.gem differ diff --git a/atmos/vendor/cache/rspec-core-2.5.1.gem b/atmos/vendor/cache/rspec-core-2.5.1.gem new file mode 100644 index 00000000..0751c798 Binary files /dev/null and b/atmos/vendor/cache/rspec-core-2.5.1.gem differ diff --git a/atmos/vendor/cache/rspec-expectations-2.5.0.gem b/atmos/vendor/cache/rspec-expectations-2.5.0.gem new file mode 100644 index 00000000..dada52d5 Binary files /dev/null and b/atmos/vendor/cache/rspec-expectations-2.5.0.gem differ diff --git a/atmos/vendor/cache/rspec-mocks-2.5.0.gem b/atmos/vendor/cache/rspec-mocks-2.5.0.gem new file mode 100644 index 00000000..1f71c1a2 Binary files /dev/null and b/atmos/vendor/cache/rspec-mocks-2.5.0.gem differ diff --git a/atmos/vendor/cache/ruby-hmac-0.4.0.gem b/atmos/vendor/cache/ruby-hmac-0.4.0.gem new file mode 100644 index 00000000..5d070197 Binary files /dev/null and b/atmos/vendor/cache/ruby-hmac-0.4.0.gem differ diff --git a/atmos/vendor/cache/sinatra-1.2.1.gem b/atmos/vendor/cache/sinatra-1.2.1.gem new file mode 100644 index 00000000..9f6ff27d Binary files /dev/null and b/atmos/vendor/cache/sinatra-1.2.1.gem differ diff --git a/atmos/vendor/cache/stringex-1.2.1.gem b/atmos/vendor/cache/stringex-1.2.1.gem new file mode 100644 index 00000000..c7f98051 Binary files /dev/null and b/atmos/vendor/cache/stringex-1.2.1.gem differ diff --git a/atmos/vendor/cache/thin-1.2.11.gem b/atmos/vendor/cache/thin-1.2.11.gem new file mode 100644 index 00000000..d4950899 Binary files /dev/null and b/atmos/vendor/cache/thin-1.2.11.gem differ diff --git a/atmos/vendor/cache/tilt-1.2.2.gem b/atmos/vendor/cache/tilt-1.2.2.gem new file mode 100644 index 00000000..a931fb8c Binary files /dev/null and b/atmos/vendor/cache/tilt-1.2.2.gem differ diff --git a/atmos/vendor/cache/uuidtools-2.1.2.gem b/atmos/vendor/cache/uuidtools-2.1.2.gem new file mode 100644 index 00000000..5f302bcb Binary files /dev/null and b/atmos/vendor/cache/uuidtools-2.1.2.gem differ diff --git a/atmos/vendor/cache/vcap_logging-0.1.3.gem b/atmos/vendor/cache/vcap_logging-0.1.3.gem new file mode 100644 index 00000000..9d7b4eda Binary files /dev/null and b/atmos/vendor/cache/vcap_logging-0.1.3.gem differ diff --git a/atmos/vendor/cache/xml-simple-1.0.15.gem b/atmos/vendor/cache/xml-simple-1.0.15.gem new file mode 100644 index 00000000..dee4cf53 Binary files /dev/null and b/atmos/vendor/cache/xml-simple-1.0.15.gem differ diff --git a/atmos/vendor/cache/yajl-ruby-0.8.3.gem b/atmos/vendor/cache/yajl-ruby-0.8.3.gem new file mode 100644 index 00000000..e3bdf3eb Binary files /dev/null and b/atmos/vendor/cache/yajl-ruby-0.8.3.gem differ diff --git a/base/lib/base/gateway.rb b/base/lib/base/gateway.rb index 2652b5bf..cde9168c 100644 --- a/base/lib/base/gateway.rb +++ b/base/lib/base/gateway.rb @@ -93,7 +93,8 @@ def start :node_timeout => node_timeout, :z_interval => @config[:z_interval], :allow_over_provisioning => @config[:allow_over_provisioning], - :max_nats_payload => @config[:max_nats_payload] + :max_nats_payload => @config[:max_nats_payload], + :additional_options => additional_options ) sg = async_gateway_class.new( :proxy => @config[:proxy], @@ -130,4 +131,8 @@ def parse_gateway_config(config_file) config end + + def additional_options + {} + end end