judo 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/TODO ADDED
@@ -0,0 +1,45 @@
1
+ ### NEEDED for new gem launch
2
+
3
+ ### [ ] judo snapshots
4
+ ### [ ] judo snapshot foo bar
5
+ ### [ ] judo clone foo bar2
6
+ ### [ ] snapshots/init/virgin
7
+ ### [ ] judo stop --force
8
+ ### [ ] judo swap (x) (y) -> swaps elastic IP's and name
9
+ ### [ ] judo swap_ip (x) (y) -> swaps elastic IP's and name
10
+
11
+ ### [ ] judo does not work with ruby 1.8.6 - :(
12
+ ### [ ] saw a timeout on volume allocation - make sure we build in re-tries - need to allocate the server all together as much as possible
13
+ ### [ ] there is a feature to allow for using that block_mapping feature - faster startup
14
+
15
+ ### [ ] two phase delete (1 hr)
16
+ ### [-] refactor availability_zone (2 hrs)
17
+ ### [ ] pick availability zone from config "X":"Y" or "X":["Y","Z"]
18
+ ### [ ] assign to state on creation ( could delay till volume creation )
19
+ ### [ ] implement auto security_group creation and setup (6 hrs)
20
+ ### [ ] bind kuzushi gem version version
21
+
22
+ ### [ ] should be able to do ALL actions except commit without the repo!
23
+ ### [ ] store git commit hash with commit to block a judo commit if there is newer material stored
24
+ ### [ ] remove the tarball - store files a sha hashes in the bucket - makes for faster commits if the files have not changed
25
+
26
+ ### [ ] use a logger service (1 hr)
27
+ ### [ ] write specs (5 hr)
28
+
29
+ ### Error Handling
30
+ ### [ ] no availability zone before making disks
31
+ ### [ ] security group does not exists
32
+
33
+ ### Do Later
34
+ ### [ ] use amazon's new conditional write tools so we never have problems from concurrent updates
35
+ ### [ ] is thor really what we want to use here?
36
+ ### [ ] need to be able to pin a config to a version of kuzushi - gem updates can/will break a lot of things
37
+ ### [ ] I want a "judo monitor" command that will make start servers if they go down, and poke a listed port to make sure a service is listening, would be cool if it also detects wrong ami, wrong secuirity group, missing/extra volumes, missing/extra elastic_ip - might not want to force a reboot quite yet in these cases
38
+ ### [ ] Implement "judo snapshot [NAME]" to take a snapshot of the ebs's blocks
39
+ ### [ ] ruby 1.9.1 support
40
+ ### [ ] find a good way to set the hostname or prompt to :name
41
+ ### [ ] remove fog/s3 dependancy
42
+ ### [ ] enforce template files end in .erb to make room for other possible templates as defined by the extensions
43
+ ### [ ] zerigo integration for automatic DNS setup
44
+ ### [ ] How cool would it be if this was all reimplemented in eventmachine and could start lots of boxes in parallel? Would need to evented AWS api calls... Never seen a library to do that - would have to write our own... "Fog Machine?"
45
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.4
data/bin/judo CHANGED
@@ -30,6 +30,11 @@ Usage: judo launch [options] SERVER ...
30
30
 
31
31
  judo commit [options] GROUP
32
32
 
33
+ judo snapshot [options] SERVER SNAPSHOT ## take an ebs snapshot of a server
34
+ judo snapshots [options] [SERVER ...] ## show current snapshots on servers
35
+ judo clone [options] SNAPSHOT SERVER ## create a new server from a snapshot
36
+ judo erase [options] SNAPSHOT ## erase an old snapshot
37
+
33
38
  judo info [options] [SERVER ...]
34
39
  judo console [options] [SERVER ...] ## shows AWS console output
35
40
  judo ssh [options] [SERVER ...] ## ssh's into the server
@@ -53,6 +58,9 @@ banner
53
58
  defaults[:judo_dir] = dir
54
59
  defaults.merge(Judo::Base.default_options(Dir.pwd, dir))
55
60
  end
61
+ opts.on( '-f', '--force', 'Force a stop or restart (immediately force detach volumes)' ) do
62
+ options[:force] = true
63
+ end
56
64
  opts.on( '-r', '--repo DIR', 'Specify the location of the repo dir' ) do |dir|
57
65
  options[:repo] = dir
58
66
  end
@@ -68,6 +76,9 @@ banner
68
76
  opts.on( '-g', '--group GROUP', 'Specify the default group of the repo dir' ) do |group|
69
77
  options[:group] = group
