diff --git a/Gemfile.lock b/Gemfile.lock index 3e8640c..6d65f36 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ PATH bcrypt (~> 3.1.5) bundler (~> 1.5) coffee-rails (~> 4.0.0) - fluentd (= 0.10.46) + fluentd (~> 0.10.48) font-awesome-rails haml-rails (~> 0.5.3) httpclient @@ -81,14 +81,14 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) - fluentd (0.10.46) + fluentd (0.10.48) cool.io (>= 1.1.1, < 2.0.0, != 1.2.0) http_parser.rb (>= 0.5.1, < 0.7.0) json (>= 1.4.3) msgpack (>= 0.4.4, < 0.6.0, != 0.5.3, != 0.5.2, != 0.5.1, != 0.5.0) sigdump (~> 0.2.2) yajl-ruby (~> 1.0) - font-awesome-rails (4.0.3.1) + font-awesome-rails (4.1.0.0) railties (>= 3.2, < 5.0) haml (4.0.5) tilt diff --git a/app/controllers/fluentd/agents_controller.rb b/app/controllers/fluentd/agents_controller.rb new file mode 100644 index 0000000..7eb5967 --- /dev/null +++ b/app/controllers/fluentd/agents_controller.rb @@ -0,0 +1,37 @@ +class Fluentd::AgentsController < ApplicationController + before_action :find_fluentd + + def show + end + + def start + unless @fluentd.agent.start + flash[:error] = t("error.fluentd_start_failed") + end + redirect_to fluentd_agent_path(@fluentd), status: 303 # 303 is change HTTP Verb GET + end + + def stop + unless @fluentd.agent.stop + flash[:error] = t("error.fluentd_stop_failed") + end + redirect_to fluentd_agent_path(@fluentd), status: 303 # 303 is change HTTP Verb GET + end + + def restart + unless @fluentd.agent.restart + flash[:error] = t("error.fluentd_restart_failed") + end + redirect_to fluentd_agent_path(@fluentd), status: 303 # 303 is change HTTP Verb GET + end + + def log + render text: @fluentd.agent.log, content_type: "text/plain" + end + + private + + def find_fluentd + @fluentd = Fluentd.find(params[:fluentd_id]) + end +end diff --git a/app/controllers/fluentd/daemons_controller.rb b/app/controllers/fluentd/daemons_controller.rb deleted file mode 100644 index 1103cf5..0000000 --- a/app/controllers/fluentd/daemons_controller.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Fluentd::DaemonsController < ApplicationController - before_action :login_required - before_action :fluentd - - def show - end - - def start - fluentd.start - render :show - end - - def stop - fluentd.stop - render :show - end - - def reload - fluentd.reload - render :show - end - - def log - render text: fluentd.log, content_type: "text/plain" - end - - private - - def fluentd - @fluentd ||= Fluentd.new(Rails.root + "tmp" + "fluentd") # TODO - end -end diff --git a/app/controllers/fluentd_controller.rb b/app/controllers/fluentd_controller.rb index 6531678..dbad858 100644 --- a/app/controllers/fluentd_controller.rb +++ b/app/controllers/fluentd_controller.rb @@ -1,7 +1,47 @@ class FluentdController < ApplicationController - before_action :login_required + before_action :find_fluentd, only: [:edit, :update, :destroy] def index - @daemons = [Fluentd.new(Rails.root + "tmp" + "fluentd")] # TODO + @fluentds = Fluentd.all + end + + def new + @fluentd = Fluentd.new(Fluentd::Agent::Fluentd::DEFAULT_OPTIONS) # TODO: not fluentd type + end + + def create + @fluentd = Fluentd.new(fluentd_params) + unless @fluentd.save + return render :new + end + redirect_to fluentd_index_path + end + + def edit + end + + def update + # TODO: should restart if changed file path? or just do "dirty" flagged? + @fluentd.update_attributes(fluentd_params) + unless @fluentd.save + return render :edit + end + redirect_to fluentd_index_path + end + + def destroy + @fluentd.agent.stop if @fluentd.agent.running? + @fluentd.destroy + redirect_to fluentd_index_path + end + + private + + def find_fluentd + @fluentd = Fluentd.find(params[:id]) + end + + def fluentd_params + params.require(:fluentd).permit(:log_file, :pid_file, :config_file, :variant) end end diff --git a/app/helpers/fluentd/daemons_helper.rb b/app/helpers/fluentd/daemons_helper.rb deleted file mode 100644 index 977e042..0000000 --- a/app/helpers/fluentd/daemons_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Fluentd::DaemonsHelper -end diff --git a/app/models/fluentd.rb b/app/models/fluentd.rb index 91e8e01..3b113d2 100644 --- a/app/models/fluentd.rb +++ b/app/models/fluentd.rb @@ -1,109 +1,64 @@ -Bundler.require(:default, :development) +class Fluentd < ActiveRecord::Base + before_validation :expand_paths + validates :variant, inclusion: { in: proc { Fluentd.variants } } + validates :log_file, presence: true + validates :pid_file, presence: true + validates :config_file, presence: true + validate :validate_permissions -require 'fluent/log' -require 'fluent/env' -require 'fluent/version' -require 'fluent/supervisor' - -class Fluentd - attr_reader :root_dir - - def initialize(root_dir) - @root_dir = root_dir - FileUtils.mkdir_p @root_dir + def self.variants + %w(fluentd) # TODO: end - def pid_file - File.join(root_dir, "fluentd.pid") + def fluentd? + variant == "fluentd" end - def pid - return unless File.exists?(pid_file) - File.read(pid_file) + def td_agent? + variant == "td-agent" end - def log_file - File.join(root_dir, "fluentd.log") - end - - def config_file - file = File.join(root_dir, "fluentd.conf") - unless File.exists?(file) - File.open(file, "w") {|f| f.write "\ntype forward\n" } # TODO - end - file - end - - def plugin_dir - dir = File.join(root_dir, "fluentd", "plugins") - unless Dir.exist?(dir) - FileUtils.mkdir_p(dir) - end - dir - end - - def options - # TODO: https://github.com/fluent/fluentd/pull/315 - { - :config_path => Fluent::DEFAULT_CONFIG_PATH, - :plugin_dirs => [Fluent::DEFAULT_PLUGIN_DIR], - :log_level => Fluent::Log::LEVEL_INFO, - :log_path => nil, - :daemonize => false, - :libs => [], - :setup_path => nil, - :chuser => nil, - :chgroup => nil, - :suppress_interval => 0, - :suppress_repeated_stacktrace => false, - :use_v1_config => false, - }.merge({ - :use_v1_config => true, - :plugin_dirs => [plugin_dir], - :config_path => config_file, - :daemonize => pid_file, - :log_path => log_file, - :log_level => Fluent::Log::LEVEL_INFO, + def agent + klass = variant.underscore.camelize + @agent ||= Agent.const_get(klass).new({ + :pid_file => pid_file, + :log_file => log_file, + :config_file => config_file, }) end - def running? - pid && system("/bin/kill -0 #{pid}", :out => File::NULL, :err => File::NULL) + def expand_paths + %w(pid_file log_file config_file).each do |column| + path = send(column) + next if path.blank? + self.send("#{column}=", File.expand_path(path)) + end end - def start - return if running? - spawn("bundle exec fluentd #{options_to_argv(options)}") # TODO + def validate_permissions + %w(pid_file log_file config_file).each do |column| + check_permission(column) + end end - def stop - return unless running? - system("/bin/kill -TERM #{pid}") - File.unlink(pid_file) - end + def check_permission(column) + path = send(column) + return if path.blank? # if empty, presence: true will catch it + if File.exist?(path) + if File.directory?(path) + errors.add(column, :is_a_directory) + end - def reload - return unless running? - system("/bin/kill -HUP #{pid}") - end - - def log - File.read log_file # TODO: large log file - end - - def config - ::Fluentd::Configuration.new(config_file) - end - - private - - def options_to_argv(options) - argv = "" - argv << " --use-v1-config" if options[:use_v1_config] - argv << " -c #{options[:config_path]}" if options[:config_path].present? - argv << " -p #{options[:plugin_dir].first}" if options[:plugin_dir].present? - argv << " -d #{options[:daemonize]}" if options[:daemonize].present? - argv << " -o #{options[:log_path]}" if options[:log_path].present? - argv + unless File.writable?(path) + errors.add(column, :lack_write_permission) + end + unless File.readable?(path) + errors.add(column, :lack_read_permission) + end + else + unless File.writable?(File.dirname(path)) + errors.add(column, :lack_write_permission) + end + end end end diff --git a/app/models/fluentd/agent.rb b/app/models/fluentd/agent.rb new file mode 100644 index 0000000..e4ac19d --- /dev/null +++ b/app/models/fluentd/agent.rb @@ -0,0 +1,28 @@ +require 'fluent/log' +require 'fluent/env' +require 'fluent/version' +require 'fluent/supervisor' +require "fluentd/agent/common" +require "fluentd/agent/fluentd" +require "fluentd/agent/td_agent" +require "fluentd/agent/remote" + +class Fluentd + class Agent + # pidfile + # td-agent: /var/run/td-agent/td-agent.pid + # - https://github.com/treasure-data/td-agent/blob/master/td-agent.logrotate#L10 + # - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.init#L25 + # fluentd: nothing (or --daemon PIDFILE) + # + # logfile + # td-agent: /var/log/td-agent/td-agent.log + # - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.init#L28 + # fluentd: stdout (or --log LOGFILE) + # + # config file + # td-agent: /etc/td-agent/td-agent.conf + # - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.postinst#L69 + # fluentd: /etc/fluent/fluent.conf (by fluentd -s) + end +end diff --git a/app/models/fluentd/agent/common.rb b/app/models/fluentd/agent/common.rb new file mode 100644 index 0000000..59796ba --- /dev/null +++ b/app/models/fluentd/agent/common.rb @@ -0,0 +1,62 @@ +# pidfile +# td-agent: /var/run/td-agent/td-agent.pid +# - https://github.com/treasure-data/td-agent/blob/master/td-agent.logrotate#L10 +# - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.init#L25 +# fluentd: nothing (or --daemon PIDFILE) +# +# logfile +# td-agent: /var/log/td-agent/td-agent.log +# - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.init#L28 +# fluentd: stdout (or --log LOGFILE) +# +# config file +# td-agent: /etc/td-agent/td-agent.conf +# - https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.postinst#L69 +# fluentd: /etc/fluent/fluent.conf (by fluentd -s) + +class Fluentd + class Agent + module Common + attr_reader :extra_options + + def initialize(options = {}) + @extra_options = options + end + + def pid + return unless File.exists?(pid_file) + File.read(pid_file).to_i rescue nil + end + + def wait_process_starting_seconds + 10.seconds # wait time for fluentd pidfile created + end + + def running? + pid && Process.kill(0, pid) + end + + def log + File.read(log_file) # TODO: large log file + end + + def pid_file + extra_options[:pid_file] || self.class.default_options[:pid_file] + end + + def log_file + extra_options[:log_file] || self.class.default_options[:log_file] + end + + def config_file + extra_options[:config_file] || self.class.default_options[:config_file] + end + + %w(start stop restart).each do |method| + define_method(method) do + raise NotImplementedError + end + end + end + end +end diff --git a/app/models/fluentd/agent/fluentd.rb b/app/models/fluentd/agent/fluentd.rb new file mode 100644 index 0000000..8308f11 --- /dev/null +++ b/app/models/fluentd/agent/fluentd.rb @@ -0,0 +1,77 @@ +class Fluentd + class Agent + class Fluentd + include Common + + def self.default_options + { + :pid_file => "/var/run/fluent.pid", + :log_file => "/var/log/fluent.log", + :config_file => "/etc/fluent/fluent.conf", + } + end + + def options_to_argv + argv = "" + argv << " --use-v1-config" + argv << " -c #{config_file}" + argv << " -d #{pid_file}" + argv << " -o #{log_file}" + argv + end + + def start + return true if running? + if validate_fluentd_options + actual_start + end + end + + def stop + return true unless running? + actual_stop + end + + def restart + return false unless running? + actual_restart + end + + private + + def validate_fluentd_options + system("bundle exec fluentd --dry-run #{options_to_argv}") + end + + def actual_start + spawn("bundle exec fluentd #{options_to_argv}") + wait_starting + end + + def actual_stop + if Process.kill(:TERM, pid) + File.unlink(pid_file) + true + end + end + + def actual_restart + Process.kill(:HUP, pid) + end + + def wait_starting + begin + timeout(wait_process_starting_seconds) do + loop do + break if pid && Process.kill(0, pid) + sleep 0.01 + end + end + true + rescue TimeoutError + false + end + end + end + end +end diff --git a/app/models/fluentd/agent/remote.rb b/app/models/fluentd/agent/remote.rb new file mode 100644 index 0000000..e5de011 --- /dev/null +++ b/app/models/fluentd/agent/remote.rb @@ -0,0 +1,7 @@ +class Fluentd + class Agent + class Remote # TODO + include Common + end + end +end diff --git a/app/models/fluentd/agent/td_agent.rb b/app/models/fluentd/agent/td_agent.rb new file mode 100644 index 0000000..7f2bbbf --- /dev/null +++ b/app/models/fluentd/agent/td_agent.rb @@ -0,0 +1,29 @@ +class Fluentd + class Agent + class TdAgent + include Common + + def self.default_options + { + :pid_file => "/var/run/td-agent/td-agent.pid", + :log_file => "/var/log/td-agent/td-agent.log", + :config_file => "/etc/td-agent/td-agent.conf", + } + end + + def start + system('/etc/init.d/td-agent start') + end + + def stop + system('/etc/init.d/td-agent stop') + end + + def restart + # NOTE: td-agent has no reload command + # https://github.com/treasure-data/td-agent/blob/master/debian/td-agent.init#L156 + system('/etc/init.d/td-agent restart') + end + end + end +end diff --git a/app/models/plugin.rb b/app/models/plugin.rb index a975f5a..6a172e0 100644 --- a/app/models/plugin.rb +++ b/app/models/plugin.rb @@ -86,6 +86,7 @@ class Plugin end def self.installed + return [] unless File.exist?(gemfile_path) File.read(gemfile_path).scan(/"(.*?)", "(.*?)"/).map do |plugin| new(gem_name: plugin[0], version: plugin[1]) end diff --git a/app/views/fluentd/_form.html.haml b/app/views/fluentd/_form.html.haml new file mode 100644 index 0000000..b266e65 --- /dev/null +++ b/app/views/fluentd/_form.html.haml @@ -0,0 +1,18 @@ +%div.col-lg-6 + - @fluentd.errors.full_messages.each do |e| + %div.alert.alert-danger= e + + = form_for(:fluentd, url: url, method: method) do |f| + %div.form-group + = f.label :variant + = f.select :variant, Fluentd.variants + %div.form-group + = f.label :pid_file + = f.text_field :pid_file, class: "form-control" + %div.form-group + = f.label :log_file + = f.text_field :log_file, class: "form-control" + %div.form-group + = f.label :config_file + = f.text_field :config_file, class: "form-control" + = f.submit btn, class: "btn btn-primary" diff --git a/app/views/fluentd/agents/show.html.haml b/app/views/fluentd/agents/show.html.haml new file mode 100644 index 0000000..806cb1d --- /dev/null +++ b/app/views/fluentd/agents/show.html.haml @@ -0,0 +1,13 @@ +- page_title t('.page_title', label: "##{@fluentd.id}") + +- if flash[:error] + %div.alert.alert-danger= flash[:error] + +%h4 + = @fluentd.agent.running? ? t(".running") : t(".stopped") + += link_to t(".start"), start_fluentd_agent_path(@fluentd), method: :put += link_to t(".stop"), stop_fluentd_agent_path(@fluentd), method: :put += link_to t(".restart"), restart_fluentd_agent_path(@fluentd), method: :put += link_to t(".log"), log_fluentd_agent_path(@fluentd) +TODO: config diff --git a/app/views/fluentd/daemons/show.html.haml b/app/views/fluentd/daemons/show.html.haml deleted file mode 100644 index 9542817..0000000 --- a/app/views/fluentd/daemons/show.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%h1 - = @fluentd.running? ? "running" : "stopped" - - = link_to "start", start_fluentd_daemon_path(id: 1), method: :put, remote: true - = link_to "stop", stop_fluentd_daemon_path(id: 1), method: :put, remote: true - = link_to "reload", reload_fluentd_daemon_path(id: 1), method: :put, remote: true - = link_to "log", log_fluentd_daemon_path(id: 1) diff --git a/app/views/fluentd/edit.html.haml b/app/views/fluentd/edit.html.haml new file mode 100644 index 0000000..3a1facb --- /dev/null +++ b/app/views/fluentd/edit.html.haml @@ -0,0 +1,3 @@ +- page_title t('.page_title') + += render partial: "form", locals: { btn: t(".update"), url: fluentd_path(@fluentd), method: :patch } diff --git a/app/views/fluentd/index.html.haml b/app/views/fluentd/index.html.haml index 4c6c2ec..45d7517 100644 --- a/app/views/fluentd/index.html.haml +++ b/app/views/fluentd/index.html.haml @@ -1,2 +1,14 @@ -- @daemons.each do |d| - = link_to d, fluentd_daemon_path(fluentd_id: 1) # TODO +- page_title t('.page_title') + +%p= link_to icon('fa-plus') << " " << t(".new"), new_fluentd_path + +- @fluentds.each do |d| + %div.col-lg-6 + %div.panel.panel-default + %div.panel-heading + %h4 + = "fluentd ##{d.id}" + = link_to t(".edit"), edit_fluentd_path(d) + = link_to t(".destroy"), fluentd_path(d), method: :delete + %div.panel-body + = link_to t(".operation"), fluentd_agent_path(d) # TODO diff --git a/app/views/fluentd/new.html.haml b/app/views/fluentd/new.html.haml new file mode 100644 index 0000000..95c4d3a --- /dev/null +++ b/app/views/fluentd/new.html.haml @@ -0,0 +1,3 @@ +- page_title t('.page_title') + += render partial: "form", locals: { btn: t(".create"), url: fluentd_index_path, method: :post } diff --git a/app/views/shared/_global_nav.html.erb b/app/views/shared/_global_nav.html.erb index fb03829..f68fab9 100644 --- a/app/views/shared/_global_nav.html.erb +++ b/app/views/shared/_global_nav.html.erb @@ -2,6 +2,14 @@
  • <%= link_to_other icon("fa-dashboard fa-fw") << " Dashboard", root_path %>
  • +
  • + <%= link_to_other icon("fa-puzzle-piece fa-fw") << " fluentd", fluentd_index_path %> + +
  • <%= link_to_other icon("fa-cogs fa-fw") << " " << t('terms.plugins') << icon('fa pull-right fa-caret-down'), plugins_path %>