cloudfoundry/cloud_controller_ng

View on GitHub
app/controllers/internal/staging_completion_controller.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'sinatra'
require 'controllers/base/base_controller'
require 'cloud_controller/diego/failure_reason_sanitizer'
require 'cloud_controller/telemetry_logger'

module VCAP::CloudController
  class StagingCompletionController < RestController::BaseController
    def self.dependencies
      [:stagers]
    end

    allow_unauthenticated_access

    def inject_dependencies(dependencies)
      super
      @stagers = dependencies.fetch(:stagers)
    end

    post '/internal/v3/staging/:staging_guid/droplet_completed', :droplet_completed # NOT USED, only for rolling deploys
    def droplet_completed(staging_guid)
      staging_response = read_body
      droplet          = DropletModel.find(guid: staging_guid)
      raise CloudController::Errors::ApiError.new_from_details('ResourceNotFound', 'Droplet not found') if droplet.nil?

      build = BuildModel.create(package: droplet.package, app: droplet.app, state: DropletModel::STAGING_STATE)

      BuildModel.db.transaction do
        build.lock!
        build.update(
          droplet: droplet,
          buildpack_lifecycle_data: droplet.buildpack_lifecycle_data
        )
      end

      if staging_response.key?(:failed)
        report_metrics(staging_response)
        staging_response = parse_bbs_task_callback(staging_response)
      end

      begin
        stagers.stager_for_build(build).staging_complete(build, staging_response, params['start'] == 'true')
      rescue CloudController::Errors::ApiError => e
        raise e
      rescue StandardError => e
        logger.error('diego.staging.completion-controller-error', error: e)
        raise CloudController::Errors::ApiError.new_from_details('ServerError')
      end

      [200, '{}']
    end

    post '/internal/v3/staging/:staging_guid/build_completed', :build_completed
    def build_completed(staging_guid)
      staging_response = read_body
      build = BuildModel.find(guid: staging_guid)
      raise CloudController::Errors::ApiError.new_from_details('ResourceNotFound', 'Build not found') if build.nil?

      if staging_response.key?(:failed)
        report_metrics(staging_response)
        staging_response = parse_bbs_task_callback(staging_response)
      end

      begin
        stagers.stager_for_build(build).staging_complete(build, staging_response, params['start'] == 'true')
      rescue CloudController::Errors::ApiError => e
        logger.error('diego.staging.completion-controller-api_err-error', error: e)
        raise e
      rescue StandardError => e
        logger.error('diego.staging.completion-controller-error', error: e)
        raise CloudController::Errors::ApiError.new_from_details('ServerError')
      end

      TelemetryLogger.internal_emit(
        'build-completed',
        {
          'app-id' => build.app.guid,
          'build-id' => build.guid
        },
        {
          'lifecycle' => build.lifecycle_type,
          'buildpacks' => build.lifecycle_data&.buildpacks,
          'stack' => build.lifecycle_data.try(:stack)
        }
      )

      [200, '{}']
    end

    private

    def parse_bbs_task_callback(staging_response)
      result = {}
      if staging_response[:failed]
        logger.error('diego.staging.completion-controller.staging-failed', staging_response)
        result[:error] = Diego::FailureReasonSanitizer.sanitize(staging_response[:failure_reason])
      else
        result[:result] = Oj.load(staging_response[:result], symbol_keys: true)
      end
      result
    end

    def report_metrics(bbs_staging_response)
      duration = (Time.now.utc.to_i * 1e9) - bbs_staging_response[:created_at]
      if bbs_staging_response[:failed]
        statsd_updater.report_staging_failure_metrics(duration)
        prometheus_updater.report_staging_failure_metrics(duration)
      else
        statsd_updater.report_staging_success_metrics(duration)
        prometheus_updater.report_staging_success_metrics(duration)
      end
    end

    def statsd_updater
      CloudController::DependencyLocator.instance.statsd_updater
    end

    def prometheus_updater
      CloudController::DependencyLocator.instance.prometheus_updater
    end

    attr_reader :stagers

    def read_body
      payload          = body.read
      Oj.load(payload, symbol_keys: true)
    rescue StandardError => e
      logger.error('diego.staging.parse-error', payload: payload, error: e.to_s)
      raise CloudController::Errors::ApiError.new_from_details('MessageParseError', payload)
    end
  end
end