70
78
  end
79
+ opts.on( '-v', '--version VERSION', 'Update the servers config version on create/start/launch' ) do |version|
80
+ options[:version] = version
81
+ end
71
82
  opts.on( '-h', '--help', 'Display this screen' ) do
72
83
  puts opts
73
84
  exit
@@ -77,6 +88,7 @@ end
77
88
  optparse.parse!
78
89
 
79
90
  judo = Judo::Base.new(defaults.merge(options))
91
+ judo.check_version
80
92
 
81
93
  begin
82
94
  case action
@@ -85,14 +97,35 @@ begin
85
97
  when "list" then do_list(judo, ARGV)
86
98
  when "groups" then do_groups(judo)
87
99
  when "info" then find_servers(judo, ARGV) { |s| do_info(judo, s) }
88
- when "console" then find_servers(judo, ARGV) { |s| puts server.console_output }
100
+ when "console" then find_servers(judo, ARGV) { |s| puts s.console_output }
89
101
  when "commit" then find_groups(judo, ARGV) { |g| g.compile }
90
102
  when "ssh" then find_servers(judo, ARGV) { |s| s.connect_ssh }
91
- when "start" then find_servers(judo, ARGV) { |s| s.start }
92
- when "restart" then find_servers(judo, ARGV) { |s| s.restart }
93
- when "stop" then find_servers(judo, ARGV) { |s| s.stop }
94
- when "create" then mk_servers(judo, ARGV) { |s| s.create }
95
- when "launch" then mk_servers(judo, ARGV) { |s| s.create; s.start }
103
+ when "start" then find_servers(judo, ARGV) { |s| s.start(options[:version]) }
104
+ when "restart" then find_servers(judo, ARGV) { |s| s.restart(options[:force]) }
105
+ when "stop" then find_servers(judo, ARGV) { |s| s.stop(options[:force]) }
106
+ when "create" then mk_servers(judo, ARGV) { |s| s.create(options[:version]) }
107
+ when "launch" then mk_servers(judo, ARGV) { |s| s.create(options[:version]); s.start }
108
+ when "snapshots" then do_snapshots(judo, ARGV)
109
+ when "erase" then
110
+ raise JudoError, "usage: judo erase SNAPSHOT" unless ARGV.size == 1
111
+ snapshot_name = ARGV.shift
112
+ judo.snapshots.select { |s| s.name == snapshot_name }.each { |s| s.destroy }
113
+ when "snapshot" then
114
+ raise JudoError, "usage: judo snapshot SERVER SNAPSHOT" unless ARGV.size == 2
115
+ server_name = ARGV.shift
116
+ snapshot_name = ARGV.shift
117
+ servers = find_servers(judo, [server_name])
118
+ raise JudoError, "You must specify a valid server to snapshot" if servers.empty?
119
+ raise JudoError, "You can only snapshot one server at a time" if servers.size > 1
120
+ servers[0].snapshot(snapshot_name)
121
+ when "clone" then
122
+ snapshot_name = ARGV.shift
123
+ raise JudoError, "You must specify a snapshot name" unless snapshot_name
124
+ new_server = ARGV.shift
125
+ raise JudoError, "You must specify a new server name" unless new_server
126
+ snapshot = judo.snapshots.detect { |s| s.name == snapshot_name }
127
+ raise JudoError, "No such snapshot #{snapshot_name}" unless snapshot
128
+ snapshot.clone(new_server,options[:version])
96
129
  when "destroy" then
97
130
  raise JudoError, "You must specify what servers to destroy" if ARGV.empty?
98
131
  find_either(judo, ARGV) do |i|
@@ -7,6 +7,8 @@ require 'right_aws'
7
7
  require 'json'
8
8
  require 'pp'
9
9
 
10
+ raise "Judo Currently Requires Ruby 1.8.7" unless RUBY_VERSION == "1.8.7"
11
+
10
12
  class JudoError < RuntimeError ; end
11
13
  class JudoInvalid < RuntimeError ; end
12
14
 
@@ -14,5 +16,6 @@ require File.dirname(__FILE__) + '/judo/base'
14
16
  require File.dirname(__FILE__) + '/judo/config'
15
17
  require File.dirname(__FILE__) + '/judo/group'
16
18
  require File.dirname(__FILE__) + '/judo/server'
19
+ require File.dirname(__FILE__) + '/judo/snapshot'
17
20
  require File.dirname(__FILE__) + '/judo/setup'
18
21
 
