mysqlnoio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'mysqlnoio'
4
+
5
+ desc "Builds the gem"
6
+ task :build do
7
+ sh "gem build mysqlnoio.gemspec"
8
+ end
9
+
10
+ desc "Publishes the gem to fury.io"
11
+ task :publish => [:build] do |t|
12
+ sh "git tag -f #{MySQLNoIo::VERSION}"
13
+ sh "git push origin #{MySQLNoIo::VERSION} -f"
14
+ sh "curl -F p1=@mysqlnoio-#{MySQLNoIo::VERSION}.gem https://push.fury.io/BEz67VknqnyqmV6p7Huq/"
15
+ end
16
+
17
+ desc "Cleans the project directory up"
18
+ task :clean do
19
+ sh "rm -rf doc/ tmp/"
20
+ sh "rm -f mysqlnoio-*.gem"
21
+ end
22
+
23
+ # Various RSpec Bitties
24
+ base_opts = '--color --order random'
25
+ desc "Run specs"
26
+ RSpec::Core::RakeTask.new(:spec) do |t|
27
+ t.verbose = false
28
+ t.rspec_opts = "#{base_opts} --tag ~integration"
29
+ end
30
+
31
+ desc "Run integration specs"
32
+ RSpec::Core::RakeTask.new(:integration) do |t|
33
+ t.verbose = false
34
+ t.rspec_opts = "#{base_opts} --tag integration"
35
+ end
36
+
37
+ desc "Run all specs"
38
+ RSpec::Core::RakeTask.new(:test) do |t|
39
+ t.verbose = false
40
+ t.rspec_opts = base_opts
41
+ end
42
+
43
+ task :default => :spec
data/Vagrantfile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ $boot = <<SCRIPT
5
+ sudo apt-get install build-essential
6
+
7
+ curl -L https://get.rvm.io | bash -s --autolibs=3 --ruby
8
+ SCRIPT
9
+
10
+ Vagrant.configure("2") do |config|
11
+ config.vm.box = "ubuntu-12.04.2-server-adm54-vmware-fusion"
12
+ config.vm.box_url = "https://s3.amazonaws.com/gsc-vagrant-boxes/ubuntu-12.04.2-server-amd64.box"
13
+
14
+ config.vm.provision :shell do |shell|
15
+ shell.inline = $script
16
+ shell.path = 'vagrant/provision.sh'
17
+ end
18
+ end
data/bin/mysql-noio ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'mysqlnoio'
5
+
6
+ credentials = {}
7
+ wraps = []
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: mysql-noio [options] <command>"
11
+
12
+ # Determine the MySQL connection parameters
13
+ opts.on("-u", "--user USER", "MySQL Username") do |v|
14
+ credentials[:username] = v
15
+ end
16
+
17
+ opts.on("-p", "--password PASSWORD", "MySQL Password") do |v|
18
+ credentials[:password] = v
19
+ end
20
+
21
+ opts.on("-h", "--host HOST", "MySQL Server Host") do |v|
22
+ credentials[:host] = v
23
+ end
24
+
25
+
26
+ opts.on("-t", "--lock-tables", "Lock tables with a read lock") do |v|
27
+ wraps << :lock
28
+ end
29
+
30
+ opts.on("-s", "--stop-slave", "Stop the slave thread") do |v|
31
+ wraps << :stop
32
+ end
33
+
34
+ opts.on
35
+ end.parse!
36
+
37
+ client = Mysql2::Client.new(credentials)
38
+
39
+ stack = MySQLNoIo::WrapStack.new
40
+ stack.add_wrap MySQLNoIo::Wraps::DeactivatedSlave.new client if wraps.include? :stop
41
+ stack.add_wrap MySQLNoIo::Wraps::GlobalLock.new client if wraps.include? :lock
42
+
43
+ stack.execute do
44
+ spawn *ARGV
45
+ Process.wait
46
+ end
47
+
48
+ exit $?.to_i
49
+
@@ -0,0 +1,12 @@
1
+
2
+ module MySQLNoIo
3
+ # Used in Gemspec and Commander
4
+ NAME = 'mysqlnoio'
5
+
6
+ # Used in Gemspec and Commander
7
+ VERSION = '0.1.0'
8
+
9
+ # Used in Gemspec and Commander
10
+ DESCRIPTION = 'mysqlnoio allows you to execute commands while MySQL is locked.'
11
+ end
12
+
@@ -0,0 +1,11 @@
1
+
2
+ module MySQLNoIo
3
+ # A collection of erros which MySQLNoIo might raise.
4
+ module Errors
5
+
6
+ # represents an argument passed in to a method is invalid.
7
+ class ArgumentError < ::ArgumentError
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,34 @@
1
+
2
+ module MySQLNoIo
3
+
4
+ # A wrap represents a open/close block which always runs a before and an
5
+ # after statement.
6
+ #
7
+ # Passing a block to #execute will execute the block from between the
8
+ # statements.
9
+ #
10
+ # Example:
11
+ # class FooWrap
12
+ # def execute &block
13
+ # puts "Hi"
14
+ #
15
+ # yeild(block)
16
+ # ensure
17
+ # puts "Bye"
18
+ # end
19
+ # end
20
+ #
21
+ # foo = FooWrap.new()
22
+ # foo.execute do
23
+ # puts "Ahh! Safety!"
24
+ # end
25
+ #
26
+ # Will output:
27
+ # Hi
28
+ # Ahh! Safety!
29
+ # Bye
30
+ #
31
+ class Wrap
32
+ end
33
+ end
34
+
@@ -0,0 +1,65 @@
1
+
2
+ module MySQLNoIo
3
+ # A WrapStack collects a series of Wraps, and runs their #execute commands
4
+ # inside one another.
5
+ #
6
+ # Example:
7
+ # stack = MySQLNoIo::WrapStack.new()a
8
+ #
9
+ # wrapFoo = Wrap::Foo.new()
10
+ # wrapBar = Wrap::Bar.new()
11
+ #
12
+ # stack.add_stack wrapFoo
13
+ # stack.add_stack wrapBar
14
+ # stack.execute do
15
+ # puts "hi"
16
+ # end
17
+ #
18
+ # Is identical to:
19
+ # wrapFoo.execute do
20
+ # wrapBar.execute do
21
+ # puts "hi"
22
+ # end
23
+ # end
24
+ #
25
+ class WrapStack
26
+ def initialize
27
+ @wraps = []
28
+ end
29
+
30
+ # Add a layer to wrap around the execution of the command
31
+ #
32
+ # @param [MySQLNoIo::Wrap] the wrapper to add to the stack
33
+ # @raise [MySQLNoIo::Errors::ArgumentError] if the wrapper is not sufficient
34
+ #
35
+ # @return [TrueClass]
36
+ def add_wrap wrap
37
+ unless wrap.respond_to?('execute')
38
+ raise MySQLNoIo::Errors::ArgumentError.new('Wrap must respond to #execute and accept a block.')
39
+ end
40
+
41
+ @wraps << wrap
42
+
43
+ true
44
+ end
45
+
46
+ # Execute your bit of code from within the safety and comfort of the
47
+ # wrappers appended to the stack.
48
+ def execute &block
49
+ # Brace yourself.
50
+ #
51
+ # This next bit of code creates a nested lamda like the WrapStack
52
+ # describes.
53
+ deep_proc = @wraps.reverse.inject(block) do |deep_proc, layer|
54
+ Proc.new do
55
+ layer.execute do
56
+ deep_proc.call
57
+ end
58
+ end
59
+ end
60
+
61
+ deep_proc.call
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,24 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Just an example Wrap for testing and examples.
4
+ class Blocks
5
+ # The before block will be run prior to the execution.
6
+ def before &block
7
+ @prior = block
8
+ end
9
+
10
+ # The after block will be run post execution.
11
+ def after &block
12
+ @post = block
13
+ end
14
+
15
+ # Call the prior block, then execute, and then call the post-block.
16
+ def execute &block
17
+ @prior.call if @prior.respond_to?(:call)
18
+ block.call
19
+ ensure
20
+ @post.call if @post.respond_to?(:call)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Stops the database slave entirely for your command
4
+ class DeactivatedSlave
5
+ def initialize mysql
6
+ @mysql = mysql
7
+ end
8
+
9
+ def execute &block
10
+ @mysql.query('STOP SLAVE;')
11
+ yield
12
+ ensure
13
+ @mysql.query('START SLAVE;')
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,21 @@
1
+ module MySQLNoIo
2
+ module Wraps
3
+ # Allows you to run commands against a MySQL database, while MySQL's
4
+ # tables are locked, and the tables are flushed.
5
+ class GlobalLock
6
+ # Prepare to set up the GlobalLock wrapper, with the mysql2 connection
7
+ def initialize mysql
8
+ @mysql = mysql
9
+ end
10
+
11
+ # Lock the tables, run our command, and then unlock the tables.
12
+ def execute &block
13
+ @mysql.query("FLUSH TABLES WITH READ LOCK;")
14
+ yield
15
+ ensure
16
+ @mysql.query("UNLOCK TABLES;")
17
+ end
18
+ end
19
+ end
20
+ end
21
+
data/lib/mysqlnoio.rb ADDED
@@ -0,0 +1,19 @@
1
+
2
+ require 'mysql2'
3
+
4
+ require 'mysqlnoio/about'
5
+
6
+ require 'mysqlnoio/errors'
7
+
8
+ require 'mysqlnoio/wrap_stack'
9
+ require 'mysqlnoio/wrap'
10
+ require 'mysqlnoio/wraps/blocks'
11
+ require 'mysqlnoio/wraps/global_lock'
12
+ require 'mysqlnoio/wraps/deactivated_slave'
13
+
14
+
15
+ # See: README.md
16
+ module MySQLNoIo
17
+
18
+ end
19
+
data/mysqlnoio.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'mysqlnoio/about'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = MySQLNoIo::NAME
9
+ gem.version = MySQLNoIo::VERSION
10
+ gem.description = MySQLNoIo::DESCRIPTION
11
+ gem.summary = 'Execute shell commands while MySQL IO is stopped.'
12
+ gem.homepage = 'https://zippykid.com'
13
+ gem.authors = ["ZippyKid", "Graham Christensen"]
14
+ gem.email = ["info@zippykid.com"]
15
+ gem.licenses = ["MIT"]
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_dependency('mysql2')
23
+
24
+ gem.add_development_dependency('rspec')
25
+ gem.add_development_dependency('debugger')
26
+
27
+ # Guard:
28
+ gem.add_development_dependency('guard')
29
+ gem.add_development_dependency('guard-rspec')
30
+ gem.add_development_dependency('guard-shell')
31
+ gem.add_development_dependency('rb-fsevent', '~> 0.9')
32
+ gem.add_development_dependency('rb-inotify', '~> 0.9')
33
+ gem.add_development_dependency('travis-lint')
34
+
35
+ # Docs
36
+ gem.add_development_dependency('yard')
37
+ gem.add_development_dependency('redcarpet')
38
+ end
39
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe "mysql-noio", :integration => true do
4
+ context "called with no parameters" do
5
+ let (:output) { `bundle exec #{project_root}/bin/mysql-noio 2>&1` }
6
+
7
+ it "exits with a zero error code" do
8
+ $?.to_i.should eq(0)
9
+ end
10
+
11
+ it "does not contain something that looks like a stack trace" do
12
+ output.should_not =~ /in `<main>'/
13
+ output.should_not =~ /syntax error/
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe MySQLNoIo::WrapStack do
4
+ let(:stack) { MySQLNoIo::WrapStack.new }
5
+ describe "#add_wrap" do
6
+
7
+ context "An invalid wrap is provided" do
8
+ it "expects the method to have an #execute method" do
9
+ expect {
10
+ stack.add_wrap(nil)
11
+ }.to raise_error(MySQLNoIo::Errors::ArgumentError, /\#execute/)
12
+ end
13
+ end
14
+
15
+ context "A valid wrap is provided" do
16
+ let (:wrap) { double("MySQLNoIo::Wrap", execute: true) }
17
+ it "only requires the #execute method to be callable" do
18
+ stack.add_wrap(wrap).should eq(true)
19
+ end
20
+ end
21
+ end
22
+
23
+ describe "#execute" do
24
+ let (:wrap_foo) do
25
+ wrap = MySQLNoIo::Wraps::Blocks.new
26
+ wrap.before do
27
+ @output << "foo-before\n"
28
+ end
29
+ wrap.after do
30
+ @output << "foo-after\n"
31
+ end
32
+
33
+ wrap
34
+ end
35
+
36
+ let (:wrap_bar) do
37
+ wrap = MySQLNoIo::Wraps::Blocks.new
38
+ wrap.before do
39
+ @output << "bar-before\n"
40
+ end
41
+ wrap.after do
42
+ @output << "bar-after\n"
43
+ end
44
+
45
+ wrap
46
+ end
47
+
48
+ it "Calls the wrappers in order, and then my bits." do
49
+ @output = ""
50
+ stack.add_wrap wrap_foo
51
+ stack.add_wrap wrap_bar
52
+ stack.execute do
53
+ @output << "Cheesy filling!\n"
54
+ end
55
+
56
+ desired_output = "foo-before\n"
57
+ desired_output << "bar-before\n"
58
+ desired_output << "Cheesy filling!\n"
59
+ desired_output << "bar-after\n"
60
+ desired_output << "foo-after\n"
61
+
62
+ @output.should eq(desired_output)
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,44 @@
1
+
2
+ describe MySQLNoIo::Wraps::Blocks do
3
+ subject { MySQLNoIo::Wraps::Blocks.new() }
4
+ context "accepts a before block" do
5
+ it { should respond_to :before }
6
+ end
7
+
8
+ context "accepts an after block" do
9
+ it { should respond_to :after }
10
+ end
11
+
12
+ describe "#execute" do
13
+ context "executing a block" do
14
+ let (:wrapper) { MySQLNoIo::Wraps::Blocks.new() }
15
+ let (:block_before) { Proc.new do @output << "chocolate" end }
16
+ let (:block_execute) { Proc.new do @output << "icing" end }
17
+ let (:block_after) { Proc.new do @output << "vanilla" end }
18
+
19
+ it "executes the block passed to #execute" do
20
+ @output = ""
21
+ wrapper.execute do block_execute.call end
22
+
23
+ @output.should eq("icing")
24
+ end
25
+
26
+ it "executes the before block ... before" do
27
+ @output = ""
28
+ wrapper.before do block_before.call end
29
+ wrapper.execute do block_execute.call end
30
+
31
+ @output.should eq("chocolateicing")
32
+ end
33
+
34
+ it "executes the after block ... after" do
35
+ @output = ""
36
+ wrapper.after do block_after.call end
37
+ wrapper.execute do block_execute.call end
38
+
39
+ @output.should eq("icingvanilla")
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,25 @@
1
+ describe MySQLNoIo::Wraps::DeactivatedSlave do
2
+ describe "#execute" do
3
+ it "Runs STOP SLAVE and START SLAVE around our command" do
4
+ @cmds = ""
5
+
6
+ mysql = double("MySQL2::Client")
7
+ mysql.should_receive(:query).exactly(2).times do |arg|
8
+ @cmds << "#{arg}\n"
9
+ end
10
+
11
+ wrap = MySQLNoIo::Wraps::DeactivatedSlave.new mysql
12
+ wrap.execute do
13
+ @cmds << "-- my command\n"
14
+ end
15
+
16
+ @cmds.should eq(<<-FOO
17
+ STOP SLAVE;
18
+ -- my command
19
+ START SLAVE;
20
+ FOO
21
+ )
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,25 @@
1
+ describe MySQLNoIo::Wraps::GlobalLock do
2
+ describe "#execute" do
3
+ it "Runs lock and unlock around our command" do
4
+ @cmds = ""
5
+
6
+ mysql = double("MySQL2::Client")
7
+ mysql.should_receive(:query).exactly(2).times do |arg|
8
+ @cmds << "#{arg}\n"
9
+ end
10
+
11
+ wrap = MySQLNoIo::Wraps::GlobalLock.new mysql
12
+ wrap.execute do
13
+ @cmds << "-- my command\n"
14
+ end
15
+
16
+ @cmds.should eq(<<-FOO
17
+ FLUSH TABLES WITH READ LOCK;
18
+ -- my command
19
+ UNLOCK TABLES;
20
+ FOO
21
+ )
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,6 @@
1
+ require 'mysqlnoio'
2
+
3
+ def project_root
4
+ File.realpath(File.dirname(__FILE__) + "/../")
5
+ end
6
+
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ apt-get update
4
+ apt-get --yes install libmysqlclient-dev mysql-server-5.5
5
+
6
+ curl -L https://get.rvm.io | bash -s stable --autolibs=3 --ruby
7
+ source /usr/local/rvm/scripts/rvm
8
+ rvm in /vagrant do bundle install
9
+
10
+ cd /vagrant
11
+
12
+ rake
13
+