vizir 0.2.2

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pierre Riteau
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = Vizir
2
+
3
+ Disconnected from OAR job 960063
4
+
5
+ Ever been disconnected from Grid'5000 machines with a message like this, losing part or all of your work?
6
+
7
+ Vizir is a simple Ruby script that monitors your interactive jobs on Grid'5000[http://www.grid5000.fr/].
8
+ It triggers Growl[http://growl.info/] notifications when your reservations are going to terminate, allowing you to save your work and/or your deployed environments.
9
+
10
+ Vizir only supports Mac OS X.
11
+
12
+ == Getting Started
13
+
14
+ Run the following if you haven't already:
15
+
16
+ $ gem sources -a http://gems.github.com
17
+
18
+ Install the gem:
19
+
20
+ $ sudo gem install priteau-vizir
21
+
22
+ Vizir uses HotCocoa to trigger Growl notifications. HotCocoa is shipped with Mac OS X 10.5, so it should work out of the box on Leopard.
23
+
24
+ Run Vizir in the background, using the <tt>--login</tt> switch with your Grid'5000 login name.
25
+
26
+ $ vizir --login priteau &
27
+
28
+ == Getting the source
29
+
30
+ Clone it from GitHub:
31
+
32
+ $ git clone git://github.com/priteau/vizir.git
33
+
34
+ == Copyright
35
+
36
+ Copyright (c) 2009 Pierre Riteau. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "vizir"
8
+ gem.summary = "Growl notifications for Grid'5000 jobs"
9
+ gem.email = "pierre.riteau@gmail.com"
10
+ gem.homepage = "http://github.com/priteau/vizir"
11
+ gem.authors = ["Pierre Riteau"]
12
+ gem.rubyforge_project = "vizir"
13
+ gem.add_dependency "eventmachine"
14
+ gem.add_dependency "growlnotifier"
15
+ gem.add_dependency "json"
16
+ gem.add_dependency "net-ssh-gateway"
17
+ gem.add_dependency "rest-client"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+
21
+ Jeweler::RubyforgeTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ require 'rake/testtask'
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << 'lib' << 'test'
29
+ test.pattern = 'test/**/test_*.rb'
30
+ test.verbose = true
31
+ end
32
+
33
+ begin
34
+ require 'rcov/rcovtask'
35
+ Rcov::RcovTask.new do |test|
36
+ test.libs << 'test'
37
+ test.pattern = 'test/**/test_*.rb'
38
+ test.verbose = true
39
+ end
40
+ rescue LoadError
41
+ task :rcov do
42
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
43
+ end
44
+ end
45
+
46
+
47
+ task :default => :test
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ if File.exist?('VERSION.yml')
52
+ config = YAML.load(File.read('VERSION.yml'))
53
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
54
+ else
55
+ version = ""
56
+ end
57
+
58
+ rdoc.rdoc_dir = 'rdoc'
59
+ rdoc.title = "vizir #{version}"
60
+ rdoc.rdoc_files.include('README*')
61
+ rdoc.rdoc_files.include('lib/**/*.rb')
62
+ end
63
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
data/bin/vizir ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
4
+
5
+ require 'eventmachine'
6
+ require 'growl'
7
+ require 'net/ssh/gateway'
8
+ require 'optparse'
9
+ require 'rest_client'
10
+ require 'vizir'
11
+
12
+ ALERT_PERIOD = 60
13
+ REFRESH_TIME = 3600
14
+
15
+ def usage
16
+ puts 'usage: vizir [ -h | --help ] [ -l | --login login_name ]'
17
+ exit 1
18
+ end
19
+
20
+ def setup_ssh_tunnel
21
+ gateway = Net::SSH::Gateway.new('access.grenoble.grid5000.fr', $login)
22
+ port = gateway.open('api.grenoble.grid5000.fr', 443)
23
+ return gateway, port
24
+ end
25
+
26
+ def apiuri(port)
27
+ return "https://localhost:#{port}/oargridapi"
28
+ end
29
+
30
+ def setup_growl
31
+ $growl = Growl::Notifier.sharedInstance
32
+ $growl.register('Vizir', ['Job ending soon'])
33
+ end
34
+
35
+ def notify_via_growl(jobid, site_name, time, time_unit)
36
+ $growl.notify('Job ending soon', 'OAR job terminating soon', "Job #{jobid} in #{site_name} ending in #{time} #{time_unit}.", :sticky => true)
37
+ end
38
+
39
+ $login = nil
40
+ opts = OptionParser.new
41
+ opts.on('-h', '--help') { usage }
42
+ opts.on('-l', '--login STRING', String) { |str| $login = str }
43
+ opts.parse(ARGV)
44
+
45
+ if $login == nil
46
+ usage
47
+ end
48
+
49
+ gateway, port = setup_ssh_tunnel
50
+ api = Vizir.get_api(apiuri(port))
51
+ setup_growl
52
+
53
+ # Learn the Grid'5000 sites
54
+ $jobs = Hash.new
55
+ $sites = Vizir.learn_sites(api, Vizir::IGNORED_SITES)
56
+ exit 1 if $sites == nil
57
+
58
+ Vizir.learn_new_jobs(api)
59
+ Vizir.alert_jobs(api)
60
+
61
+ EventMachine::run {
62
+ EventMachine::PeriodicTimer.new(REFRESH_TIME) { Vizir.learn_new_jobs(api) }
63
+ EventMachine::PeriodicTimer.new(ALERT_PERIOD) { Vizir.alert_jobs(api) }
64
+ }
65
+
66
+ gateway.shutdown!
data/lib/vizir.rb ADDED
@@ -0,0 +1,138 @@
1
+ require 'json'
2
+ require 'restclient'
3
+
4
+ module Vizir
5
+ APIURL = "https://localhost:8080/oargridapi"
6
+ IGNORED_SITES = ['grenoble-exp', 'grenoble-ext', 'grenoble-obs', 'luxembourg', 'portoalegre']
7
+ FIRST_ALERT_TIME = 600
8
+
9
+ def Vizir.humanize_time(time)
10
+ fail "Can't humanize negative period" if time < 0
11
+
12
+ if time >= 60
13
+ remaining_time = time / 60
14
+ remaining_time_unit = "minute"
15
+ else
16
+ remaining_time = time
17
+ remaining_time_unit = "second"
18
+ end
19
+
20
+ if remaining_time > 1
21
+ remaining_time_unit += "s"
22
+ end
23
+
24
+ return remaining_time, remaining_time_unit
25
+ end
26
+
27
+ def Vizir.get_remaining_time(end_time, now)
28
+ return (end_time - now).round
29
+ end
30
+
31
+ def Vizir.get(api, uri)
32
+ begin
33
+ return JSON.parse(api[uri + '?structure=simple'].get(:accept => 'application/json'))
34
+ rescue RestClient::RequestTimeout => e
35
+ # This should really be elsewhere, but will do for now
36
+ # Were we querying a specific site? If yes, remove it from $sites
37
+ if uri =~ /\/sites\/(\w+)/
38
+ $sites.delete_if { |site| site['site'] == "#{$1}" }
39
+ return nil
40
+ else
41
+ raise e
42
+ end
43
+ rescue => e
44
+ if e.respond_to?('http_code')
45
+ puts "Error: #{e.http_code}:\n #{e.response.body}"
46
+ return nil
47
+ else
48
+ puts e.inspect
49
+ exit 1
50
+ end
51
+ end
52
+ end
53
+
54
+ def Vizir.learn_sites(api, ignored_sites)
55
+ return get(api, '/sites').delete_if { |hash| ignored_sites.include?(hash["site"]) }
56
+ end
57
+
58
+ def Vizir.get_api(uri)
59
+ return RestClient::Resource.new(uri, :timeout => 15)
60
+ end
61
+
62
+ def Vizir.learn_new_jobs_on_site(api, site)
63
+ site_name = site['site']
64
+ jobs = Vizir.get(api, "#{site['uri']}/jobs")
65
+ return if jobs.nil?
66
+ jobs.each do |job|
67
+ if job['owner'] == $login
68
+ job_details = Vizir.get(api, "#{job['uri']}")
69
+ break if job_details == nil
70
+ if job_details['jobType'] == 'INTERACTIVE'
71
+ jobid = job_details['Job_Id']
72
+ # If we don't yet know the job, record it in $jobs
73
+ if $jobs[jobid].nil?
74
+ $jobs[jobid] = Vizir::Job.new(jobid, Time.at(Integer(job_details['startTime']) + Integer(job_details['walltime'])), site_name.capitalize)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def Vizir.learn_new_jobs(api)
82
+ $sites.each do |site|
83
+ Vizir.learn_new_jobs_on_site(api, site)
84
+ end
85
+ end
86
+
87
+ def Vizir.first_ending_job
88
+ $jobs.sort{|a,b| a[1].end_time <=> b[1].end_time}.first
89
+ end
90
+
91
+ def Vizir.alert_jobs(api)
92
+ $jobs.each do |jobid, job|
93
+ if job.should_be_ending?
94
+ # Check if the job still exists before sending a notification
95
+ if job.is_ended?(api)
96
+ # Job is not running anymore, remove it from the hash
97
+ $jobs.delete(jobid)
98
+ next
99
+ end
100
+ remaining_sec = Vizir.get_remaining_time(job.end_time, Time.now)
101
+ if remaining_sec < 0
102
+ $stderr.puts "Error: negative time"
103
+ next
104
+ end
105
+ remaining_time, remaining_time_unit = Vizir.humanize_time(remaining_sec)
106
+ notify_via_growl(jobid, job.site_name, remaining_time, remaining_time_unit)
107
+ end
108
+ end
109
+ end
110
+
111
+ class Job
112
+ attr_reader :id, :end_time, :site_name
113
+
114
+ def initialize(id, end_time, site_name)
115
+ @id = id
116
+ @end_time = end_time
117
+ @site_name = site_name
118
+ end
119
+
120
+ def Job.create_from_json(json, site_name)
121
+ return Job.new(json['Job_Id'], Time.at(Integer(json['startTime']) + Integer(json['walltime'])), site_name.capitalize)
122
+ end
123
+
124
+ def fetch_details(api)
125
+ return Vizir.get(api, "/sites/#{@site_name.downcase}/jobs/#{@id}")
126
+ end
127
+
128
+ def should_be_ending?(time=Time.now())
129
+ return Vizir.get_remaining_time(self.end_time, time) <= Vizir::FIRST_ALERT_TIME
130
+ end
131
+
132
+ def is_ended?(api)
133
+ updatedjob = self.fetch_details(api)
134
+ return updatedjob['state'] != 'Running'
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ require 'fakeweb'
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+ require 'vizir'
10
+
11
+ class Test::Unit::TestCase
12
+ end
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+
3
+ class VizirTest < Test::Unit::TestCase
4
+ TIMES = [
5
+ [0, [0, "second"]],
6
+ [1, [1, "second"]],
7
+ [30, [30, "seconds"]],
8
+ [59, [59, "seconds"]],
9
+ [60, [1, "minute"]],
10
+ [119, [1, "minute"]],
11
+ [120, [2, "minutes"]]
12
+ ]
13
+ def test_humanize_time
14
+ TIMES.each do |seconds, result|
15
+ assert_equal(result, Vizir.humanize_time(seconds))
16
+ end
17
+ e = assert_raise(RuntimeError) { Vizir.humanize_time(-1) }
18
+ assert_equal("Can't humanize negative period", e.message)
19
+ end
20
+
21
+ def test_learn_sites
22
+ FakeWeb.allow_net_connect = false
23
+ o = [
24
+ {
25
+ "site" => "nancy",
26
+ "uri" => "/sites/nancy"
27
+ },
28
+ {
29
+ "site" => "rennes",
30
+ "uri" => "/sites/rennes"
31
+ },
32
+ {
33
+ "site" => "luxembourg",
34
+ "uri" => "/sites/luxembourg"
35
+ }]
36
+ json = JSON.generate(o)
37
+ FakeWeb.register_uri(:get, "#{Vizir::APIURL}/sites?structure=simple", :string => json)
38
+ api = Vizir.get_api(Vizir::APIURL)
39
+ sites = o.delete_if { |hash| hash["site"] == "luxembourg" }
40
+ ignored_sites = ["luxembourg", "portoalegre"]
41
+ assert_equal(sites, Vizir.learn_sites(api, ignored_sites))
42
+ end
43
+
44
+ context "A Job instance" do
45
+ setup do
46
+ @job_endtime = Time.at(123456789)
47
+ @job = Vizir::Job.new('1234', @job_endtime, 'Rennes')
48
+ end
49
+
50
+ context 'ending in FIRST_ALERT_TIME seconds' do
51
+ setup do
52
+ @now = Time.at(Integer(@job_endtime) - Integer(Vizir::FIRST_ALERT_TIME))
53
+ end
54
+
55
+ should 'be considered as ending soon' do
56
+ assert_equal true, @job.should_be_ending?(@now)
57
+ end
58
+ end
59
+
60
+ context 'ending in more than FIRST_ALERT_TIME seconds' do
61
+ setup do
62
+ @now = Time.at(Integer(@job_endtime) - Integer(Vizir::FIRST_ALERT_TIME + 1))
63
+ end
64
+
65
+ should 'not be considered as ending soon' do
66
+ assert_equal false, @job.should_be_ending?(@now)
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'A site-specific query' do
72
+ setup do
73
+ @uri = "#{Vizir::APIURL}/sites/rennes/jobs?structure=simple"
74
+ end
75
+
76
+ context 'which timeouts' do
77
+ setup do
78
+ FakeWeb.register_uri(:get, @uri, :exception => Timeout::Error)
79
+ @api = Vizir.get_api(Vizir::APIURL)
80
+ end
81
+
82
+ should 'remove the site from the sites list' do
83
+ $sites = [
84
+ {
85
+ "site" => "nancy",
86
+ "uri" => "/sites/nancy"
87
+ },
88
+ {
89
+ "site" => "rennes",
90
+ "uri" => "/sites/rennes"
91
+ }
92
+ ]
93
+ Vizir.learn_new_jobs_on_site(@api, $sites[1])
94
+ assert_equal [{ "site" => "nancy", "uri" => "/sites/nancy" }], $sites
95
+ end
96
+ end
97
+ end
98
+
99
+ end
data/vizir.gemspec ADDED
@@ -0,0 +1,66 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{vizir}
5
+ s.version = "0.2.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Pierre Riteau"]
9
+ s.date = %q{2009-06-10}
10
+ s.default_executable = %q{vizir}
11
+ s.email = %q{pierre.riteau@gmail.com}
12
+ s.executables = ["vizir"]
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ ".document",
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/vizir",
25
+ "lib/vizir.rb",
26
+ "test/test_helper.rb",
27
+ "test/test_vizir.rb",
28
+ "vizir.gemspec"
29
+ ]
30
+ s.has_rdoc = true
31
+ s.homepage = %q{http://github.com/priteau/vizir}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubyforge_project = %q{vizir}
35
+ s.rubygems_version = %q{1.3.1}
36
+ s.summary = %q{Growl notifications for Grid'5000 jobs}
37
+ s.test_files = [
38
+ "test/test_helper.rb",
39
+ "test/test_vizir.rb"
40
+ ]
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 2
45
+
46
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
48
+ s.add_runtime_dependency(%q<growlnotifier>, [">= 0"])
49
+ s.add_runtime_dependency(%q<json>, [">= 0"])
50
+ s.add_runtime_dependency(%q<net-ssh-gateway>, [">= 0"])
51
+ s.add_runtime_dependency(%q<rest-client>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<eventmachine>, [">= 0"])
54
+ s.add_dependency(%q<growlnotifier>, [">= 0"])
55
+ s.add_dependency(%q<json>, [">= 0"])
56
+ s.add_dependency(%q<net-ssh-gateway>, [">= 0"])
57
+ s.add_dependency(%q<rest-client>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<eventmachine>, [">= 0"])
61
+ s.add_dependency(%q<growlnotifier>, [">= 0"])
62
+ s.add_dependency(%q<json>, [">= 0"])
63
+ s.add_dependency(%q<net-ssh-gateway>, [">= 0"])
64
+ s.add_dependency(%q<rest-client>, [">= 0"])
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vizir
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Pierre Riteau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-10 00:00:00 +02:00
13
+ default_executable: vizir
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: growlnotifier
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: net-ssh-gateway
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: rest-client
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ description:
66
+ email: pierre.riteau@gmail.com
67
+ executables:
68
+ - vizir
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - LICENSE
73
+ - README.rdoc
74
+ files:
75
+ - .document
76
+ - .gitignore
77
+ - LICENSE
78
+ - README.rdoc
79
+ - Rakefile
80
+ - VERSION
81
+ - bin/vizir
82
+ - lib/vizir.rb
83
+ - test/test_helper.rb
84
+ - test/test_vizir.rb
85
+ - vizir.gemspec
86
+ has_rdoc: true
87
+ homepage: http://github.com/priteau/vizir
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --charset=UTF-8
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: "0"
98
+ version:
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: "0"
104
+ version:
105
+ requirements: []
106
+
107
+ rubyforge_project: vizir
108
+ rubygems_version: 1.3.1
109
+ signing_key:
110
+ specification_version: 2
111
+ summary: Growl notifications for Grid'5000 jobs
112
+ test_files:
113
+ - test/test_helper.rb
114
+ - test/test_vizir.rb