@@ -1,3 +1,27 @@
1
+
2
+ =begin
3
+ module Aws
4
+ class Ec2
5
+ API_VERSION = "2009-11-30" ## this didnt work
6
+ def start_instances(instance_id)
7
+ link = generate_request("StartInstances", { 'InstanceId' => instance_id } )
8
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
9
+ rescue Exception
10
+ on_exception
11
+ end
12
+
13
+ def stop_instances(instance_id)
14
+ link = generate_request("StopInstances", { 'InstanceId' => instance_id } )
15
+ puts link.inspect
16
+ result = request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
17
+ puts result.inspect
18
+ rescue Exception
19
+ on_exception
20
+ end
21
+ end
22
+ end
23
+ =end
24
+
1
25
  module Judo
2
26
  class Base
3
27
  attr_accessor :judo_dir, :repo, :group
@@ -12,7 +36,7 @@ module Judo
12
36
  end
13
37
 
14
38
  def volumes
15
- @volumes ||= ec2.describe_volumes.map do |v|
39
+ @volumes ||= ec2_volumes.map do |v|
16
40
  {
17
41
  :id => v[:aws_id],
18
42
  :size => v[:aws_size],
@@ -69,6 +93,16 @@ module Judo
69
93
  @sdb ||= Aws::SdbInterface.new(access_id, access_secret, :logger => Logger.new(nil))
70
94
  end
71
95
 
96
+ def fetch_snapshots_state
97
+ s = {}
98
+ sdb.select("select * from #{Judo::Snapshot.domain}")[:items].each do |group|
99
+ group.each do |key,val|
100
+ s[key] = val
101
+ end
102
+ end
103
+ s
104
+ end
105
+
72
106
  def fetch_servers_state
73
107
  s = {}
74
108
  sdb.select("select * from #{Judo::Server.domain}")[:items].each do |group|
@@ -79,10 +113,18 @@ module Judo
79
113
  s
80
114
  end
81
115
 
116
+ def snapshots_state
117
+ @snapshots_state ||= fetch_snapshots_state
118
+ end
119
+
82
120
  def servers_state
83
121
  @servers_state ||= fetch_servers_state
84
122
  end
85
123
 
124
+ def snapshots
125
+ @snapshots ||= snapshots_state.map { |name,data| Judo::Snapshot.new(self, name, data["server"].first) }
126
+ end
127
+
86
128
  def servers
87
129
  @servers ||= servers_state.map { |name,data| Judo::Server.new(self, name, data["group"].first) }
88
130
  end
@@ -92,9 +134,15 @@ module Judo
92
134
  servers << s
93
135
  s
94
136
  end
95
-
137
+
138
+ def new_snapshot(name, server)
139
+ s = Judo::Snapshot.new(self, name, server)
140
+ snapshots << s
141
+ s
142
+ end
143
+
96
144
  def get_group(name)
97
- group = groups.detect { |g| g.to_s == name }
145
+ group = groups.detect { |g| g.name == name }
98
146
  group ||= Judo::Group.new(self, name, 0)
99
147
  group
100
148
  end
@@ -119,6 +167,14 @@ module Judo
119
167
  @ec2_instance = nil
120
168
  end
121
169
 
170
+ def ec2_volumes
171
+ @ec2_volumes ||= ec2.describe_volumes
172
+ end
173
+
174
+ def ec2_snapshots
175
+ @ec2_snapshots ||= ec2.describe_snapshots
176
+ end
177
+
122
178
  def ec2_instances
123
179
  @ec2_instance ||= ec2.describe_instances
124
180
  end
@@ -182,18 +238,34 @@ module Judo
182
238
  end
183
239
 
184
240
  def db_version
185
- 1
241
+ 2
242
+ end
243
+
244
+ def upgrade_db
245
+ case get_db_version
246
+ when 0
247
+ raise JudoError, "Your db appears to be unititialized. You will need to do a judo init"
248
+ when 1
249
+ task("Upgrading Judo: Creating Snapshots SDB Domain") do
250
+ sdb.create_domain(Judo::Snapshot.domain)
251
+ set_db_version(2)
252
+ end
253
+ else
254
+ raise JduoError, "judo db is newer than the current gem - upgrade judo and try again"
255
+ end
256
+ end
257
+
258
+ def set_db_version(new_version)
259
+ @db_version = new_version
260
+ sdb.put_attributes("judo_config", "judo", { "dbversion" => new_version }, :replace)
186
261
  end
187
262
 
188
263
  def get_db_version
189
- version = @sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"]
190
- version and version.first.to_i or db_version
264
+ @db_version ||= sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"].first.to_i
191
265
  end
192
266
 
193
267
  def check_version
194
- ## FIXME - call this somewhere
195
- raise JduoError, "judo db is newer than the current gem - upgrade judo and try again" if get_db_version > db_version
268
+ upgrade_db if get_db_version != db_version
196
269
  end
197
-
198
270
  end
199
271
  end
@@ -16,7 +16,7 @@ module JudoCommandLineHelpers
16
16
  end
17
17
 
18
18
  def find_groups(judo, args, &blk)
19
- raise JudoError, "No groups #{specified}" if args.empty? and judo.group.nil?
19
+ raise JudoError, "No groups specified" if args.empty? and judo.group.nil?
20
20
 
21
21
  args << ":#{judo.group}" if args.empty? ## use default group if none specified
22
22
 
@@ -65,9 +65,10 @@ module JudoCommandLineHelpers
65
65
  raise JudoError, "No servers" if servers.empty?
66
66
 
67
67
  servers.each { |s| judo_yield(s,blk) if blk }
68
+ servers
68
69
  end
69
70
 
70
- def find_server(judo, arg, use_default)
71
+ def find_server(judo, arg, use_default = false)
71
72
  ## this assumes names are unique no matter the group
72
73
  name,group = split(arg)
73
74
  if name != ""
@@ -118,12 +119,23 @@ module JudoCommandLineHelpers
118
119
  end
119
120
  end
120
121
 
122
+ def do_snapshots(judo, args)
123
+ servers = find_servers(judo, args)
124
+ printf " SNAPSHOTS\n"
125
+ printf "%s\n", ("-" * 80)
126
+ ## FIXME - listing snapshots for deleted servers?
127
+ judo.snapshots.each do |snapshot|
128
+ # next if args and not servers.map { |s| s.name }.include?(snapshot.server_name)
129
+ printf "%-15s %-25s %-15s %-10s %s\n", snapshot.name, snapshot.server_name, snapshot.group_name, snapshot.version_desc, "#{snapshot.num_ec2_snapshots}v"
130
+ end
131
+ end
132
+
121
133
  def do_list(judo, args)
122
134
  servers = find_servers(judo, args)
123
135
  printf " SERVERS\n"
124
136
  printf "%s\n", ("-" * 80)
125
137
  servers.sort.each do |s|
126
- printf "%-18s %-12s %-7s %-11s %-11s %-13s %-10s %-10s %s\n", s.name, s.group.name, s.version_desc, s.state["instance_id"], s.size_desc, s.ami, s.ec2_state, "#{s.volumes.keys.size} volumes", s.ip
138
+ printf "%-32s %-12s %-7s %-11s %-11s %-10s %-3s %s\n", s.name, s.group.name, s.version_desc, s.state["instance_id"], s.size_desc, s.ec2_state, "#{s.volumes.keys.size}v", s.has_ip? ? "ip" : ""
127
139
  end
128
140
  end
129
141
 
@@ -145,7 +157,5 @@ module JudoCommandLineHelpers
145
157
  v[:aws_attachment_status],
146
158
  v[:aws_device]
147
159
  end
148
- puts "\t[ CONFIG ]"
149
- pp server.config
150
160
  end
151
161
  end
@@ -2,47 +2,11 @@ module Judo
2
2
  class Group
3
3
  attr_accessor :name, :version
4
4
 
5
- # def self.dirs
6
- # Dir["#{@base.repor}/*/config.json"].map { |d| File.dirname(d) }
7
- # end
8
- #
9
- # def self.all
10
- # @@all ||= (dirs.map { |d| new(d) })
11
- # end
12
- #
13
- # def self.find(name)
14
- # all.detect { |d| d.name == name }
15
- # end
16
- #
17
- # def self.[](name)
18
- # find(name)
19
- # end
20
- #
21
- # def self.current
22
- # all.detect { |d| Dir.pwd == d.dir or Dir.pwd =~ /^#{d.dir}\// }
23
- # end
24
-
25
- # def initialize(base, dir, name = File.basename(dir))
26
5
  def initialize(base, name, version)
27
6
  @base = base
28
7
  @name = name
29
8
  @version = version
30
- # @dir = dir
31
- end
32
-
33
- # def create_server(server_name)
34
- # abort("Server needs a name") if server_name.nil?
35
- #
36
- ## abort("Already a server named #{server_name}") if Judo::Server.find_by_name(attrs[:name]) ## FIXME
37
- ## @base.read_config(attrs[:group]) ## make sure the config is valid ## FIXME
38
- #
39
- # server = Judo::Server.new base, server_name, name
40
- # server.task("Creating server #{server_name}") do
41
- # server.update "name" => server_name, "group" => name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36)
42
- # @base.sdb.put_attributes("judo_config", "groups", name => server_name)
43
- # end
44
- # server
45
- # end
9
+ end
46
10
 
47
11
  def config
48
12
  @config ||= load_config
@@ -60,25 +24,20 @@ module Judo
60
24
  @base.servers.select { |s| server_names.include?(s.name) }
61
25
  end
62
26
 
63
- # def version
64
- # @version ||= (@base.group_versions[@name] || ["0"]).first.to_i
65
- # end
66
-
67
27
  def load_config
68
28
  JSON.load @base.s3_get(version_config_file)
69
29
  rescue Aws::AwsError
70
30
  raise JudoError, "No config stored: try 'judo commit #{to_s}'"
71
31
  end
72
32
 
73
- def set_version(new_version)
74
- @version = new_version
75
- @base.sdb.put_attributes("judo_config", "group_versions", { name => new_version }, :replace)
33
+ def set_version
34
+ @base.sdb.put_attributes("judo_config", "group_versions", { name => version }, :replace)
76
35
  end
77
36
 
78
37
  def compile
79
38
  tmpdir = "/tmp/kuzushi/#{name}"
80
- set_version(version + 1)
81
39
  puts "Compiling #{self} version #{version}"
40
+ @version = @version + 1
82
41
  FileUtils.rm_rf(tmpdir)
83
42
  FileUtils.mkdir_p(tmpdir)
84
43
  new_config = build_config
@@ -101,6 +60,7 @@ module Judo
101
60
  @base.s3_put(version_config_file, new_config.to_json)
102
61
  end
103
62
  end
63
+ set_version
104
64
  end
105
65
 
106
66
  def version_config_file
@@ -121,7 +81,6 @@ module Judo
121
81
 
122
82
  def extract_file(type, name, files)
123
83
  path = "#{dir}/#{type}s/#{name}"
124
- puts "[#{name}] #{path}"
125
84
  found = Dir[path]
126
85
  if not found.empty?
127
86
  found.each { |f| files["#{type}s/#{File.basename(f)}"] = f }
@@ -147,12 +106,15 @@ module Judo
147
106
  when "local_packages"
148
107
  extract_file(:package, "#{v}_i386.deb", files)
149
108
  extract_file(:package, "#{v}_amd64.deb", files)
109
+ when "git"
110
+ ## do nothing - is a remote
150
111
  when "template"
151
112
  extract_file(:template, v, files)
152
113
  when "source"
153
114
  extract_file(:file, v, files) unless config["template"] or config["package"]
154
115
  when "file"
155
- extract_file(:file, File.basename(v), files) unless config["template"] or config["source"]
116
+ ## this is getting messy
117
+ extract_file(:file, File.basename(v), files) unless config["template"] or config["source"] or config["git"]
156
118
  end
157
119
  end
158
120
  end
@@ -216,6 +178,14 @@ module Judo
216
178
  def sdb
217
179
  @base.sdb
218
180
  end
181
+
182
+ def version_desc(v)
183
+ if v == version
184
+ "v#{v}"
185
+ else
186
+ "v#{v}/#{version}"
187
+ end
188
+ end
219
189
  end
220
190
  end
221
191
 
@@ -1,51 +1,14 @@
1
- ### NEEDED for new gem launch
2
-
3
- ### [ ] return right away.. (1 hr)
4
- ### [ ] two phase delete (1 hr)
5
- ### [-] refactor availability_zone (2 hrs)
6
- ### [ ] pick availability zone from config "X":"Y" or "X":["Y","Z"]
7
- ### [ ] assign to state on creation ( could delay till volume creation )
8
- ### [ ] implement auto security_group creation and setup (6 hrs)
9
- ### [ ] write some examples - simple postgres/redis/couchdb server (5hrs)
10
- ### [ ] write new README (4 hrs)
11
- ### [ ] bind kuzushi gem version version
12
- ### [ ] realase new gem! (1 hr)
13
-
14
- ### [ ] should be able to do ALL actions except commit without the repo!
15
- ### [ ] store git commit hash with commit to block a judo commit if there is newer material stored
16
- ### [ ] remove the tarball - store files a sha hashes in the bucket - makes for faster commits if the files have not changed
17
-
18
- ### [ ] use a logger service (1 hr)
19
- ### [ ] write specs (5 hr)
20
-
21
- ### Error Handling
22
- ### [ ] no availability zone before making disks
23
- ### [ ] security group does not exists
24
-
25
- ### Do Later
26
- ### [ ] use amazon's new conditional write tools so we never have problems from concurrent updates
27
- ### [ ] is thor really what we want to use here?
28
- ### [ ] need to be able to pin a config to a version of kuzushi - gem updates can/will break a lot of things
29
- ### [ ] I want a "judo monitor" command that will make start servers if they go down, and poke a listed port to make sure a service is listening, would be cool if it also detects wrong ami, wrong secuirity group, missing/extra volumes, missing/extra elastic_ip - might not want to force a reboot quite yet in these cases
30
- ### [ ] Implement "judo snapshot [NAME]" to take a snapshot of the ebs's blocks
31
- ### [ ] ruby 1.9.1 support
32
- ### [ ] find a good way to set the hostname or prompt to :name
33
- ### [ ] remove fog/s3 dependancy
34
- ### [ ] enforce template files end in .erb to make room for other possible templates as defined by the extensions
35
- ### [ ] zerigo integration for automatic DNS setup
36
- ### [ ] How cool would it be if this was all reimplemented in eventmachine and could start lots of boxes in parallel? Would need to evented AWS api calls... Never seen a library to do that - would have to write our own... "Fog Machine?"
37
-
38
1
  module Judo
39
2
  class Server
40
3
  attr_accessor :name
41
4
 
42
- def initialize(base, name, group)
5
+ def initialize(base, name, group, version = nil)
43
6
  @base = base
44
7
  @name = name
45
8
  @group_name = group
46
9
  end
47
10
 
48
- def create
11
+ def create(version = group.version, snapshots = nil)
49
12
  raise JudoError, "no group specified" unless @group_name
50
13
 
51
14
  if @name.nil?
@@ -56,11 +19,12 @@ module Judo
56
19
  raise JudoError, "there is already a server named #{name}" if @base.servers.detect { |s| s.name == @name and s != self}
57
20
 
58
21
  task("Creating server #{name}") do
59
- update "name" => name, "group" => @group_name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36)
22
+ update "name" => name, "group" => @group_name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36), "version" => version
60
23
  @base.sdb.put_attributes("judo_config", "groups", @group_name => name)
