github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/integration/assets/service_broker/service_broker.rb (about)

     1  ENV['RACK_ENV'] ||= 'development'
     2  
     3  require 'rubygems'
     4  require 'sinatra/base'
     5  require 'json'
     6  require 'pp'
     7  require 'logger'
     8  require 'rainbow/ext/string'
     9  
    10  require 'bundler'
    11  Bundler.require :default, ENV['RACK_ENV'].to_sym
    12  Rainbow.enabled = true
    13  
    14  $stdout.sync = true
    15  $stderr.sync = true
    16  
    17  class ServiceInstance
    18    attr_reader :provision_data, :fetch_count, :deleted
    19  
    20    def initialize(opts={})
    21      @provision_data = opts.fetch(:provision_data)
    22      @fetch_count = opts.fetch(:fetch_count, 0)
    23      @deleted = opts.fetch(:deleted, false)
    24    end
    25  
    26    def plan_id
    27      @provision_data['plan_id']
    28    end
    29  
    30    def update!(updated_data)
    31      @provision_data.merge!(updated_data)
    32      @fetch_count = 0
    33      self
    34    end
    35  
    36    def delete!
    37      @deleted = true
    38      @fetch_count = 0
    39      self
    40    end
    41  
    42    def increment_fetch_count
    43      @fetch_count += 1
    44    end
    45  
    46    def to_json(opts={})
    47      {
    48        provision_data: provision_data,
    49        fetch_count: fetch_count,
    50        deleted: deleted
    51      }.to_json(opts)
    52    end
    53  end
    54  
    55  class DataSource
    56    attr_reader :data
    57  
    58    def initialize(data = nil)
    59      @data = data || JSON.parse(File.read(File.absolute_path('data.json')))
    60    end
    61  
    62    def max_fetch_service_instance_requests
    63      @data['max_fetch_service_instance_requests'] || 1
    64    end
    65  
    66    def service_instance_by_id(cc_id)
    67      @data['service_instances'][cc_id]
    68    end
    69  
    70    def create_service_instance(cc_id, json_data)
    71      service_instance = ServiceInstance.new(
    72        provision_data: json_data,
    73      )
    74  
    75      @data['service_instances'][cc_id] = service_instance
    76  
    77      service_instance
    78    end
    79  
    80    def create_service_binding(instance_id, binding_id, binding_data)
    81      @data['service_instances'][binding_id] = {
    82        'binding_data' => binding_data,
    83        'instance_id' => instance_id,
    84      }
    85    end
    86  
    87    def delete_service_binding(binding_id)
    88      @data['service_instances'].delete(binding_id)
    89    end
    90  
    91    def merge!(data)
    92      data = data.dup
    93      data['service_instances'] = data.fetch('service_instances', {}).inject({}) do |service_instances, (guid, instance_data)|
    94        symbolized_data = instance_data.inject({}) do |memo,(k,v)|
    95          memo[k.to_sym] = v
    96          memo
    97        end
    98  
    99        service_instances[guid] = ServiceInstance.new(symbolized_data)
   100        service_instances
   101      end
   102  
   103      data.each_pair do |key, value|
   104        if @data[key] && @data[key].is_a?(Hash)
   105          @data[key].merge!(value)
   106        else
   107          @data[key] = value
   108        end
   109      end
   110    end
   111  
   112    def without_instances_or_bindings
   113      @data.reject { |key| %w(service_instances service_bindings).include?(key) }
   114    end
   115  
   116    def behavior_for_type(type, plan_id)
   117      plans_or_default_behavior = @data['behaviors'][type.to_s]
   118  
   119      return plans_or_default_behavior if type == :catalog
   120  
   121      raise "Behavior object is missing key: #{type} (tried to lookup plan_id #{plan_id})" unless plans_or_default_behavior
   122  
   123      if plan_id && plans_or_default_behavior.has_key?(plan_id)
   124        plans_or_default_behavior[plan_id]
   125      else
   126        $log.info("Could not find response for plan id: #{plan_id}")
   127        return plans_or_default_behavior['default'] if plans_or_default_behavior['default']
   128        raise "Behavior for #{type} is missing response for plan_id #{plan_id} and default response."
   129      end
   130    end
   131  end
   132  
   133  class ServiceBroker < Sinatra::Base
   134    set :logging, true
   135  
   136    configure :production, :development, :test do
   137      $datasource = DataSource.new
   138      $log = Logger.new(STDOUT)
   139      $log.level = Logger::INFO
   140      $log.formatter = proc do |severity, datetime, progname, msg|
   141        "#{severity}: #{msg}\n"
   142      end
   143    end
   144  
   145    def log(request)
   146      $log.info "#{request.env['REQUEST_METHOD']} #{request.env['PATH_INFO']} #{request.env['QUERY_STRING']}".color(:yellow)
   147      request.body.rewind
   148      headers = find_headers(request)
   149      $log.info "Request headers: #{headers}".color(:cyan)
   150      $log.info "Request body: #{request.body.read}".color(:yellow)
   151      request.body.rewind
   152    end
   153  
   154    def find_headers(request)
   155      request.env.select { |key, _| key =~ /HTTP/ }
   156    end
   157  
   158    def log_response(status, body)
   159      $log.info "Response: status=#{status}, body=#{body}".color(:green)
   160      body
   161    end
   162  
   163    def respond_with_behavior(behavior, accepts_incomplete=false)
   164      sleep behavior['sleep_seconds']
   165  
   166      if behavior['async_only'] && !accepts_incomplete
   167        respond_async_required
   168      else
   169        respond_from_config(behavior)
   170      end
   171    end
   172  
   173    def respond_async_required
   174      status 422
   175      log_response(status, {
   176        'error' => 'AsyncRequired',
   177        'description' => 'This service plan requires client support for asynchronous service operations.'
   178       }.to_json)
   179    end
   180  
   181    def respond_from_config(behavior)
   182      status behavior['status']
   183      if behavior['body']
   184        log_response(status, behavior['body'].to_json)
   185      else
   186        log_response(status, behavior['raw_body'])
   187      end
   188    end
   189  
   190    before do
   191      log(request)
   192    end
   193  
   194    # fetch catalog
   195    get '/v2/catalog/?' do
   196      respond_with_behavior($datasource.behavior_for_type(:catalog, nil))
   197    end
   198  
   199    # provision
   200    put '/v2/service_instances/:id/?' do |id|
   201      json_body = JSON.parse(request.body.read)
   202      service_instance = $datasource.create_service_instance(id, json_body)
   203      respond_with_behavior($datasource.behavior_for_type(:provision, service_instance.plan_id), params['accepts_incomplete'])
   204    end
   205  
   206    # fetch service instance
   207    get '/v2/service_instances/:id/last_operation/?' do |id|
   208      service_instance = $datasource.service_instance_by_id(id)
   209      if service_instance
   210        plan_id = service_instance.plan_id
   211  
   212        if service_instance.increment_fetch_count > $datasource.max_fetch_service_instance_requests
   213          state = 'finished'
   214        else
   215          state = 'in_progress'
   216        end
   217  
   218        behavior = $datasource.behavior_for_type('fetch', plan_id)[state]
   219        sleep behavior['sleep_seconds']
   220        status behavior['status']
   221  
   222        if behavior['body']
   223          log_response(status, behavior['body'].to_json)
   224        else
   225          log_response(status, behavior['raw_body'])
   226        end
   227      else
   228        status 200
   229        log_response(status, {
   230          state: 'failed',
   231          description: "Broker could not find service instance by the given id #{id}",
   232        }.to_json)
   233      end
   234    end
   235  
   236    # update service instance
   237    patch '/v2/service_instances/:id/?' do |id|
   238      json_body = JSON.parse(request.body.read)
   239      service_instance = $datasource.service_instance_by_id(id)
   240      plan_id = json_body['plan_id']
   241  
   242      behavior = $datasource.behavior_for_type(:update, plan_id)
   243      if [200, 202].include?(behavior['status'])
   244        service_instance.update!(json_body) if service_instance
   245      end
   246  
   247      respond_with_behavior(behavior, params['accepts_incomplete'])
   248    end
   249  
   250    # deprovision
   251    delete '/v2/service_instances/:id/?' do |id|
   252      service_instance = $datasource.service_instance_by_id(id)
   253      if service_instance
   254        service_instance.delete!
   255        respond_with_behavior($datasource.behavior_for_type(:deprovision, service_instance.plan_id), params[:accepts_incomplete])
   256      else
   257        respond_with_behavior($datasource.behavior_for_type(:deprovision, nil), params[:accepts_incomplete])
   258      end
   259    end
   260  
   261    # create service binding
   262    put '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id|
   263      content_type :json
   264      json_body = JSON.parse(request.body.read)
   265  
   266      service_binding = $datasource.create_service_binding(instance_id, binding_id, json_body)
   267      respond_with_behavior($datasource.behavior_for_type(:bind, service_binding['binding_data']['plan_id']))
   268    end
   269  
   270    # delete service binding
   271    delete '/v2/service_instances/:instance_id/service_bindings/:id' do |instance_id, binding_id|
   272      content_type :json
   273  
   274      service_binding = $datasource.delete_service_binding(binding_id)
   275      if service_binding
   276        respond_with_behavior($datasource.behavior_for_type(:unbind, service_binding['binding_data']['plan_id']))
   277      else
   278        respond_with_behavior($datasource.behavior_for_type(:unbind, nil))
   279      end
   280    end
   281  
   282    get '/config/all/?' do
   283      log_response(status, JSON.pretty_generate($datasource.data))
   284    end
   285  
   286    get '/config/?' do
   287      log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings))
   288    end
   289  
   290    post '/config/?' do
   291      json_body = JSON.parse(request.body.read)
   292      $datasource.merge!(json_body)
   293      log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings))
   294    end
   295  
   296    post '/config/reset/?' do
   297      $datasource = DataSource.new
   298      log_response(status, JSON.pretty_generate($datasource.without_instances_or_bindings))
   299    end
   300  
   301    error do
   302      status 500
   303      e = env['sinatra.error']
   304      log_response(status, JSON.pretty_generate({
   305        error: true,
   306        message: e.message,
   307        path: request.url,
   308        timestamp: Time.new,
   309        type: '500',
   310        backtrace: e.backtrace
   311      }))
   312    end
   313  
   314    run! if app_file == $0
   315  end