Permalink
Browse files

Merge branch 'snapshots_v2'

Conflicts:
	Gemfile
	Gemfile.lock
	Rakefile
	lib/cloud_controller/legacy_api/legacy_service_lifecycle.rb
	spec/api/legacy_service_lifecycle_spec.rb
  • Loading branch information...
2 parents 475d2f8 + 6791377 commit 79f91c2b27115a0a7447aed888d49120098bbe45 Andrew Liu and David Stevenson committed Mar 21, 2013
View
2 Gemfile.lock
@@ -91,7 +91,7 @@ GEM
guard (>= 1.1)
hashie (1.2.0)
http_parser.rb (0.5.3)
- httpclient (2.2.5)
+ httpclient (2.3.3)
i18n (0.6.1)
json_pure (1.7.4)
listen (0.5.3)
View
3 Rakefile
@@ -12,7 +12,7 @@ require "cloud_controller/db"
ENV['CI_REPORTS'] = File.join("spec", "artifacts", "reports")
-task 'default' => 'spec'
+task default: :spec
namespace :spec do
desc "Run specs producing results for CI"
@@ -43,6 +43,7 @@ RSpec::Core::RakeTask.new do |t|
)
end
+
desc "Run specs with code coverage"
task :coverage do
require "simplecov"
View
1 lib/cloud_controller.rb
@@ -136,7 +136,6 @@ def current_user_admin?(token_information)
require "cloud_controller/legacy_api/legacy_apps"
require "cloud_controller/legacy_api/legacy_services"
require "cloud_controller/legacy_api/legacy_service_gateway"
-require "cloud_controller/legacy_api/legacy_service_lifecycle"
require "cloud_controller/legacy_api/legacy_bulk"
require "cloud_controller/legacy_api/legacy_staging"
require "cloud_controller/legacy_api/legacy_resources"
View
68 lib/cloud_controller/api/snapshot.rb
@@ -0,0 +1,68 @@
+module VCAP::CloudController
+ rest_controller :Snapshots do
+ disable_default_routes
+ path_base "/v2"
+ model_class_name :ServiceInstance
+
+ permissions_required do
+ full Permissions::CFAdmin
+ update Permissions::SpaceDeveloper
+ read Permissions::SpaceDeveloper
+ end
+
+ define_attributes do
+ attribute :name, String
+ to_one :service_instance
+ end
+ define_messages
+
+ def create
+ req = self.class::CreateMessage.decode(body)
+ instance = VCAP::CloudController::Models::ServiceInstance.find(:guid => req.service_instance_guid)
+ validate_access(:update, instance, user, roles)
+ snapshot = instance.create_snapshot(req.name)
+ snapguid = "%s_%s" % [instance.guid, snapshot.snapshot_id]
+ [
+ HTTP::CREATED,
+ Yajl::Encoder.encode(
+ "metadata" => {
+ "url" => "/v2/snapshots/#{snapguid}",
+ "guid" => snapguid,
+ "created_at" => snapshot.created_time,
+ "updated_at" => nil
+ },
+ "entity" => snapshot.extract
+ ),
+ ]
+ end
+
+ def index(service_guid)
+ instance = VCAP::CloudController::Models::ServiceInstance.find(:guid => service_guid)
+ validate_access(:read, instance, user, roles)
+ snapshots = instance.enum_snapshots
+ [
+ HTTP::OK,
+ Yajl::Encoder.encode(
+ "total_results" => snapshots.length,
+ "total_pages" => 1,
+ "prev_url" => nil,
+ "next_url" => nil,
+ "resources" => snapshots.collect do |s|
+ {
+ "metadata" => {
+ "guid" => "#{service_guid}_#{s.snapshot_id}",
+ "url" => "/v2/snapshots/#{service_guid}_#{s.snapshot_id}",
+ "created_at" => s.created_time,
+ "updated_at" => nil,
+ },
+ "entity" => s.extract
+ }
+ end
+ ),
+ ]
+ end
+
+ post "/v2/snapshots", :create
+ get "/v2/service_instances/:service_guid/snapshots", :index
+ end
+end
View
143 lib/cloud_controller/legacy_api/legacy_service_lifecycle.rb
@@ -1,143 +0,0 @@
-# Copyright (c) 2009-2012 VMware, Inc.
-
-require "cloud_controller/legacy_api/legacy_api_base"
-
-module VCAP::CloudController
- class LegacyServiceLifecycle < LegacyApiBase
- attr_accessor :service_instance
-
- def job_info(gateway_name, job_id)
- service_instance.job_info(job_id)
- end
-
- def create(gateway_name)
- service_instance.create_snapshot
- end
-
- def enumerate(gateway_name)
- service_instance.enum_snapshots
- end
-
- def read(gateway_name, snapshot_id)
- service_instance.snapshot_details(snapshot_id)
- end
-
- def rollback(gateway_name, snapshot_id)
- service_instance.rollback_snapshot(snapshot_id)
- end
-
- def delete(gateway_name, snapshot_id)
- service_instance.delete_snapshot(snapshot_id)
- end
-
- def create_serialized_url(gateway_name, snapshot_id)
- service_instance.create_serialized_url(snapshot_id)
- end
-
- def read_serialized_url(gateway_name, snapshot_id)
- service_instance.serialized_url(snapshot_id)
- end
-
- def get_upload_url(gateway_name)
- #TODO: switch upload url returns to point to sds
- "#{[*@config[:external_domain]].first}/services/v1/configurations/#{gateway_name}/serialized/data"
- end
-
- def import_from_url(gateway_name)
- service_instance.import_from_url(VCAP::Services::Api::SerializedURL.decode(body))
- end
-
- def import_from_data(gateway_name)
- file_path = data_file_path
-
- begin
- # Check the service and user's permission
- upload_token = config[:service_lifecycle][:upload_token]
-
- # Check the size of the uploaded file
- max_upload_size_mb = config[:service_lifecycle][:max_upload_size]
- max_upload_size = max_upload_size_mb * 1024 * 1024
- unless File.size(file_path) < max_upload_size
- raise BadQueryParameter, "data_file too large"
- end
-
- # Select a serialization data server
- active_sds = config[:service_lifecycle][:serialization_data_server]
- if active_sds.empty?
- raise SDSNotAvailable
- end
- upload_url = active_sds.sample
-
- req = {
- :upload_url => upload_url,
- :upload_token => upload_token,
- :data_file_path => data_file_path,
- :upload_timeout => config[:service_lifecycle][:upload_timeout],
- }
- logger.debug("import_from_data - request is #{req.inspect}")
-
- serialized_url = service_instance.import_from_data(req)
- service_instance.import_from_url(serialized_url)
- ensure
- FileUtils.rm_rf(file_path)
- end
- end
-
- def data_file_path
- path = nil
- if config[:nginx][:use_nginx]
- path = params.fetch("data_file_path")
- raise BadQueryParameter, "data_file_path" unless path && File.exist?(path)
- else
- file = params.fetch("data_file")
- if file && file.path && File.exist?(file.path)
- path = file.path
- else
- raise BadQueryParameter, "data_file"
- end
- end
- path
- end
-
- def dispatch(op, gateway_name, *args)
- # FIXME: should really be unauthenticated
- raise NotAuthorized unless user
- @service_instance = Models::ServiceInstance.user_visible[:gateway_name => gateway_name]
- raise ServiceInstanceNotFound, gateway_name unless service_instance
- json_message = super
- # maybe this .encode fits better in the methods so that it's more explicit
- json_message.encode
- rescue VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse
- logger.debug("service instance not found while performing #{op}: #{gateway_name}")
- raise SnapshotNotFound, gateway_name
- rescue VCAP::Services::Api::ServiceGatewayClient::GatewayInternalResponse => e
- logger.error("service gateway error while performing #{op}: #{e}")
- raise ServiceGatewayError, e
- rescue VCAP::Services::Api::ServiceGatewayClient::ErrorResponse => e
- if e.status == 501
- logger.warn("We are running a service gateway that doesn't support lifecycle api: op: #{op}, error: #{e}")
- # make sure we pass on the 501 instead of a generic 500
- raise ServiceNotImplemented
- else
- logger.error("service gateway client error while performing #{op}: #{e}")
- raise ServerError
- end
- end
-
- get "/services/v1/configurations/:gateway_name/jobs/:job_id", :job_info
-
- post "/services/v1/configurations/:gateway_name/snapshots", :create
- get "/services/v1/configurations/:gateway_name/snapshots", :enumerate
- get "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id", :read
- put "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id", :rollback
- delete "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id", :delete
-
- post "/services/v1/configurations/:gateway_name/serialized/url/snapshots/:snapshot_id", :create_serialized_url
- get "/services/v1/configurations/:gateway_name/serialized/url/snapshots/:snapshot_id", :read_serialized_url
-
- put "/services/v1/configurations/:gateway_name/serialized/url", :import_from_url
- put "/services/v1/configurations/:gateway_name/serialized/data", :import_from_data
-
- post "/services/v1/configurations/:gateway_name/serialized/uploads", :get_upload_url
- end
-end
View
52 lib/cloud_controller/models/service_instance.rb
@@ -1,9 +1,51 @@
-# Copyright (c) 2009-2012 VMware, Inc.
require "services/api"
module VCAP::CloudController::Models
class ServiceInstance < Sequel::Model
class InvalidServiceBinding < StandardError; end
+ class MissingServiceAuthToken < StandardError; end
+ class ServiceGatewayError < StandardError; end
+
+ class NGServiceGatewayClient
+ attr_accessor :service, :token, :service_id
+
+ def initialize(service, service_id)
+ @service = service
+ @token = service.service_auth_token
+ @service_id = service_id
+ unless token
+ raise MissingServiceAuthToken, "ServiceAuthToken not found for service #{service}"
+ end
+ end
+
+ def create_snapshot(name)
+ VCAP::Services::Api::SnapshotV2.decode(do_request(:post, Yajl::Encoder.encode({name: name})))
+ end
+
+ def enum_snapshots
+ list = VCAP::Services::Api::SnapshotListV2.decode(do_request(:get))
+ list.snapshots.collect{|e| VCAP::Services::Api::SnapshotV2.new(e) }
+ end
+
+ private
+
+ def do_request(method, payload=nil)
+ client = HTTPClient.new
+ u = URI.parse(service.url)
+ u.path = "/gateway/v2/configurations/#{service_id}/snapshots"
+
+ response = client.public_send(method, u,
+ :header => { VCAP::Services::Api::GATEWAY_TOKEN_HEADER => token.token,
+ "Content-Type" => "application/json"
+ },
+ :body => payload)
+ if response.ok?
+ response.body
+ else
+ raise ServiceGatewayError, "Service gateway upstream failure, responded with #{response.status}: #{response.body}"
+ end
+ end
+ end
class << self
def gateway_client_class
@@ -200,14 +242,12 @@ def deprovision_on_gateway
logger.error "deprovision failed #{e}"
end
- def create_snapshot
- service_gateway_client
- client.create_snapshot(:service_id => gateway_name)
+ def create_snapshot(name)
+ NGServiceGatewayClient.new(service_plan.service, gateway_name).create_snapshot(name)
end
def enum_snapshots
- service_gateway_client
- client.enum_snapshots(:service_id => gateway_name)
+ NGServiceGatewayClient.new(service_plan.service, gateway_name).enum_snapshots
end
def snapshot_details(sid)
View
496 spec/api/legacy_service_lifecycle_spec.rb
@@ -1,496 +0,0 @@
-# Copyright (c) 2009-2012 VMware, Inc.
-
-require File.expand_path("../spec_helper", __FILE__)
-require "webmock/rspec"
-
-module VCAP::CloudController
- describe VCAP::CloudController::LegacyServiceLifecycle do
- before :each do
- @user = make_user_with_default_space
- @mock_client = double("mock service gateway client")
- @mock_client.stub(:provision).and_return(
- VCAP::Services::Api::GatewayHandleResponse.new(
- :service_id => "lifecycle",
- :configuration => "abc",
- :credentials => { :password => "foo" }
- )
- )
- @mock_client.stub(:unprovision)
- # I miss dependency injection
- Models::ServiceInstance.any_instance.stub(:service_gateway_client)
- Models::ServiceInstance.any_instance.stub(:client).and_return(@mock_client)
- # Machinist 1.0's make_unsaved doesn't work, so
- # here's poor man's make_unsaved
- @unknown_user = Models::User.make.destroy
-
- service = Models::Service.make(
- :label => "jazz",
- :url => "http://12.34.56.78",
- )
- Models::ServiceInstance.make(
- :gateway_name => "lifecycle",
- :name => "bar",
- :space => make_space_for_user(@user),
- :service_plan => Models::ServicePlan.make(
- :service => service,
- ),
- )
- end
-
- describe "POST", "/services/v1/configurations/:gateway_name/snapshots" do
- it "should return not authorized for unknown users" do
- post "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown gateway name" do
- post "/services/v1/configurations/xxx/snapshots", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should create a snapshot job" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1",
- )
- @mock_client.stub(:create_snapshot).
- with(:service_id => "lifecycle").and_return(job)
-
- post "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
-
- it "returns a 501 Not Implemented if upstream says so" do
- Models::ServiceInstance.any_instance.unstub(:service_gateway_client)
- Models::ServiceInstance.any_instance.unstub(:client)
- gateway_url = "http://12.34.56.78/gateway/v1/configurations/lifecycle/snapshots"
- stub_request(:post, gateway_url).to_return(
- :status => 501,
- :body => "{\"code\":1, \"description\":\"value\"}",
- )
- post "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@user)
- last_response.status.should == 501
- end
- end
-
- describe "GET", "/services/v1/configurations/:gateway_name/snapshots" do
- it "should return not authorized for unknown users" do
- get "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- get "/services/v1/configurations/xxx/snapshots", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should enumerate snapshots" do
- snapshots = VCAP::Services::Api::SnapshotList.new(
- :snapshots => [
- {:snapshot_id => "abc"},
- ],
- )
- @mock_client.stub(:enum_snapshots).
- with(:service_id => "lifecycle").and_return(snapshots)
-
- get "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["snapshots"].size.should == 1
- decoded_response["snapshots"][0]["snapshot_id"].should == "abc"
- end
-
- it "returns a 501 Not Implemented if upstream says so" do
- Models::ServiceInstance.any_instance.unstub(:service_gateway_client)
- Models::ServiceInstance.any_instance.unstub(:client)
- gateway_url = "http://12.34.56.78/gateway/v1/configurations/lifecycle/snapshots"
- stub_request(:get, gateway_url).to_return(
- :status => 501,
- :body => "{\"code\":1, \"description\":\"value\"}",
- )
- get "/services/v1/configurations/lifecycle/snapshots", {}, headers_for(@user)
- last_response.status.should == 501
- end
- end
-
- describe "GET", "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id" do
- it "should return not authorized for unknown users" do
- get "/services/v1/configurations/lifecycle/snapshots/yyy", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- get "/services/v1/configurations/xxx/snapshots/yyy", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should get snapshot_details" do
- snapshot = VCAP::Services::Api::Snapshot.new(
- :snapshot_id => "abc",
- :date => "1",
- :size => 123,
- :name => "def"
- )
- @mock_client.stub(:snapshot_details).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_return(snapshot)
-
- get "/services/v1/configurations/lifecycle/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["snapshot_id"].should == "abc"
- end
-
- it "should handle not found error in snapshot details" do
- err = VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse.new(
- VCAP::Services::Api::ServiceErrorResponse.new(
- :code => 10000,
- :description => "not found",
- ),
- )
- @mock_client.stub(:snapshot_details).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_raise(err)
-
- get "/services/v1/configurations/lifecycle/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 404
- end
- end
-
- describe "PUT", "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id" do
- it "should return not authorized for unknown users" do
- put "/services/v1/configurations/lifecycle/snapshots/yyy", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- put "/services/v1/configurations/xxx/snapshots/yyy", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should rollback a snapshot" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1",
- )
-
- @mock_client.stub(:rollback_snapshot).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_return(job)
-
- put "/services/v1/configurations/lifecycle/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
-
- it "should handle not found error in rollback snapshot" do
- err = VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse.new(
- VCAP::Services::Api::ServiceErrorResponse.decode(
- {:code => 10000, :description => "not found"}.to_json
- )
- )
- @mock_client.stub(:rollback_snapshot).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_raise(err)
-
- put "/services/v1/configurations/lifecycle/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 404
- end
- end
-
- describe "DELETE", "/services/v1/configurations/:gateway_name/snapshots/:snapshot_id" do
- it "should return not authorized for unknown users" do
- delete "/services/v1/configurations/bar/snapshots/yyy", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- delete "/services/v1/configurations/xxx/snapshots/yyy", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should delete a snapshot" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1",
- )
- @mock_client.should_receive(:delete_snapshot).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_return(job)
-
- delete "/services/v1/configurations/lifecycle/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
-
- it "should handle not found error in delete snapshot" do
- err = VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse.new(
- VCAP::Services::Api::ServiceErrorResponse.decode(
- {:code => 10000, :description => "not found"}.to_json
- )
- )
- snapshot_id = "abc"
- @mock_client.stub(:delete_snapshot).with(:service_id => "lifecycle", :snapshot_id => "abc").and_raise(err)
-
- delete :delete_snapshot, :id => "lifecycle", :sid => "abc"
- last_response.status.should == 404
- end
- end
-
- describe "GET", "/services/v1/configurations/:gateway_name/serialized/url/snapshots/:snapshot_id" do
- it "should return not authorized for unknown users" do
- get "/services/v1/configurations/lifecycle/serialized/url/snapshots/yyy", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- get "/services/v1/configurations/xxx/serialized/url/snapshots/1", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should get serialized url" do
- url = "http://api.vcap.me"
- snapshot_id = "abc"
- serialized_url = VCAP::Services::Api::SerializedURL.new(:url => url)
- @mock_client.stub(:serialized_url).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_return(serialized_url)
-
- get "/services/v1/configurations/lifecycle/serialized/url/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["url"].should == url
- end
- end
-
- describe "POST", "/services/v1/configurations/:gateway_name/serialized/url/snapshots/:snapshot_id" do
- it "should return not authorized for unknown users" do
- post "/services/v1/configurations/lifecycle/serialized/url/snapshots/1", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- post "/services/v1/configurations/xxx/serialized/url/snapshots/1", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should create serialized url job" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1"
- )
- snapshot_id = "abc"
- @mock_client.stub(:create_serialized_url).with(
- :service_id => "lifecycle",
- :snapshot_id => "abc",
- ).and_return(job)
-
- post "/services/v1/configurations/lifecycle/serialized/url/snapshots/abc", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
- end
-
- describe "PUT", "/services/v1/configurations/:gateway_name/serialized/url" do
- it "should return not authorized for unknown users" do
- put "/services/v1/configurations/lifecycle/serialized/url", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- put "/services/v1/configurations/xxx/serialized/url",
- VCAP::Services::Api::SerializedURL.new(:url => 'http://api.vcap.me').encode,
- headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should return bad request for malformed request" do
- put "/services/v1/configurations/xxx/serialized/url",
- %q({"data":"raw_data"}),
- headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should create import from url job" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1"
- )
- url = "http://api.cloudfoundry.com"
-
- @mock_client.should_receive(:import_from_url).with(
- hash_including(
- :service_id => kind_of(String),
- :msg => kind_of(JsonMessage),
- ),
- ).and_return(job)
-
- put "/services/v1/configurations/lifecycle/serialized/url",
- VCAP::Services::Api::SerializedURL.new(:url => url).encode,
- headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
- end
-
- describe "PUT", "/services/v1/configurations/:gateway_name/serialized/data" do
- before :each do
- config_override(
- :service_lifecycle => {
- :max_upload_size => 1,
- :upload_token => "changeme",
- :upload_timeout => 2,
- :serialization_data_server => ["http://sds.example.com"],
- },
- )
- @data_file = f = Tempfile.new("import_from_data")
- f.write("bar\n")
- f.close
-
- @mock_sds_client = mock("mock sds client")
- Models::ServiceInstance.any_instance.stub(:sds_client).with(
- "http://sds.example.com",
- "changeme",
- 2,
- ).and_return(@mock_sds_client)
- end
-
- after :each do
- @data_file.unlink if @data_file
- end
-
- # disabled until upstream frees up the 501 status code ...
- xit "returns a 501 Not Implemented if upstream says so" do
- # FIXME: this should be PUT
- stub_request(:post, @sds_url).to_return(
- :status => 501,
- :body => "{\"code\":1, \"description\":\"not implemented\"}",
- )
- put "/services/v1/configurations/lifecycle/serialized/data", {
- "data_file" => Rack::Test::UploadedFile.new(@data_file.path),
- }, headers_for(@user)
- last_response.status.should == 501
- end
-
- it "should return not authorized for unknown users" do
- put "/services/v1/configurations/lifecycle/serialized/data", {
- "data_file" => Rack::Test::UploadedFile.new(@data_file.path),
- }, headers_for(@unknown_user)
- # FIXME: shouldn't this be 401?
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- put "/services/v1/configurations/xxx/serialized/data", {
- "data_file" => Rack::Test::UploadedFile.new(@data_file.path),
- }, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should create import from data job" do
- job = VCAP::Services::Api::Job.new(
- :job_id => "abc",
- :status => "queued",
- :start_time => "1",
- )
-
- serialized_url = VCAP::Services::Api::SerializedURL.new(
- :url => "http://api.cloudfoundry",
- )
- @mock_sds_client.should_receive(:import_from_data).with(hash_including(
- :service => "jazz",
- :service_id => "lifecycle",
- )).and_return(serialized_url)
- @mock_client.should_receive(:import_from_url).with(
- :service_id => "lifecycle",
- :msg => serialized_url,
- ).and_return(job)
- put "/services/v1/configurations/lifecycle/serialized/data", {
- "data_file" => Rack::Test::UploadedFile.new(@data_file.path),
- }, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == "abc"
- end
- end
-
- describe "POST", "/services/v1/configurations/:gateway_name/serialized/uploads" do
- it "should return not authorized for unknown users" do
- post "/services/v1/configurations/lifecycle/serialized/uploads", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- post "/services/v1/configurations/xxx/serialized/uploads", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should create a upload url" do
- config_override(:external_domain => 'http://api.fake.com')
- post "/services/v1/configurations/lifecycle/serialized/uploads", {}, headers_for(@user)
- last_response.status.should == 200
- last_response.body.should == "http://api.fake.com/services/v1/configurations/lifecycle/serialized/data"
- end
-
- it "should create a upload url" do
- config_override(:external_domain => %w{http://api.fake.com http://api.fake2.com/})
- post "/services/v1/configurations/lifecycle/serialized/uploads", {}, headers_for(@user)
- last_response.status.should == 200
- last_response.body.should == "http://api.fake.com/services/v1/configurations/lifecycle/serialized/data"
- end
- end
-
- describe "GET", "/services/v1/configurations/:gateway_name/jobs/:job_id" do
- it "should return not authorized for unknown users" do
- get "/services/v1/configurations/lifecycle/jobs/yyy", {}, headers_for(@unknown_user)
- last_response.status.should == 403
- end
-
- it "should return not found for unknown ids" do
- get "/services/v1/configurations/xxx/jobs/yyy", {}, headers_for(@user)
- last_response.status.should == 404
- end
-
- it "should return job_info" do
- job_id = "job1"
- job = VCAP::Services::Api::Job.new(
- :job_id => job_id,
- :status => "queued",
- :start_time => "1"
- )
- @mock_client.stub(:job_info).with(
- :service_id => "lifecycle",
- :job_id => job_id,
- ).and_return(job)
-
- get "/services/v1/configurations/lifecycle/jobs/job1", {}, headers_for(@user)
- last_response.status.should == 200
- decoded_response["job_id"].should == job_id
- end
-
- it "should handle not found error in get job_info" do
- err = VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse.new(
- VCAP::Services::Api::ServiceErrorResponse.decode(
- {:code => 10000, :description => "job not found"}.to_json
- )
- )
- job_id = "job1"
- @mock_client.stub(:job_info).with(
- :service_id => "lifecycle",
- :job_id => job_id,
- ).and_raise(err)
-
- get :job_info, :id => "lifecycle", :job_id => job_id
- last_response.status.should == 404
- end
- end
- end
-end
View
154 spec/api/snapshot_spec.rb
@@ -0,0 +1,154 @@
+require File.expand_path("../spec_helper", __FILE__)
+
+module VCAP::CloudController
+ describe VCAP::CloudController::Snapshots do
+ let(:service_instance) do
+ Models::ServiceInstance.make
+ end
+
+ describe "POST", "/v2/snapshots" do
+ let(:new_name) { 'new name' }
+ let(:snapshot_created_at) { Time.now.to_s }
+ let(:new_snapshot) { VCAP::Services::Api::SnapshotV2.new(snapshot_id: '1', name: 'foo', state: 'empty', size: 0, created_time: snapshot_created_at)}
+ let(:payload) {
+ Yajl::Encoder.encode(:service_instance_guid => service_instance.guid,
+ :name => new_name)
+ }
+ before do
+ Models::ServiceInstance.any_instance.stub(:create_snapshot).and_return(new_snapshot)
+ end
+
+ context "for an unauthenticated user" do
+ it "requires authentication" do
+ post "/v2/snapshots", payload, {}
+ last_response.status.should == 401
+ end
+ end
+
+ context "for an admin" do
+ let(:admin_headers) do
+ user = Models::User.make(:admin => true)
+ headers_for(user)
+ end
+
+ it "should allow them to create a snapshot" do
+ post "/v2/snapshots", payload, admin_headers
+ last_response.status.should == 201
+ end
+ end
+
+ context "for a developer not in the space" do
+ let(:another_space) { Models::Space.make }
+ let(:developer) { make_developer_for_space(another_space) }
+ it "denies access" do
+ post "/v2/snapshots", payload, headers_for(developer)
+ last_response.status.should eq 403
+ end
+ end
+
+ context "once authenticated" do
+ let(:developer) {make_developer_for_space(service_instance.space)}
+
+ context "without service_instance_id" do
+ it "returns a 400 status code" do
+ post "/v2/snapshots", '{}', headers_for(developer)
+ last_response.status.should == 400
+ end
+
+ it "does not create a snapshot" do
+ Models::ServiceInstance.any_instance.should_not_receive(:create_snapshot)
+ post "/v2/snapshots", '{}', headers_for(developer)
+ end
+ end
+
+ it "invokes create_snapshot on the corresponding service instance" do
+ Models::ServiceInstance.should_receive(:find).
+ with(:guid => service_instance.guid).
+ and_return(service_instance)
+ service_instance.should_receive(:create_snapshot).with(new_name)
+ post "/v2/snapshots", payload, headers_for(developer)
+ end
+
+ context "when the gateway successfully creates the snapshot" do
+ it "returns the details of the new snapshot" do
+ post "/v2/snapshots", payload, headers_for(developer)
+ last_response.status.should == 201
+ snapguid = "#{service_instance.guid}_1"
+ decoded_response['metadata'].should == {
+ "guid" => snapguid,
+ "url" => "/v2/snapshots/#{snapguid}",
+ "created_at" => snapshot_created_at,
+ "updated_at" => nil
+ }
+ decoded_response['entity'].should include({"state" => "empty", "name" => "foo"})
+ end
+ end
+ end
+ end
+
+ describe "GET /v2/service_instances/:service_id/snapshots" do
+ let(:snapshots_url) { "/v2/service_instances/#{service_instance.guid}/snapshots" }
+
+ it 'requires authentication' do
+ get snapshots_url
+ last_response.status.should == 401
+ end
+
+ context "once authenticated" do
+ let(:developer) {make_developer_for_space(service_instance.space)}
+ before do
+ Models::ServiceInstance.stub(:find).
+ with(:guid => service_instance.guid).
+ and_return(service_instance)
+ end
+
+ it "returns an empty list" do
+ service_instance.stub(:enum_snapshots).and_return []
+ get snapshots_url, {} , headers_for(developer)
+ last_response.status.should == 200
+ decoded_response['resources'].should == []
+ end
+
+ it "returns an list of snapshots" do
+ created_time = Time.now.to_s
+ service_instance.should_receive(:enum_snapshots) do
+ [VCAP::Services::Api::SnapshotV2.new(
+ "snapshot_id" => "1234",
+ "name" => "something",
+ "state" => "empty",
+ "size" => 0,
+ "created_time" => created_time)
+ ]
+ end
+ get snapshots_url, {} , headers_for(developer)
+ decoded_response.should == {
+ "total_results" => 1,
+ "total_pages" => 1,
+ "prev_url" => nil,
+ "next_url" => nil,
+ "resources"=>[
+ {
+ "metadata" => {
+ "guid" => "#{service_instance.guid}_1234",
+ "url" => "/v2/snapshots/#{service_instance.guid}_1234",
+ "created_at" => created_time,
+ "updated_at" => nil
+ },
+ "entity" => {
+ "snapshot_id" => "1234", "name" => "something", "state" => "empty", "size" => 0, "created_time" => created_time
+ }
+ }
+ ]
+ }
+ last_response.status.should == 200
+ end
+
+ it "checks for permission to read the service" do
+ another_developer = make_developer_for_space(Models::Space.make)
+ get snapshots_url, {} , headers_for(another_developer)
+ last_response.status.should == 403
+ end
+ end
+ end
+ end
+end
View
96 spec/models/service_instance_spec.rb
@@ -278,4 +278,100 @@ module VCAP::CloudController
end
end
end
+
+ describe "#enum_snapshots" do
+ subject { Models::ServiceInstance.make()}
+ let(:enum_snapshots_url_matcher) {"gw.example.com:12345/gateway/v2/configurations/#{subject.gateway_name}/snapshots"}
+ let(:service_auth_token) { "tokenvalue" }
+ before do
+ subject.service_plan.service.update(:url => "http://gw.example.com:12345/")
+ subject.service_plan.service.service_auth_token.update(:token => service_auth_token)
+ end
+
+ context "when there isn't a service auth token" do
+ it "fails" do
+ subject.service_plan.service.service_auth_token.destroy
+ subject.refresh
+ expect do
+ subject.enum_snapshots
+ end.to raise_error(Models::ServiceInstance::MissingServiceAuthToken)
+ end
+ end
+
+ context "returns a list of snapshots" do
+ let(:success_response) { Yajl::Encoder.encode({snapshots: [{snapshot_id: '1', name: 'foo', state: 'ok', size: 0},
+ {snapshot_id: '2', name: 'bar', state: 'bad', size: 0} ]}) }
+ before do
+ stub_request(:get, enum_snapshots_url_matcher).to_return(:body => success_response)
+ end
+
+ it "return a list of snapshot from the gateway" do
+ snapshots = subject.enum_snapshots
+ snapshots.should have(2).items
+ snapshots.first.snapshot_id.should == '1'
+ snapshots.first.state.should == 'ok'
+ snapshots.last.snapshot_id.should == '2'
+ snapshots.last.state.should == 'bad'
+ a_request(:get, enum_snapshots_url_matcher).with(:headers => {
+ "Content-Type" => "application/json",
+ "X-Vcap-Service-Token" => "tokenvalue"
+ }).should have_been_made
+ end
+ end
+ end
+
+ describe "#create_snapshot" do
+ let(:name) { 'New snapshot' }
+ subject { Models::ServiceInstance.make()}
+ let(:create_snapshot_url_matcher) { "gw.example.com:12345/gateway/v2/configurations/#{subject.gateway_name}/snapshots" }
+ before do
+ subject.service_plan.service.update(:url => "http://gw.example.com:12345/")
+ subject.service_plan.service.service_auth_token.update(:token => "tokenvalue")
+ end
+
+ context "when there isn't a service auth token" do
+ it "fails" do
+ subject.service_plan.service.service_auth_token.destroy
+ subject.refresh
+ expect do
+ subject.create_snapshot(name)
+ end.to raise_error(Models::ServiceInstance::MissingServiceAuthToken)
+ end
+ end
+
+ context "when the request succeeds" do
+ let(:success_response) { %Q({"snapshot_id": "1", "state": "empty", "name": "foo", "size": 0}) }
+ before do
+ stub_request(:post, create_snapshot_url_matcher).to_return(:body => success_response)
+ end
+
+ it "makes an HTTP call to the corresponding service gateway and returns the decoded response" do
+ snapshot = subject.create_snapshot(name)
+ snapshot.snapshot_id.should == '1'
+ snapshot.state.should == 'empty'
+ a_request(:post, create_snapshot_url_matcher).should have_been_made
+ end
+
+ it "uses the correct svc auth token" do
+ subject.create_snapshot(name)
+
+ a_request(:post, create_snapshot_url_matcher).with(
+ headers: {"X-VCAP-Service-Token" => 'tokenvalue'}).should have_been_made
+ end
+
+ it "has the name in the payload" do
+ payload = Yajl::Encoder.encode({name: name})
+ subject.create_snapshot(name)
+
+ a_request(:post, create_snapshot_url_matcher).with(:body => payload).should have_been_made
+ end
+ end
+
+ context "when the request fails" do
+ it "should raise an error" do
+ stub_request(:post, create_snapshot_url_matcher).to_return(:body => "Something went wrong", :status => 500)
+ expect { subject.create_snapshot(name) }.to raise_error(Models::ServiceInstance::ServiceGatewayError, /upstream failure/)
+ end
+ end
+ end
end
View
1 spec/spec_helper.rb
@@ -170,6 +170,7 @@ def config
def configure_components(config)
mbus = MockMessageBus.new(config)
VCAP::CloudController::MessageBus.instance = mbus
+ # FIXME: this is better suited for a before-each stub so that we can unstub it in examples
VCAP::CloudController::Models::ServiceInstance.gateway_client_class =
VCAP::Services::Api::ServiceGatewayClientFake

0 comments on commit 79f91c2

Please sign in to comment.