61
24
  end
62
25
 
63
- allocate_resources
26
+ allocate_disk(snapshots)
27
+ allocate_ip
64
28
 
65
29
  self
66
30
  end
@@ -98,18 +62,37 @@ module Judo
98
62
  end
99
63
 
100
64
  def version_desc
101
- return "" unless running?
102
- if version == group.version
103
- "v#{version}"
104
- else
105
- "v#{version}/#{group.version}"
106
- end
65
+ group.version_desc(version)
107
66
  end
108
67
 
109
68
  def version
110
69
  get("version").to_i
111
70
  end
112
71
 
72
+ def update_version(new_version)
73
+ update "version" => new_version
74
+ end
75
+
76
+ def kuzushi_action
77
+ if virgin?
78
+ if cloned?
79
+ "start"
80
+ else
81
+ "init"
82
+ end
83
+ else
84
+ "start"
85
+ end
86
+ end
87
+
88
+ def clone
89
+ get("clone")
90
+ end
91
+
92
+ def cloned?
93
+ !!clone
94
+ end
95
+
113
96
  def virgin?
114
97
  get("virgin").to_s == "true" ## I'm going to set it to true and it will come back from the db as "true" -> could be "false" or false or nil also
115
98
  end
@@ -118,6 +101,10 @@ module Judo
118
101
  get "secret"
