diff --git a/lib/procodile/cli.rb b/lib/procodile/cli.rb index 43db7c3..fe5e064 100644 --- a/lib/procodile/cli.rb +++ b/lib/procodile/cli.rb @@ -143,6 +143,7 @@ def dispatch(command) instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag], :port_allocations => @options[:port_allocations]) if instances.empty? puts "No processes to start." + report_processes_below_quantity else instances.each do |instance| puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})" @@ -475,6 +476,32 @@ def self.pid_active?(pid) false end + # `start` only starts process types with no running instances at all (see + # Supervisor#start_processes), so a type running below its configured quantity is + # skipped and the user is told "No processes to start." with no indication that + # anything is wrong. Name the command that actually tops it up. + def report_processes_below_quantity + status = ControlClient.run(@config.sock_path, 'status') + below = processes_below_quantity(status['messages'], process_names_from_cli_option) + return if below.empty? + + verb = below.size == 1 ? 'is' : 'are' + puts "#{below.join(', ')} #{verb} running below the configured quantity." + puts "Run `procodile check_concurrency` to start the missing process(es)." + rescue Procodile::Error + # This is advisory output only -- never turn a successful no-op into a failure. + nil + end + + # Names of process types that the supervisor reports as below their configured + # quantity, optionally narrowed to the types the user asked to start. Instance + # suffixes ("worker.1") are reduced to the type name. + def processes_below_quantity(messages, requested = nil) + below = messages.select { |m| m['type'] == 'incorrect_quantity' }.map { |m| m['process'] } + return below if requested.nil? + below & requested.map { |name| name.split('.', 2).first } + end + def process_names_from_cli_option if @options[:processes] processes = @options[:processes].split(',') diff --git a/spec/specs/cli_spec.rb b/spec/specs/cli_spec.rb new file mode 100644 index 0000000..0b533a5 --- /dev/null +++ b/spec/specs/cli_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require 'procodile/cli' + +describe Procodile::CLI do + subject(:cli) { described_class.new } + + let(:worker_below) do + {'type' => 'incorrect_quantity', 'process' => 'worker', 'current' => 23, 'desired' => 25} + end + + let(:web_below) do + {'type' => 'incorrect_quantity', 'process' => 'web', 'current' => 1, 'desired' => 2} + end + + let(:not_running) do + {'type' => 'not_running', 'instance' => 'worker.1', 'status' => 'Failed'} + end + + describe "#processes_below_quantity" do + it "returns every under-quantity process when nothing specific was requested" do + result = cli.send(:processes_below_quantity, [worker_below, web_below], nil) + expect(result).to eq(['worker', 'web']) + end + + it "ignores messages that are not about quantity" do + result = cli.send(:processes_below_quantity, [not_running], nil) + expect(result).to be_empty + end + + it "narrows to the processes the user asked to start" do + result = cli.send(:processes_below_quantity, [worker_below, web_below], ['web']) + expect(result).to eq(['web']) + end + + it "matches a requested instance name against its process type" do + result = cli.send(:processes_below_quantity, [worker_below], ['worker.1']) + expect(result).to eq(['worker']) + end + + it "returns nothing when the requested process is at its quantity" do + result = cli.send(:processes_below_quantity, [worker_below], ['web']) + expect(result).to be_empty + end + + it "returns nothing when there are no messages" do + expect(cli.send(:processes_below_quantity, [], nil)).to be_empty + end + end +end