xp5k 0.0.6 → 0.0.7
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.
- 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
|