119
102
  end
120
103
 
104
+ def snapshots
105
+ base.snapshots.select { |s| s.server == self }
106
+ end
107
+
121
108
  def volumes
122
109
  Hash[ (state["volumes"] || []).map { |a| a.split(":") } ]
123
110
  end
@@ -147,7 +134,7 @@ module Judo
147
134
  end
148
135
 
149
136
  def delete
150
- group.delete_server(self)
137
+ group.delete_server(self) if group
151
138
  @base.sdb.delete_attributes(self.class.domain, name)
152
139
  end
153
140
 
@@ -165,7 +152,24 @@ module Judo
165
152
  "#{name}:#{@group_name}"
166
153
  end
167
154
 
168
- def allocate_resources
155
+ def allocate_disk(snapshots)
156
+ if snapshots
157
+ clone_snapshots(snapshots)
158
+ else
159
+ create_volumes
160
+ end
161
+ end
162
+
163
+ def clone_snapshots(snapshots)
164
+ snapshots.each do |device,snap_id|
165
+ task("Creating EC2 Volume #{device} from #{snap_id}") do
166
+ volume_id = @base.ec2.create_volume(snap_id, nil, config["availability_zone"])[:aws_id]
167
+ add_volume(volume_id, device)
168
+ end
169
+ end
170
+ end
171
+
172
+ def create_volumes
169
173
  if config["volumes"]
