From 96679396d6c4476d1db27d410f4eb6a7f1b9c3ff Mon Sep 17 00:00:00 2001 From: Igor Rzegocki Date: Fri, 2 Sep 2016 18:00:08 +0200 Subject: [PATCH] feat(appserver): "Thin" support added Resolves #39 --- README.md | 14 +++- attributes/default.rb | 15 +++-- libraries/drivers_appserver_thin.rb | 27 ++++++++ libraries/drivers_base.rb | 5 ++ libraries/drivers_webserver_nginx.rb | 2 +- spec/fixtures/node.rb | 3 +- spec/unit/recipes/configure_spec.rb | 97 +++++++++++++++++++++++++++- templates/default/thin.yml.erb | 15 +++++ 8 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 libraries/drivers_appserver_thin.rb create mode 100644 templates/default/thin.yml.erb diff --git a/README.md b/README.md index db5566f6..39bc8a76 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ then [add recipes to the corresponding OpsWorks actions](#recipes). * App server * Null (no appserver) * Puma + * Thin * Unicorn * Web server * Null (no webserver) @@ -164,7 +165,7 @@ and `Puma` are supported. * `app['appserver']['adapter']` * **Default:** `unicorn` - * **Supported values:** `puma`, `unicorn`, `null` + * **Supported values:** `puma`, `thin`, `unicorn`, `null` * Server on the application side, which will receive requests from webserver in front. `null` means no appserver enabled. * `app['appserver']['application_yml']` @@ -219,6 +220,17 @@ and `Puma` are supported. * [`app['appserver']['worker_processes']`](https://github.com/puma/puma/blob/c169853ff233dd3b5c4e8ed17e84e1a6d8cb565c/examples/config.rb#L107) * **Default:** `4` +#### thin + +* `app['appserver']['max_connections']` + * **Default:** `1024` +* `app['appserver']['max_persistent_connections']` + * **Default:** `512` +* `app['appserver']['timeout']` + * **Default:** `60` +* `app['appserver']['worker_processes']` + * **Default:** `4` + ### webserver Webserver configuration. Proxy passing to application is handled out-of-the-box. diff --git a/attributes/default.rb b/attributes/default.rb index ef6c43bb..43f1c575 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -44,6 +44,16 @@ default['defaults']['appserver']['timeout'] = 60 default['defaults']['appserver']['worker_processes'] = 4 +## puma + +default['defaults']['appserver']['log_requests'] = false +default['defaults']['appserver']['thread_min'] = 0 +default['defaults']['appserver']['thread_max'] = 16 + +## thin +default['defaults']['appserver']['max_connections'] = 1024 +default['defaults']['appserver']['max_persistent_connections'] = 512 + ## unicorn default['defaults']['appserver']['accept_filter'] = 'httpready' @@ -53,11 +63,6 @@ default['defaults']['appserver']['tcp_nopush'] = false default['defaults']['appserver']['tries'] = 5 -## puma -default['defaults']['appserver']['log_requests'] = false -default['defaults']['appserver']['thread_min'] = 0 -default['defaults']['appserver']['thread_max'] = 16 - # webserver ## common diff --git a/libraries/drivers_appserver_thin.rb b/libraries/drivers_appserver_thin.rb new file mode 100644 index 00000000..bb05e2b9 --- /dev/null +++ b/libraries/drivers_appserver_thin.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +module Drivers + module Appserver + class Thin < Drivers::Appserver::Base + adapter :thin + allowed_engines :thin + output filter: [:max_connections, :max_persistent_connections, :timeout, :worker_processes] + + def add_appserver_config(context) + opts = { environment: app['environment'], deploy_dir: deploy_dir(app), out: out, + deploy_env: globals[:environment] } + + context.template File.join(opts[:deploy_dir], File.join('shared', 'config', 'thin.yml')) do + owner node['deployer']['user'] + group www_group + mode '0644' + source 'thin.yml.erb' + variables opts + end + end + + def appserver_command(_context) + 'thin -C #{ROOT_PATH}/shared/config/thin.yml' + end + end + end +end diff --git a/libraries/drivers_base.rb b/libraries/drivers_base.rb index 70a23c4f..9cccca7e 100644 --- a/libraries/drivers_base.rb +++ b/libraries/drivers_base.rb @@ -19,6 +19,11 @@ def self.adapter(adapter = nil) (@adapter || self.class.name.underscore).to_s end + def self.adapters(options = { include_null: false }) + adapters = descendants.select { |descendant| descendant.respond_to?(:adapter) }.map(&:adapter) + options[:include_null] ? adapters : adapters - ['null'] + end + # Dummy methods for children to redefine def setup(_context) end diff --git a/libraries/drivers_webserver_nginx.rb b/libraries/drivers_webserver_nginx.rb index b43e41f7..720028be 100644 --- a/libraries/drivers_webserver_nginx.rb +++ b/libraries/drivers_webserver_nginx.rb @@ -90,7 +90,7 @@ def add_dhparams(context) def add_appserver_config(context) opts = { application: app, deploy_dir: deploy_dir(app), out: out, name: Drivers::Appserver::Factory.build(app, node).adapter } - return unless %w(unicorn puma).include?(opts[:name]) # @todo + return unless Drivers::Appserver::Base.adapters.include?(opts[:name]) context.template "/etc/nginx/sites-available/#{app['shortname']}" do owner 'root' diff --git a/spec/fixtures/node.rb b/spec/fixtures/node.rb index 3781f838..c030e4cb 100644 --- a/spec/fixtures/node.rb +++ b/spec/fixtures/node.rb @@ -36,7 +36,8 @@ def node(override = {}) adapter: 'unicorn', delay: 3, thread_min: 0, - thread_max: 16 + thread_max: 16, + max_connections: 4096 }, webserver: { adapter: 'nginx', diff --git a/spec/unit/recipes/configure_spec.rb b/spec/unit/recipes/configure_spec.rb index d1e6e488..3350c8be 100644 --- a/spec/unit/recipes/configure_spec.rb +++ b/spec/unit/recipes/configure_spec.rb @@ -409,13 +409,23 @@ end end - context 'Sqlite3' do + context 'Sqlite3 + Thin' do let(:dummy_node) do - node(deploy: { dummy_project: { database: { adapter: 'sqlite3' }, environment: 'staging' } }) + node( + deploy: { + dummy_project: { + database: { adapter: 'sqlite3' }, + environment: 'staging', + appserver: node['deploy']['dummy_project']['appserver'].merge('adapter' => 'thin'), + webserver: node['deploy']['dummy_project']['webserver'] + } + } + ) end let(:chef_run) do ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node| solo_node.set['deploy'] = dummy_node['deploy'] + solo_node.set['nginx'] = node['nginx'] end.converge(described_recipe) end @@ -433,6 +443,89 @@ JSON.parse({ development: db_config, production: db_config, staging: db_config }.to_json).to_yaml ) end + + it 'creates proper thin.yml file' do + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/thin.yml") + .with_content('servers: 4') + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/thin.yml") + .with_content('environment: "staging"') + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/thin.yml") + .with_content('max_conns: 4096') + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/thin.yml") + .with_content('timeout: 60') + end + + it 'creates proper thin.service file' do + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service") + .with_content("APP_NAME=\"#{aws_opsworks_app['shortname']}\"") + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service") + .with_content("ROOT_PATH=\"/srv/www/#{aws_opsworks_app['shortname']}\"") + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service") + .with_content('DEPLOY_ENV="staging"') + expect(chef_run) + .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service") + .with_content('thin -C #{ROOT_PATH}/shared/config/thin.yml') + end + + it 'defines thin service' do + service = chef_run.service("thin_#{aws_opsworks_app['shortname']}") + expect(service).to do_nothing + expect(service.start_command) + .to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service start" + expect(service.stop_command) + .to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service stop" + expect(service.restart_command) + .to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service restart" + expect(service.status_command) + .to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/thin.service status" + end + + it 'creates nginx thin proxy handler config' do + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('upstream thin_dummy-project.example.com {') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('client_max_body_size 125m;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('client_body_timeout 30;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('keepalive_timeout 15;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_certificate_key /etc/nginx/ssl/dummy-project.example.com.key;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_dhparam /etc/nginx/ssl/dummy-project.example.com.dhparams.pem;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_ecdh_curve secp384r1;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_stapling on;') + expect(chef_run) + .not_to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('ssl_session_tickets off;') + expect(chef_run) + .to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('extra_config {}') + expect(chef_run) + .not_to render_file("/etc/nginx/sites-available/#{aws_opsworks_app['shortname']}") + .with_content('extra_config_ssl {}') + expect(chef_run).to create_link("/etc/nginx/sites-enabled/#{aws_opsworks_app['shortname']}") + end end context 'No RDS (Database defined in node)' do diff --git a/templates/default/thin.yml.erb b/templates/default/thin.yml.erb new file mode 100644 index 00000000..6ffebfed --- /dev/null +++ b/templates/default/thin.yml.erb @@ -0,0 +1,15 @@ +--- +user: "<%= node['deployer']['user'] %>" +pid: "<%= @deploy_dir %>/shared/pids/thin.pid" +timeout: <%= @out[:timeout] %> +wait: <%= @out[:timeout] %> +log: "<%= @deploy_dir %>/shared/log/thin.log" +max_conns: <%= @out[:max_connections] %> +environment: "<%= @deploy_env %>" +max_persistent_conns: <%= @out[:max_persistent_connections] %> +servers: <%= @out[:worker_processes] %> +threaded: true +no-epoll: true +daemonize: true +socket: "<%= @deploy_dir %>/shared/sockets/thin.sock" +chdir: "<%= @deploy_dir %>/current"