From 067a4a9b2abe03f48531d7bdf0d31cfe742f6ef2 Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Thu, 22 Mar 2012 20:47:43 +0800 Subject: [PATCH 1/2] [cc] add delete_snapshot function Change-Id: Ib382a84cb1b2e6d0a47132369cb131f6522fe108 --- lib/services/api/clients/service_gateway_client.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/api/clients/service_gateway_client.rb b/lib/services/api/clients/service_gateway_client.rb index 8bed421..6a7eb78 100644 --- a/lib/services/api/clients/service_gateway_client.rb +++ b/lib/services/api/clients/service_gateway_client.rb @@ -63,6 +63,11 @@ def rollback_snapshot(args) VCAP::Services::Api::Job.decode(resp.body) end + def delete_snapshot(args) + resp = perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") + VCAP::Services::Api::Job.decode(resp.body) + end + def serialized_url(args) resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url") VCAP::Services::Api::Job.decode(resp.body) From 93c923b84e7469c37629a06875ce0ccc4495a23f Mon Sep 17 00:00:00 2001 From: Andrew Liu Date: Sat, 31 Mar 2012 20:15:10 +0800 Subject: [PATCH 2/2] [cc] simplify service gateway client - CC use unified interface to talk to gateway. - gateway client returns none 200 response to CC. Change-Id: I958998608b1475c90e929e590a8a267bf7a90164 --- .../api/clients/service_gateway_client.rb | 221 +++++++++++------- lib/services/api/messages.rb | 5 + spec/spec_helper.rb | 2 + spec/unit/service_gateway_client_spec.rb | 81 +++++++ 4 files changed, 231 insertions(+), 78 deletions(-) create mode 100644 spec/unit/service_gateway_client_spec.rb diff --git a/lib/services/api/clients/service_gateway_client.rb b/lib/services/api/clients/service_gateway_client.rb index 6a7eb78..a5d0fe9 100644 --- a/lib/services/api/clients/service_gateway_client.rb +++ b/lib/services/api/clients/service_gateway_client.rb @@ -1,5 +1,6 @@ # Copyright (c) 2009-2011 VMware, Inc. require 'net/http' +require 'uri' require 'services/api/const' require 'services/api/messages' @@ -11,102 +12,166 @@ module Api end end -class VCAP::Services::Api::ServiceGatewayClient - - class UnexpectedResponse < StandardError - attr_reader :response +module VCAP::Services::Api + class ServiceGatewayClient + METHODS_MAP = { + :get => Net::HTTP::Get, + :post=> Net::HTTP::Post, + :put => Net::HTTP::Put, + :delete => Net::HTTP::Delete, + } - def initialize(resp) - @response = resp + # Public: Indicate gateway client encounter an unexpcted error, + # such as can't connect to gateway or can't decode response. + # + class UnexpectedResponse < StandardError; end + + # Pubilc: Indicate an error response from gateway + # + class ErrorResponse < StandardError + attr_reader :status, :error + + # status - the http status + # error - a ServiceErrorResponse object + # + def initialize(status, error) + @status = status + @error = error + end + + def to_s + "Reponse status:#{status},error:[#{error.extract}]" + end end - end - attr_reader :host, :port, :token + class NotFoundResponse < ErrorResponse + def initialize(error) + super(404, error) + end + end - def initialize(host, token, port=80) - @host = host - @port = port - @token = token - @hdrs = { - 'Content-Type' => 'application/json', - VCAP::Services::Api::GATEWAY_TOKEN_HEADER => @token - } - end + class GatewayInternalResponse < ErrorResponse + def initialize(error) + super(503, error) + end + end - def provision(args) - msg = VCAP::Services::Api::GatewayProvisionRequest.new(args) - resp = perform_request(Net::HTTP::Post, '/gateway/v1/configurations', msg) - VCAP::Services::Api::GatewayProvisionResponse.decode(resp.body) - end + attr_reader :host, :port, :token + def initialize(url, token, timeout, opts={}) + @url = url + @timeout = timeout + @token = token + @hdrs = { + 'Content-Type' => 'application/json', + GATEWAY_TOKEN_HEADER => @token + } + end - def unprovision(args) - perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{args[:service_id]}") - end + def provision(args) + msg = GatewayProvisionRequest.new(args) + resp = perform_request(:post, '/gateway/v1/configurations', msg) + GatewayProvisionResponse.decode(resp) + end - def create_snapshot(args) - resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots") - VCAP::Services::Api::Job.decode(resp.body) - end + def unprovision(args) + resp = perform_request(:delete, "/gateway/v1/configurations/#{args[:service_id]}") + EMPTY_REQUEST + end - def enum_snapshots(args) - resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots") - VCAP::Services::Api::SnapshotList.decode(resp.body) - end + def create_snapshot(args) + resp = perform_request(:post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots") + Job.decode(resp) + end - def snapshot_details(args) - resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") - VCAP::Services::Api::Snapshot.decode(resp.body) - end + def enum_snapshots(args) + resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots") + SnapshotList.decode(resp) + end - def rollback_snapshot(args) - resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") - VCAP::Services::Api::Job.decode(resp.body) - end + def snapshot_details(args) + resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") + Snapshot.decode(resp) + end - def delete_snapshot(args) - resp = perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") - VCAP::Services::Api::Job.decode(resp.body) - end + def rollback_snapshot(args) + resp = perform_request(:put, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") + Job.decode(resp) + end - def serialized_url(args) - resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url") - VCAP::Services::Api::Job.decode(resp.body) - end + def delete_snapshot(args) + resp = perform_request(:delete, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}") + Job.decode(resp) + end - def import_from_url(args) - resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url", args[:msg]) - VCAP::Services::Api::Job.decode(resp.body) - end + def serialized_url(args) + resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url") + Job.decode(resp) + end - def import_from_data(args) - resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/data", args[:msg]) - VCAP::Services::Api::Job.decode(resp.body) - end + def import_from_url(args) + resp = perform_request(:put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url", args[:msg]) + Job.decode(resp) + end - def job_info(args) - resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/jobs/#{args[:job_id]}") - VCAP::Services::Api::Job.decode(resp.body) - end + def import_from_data(args) + resp = perform_request(:put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/data", args[:msg]) + Job.decode(resp) + end - def bind(args) - msg = VCAP::Services::Api::GatewayBindRequest.new(args) - resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{msg.service_id}/handles", msg) - VCAP::Services::Api::GatewayBindResponse.decode(resp.body) - end + def job_info(args) + resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/jobs/#{args[:job_id]}") + Job.decode(resp) + end - def unbind(args) - msg = VCAP::Services::Api::GatewayUnbindRequest.new(args) - perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{msg.service_id}/handles/#{msg.handle_id}", msg) - end + def bind(args) + msg = GatewayBindRequest.new(args) + resp = perform_request(:post, "/gateway/v1/configurations/#{msg.service_id}/handles", msg) + GatewayBindResponse.decode(resp) + end - protected + def unbind(args) + msg = GatewayUnbindRequest.new(args) + perform_request(:delete, "/gateway/v1/configurations/#{msg.service_id}/handles/#{msg.handle_id}", msg) + EMPTY_REQUEST + end - def perform_request(klass, path, msg=VCAP::Services::Api::EMPTY_REQUEST) - req = klass.new(path, initheader=@hdrs) - req.body = msg.encode - resp = Net::HTTP.new(@host, @port).start {|http| http.request(req)} - raise UnexpectedResponse, resp unless resp.is_a? Net::HTTPOK - resp + protected + + def perform_request(http_method, path, msg=VCAP::Services::Api::EMPTY_REQUEST) + result = nil + uri = URI.parse(@url) + if EM.reactor_running? + url = uri.merge!(path) + http = AsyncHttpRequest.fibered(url, @token, http_method, @timeout, msg) + raise UnexpectedResponse, "Error sending request #{msg.extract.to_json} to gateway #{@url}: #{http.error}" unless http.error.empty? + code = http.response_header.status.to_i + body = http.response + else + klass = METHODS_MAP[http_method] + req = klass.new(path, initheader=@hdrs) + req.body = msg.encode + resp = Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req)} + code = resp.code.to_i + body = resp.body + end + case code + when 200 + body + when 404 + err = ServiceErrorResponse.decode(body) + raise NotFoundResponse.new(err) + when 503 + err = ServiceErrorResponse.decode(body) + raise GatewayInternalResponse.new(err) + else + begin + # try to decode the response + err = ServiceErrorResponse.decode(body) + raise ErrorResponse.new(code, err) + rescue => e + raise UnexpectedResponse, "Can't decode gateway response. status code:#{code}, response body:#{body}" + end + end + end end - end diff --git a/lib/services/api/messages.rb b/lib/services/api/messages.rb index ab0cfbb..0aec90b 100644 --- a/lib/services/api/messages.rb +++ b/lib/services/api/messages.rb @@ -148,6 +148,11 @@ class SerializedURL < JsonMessage class SerializedData < JsonMessage required :data, String end + + class ServiceErrorResponse < JsonMessage + required :code, Integer + required :description, String + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ceed41..51564a9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,8 @@ require "vcap/config" require "vcap/priority_queue" require 'vcap/quota' +require 'services/api/clients/service_gateway_client' +require 'services/api/async_requests' require 'benchmark' RSpec::Matchers.define :take_less_than do |n| diff --git a/spec/unit/service_gateway_client_spec.rb b/spec/unit/service_gateway_client_spec.rb new file mode 100644 index 0000000..2fbf9df --- /dev/null +++ b/spec/unit/service_gateway_client_spec.rb @@ -0,0 +1,81 @@ +# Copyright (c) 2009-2012 VMware, Inc. +require 'spec_helper' + +module VCAP::Services::Api + class ServiceGatewayClient + public :perform_request + end +end +describe VCAP::Services::Api::ServiceGatewayClient do + describe '#perform_request' do + before :all do + @url = "http://localhost" + @token = "mytoken" + @timeout = 10 + end + + it "should use async http client when EM is running" do + client = VCAP::Services::Api::ServiceGatewayClient.new(@url, @token, @timeout) + EM.should_receive(:reactor_running?).and_return true + + path = "/path1" + resp = mock("resq") + message = "data" + resp.should_receive(:response).and_return(message) + resp.should_receive(:error).and_return [] + + resp_header = mock("resq_header") + resp_header.should_receive(:status).and_return(200) + resp.should_receive(:response_header).and_return resp_header + http_method = :get + + VCAP::Services::Api::AsyncHttpRequest.should_receive(:fibered).with(anything, @token, http_method, @timeout, anything).and_return resp + + result = client.perform_request(http_method, path) + result.should == message + end + + it "should use net/http client when EM is not running" do + client = VCAP::Services::Api::ServiceGatewayClient.new(@url, @token, @timeout) + EM.should_receive(:reactor_running?).and_return nil + + path = "/path1" + resp = mock("resq") + message = "data" + resp.should_receive(:body).and_return(message) + resp.should_receive(:code).and_return 200 + resp.should_receive(:start).and_return resp + + http_method = :get + + Net::HTTP.should_receive(:new).with("localhost", 80).and_return resp + + result = client.perform_request(http_method, path) + result.should == message + end + + + it "should should raise error with none 200 response" do + client = VCAP::Services::Api::ServiceGatewayClient.new(@url, @token, @timeout) + EM.should_receive(:reactor_running?).any_number_of_times.and_return nil + + path = "/path1" + resp = mock("resq") + resp.should_receive(:body).and_return( + {:code => 40400, :description=> "not found"}.to_json, + {:code => 50300, :description=> "internal"}.to_json, + {:code => 50100, :description=> "not done yet"}.to_json, + ) + resp.should_receive(:code).and_return(404, 503, 500) + resp.should_receive(:start).any_number_of_times.and_return resp + + http_method = :get + + Net::HTTP.should_receive(:new).with("localhost", 80).any_number_of_times.and_return resp + + expect {client.perform_request(http_method, path)}.should raise_error(VCAP::Services::Api::ServiceGatewayClient::NotFoundResponse) + expect {client.perform_request(http_method, path)}.should raise_error(VCAP::Services::Api::ServiceGatewayClient::GatewayInternalResponse) + expect {client.perform_request(http_method, path)}.should raise_error(VCAP::Services::Api::ServiceGatewayClient::UnexpectedResponse) + end + end +end