170
174
  [config["volumes"]].flatten.each do |volume_config|
171
175
  device = volume_config["device"]
@@ -185,7 +189,9 @@ module Judo
185
189
  end
186
190
  end
187
191
  end
192
+ end
188
193
 
194
+ def allocate_ip
189
195
  begin
190
196
  if config["elastic_ip"] and not elastic_ip
191
197
  ### EC2 allocate_address
@@ -259,17 +265,18 @@ module Judo
259
265
  ["pending", "running", "shutting_down", "degraded"].include?(ec2_state)
260
266
  end
261
267
 
262
- def start
268
+ def start(new_version = nil)
263
269
  invalid "Already running" if running?
264
270
  invalid "No config has been commited yet, type 'judo commit'" unless group.version > 0
271
+ task("Updating server version") { update_version(new_version) } if new_version
265
272
  task("Starting server #{name}") { launch_ec2 }
266
273
  task("Wait for server") { wait_for_running } if elastic_ip or has_volumes?
267
274
  task("Attaching ip") { attach_ip } if elastic_ip
268
275
  task("Attaching volumes") { attach_volumes } if has_volumes?
269
276
  end
270
277
 
271
- def restart
272
- stop if running?
278
+ def restart(force = false)
279
+ stop(force) if running?
273
280
  start
