xp5k 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENCE +3 -0
- data/README.md +187 -80
- data/lib/xp5k/role.rb +63 -5
- data/lib/xp5k/version.rb +1 -1
- data/lib/xp5k/xp.rb +13 -21
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66269fa663c38a7f293bcea2205b809e677c1332
|
4
|
+
data.tar.gz: 80597c35a94b407e3a9944e3935c46931629e9c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e28427dd47bdbdd3020dcd2718926ef42fc6ef02314b6123dfd7bf0f97cb2fc7f688c878e39d25fc68f6e6d3e3200aeab9013d1c3d3c0fb2419c1470fafefba3
|
7
|
+
data.tar.gz: 91024c08522a417cc600ae5aab4d00873bedf50162b18dcdb298814fb512485dc946314cff799f0a2e1ce25c898cd77a467bcdeef10cfebdd5627d8e2bcf15b1
|
data/LICENCE
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
Copyright (c) 2012 Pascal Morillon
|
2
2
|
Copyright (c) 2012 Université Rennes 1
|
3
3
|
|
4
|
+
Contributors:
|
5
|
+
- Matthieu Simonin (INRIA)
|
6
|
+
|
4
7
|
Permission is hereby granted, free of charge, to any person obtaining
|
5
8
|
a copy of this software and associated documentation files (the
|
6
9
|
"Software"), to deal in the Software without restriction, including
|
data/README.md
CHANGED
@@ -1,82 +1,189 @@
|
|
1
|
-
##
|
2
|
-
|
3
|
-
require 'xp5k'
|
4
|
-
|
5
|
-
XP5K::Config.load
|
6
|
-
|
7
|
-
@myxp = XP5K::XP.new(:logger => logger)
|
8
|
-
|
9
|
-
@myxp.define_job({
|
10
|
-
:resources => "nodes=2,walltime=1",
|
11
|
-
:site => XP5K::Config[:site] || 'rennes',
|
12
|
-
:types => ["deploy"],
|
13
|
-
:name => "job1",
|
14
|
-
:command => "sleep 86400"
|
15
|
-
})
|
16
|
-
|
17
|
-
@myxp.define_job({
|
18
|
-
:resources => "nodes=6,walltime=1",
|
19
|
-
:site => XP5K::Config[:site] || 'rennes',
|
20
|
-
:types => ["deploy"],
|
21
|
-
:name => "job2",
|
22
|
-
:roles => [
|
23
|
-
XP5K::Role.new({ :name => 'server', :size => 1 }),
|
24
|
-
XP5K::Role.new({ :name => 'nodes', :size => 5 })
|
25
|
-
]
|
26
|
-
:command => "sleep 86400"
|
27
|
-
})
|
28
|
-
|
29
|
-
@myxp.define_deployment({
|
30
|
-
:site => XP5K::Config[:site] || 'rennes',
|
31
|
-
:environment => "squeeze-x64-nfs",
|
32
|
-
:jobs => %w{ job1 },
|
33
|
-
:roles => %w{ server }
|
34
|
-
:key => File.read(XP5K::Config[:public_key])
|
35
|
-
})
|
36
|
-
|
37
|
-
role :job1 do
|
38
|
-
@myxp.job_with_name('job1')['assigned_nodes'].first
|
39
|
-
end
|
40
|
-
|
41
|
-
role :job2 do
|
42
|
-
@myxp.job_with_name('job2')['assigned_nodes'].first
|
43
|
-
end
|
44
|
-
|
45
|
-
role :nodes do
|
46
|
-
@myxp.role_with_name('nodes').servers
|
47
|
-
end
|
48
|
-
|
49
|
-
desc 'Submit jobs'
|
50
|
-
task :submit do
|
51
|
-
@myxp.submit
|
52
|
-
end
|
53
|
-
|
54
|
-
desc 'Deploy with Kadeplopy'
|
55
|
-
task :deploy do
|
56
|
-
@myxp.deploy
|
57
|
-
end
|
58
|
-
|
59
|
-
desc 'Status'
|
60
|
-
task :status do
|
61
|
-
@myxp.status
|
62
|
-
end
|
63
|
-
|
64
|
-
desc 'Remove all running jobs'
|
65
|
-
task :clean do
|
66
|
-
logger.debug "Clean all Grid'5000 running jobs..."
|
67
|
-
@myxp.clean
|
68
|
-
end
|
69
|
-
|
70
|
-
desc 'Run date command'
|
71
|
-
task :date, :roles => [:job1, :job2] do
|
72
|
-
set :user, 'root'
|
73
|
-
run 'date'
|
74
|
-
end
|
75
|
-
|
76
|
-
|
77
|
-
## _xp.conf_ sample file
|
78
|
-
|
79
|
-
site 'rennes'
|
80
|
-
public_key '/Users/pmorillon/.ssh/id_rsa_g5k.pub'
|
1
|
+
## Installation
|
81
2
|
|
3
|
+
Add this to your application Gemfile:
|
82
4
|
|
5
|
+
```ruby
|
6
|
+
gem "xp5k"
|
7
|
+
gem "capistrano", "< 3.0.0"
|
8
|
+
```
|
9
|
+
|
10
|
+
and then run
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
bundle install
|
14
|
+
```
|
15
|
+
|
16
|
+
Configure restfully :
|
17
|
+
|
18
|
+
```bash
|
19
|
+
$) cat ~/.restfully/api.grid5000.fr.yml
|
20
|
+
base_uri: https://api.grid5000.fr/3.0
|
21
|
+
username: "###"
|
22
|
+
password: "###"
|
23
|
+
```
|
24
|
+
|
25
|
+
You are now ready to use ```xp5k``` and ```capistrano```.
|
26
|
+
|
27
|
+
You will find the documentation of capistrano in the following link :
|
28
|
+
https://github.com/capistrano/capistrano/wiki
|
29
|
+
|
30
|
+
## Sample
|
31
|
+
|
32
|
+
Here is an example of a ```Capfile``` :
|
33
|
+
```ruby
|
34
|
+
require 'xp5k'
|
35
|
+
require 'capistrano'
|
36
|
+
|
37
|
+
set :g5k_user, "msimonin"
|
38
|
+
# gateway
|
39
|
+
set :gateway, "#{g5k_user}@access.grid5000.fr"
|
40
|
+
# These keys will used to access the gateway and nodes
|
41
|
+
ssh_options[:keys]= [File.join(ENV["HOME"], ".ssh", "id_rsa"), File.join(ENV["HOME"], ".ssh", "id_rsa_insideg5k")]
|
42
|
+
# # This key will be installed on nodes
|
43
|
+
set :ssh_public, File.join(ENV["HOME"], ".ssh", "id_rsa_insideg5k.pub")
|
44
|
+
|
45
|
+
XP5K::Config.load
|
46
|
+
|
47
|
+
@myxp = XP5K::XP.new(:logger => logger)
|
48
|
+
|
49
|
+
@myxp.define_job({
|
50
|
+
:resources => "nodes=2,walltime=1",
|
51
|
+
:site => 'rennes',
|
52
|
+
:types => ["deploy"],
|
53
|
+
:name => "job1",
|
54
|
+
:command => "sleep 86400"
|
55
|
+
})
|
56
|
+
|
57
|
+
@myxp.define_deployment({
|
58
|
+
:site => 'rennes',
|
59
|
+
:environment => "wheezy-x64-nfs",
|
60
|
+
:jobs => %w{ job1 },
|
61
|
+
:key => File.read("#{ssh_public}")
|
62
|
+
})
|
63
|
+
|
64
|
+
role :job1 do
|
65
|
+
@myxp.job_with_name('job1')['assigned_nodes'].first
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'Submit jobs'
|
69
|
+
task :submit do
|
70
|
+
@myxp.submit
|
71
|
+
@myxp.wait_for_jobs
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'Deploy with Kadeplopy'
|
75
|
+
task :deploy do
|
76
|
+
@myxp.deploy
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'Status'
|
80
|
+
task :status do
|
81
|
+
@myxp.status
|
82
|
+
end
|
83
|
+
|
84
|
+
desc 'Remove all running jobs'
|
85
|
+
task :clean do
|
86
|
+
logger.debug "Clean all Grid'5000 running jobs..."
|
87
|
+
@myxp.clean
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'Run date command'
|
91
|
+
task :date, :roles => [:job1] do
|
92
|
+
set :user, 'root'
|
93
|
+
run 'date'
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
After filling the Capfile you can start working with Capistrano :
|
98
|
+
|
99
|
+
```bash
|
100
|
+
➤ cap -T
|
101
|
+
cap clean # Remove all running jobs
|
102
|
+
cap date # Run date command
|
103
|
+
cap deploy # Deploy with Kadeplopy
|
104
|
+
cap invoke # Invoke a single command on the remote servers.
|
105
|
+
cap shell # Begin an interactive Capistrano session.
|
106
|
+
cap status # Status
|
107
|
+
cap submit # Submit jobs
|
108
|
+
```
|
109
|
+
|
110
|
+
For instance you can launch : ```cap submit deploy date```
|
111
|
+
|
112
|
+
## Extra features
|
113
|
+
|
114
|
+
### Xp5k Roles
|
115
|
+
|
116
|
+
You can define specific roles in you job submission.
|
117
|
+
This all
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
@myxp.define_job({
|
121
|
+
:resources => "nodes=6,walltime=1",
|
122
|
+
:site => XP5K::Config[:site] || 'rennes',
|
123
|
+
:types => ["deploy"],
|
124
|
+
:name => "job2",
|
125
|
+
:roles => [
|
126
|
+
XP5K::Role.new({ :name => 'server', :size => 1 }),
|
127
|
+
XP5K::Role.new({ :name => 'clients', :size => 4 }),
|
128
|
+
XP5K::Role.new({ :name => 'frontend', :size => 1 })
|
129
|
+
],
|
130
|
+
:command => "sleep 86400"
|
131
|
+
})
|
132
|
+
|
133
|
+
@myxp.define_deployment({
|
134
|
+
:site => 'rennes',
|
135
|
+
:environment => "wheezy-x64-nfs",
|
136
|
+
:roles => %w{ server frontend },
|
137
|
+
:key => File.read("#{ssh_public}")
|
138
|
+
})
|
139
|
+
|
140
|
+
@myxp.define_deployment({
|
141
|
+
:site => 'rennes',
|
142
|
+
:environment => "squeeze-x64-nfs",
|
143
|
+
:roles => %w{ clients },
|
144
|
+
:key => File.read("#{ssh_public}")
|
145
|
+
})
|
146
|
+
|
147
|
+
|
148
|
+
role :server do
|
149
|
+
@myxp.role_with_name('server').servers
|
150
|
+
end
|
151
|
+
|
152
|
+
role :server do
|
153
|
+
@myxp.role_with_name('frontend').servers
|
154
|
+
end
|
155
|
+
|
156
|
+
role :server do
|
157
|
+
@myxp.role_with_name('clients').servers
|
158
|
+
end
|
159
|
+
```
|
160
|
+
### Get the deployed nodes
|
161
|
+
|
162
|
+
Some time nodes fail to be deployed. You can get the exact set
|
163
|
+
of nodes deployed in your xp5k job or role using the ```get_deployed_nodes```
|
164
|
+
method.
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
role :clients do
|
168
|
+
@myxp.get_deployed_nodes('clients')
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
### Automatic redeployment
|
173
|
+
|
174
|
+
If some nodes fail to be deployed, ```xp5k``` will by default
|
175
|
+
retry to deploy them up to 3 times.
|
176
|
+
You can control this behaviour passing special keys in the deployment hash.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# disable the retry
|
180
|
+
@myxp.define_deployment({
|
181
|
+
...
|
182
|
+
:retry => true | false # enable / disable retry mechanism
|
183
|
+
# true by default
|
184
|
+
:retries => 3 # number of retries
|
185
|
+
|
186
|
+
:goal => 2 # min number of deployed nodes wanted
|
187
|
+
# can be a percentage : "80%"
|
188
|
+
})
|
189
|
+
```
|
data/lib/xp5k/role.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
class XP5K::Role
|
2
2
|
|
3
|
-
attr_accessor :name, :size, :desc, :servers, :jobid
|
3
|
+
attr_accessor :name, :size, :desc, :servers, :jobid, :inner
|
4
|
+
|
5
|
+
@@roles = []
|
4
6
|
|
5
7
|
def initialize(options = {})
|
8
|
+
# Defaults
|
9
|
+
@inner = false
|
10
|
+
@servers = []
|
11
|
+
@desc = ""
|
12
|
+
|
6
13
|
# Required parameters
|
7
|
-
@name = ""
|
8
|
-
@size = 0
|
9
14
|
%w{ name size }.each do |param|
|
10
15
|
if options[param.to_sym].nil?
|
11
16
|
raise XP5K::Exceptions::Role, "#{self.to_s}: #{param.to_sym} needed at initialization"
|
@@ -15,9 +20,62 @@ class XP5K::Role
|
|
15
20
|
end
|
16
21
|
|
17
22
|
# Optional parameters
|
18
|
-
%w{ desc servers }.each do |param|
|
19
|
-
instance_variable_set("@#{param}", options[param.to_sym])
|
23
|
+
%w{ desc servers inner }.each do |param|
|
24
|
+
instance_variable_set("@#{param}", options[param.to_sym]) if options[param.to_sym]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_roles(job, job_definition)
|
29
|
+
# Definition will return list of roles
|
30
|
+
roles = []
|
31
|
+
|
32
|
+
# Test if job contain enough nodes for all roles
|
33
|
+
count_needed_nodes = 0
|
34
|
+
job_definition[:roles].each { |role| count_needed_nodes += role.size if not role.inner }
|
35
|
+
if job['assigned_nodes'].length < count_needed_nodes
|
36
|
+
raise "Job ##{job['uid']} require more nodes for required roles"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sort nodes assigned to the job
|
40
|
+
available_nodes = { 'job' => job['assigned_nodes'].sort }
|
41
|
+
|
42
|
+
|
43
|
+
# Sort roles to manage inner roles at the end
|
44
|
+
defined_roles = job_definition[:roles].sort do |x,y|
|
45
|
+
a = x.inner ? 1 : 0
|
46
|
+
b = y.inner ? 1 : 0
|
47
|
+
a <=> b
|
20
48
|
end
|
49
|
+
|
50
|
+
# Attributes nodes to roles
|
51
|
+
defined_roles.each do |role|
|
52
|
+
next if self.exists?(role.name)
|
53
|
+
if role.inner
|
54
|
+
available_nodes[role.inner] ||= self.findByName(role.inner).servers
|
55
|
+
role.servers = available_nodes[role.inner][0..(role.size - 1)]
|
56
|
+
available_nodes[role.inner] -= role.servers
|
57
|
+
else
|
58
|
+
role.servers = available_nodes['job'][0..(role.size - 1)]
|
59
|
+
available_nodes['job'] -= role.servers
|
60
|
+
end
|
61
|
+
role.jobid = job['uid']
|
62
|
+
roles << role
|
63
|
+
@@roles << role
|
64
|
+
end
|
65
|
+
return roles
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.list()
|
69
|
+
@@roles
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.findByName(name)
|
73
|
+
roles = @@roles.select { |x| x.name == name }
|
74
|
+
roles.empty? ? nil : roles.first
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.exists?(name)
|
78
|
+
@@roles.select { |x| x.name == name }.empty? ? false : true
|
21
79
|
end
|
22
80
|
|
23
81
|
end
|
data/lib/xp5k/version.rb
CHANGED
data/lib/xp5k/xp.rb
CHANGED
@@ -79,11 +79,11 @@ module XP5K
|
|
79
79
|
datas = JSON.parse(File.read(".xp_cache"))
|
80
80
|
uid = datas["jobs"].select { |x| x["name"] == job_hash[:name] }.first["uid"]
|
81
81
|
unless uid.nil?
|
82
|
-
job = @connection.root.sites[job_hash[:site].to_sym].jobs(:query => { :user => @connection.config.options[:username] })["#{uid}".to_sym]
|
82
|
+
job = @connection.root.sites[job_hash[:site].to_sym].jobs(:query => { :user => @connection.config.options[:username] || ENV['USER'] })["#{uid}".to_sym]
|
83
83
|
if (not job.nil? or job["state"] == "running")
|
84
84
|
j = job.reload
|
85
85
|
self.jobs << j
|
86
|
-
create_roles(j, job_hash) unless job_hash[:roles].nil?
|
86
|
+
self.roles += Role.create_roles(j, job_hash) unless job_hash[:roles].nil?
|
87
87
|
end
|
88
88
|
end
|
89
89
|
# reload last deployed nodes
|
@@ -108,15 +108,19 @@ module XP5K
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def wait_for_jobs
|
111
|
-
logger.info "Waiting for running state"
|
111
|
+
logger.info "Waiting for running state (Ctrl+c to stop waiting)"
|
112
112
|
ready = false
|
113
113
|
jobs_status = []
|
114
|
+
trap("SIGINT") do
|
115
|
+
logger.info "Stop waiting job."
|
116
|
+
exit
|
117
|
+
end
|
114
118
|
until ready
|
115
119
|
self.jobs.each.with_index do |job, id|
|
116
120
|
jobs_status[id] = job.reload["state"]
|
117
121
|
case jobs_status[id]
|
118
122
|
when "running"
|
119
|
-
create_roles(job, jobs2submit[id]) unless jobs2submit[id][:roles].nil?
|
123
|
+
self.roles += Role.create_roles(job, jobs2submit[id]) unless jobs2submit[id][:roles].nil?
|
120
124
|
logger.info "Job #{job['uid']} is running"
|
121
125
|
when /terminated|error/
|
122
126
|
logger.info "Job #{job['uid']} is terminated"
|
@@ -128,22 +132,8 @@ module XP5K
|
|
128
132
|
sleep 3
|
129
133
|
end
|
130
134
|
update_cache()
|
131
|
-
|
132
|
-
|
133
|
-
def create_roles(job, job_definition)
|
134
|
-
count_needed_nodes = 0
|
135
|
-
job_definition[:roles].each { |role| count_needed_nodes += role.size }
|
136
|
-
if job['assigned_nodes'].length < count_needed_nodes
|
137
|
-
self.clean
|
138
|
-
raise "Job ##{job['uid']} require more nodes for required roles"
|
139
|
-
end
|
140
|
-
available_nodes = job['assigned_nodes'].sort
|
141
|
-
job_definition[:roles].each do |role|
|
142
|
-
role.servers = available_nodes[0..(role.size - 1)]
|
143
|
-
available_nodes -= role.servers
|
144
|
-
role.jobid = job['uid']
|
145
|
-
next if not self.roles.select { |x| x.name == role.name }.empty?
|
146
|
-
self.roles << role
|
135
|
+
trap "SIGINT" do
|
136
|
+
raise
|
147
137
|
end
|
148
138
|
end
|
149
139
|
|
@@ -152,7 +142,9 @@ module XP5K
|
|
152
142
|
end
|
153
143
|
|
154
144
|
def role_with_name(name)
|
155
|
-
self.roles.select { |x| x.name == name}.first
|
145
|
+
role = self.roles.select { |x| x.name == name}.first
|
146
|
+
logger.debug "Role #{name} not found." if role.nil?
|
147
|
+
return role
|
156
148
|
end
|
157
149
|
|
158
150
|
def get_deployed_nodes(job_or_role_name)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xp5k
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pascal Morillon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: restfully
|
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
106
|
version: 1.3.6
|
107
107
|
requirements: []
|
108
108
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.1.10
|
110
110
|
signing_key:
|
111
111
|
specification_version: 4
|
112
112
|
summary: A small Grid'5000 helper to submit jobs and deploy environments via REST
|