From 7867815b2d9231a236deb0a0c374f9e673185cc7 Mon Sep 17 00:00:00 2001 From: Alex Suraci Date: Tue, 12 Jul 2011 17:50:26 -0700 Subject: [PATCH] Application debug modes. Includes the addition of a global cloud controller route, `/info/runtimes', which responds with all runtime information from the DEAs. Also includes some renovations for how we store app metadata. Change-Id: I11b076b6c53b962acfa58407b7ebe857060bb08a --- .../app/controllers/apps_controller.rb | 7 ++++ .../app/controllers/default_controller.rb | 4 ++ cloud_controller/app/models/app.rb | 24 ++++++------ cloud_controller/app/models/app_manager.rb | 8 ++-- cloud_controller/config/cloud_controller.yml | 18 +++++++++ cloud_controller/config/routes.rb | 1 + ...43_rename_app_metadata_json_to_metadata.rb | 9 +++++ cloud_controller/db/schema.rb | 4 +- cloud_controller/lib/cloud_error.rb | 1 + dea/config/dea.yml | 5 +++ dea/lib/dea/agent.rb | 38 +++++++++++++++---- 11 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 cloud_controller/db/migrate/20110707170643_rename_app_metadata_json_to_metadata.rb diff --git a/cloud_controller/app/controllers/apps_controller.rb b/cloud_controller/app/controllers/apps_controller.rb index 12c2116..3aaae11 100644 --- a/cloud_controller/app/controllers/apps_controller.rb +++ b/cloud_controller/app/controllers/apps_controller.rb @@ -225,6 +225,13 @@ class AppsController < ApplicationController changed = app.changed CloudController.logger.debug "app: #{app.id} Updating #{changed.inspect}" + # reject attempts to start in debug mode if debugging is disabled + if body_params[:debug] and app.state == 'STARTED' and !AppConfig[:allow_debug] + raise CloudError.new(CloudError::APP_DEBUG_DISALLOWED) + end + + app.metadata[:debug] = body_params[:debug] if body_params + # 'app.save' can actually raise an exception, if whatever is # invalid happens all the way down at the DB layer. begin diff --git a/cloud_controller/app/controllers/default_controller.rb b/cloud_controller/app/controllers/default_controller.rb index f770cb9..b0bf2d0 100644 --- a/cloud_controller/app/controllers/default_controller.rb +++ b/cloud_controller/app/controllers/default_controller.rb @@ -19,6 +19,10 @@ class DefaultController < ApplicationController render :json => info end + def runtime_info + render :json => AppConfig[:runtimes] + end + def service_info svcs = Service.active_services.select {|svc| svc.visible_to_user?(user)} CloudController.logger.debug("Global service listing found #{svcs.length} services.") diff --git a/cloud_controller/app/models/app.rb b/cloud_controller/app/models/app.rb index c252a5a..c539808 100644 --- a/cloud_controller/app/models/app.rb +++ b/cloud_controller/app/models/app.rb @@ -12,6 +12,10 @@ class App < ActiveRecord::Base before_validation :normalize_legacy_staging_strings! + serialize :metadata, Hash + + after_initialize :set_defaults + after_create :add_owner_as_collaborator scope :started, lambda { where("apps.state = ?", 'STARTED') } @@ -78,6 +82,10 @@ class App < ActiveRecord::Base result end + def set_defaults + self.metadata ||= {} + end + def total_memory instances * memory end @@ -93,7 +101,11 @@ class App < ActiveRecord::Base :services => bound_services, :version => generate_version, :env => environment, - :meta => {:version => lock_version, :created => Time.now.to_i} } + :meta => metadata.merge({ + :version => lock_version, + :created => Time.now.to_i + }) + } end # Called by AppManager when staging this app. @@ -338,16 +350,6 @@ class App < ActiveRecord::Base end end - def metadata - json = read_attribute(:metadata_json) - return {} if json.blank? - Yajl::Parser.parse(json, :symbolize_keys => true) - end - - def metadata=(hash) - write_attribute(:metadata_json, Yajl::Encoder.encode(hash)) - end - def environment json_crypt = read_attribute(:environment_json) return [] if json_crypt.blank? diff --git a/cloud_controller/app/models/app_manager.rb b/cloud_controller/app/models/app_manager.rb index 0c7ea07..741a3ec 100644 --- a/cloud_controller/app/models/app_manager.rb +++ b/cloud_controller/app/models/app_manager.rb @@ -191,6 +191,7 @@ class AppManager f = Fiber.new do message = start_message.dup message[:executableUri] = download_app_uri(message[:executableUri]) + message[:debug] = @app.metadata[:debug] (index...max_to_start).each do |i| message[:index] = i dea_id = find_dea_for(message) @@ -323,8 +324,8 @@ class AppManager raise e # re-raise here to propogate to the API call. end - # Returns an array of hashes containing 'index', 'state', and 'since'(timestamp) - # for all instances running, or trying to run, the app. + # Returns an array of hashes containing 'index', 'state', 'since'(timestamp), + # and 'debug_port' for all instances running, or trying to run, the app. def find_instances return [] unless app.started? instances = app.instances @@ -370,7 +371,8 @@ class AppManager indices[index] = { :index => index, :state => instance_json[:state], - :since => instance_json[:state_timestamp] + :since => instance_json[:state_timestamp], + :debug_port => instance_json[:debug_port] } end end diff --git a/cloud_controller/config/cloud_controller.yml b/cloud_controller/config/cloud_controller.yml index f0ffac9..80a8eb0 100644 --- a/cloud_controller/config/cloud_controller.yml +++ b/cloud_controller/config/cloud_controller.yml @@ -81,6 +81,8 @@ staging: max_staging_runtime: 120 # secs secure: false +allow_debug: false + # admin support admins: [derek@gmail.com, foobar@vmware.com] https_required: false @@ -105,3 +107,19 @@ builtin_services: token: "0xdeadbeef" rabbitmq: token: "0xdeadbeef" + +# used for /info/runtimes endpoint (served unfiltered as JSON) +runtimes: + ruby18: + version: 1.8.7 + ruby19: + version: 1.9.2 + node: + version: 0.4.[2-9] + java: + version: 1.6.0 + debug_modes: + - run + - wait + erlangR14B02: + version: ".* 5.8.3" diff --git a/cloud_controller/config/routes.rb b/cloud_controller/config/routes.rb index 687ab07..7686996 100644 --- a/cloud_controller/config/routes.rb +++ b/cloud_controller/config/routes.rb @@ -4,6 +4,7 @@ CloudController::Application.routes.draw do get 'info' => 'default#info', :as => :cloud_info get 'info/services' => 'default#service_info', :as => :cloud_service_info + get 'info/runtimes' => 'default#runtime_info', :as => :cloud_runtime_info get 'users' => 'users#list', :as => :list_users post 'users' => 'users#create', :as => :create_user get 'users/*email' => 'users#info', :as => :user_info diff --git a/cloud_controller/db/migrate/20110707170643_rename_app_metadata_json_to_metadata.rb b/cloud_controller/db/migrate/20110707170643_rename_app_metadata_json_to_metadata.rb new file mode 100644 index 0000000..5931dad --- /dev/null +++ b/cloud_controller/db/migrate/20110707170643_rename_app_metadata_json_to_metadata.rb @@ -0,0 +1,9 @@ +class RenameAppMetadataJsonToMetadata < ActiveRecord::Migration + def self.up + rename_column :apps, :metadata_json, :metadata + end + + def self.down + rename_column :apps, :metadata, :metadata_json + end +end diff --git a/cloud_controller/db/schema.rb b/cloud_controller/db/schema.rb index 54c6f42..08d5e99 100644 --- a/cloud_controller/db/schema.rb +++ b/cloud_controller/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110521014004) do +ActiveRecord::Schema.define(:version => 20110707170643) do create_table "app_collaborations", :force => true do |t| t.integer "app_id" @@ -32,7 +32,7 @@ ActiveRecord::Schema.define(:version => 20110521014004) do t.string "package_state", :default => "PENDING" t.string "package_hash" t.text "environment_json" - t.text "metadata_json" + t.text "metadata" t.boolean "external_secret", :default => false t.datetime "created_at" t.datetime "updated_at" diff --git a/cloud_controller/lib/cloud_error.rb b/cloud_controller/lib/cloud_error.rb index 7d7d7c8..3db34d3 100644 --- a/cloud_controller/lib/cloud_error.rb +++ b/cloud_controller/lib/cloud_error.rb @@ -39,6 +39,7 @@ class CloudError < StandardError APP_FILE_ERROR = [306, HTTP_INTERNAL_SERVER_ERROR, "Error retrieving file '%s'"] APP_INVALID_RUNTIME = [307, HTTP_BAD_REQUEST, "Invalid runtime specification [%s] for framework: '%s'"] APP_INVALID_FRAMEWORK = [308, HTTP_BAD_REQUEST, "Invalid framework description: '%s'"] + APP_DEBUG_DISALLOWED = [309, HTTP_BAD_REQUEST, "Cloud controller has disallowed debugging."] # Bits RESOURCES_UNKNOWN_PACKAGE_TYPE = [400, HTTP_BAD_REQUEST, "Unknown package type requested: \"%\""] diff --git a/dea/config/dea.yml b/dea/config/dea.yml index b836824..df32386 100644 --- a/dea/config/dea.yml +++ b/dea/config/dea.yml @@ -43,6 +43,11 @@ runtimes: version: 1.6.0 version_flag: '-version' environment: + debug_env: + run: + - JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=$VCAP_DEBUG_PORT,server=y,suspend=n" + wait: + - JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=$VCAP_DEBUG_PORT,server=y,suspend=y" php: executable: php version: 5.3.[2-6] diff --git a/dea/lib/dea/agent.rb b/dea/lib/dea/agent.rb index 47a3113..9271674 100644 --- a/dea/lib/dea/agent.rb +++ b/dea/lib/dea/agent.rb @@ -399,7 +399,8 @@ module DEA :state_timestamp => instance[:state_timestamp], :file_uri => "http://#{@local_ip}:#{@file_viewer_port}/droplets/", :credentials => @file_auth, - :staged => instance[:staged] + :staged => instance[:staged], + :debug_port => instance[:debug_port] } if include_stats && instance[:state] == :RUNNING response[:stats] = { @@ -494,6 +495,7 @@ module DEA users = message_json['users'] runtime = message_json['runtime'] framework = message_json['framework'] + debug = message_json['debug'] # Limits processing mem = DEFAULT_APP_MEM @@ -560,16 +562,26 @@ module DEA end start_operation = proc do - port = VCAP.grab_ephemeral_port - @logger.debug('Completed download') - @logger.info("Starting up instance #{instance[:log_id]} on port:#{port}") + + port = VCAP.grab_ephemeral_port + instance[:port] = port + + starting = "Starting up instance #{instance[:log_id]} on port:#{port}" + + if debug + debug_port = VCAP.grab_ephemeral_port + instance[:debug_port] = debug_port + instance[:debug_mode] = debug + + @logger.info("#{starting} with debugger:#{debug_port}") + else + @logger.info(starting) + end @logger.debug("Clients: #{@num_clients}") @logger.debug("Reserved Memory Usage: #{@reserved_mem} MB of #{@max_memory} MB TOTAL") - instance[:port] = port - manifest_file = File.join(instance[:dir], 'droplet.yaml') manifest = {} manifest = File.open(manifest_file) { |f| YAML.load(f) } if File.file?(manifest_file) @@ -856,7 +868,7 @@ module DEA if state && state['state'] == 'RUNNING' block.call(true) timer.cancel - else + elsif instance[:debug_mode] != "wait" attempts += 1 if attempts > 600 || instance[:state] != :STARTING # 5 minutes or instance was stopped block.call(false) @@ -1048,6 +1060,12 @@ module DEA env_hash.to_json end + def debug_env(instance) + return unless instance[:debug_port] + return unless envs = @runtimes[instance[:runtime]]['debug_env'] + envs[instance[:debug_mode]] + end + def setup_instance_env(instance, app_env, services) env = [] @@ -1056,6 +1074,12 @@ module DEA env << "VCAP_SERVICES='#{create_services_for_env(services)}'" env << "VCAP_APP_HOST='#{@local_ip}'" env << "VCAP_APP_PORT='#{instance[:port]}'" + env << "VCAP_DEBUG_PORT='#{instance[:debug_port]}'" + + if vars = debug_env(instance) + @logger.info("Debugger environment variables: #{vars.inspect}") + env += vars + end # LEGACY STUFF env << "VMC_WARNING_WARNING='All VMC_* environment variables are deprecated, please use VCAP_* versions.'"