274
281
  end
275
282
 
@@ -285,10 +292,19 @@ module Judo
285
292
  raise JudoInvalid, str
286
293
  end
287
294
 
288
- def stop
295
+ def force_detach_volumes
296
+ volumes.each do |device,volume_id|
297
+ task("Force detaching #{volume_id}") do
298
+ @base.ec2.detach_volume(volume_id, instance_id, device, true)
299
+ end
300
+ end
301
+ end
302
+
303
+ def stop(force = false)
289
304
  invalid "not running" unless running?
290
305
  ## EC2 terminate_isntaces
291
306
  task("Terminating instance") { @base.ec2.terminate_instances([ instance_id ]) }
307
+ force_detach_volumes if force
292
308
  task("Wait for volumes to detach") { wait_for_volumes_detached } if volumes.size > 0
293
309
  remove "instance_id"
294
310
  end
@@ -305,8 +321,7 @@ module Judo
305
321
  :key_name => config["key_name"],
306
322
  :group_ids => security_groups,
307
323
  :user_data => ud).first
308
-
309
- update "instance_id" => result[:aws_instance_id], "virgin" => false, "version" => group.version
324
+ update "instance_id" => result[:aws_instance_id], "virgin" => false
310
325
  end
311
326
 
312
327
  def debug(str)
@@ -356,10 +371,15 @@ module Judo
356
371
  end
357
372
 
358
373
  def wait_for_volumes_detached
359
- ## FIXME - force if it takes too long
360
- loop do
361
- break if ec2_volumes.reject { |v| v[:aws_status] == "available" }.empty?
362
- sleep 2
374
+ begin
375
+ Timeout::timeout(30) do
376
+ loop do
377
+ break if ec2_volumes.reject { |v| v[:aws_status] == "available" }.empty?
378
+ sleep 2
379
+ end
380
+ end
381
+ rescue Timeout::Error
382
+ force_detach_volumes
363
383
  end
364
384
  end
365
385
 
@@ -505,6 +525,11 @@ USER_DATA
505
525
  end
506
526
  end
507
527
 
528
+ def snapshot(name)
529
+ snap = @base.new_snapshot(name, self.name)
530
+ snap.create
531
+ end
532
+
508
533
  def <=>(s)
509
534
  [group.name, name] <=> [s.group.name, s.name]
510
535
  end
@@ -63,7 +63,8 @@ DEFAULT
63
63
 
64
64
  def setup_db
65
65
  puts "Trying to connect to SimpleDB with #{@aws_access_id}"
66
- sdb.create_domain("judo_servers")
66
+ sdb.create_domain(Judo::Server.domain)
67
+ sdb.create_domain(Judo::Snapshot.domain)
67
68
  sdb.create_domain("judo_config")
68
69
  olddb = sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"]
69
70
  abort "There is an existing judo database of a newer version - upgrade judo and try again" if olddb and olddb.first.to_i > Judo::Config.db_version
@@ -0,0 +1,108 @@
1
+ module Judo
2
+ class Snapshot
3
+ attr_accessor :name, :server_name
4
+
5
+ def self.domain
6
+ "judo_snapshots"
7
+ end
8
+
9
+ def initialize(base, name, server_name)
10
+ @base = base
11
+ @name = name
12
+ @server_name = server_name
13
+ end
14
+
15
+ def server
16
+ @server ||= @base.servers.detect { |s| s.name == @server_name }
17
+ end
18
+
19
+ def fetch_state
20
+ @base.sdb.get_attributes(self.class.domain, name)[:attributes]
21
+ end
22
+
23
+ def state
24
+ @base.snapshots_state[name] ||= fetch_state
25
+ end
26
+
27
+ def group_name
28
+ get("group")
29
+ end
30
+
31
+ def version
32
+ get("version").to_i
33
+ end
34
+
35
+ def num_ec2_snapshots
36
+ devs.size
37
+ end
38
+
39
+ def devs
40
+ Hash[ (state["devs"] || []).map { |a| a.split(":") } ]
41
+ end
42
+
43
+ def create
44
+ raise JudoError,"snapshot already exists" unless state.empty?
45
+ raise JudoError,"server has no disks to clone: #{server.volumes}" if server.volumes.empty?
46
+ task("Snapshotting #{server.name}") do
47
+ devs = server.volumes.map do |dev,vol|
48
+ "#{dev}:#{@base.ec2.create_snapshot(vol)[:aws_id]}"
49
+ end
50
+ @base.sdb.put_attributes(self.class.domain, name, { "version" => server.version, "devs" => devs, "server" => server.name, "group" => server.group.name }, :replace)
51
+ server.add "snapshots", name
52
+ end
53
+ end
54
+
55
+ def clone(new_server, version = self.version)
56
+ raise JudoError, "cannot clone, snapshotting not complete" unless completed?
57
+ server = @base.new_server(new_server, group_name)
58
+ server.create(version, devs)
59
+ server.update "clone" => name ##, "secret" => rand(2 ** 128).to_s(36) ## cant change this till kuzushi knows about a post-clone operation
60
+ end
61
+
62
+ def delete
63
+ @base.sdb.delete_attributes(self.class.domain, name)
64
+ server.remove "snapshots", name
65
+ end
66
+
67
+ def get(key)
68
+ state[key] && [state[key]].flatten.first
69
+ end
70
+
71
+ def destroy
72
+ devs.each do |dev,snapshot_id|
73
+ task("Deleting #{dev} #{snapshot_id}") do
74
+ begin
75
+ @base.ec2.delete_snapshot(snapshot_id)
76
+ rescue Object => e
77
+ puts "Error destrotying snapshot #{e.message}"
78
+ end
79
+ end
80
+ end
81
+ delete
82
+ end
83
+
84
+ def ec2_ids
85
+ devs.values
86
+ end
87
+
88
+ def completed?
89
+ not @base.ec2_snapshots.select { |s| ec2_ids.include? s[:aws_id] }.detect { |s| s[:aws_status] != "completed" }
90
+ end
91
+
92
+ def size(snap_id)
93
+ @base.ec2_snapshots.detect { |s| s[:aws_id] == snap_id }
94
+ end
95
+
96
+ def version_desc
97
+ group.version_desc(version)
98
+ end
99
+
100
+ def group
101
+ @group ||= @base.groups.detect { |g| g.name == group_name }
102
+ end
103
+
104
+ def fetch_state
105
+ @base.sdb.get_attributes(self.class.domain, name)[:attributes]
106
+ end
107
+ end
108
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 4
9
+ version: 0.1.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Orion Henry
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-08 00:00:00 -04:00
17
+ date: 2010-05-06 00:00:00 -04:00
18
18
  default_executable: judo
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -51,9 +51,11 @@ extensions: []
51
51
 
52
52
  extra_rdoc_files:
53
53
  - README.markdown
54
+ - TODO
54
55
  files:
55
56
  - README.markdown
56
57
  - Rakefile
58
+ - TODO
57
59
  - VERSION
58
60
  - bin/judo
59
61
  - lib/judo.rb
@@ -63,6 +65,7 @@ files:
63
65
  - lib/judo/group.rb
64
66
  - lib/judo/server.rb
65
67
  - lib/judo/setup.rb
68
+ - lib/judo/snapshot.rb
66
69
  - spec/base.rb
67
70
  - spec/base_spec.rb
68
71
  - spec/server_spec.rb