vagrant-g5k 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +14 -0
- data/LICENSE +8 -0
- data/README.md +53 -0
- data/lib/vagrant-g5k.rb +18 -0
- data/lib/vagrant-g5k/.config.rb.swp +0 -0
- data/lib/vagrant-g5k/action.rb +79 -0
- data/lib/vagrant-g5k/action/.message_not_created.rb.swp +0 -0
- data/lib/vagrant-g5k/action/connect_g5k.rb +28 -0
- data/lib/vagrant-g5k/action/create_local_working_dir.rb +27 -0
- data/lib/vagrant-g5k/action/is_created.rb +18 -0
- data/lib/vagrant-g5k/action/message_already_created.rb +16 -0
- data/lib/vagrant-g5k/action/message_not_created.rb +16 -0
- data/lib/vagrant-g5k/action/read_ssh_info.rb +30 -0
- data/lib/vagrant-g5k/action/read_state.rb +36 -0
- data/lib/vagrant-g5k/action/run_instance.rb +37 -0
- data/lib/vagrant-g5k/command.rb +21 -0
- data/lib/vagrant-g5k/config.rb +58 -0
- data/lib/vagrant-g5k/errors.rb +19 -0
- data/lib/vagrant-g5k/plugin.rb +78 -0
- data/lib/vagrant-g5k/provider.rb +49 -0
- data/lib/vagrant-g5k/util/.g5k_utils.rb.swp +0 -0
- data/lib/vagrant-g5k/util/g5k_utils.rb +177 -0
- data/lib/vagrant-g5k/util/launch_vm.sh +38 -0
- data/lib/vagrant-g5k/util/launch_vm_fwd.sh +44 -0
- data/lib/vagrant-g5k/version.rb +5 -0
- data/locales/en.yml +159 -0
- data/vagrant-g5k.gemspec +62 -0
- metadata +177 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module G5K
|
5
|
+
module Action
|
6
|
+
# This action reads the state of the machine and puts it in the
|
7
|
+
# `:machine_state_id` key in the environment.
|
8
|
+
class ReadState
|
9
|
+
def initialize(app, env)
|
10
|
+
@app = app
|
11
|
+
@logger = Log4r::Logger.new("vagrant_g5k::action::read_state")
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
env[:machine_state_id] = read_state(env[:machine], env[:g5k_connection])
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_state(machine, conn)
|
20
|
+
return :not_created if machine.id.nil?
|
21
|
+
# is there a job running for this vm ?
|
22
|
+
subnet = conn.check_or_reserve_subnet()
|
23
|
+
if subnet.nil?
|
24
|
+
return :missing_subnet
|
25
|
+
end
|
26
|
+
job = conn.check_job(machine.id)
|
27
|
+
if job.nil? # TODO or fraged
|
28
|
+
return :not_created
|
29
|
+
end
|
30
|
+
|
31
|
+
return job["state"].to_sym
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "log4r"
|
2
|
+
require 'json'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
|
6
|
+
module VagrantPlugins
|
7
|
+
module G5K
|
8
|
+
module Action
|
9
|
+
# This runs the configured instance.
|
10
|
+
class RunInstance
|
11
|
+
include Vagrant::Util::Retryable
|
12
|
+
|
13
|
+
def initialize(app, env)
|
14
|
+
@app = app
|
15
|
+
@logger = Log4r::Logger.new("vagrant_g5k::action::run_instance")
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
conn = env[:g5k_connection]
|
20
|
+
conn.launch_vm(env)
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
def recover(env)
|
25
|
+
return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
|
26
|
+
|
27
|
+
if env[:machine].provider.state.id != :not_created
|
28
|
+
# Undo the import
|
29
|
+
terminate(env)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'net/ssh/multi'
|
2
|
+
require 'vagrant-g5k/util/g5k_utils'
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module G5K
|
6
|
+
class Command < Vagrant.plugin('2', :command)
|
7
|
+
|
8
|
+
# Show description when `vagrant list-commands` is triggered
|
9
|
+
def self.synopsis
|
10
|
+
"plugin: vagrant-g5k: manage virtual machines on grid'5000"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
conn = Connection.instance
|
15
|
+
# someday pretty print the output
|
16
|
+
puts conn.list_images
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
require "iniparse"
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module G5K
|
6
|
+
class Config < Vagrant.plugin("2", :config)
|
7
|
+
# G5K username
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
attr_accessor :username
|
11
|
+
|
12
|
+
# G5K site
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
attr_accessor :site
|
16
|
+
# G5K image location (path)
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
attr_accessor :image_location
|
20
|
+
|
21
|
+
# G5K image location (path)
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_accessor :image_type
|
25
|
+
|
26
|
+
# G5K image strategy
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
attr_accessor :image_strategy
|
31
|
+
|
32
|
+
|
33
|
+
def initialize()
|
34
|
+
@username = nil
|
35
|
+
@image_location = nil
|
36
|
+
@site = "rennes"
|
37
|
+
@image_type = "local"
|
38
|
+
@image_strategy = "snapshot"
|
39
|
+
end
|
40
|
+
|
41
|
+
def finalize!()
|
42
|
+
# This is call by the plugin mecanism after initialize
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def validate(machine)
|
47
|
+
errors = _detected_errors
|
48
|
+
|
49
|
+
errors << "g5k username is required" if @username.nil?
|
50
|
+
errors << "g5k image_location is required" if @image_location.nil?
|
51
|
+
|
52
|
+
{ "G5K Provider" => errors }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module G5K
|
5
|
+
module Errors
|
6
|
+
class VagrantG5KError < Vagrant::Errors::VagrantError
|
7
|
+
error_namespace("vagrant_g5k.errors")
|
8
|
+
end
|
9
|
+
|
10
|
+
class TimeoutOnJobSubmissionError < VagrantG5KError
|
11
|
+
error_key("tired of waiting")
|
12
|
+
end
|
13
|
+
|
14
|
+
class JobNotRunning < VagrantG5KError
|
15
|
+
error_key("tired of waiting")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
begin
|
2
|
+
require "vagrant"
|
3
|
+
rescue LoadError
|
4
|
+
raise "The Vagrant G5K plugin must be run within Vagrant."
|
5
|
+
end
|
6
|
+
# This is a sanity check to make sure no one is attempting to install
|
7
|
+
# this into an early Vagrant version.
|
8
|
+
if Vagrant::VERSION < "1.2.0"
|
9
|
+
raise "The Vagrant G5K plugin is only compatible with Vagrant 1.2+"
|
10
|
+
end
|
11
|
+
|
12
|
+
module VagrantPlugins
|
13
|
+
module G5K
|
14
|
+
class Plugin < Vagrant.plugin("2")
|
15
|
+
|
16
|
+
name "G5K"
|
17
|
+
description <<-DESC
|
18
|
+
This plugin installs a provider that allows Vagrant to manage
|
19
|
+
machines in G5K.
|
20
|
+
DESC
|
21
|
+
|
22
|
+
config(:g5k, :provider) do
|
23
|
+
require_relative "config"
|
24
|
+
Config
|
25
|
+
end
|
26
|
+
|
27
|
+
provider(:g5k, parallel: true) do
|
28
|
+
# Setup logging and i18n
|
29
|
+
setup_logging
|
30
|
+
setup_i18n
|
31
|
+
|
32
|
+
# Return the provider
|
33
|
+
require_relative "provider"
|
34
|
+
Provider
|
35
|
+
end
|
36
|
+
|
37
|
+
command(:vmlist) do
|
38
|
+
require_relative 'command'
|
39
|
+
Command
|
40
|
+
end
|
41
|
+
|
42
|
+
# This initializes the internationalization strings.
|
43
|
+
def self.setup_i18n
|
44
|
+
I18n.load_path << File.expand_path("locales/en.yml", G5K.source_root)
|
45
|
+
I18n.reload!
|
46
|
+
end
|
47
|
+
|
48
|
+
# This sets up our log level to be whatever VAGRANT_LOG is.
|
49
|
+
def self.setup_logging
|
50
|
+
require "log4r"
|
51
|
+
|
52
|
+
level = nil
|
53
|
+
begin
|
54
|
+
level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
|
55
|
+
rescue NameError
|
56
|
+
# This means that the logging constant wasn't found,
|
57
|
+
# which is fine. We just keep `level` as `nil`. But
|
58
|
+
# we tell the user.
|
59
|
+
level = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Some constants, such as "true" resolve to booleans, so the
|
63
|
+
# above error checking doesn't catch it. This will check to make
|
64
|
+
# sure that the log level is an integer, as Log4r requires.
|
65
|
+
level = nil if !level.is_a?(Integer)
|
66
|
+
|
67
|
+
# Set the logging level on all "vagrant" namespaced
|
68
|
+
# logs as long as we have a valid level.
|
69
|
+
if level
|
70
|
+
logger = Log4r::Logger.new("vagrant_g5k")
|
71
|
+
logger.outputters = Log4r::Outputter.stderr
|
72
|
+
logger.level = level
|
73
|
+
logger = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "log4r"
|
2
|
+
require "vagrant"
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module G5K
|
6
|
+
class Provider < Vagrant.plugin("2", :provider)
|
7
|
+
def initialize(machine)
|
8
|
+
@machine = machine
|
9
|
+
end
|
10
|
+
|
11
|
+
def action(name)
|
12
|
+
# Attempt to get the action method from the Action class if it
|
13
|
+
# exists, otherwise return nil to show that we don't support the
|
14
|
+
# given action.
|
15
|
+
action_method = "action_#{name}"
|
16
|
+
return Action.send(action_method) if Action.respond_to?(action_method)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def ssh_info
|
21
|
+
# Run a custom action called "read_ssh_info" which does what it
|
22
|
+
# says and puts the resulting SSH info into the `:machine_ssh_info`
|
23
|
+
# key in the environment.
|
24
|
+
env = @machine.action("read_ssh_info", lock: false)
|
25
|
+
env[:machine_ssh_info]
|
26
|
+
end
|
27
|
+
|
28
|
+
def state
|
29
|
+
# Run a custom action we define called "read_state" which does
|
30
|
+
# what it says. It puts the state in the `:machine_state_id`
|
31
|
+
# key in the environment.
|
32
|
+
env = @machine.action("read_state", lock: false)
|
33
|
+
|
34
|
+
state_id = env[:machine_state_id]
|
35
|
+
|
36
|
+
short = "#{state_id}"
|
37
|
+
long = "#{state_id}"
|
38
|
+
|
39
|
+
# Return the MachineState object
|
40
|
+
Vagrant::MachineState.new(state_id, short, long)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
id = @machine.id.nil? ? "new" : @machine.id
|
45
|
+
"G5K (#{id})"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
Binary file
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'net/ssh/multi'
|
2
|
+
require 'net/scp'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'vagrant/util/retryable'
|
6
|
+
|
7
|
+
WORKING_DIR = ".vagrant-g5k"
|
8
|
+
LAUNCHER_SCRIPT = "launch_vm.sh"
|
9
|
+
JOB_SUBNET_NAME = "vagrant-g5k-subnet"
|
10
|
+
WALLTIME="01:00:00"
|
11
|
+
|
12
|
+
module VagrantPlugins
|
13
|
+
module G5K
|
14
|
+
class Connection
|
15
|
+
include Vagrant::Util::Retryable
|
16
|
+
|
17
|
+
attr_accessor :session
|
18
|
+
|
19
|
+
attr_accessor :username
|
20
|
+
|
21
|
+
attr_accessor :site
|
22
|
+
|
23
|
+
attr_accessor :image_location
|
24
|
+
|
25
|
+
attr_accessor :logger
|
26
|
+
|
27
|
+
attr_accessor :node
|
28
|
+
|
29
|
+
attr_accessor :pool
|
30
|
+
|
31
|
+
attr_accessor :ip
|
32
|
+
|
33
|
+
attr_accessor :mac
|
34
|
+
|
35
|
+
@@locations = [
|
36
|
+
{
|
37
|
+
:path => "/grid5000/virt-images",
|
38
|
+
:type => "local"
|
39
|
+
}
|
40
|
+
]
|
41
|
+
|
42
|
+
def initialize(args)
|
43
|
+
# initialize
|
44
|
+
args.each do |k,v|
|
45
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
gateway = Net::SSH::Gateway.new("access.grid5000.fr", "msimonin", :forward_agent => true)
|
49
|
+
@session = gateway.ssh(@site, "msimonin")
|
50
|
+
end
|
51
|
+
|
52
|
+
def list_images()
|
53
|
+
images = []
|
54
|
+
@@locations.each do |location|
|
55
|
+
if location[:type] == "local"
|
56
|
+
stdout = ""
|
57
|
+
@session.exec!("ls #{location[:path]}/*.qcow2") do |channel, stream, data|
|
58
|
+
stdout << data
|
59
|
+
end
|
60
|
+
images += stdout.split("\n").map{ |i| {:path => i, :type => 'local'} }
|
61
|
+
#elsif location[:type] == "ceph"
|
62
|
+
# stdout = ""
|
63
|
+
# @session.exec!("rbd --pool #{location[:pool]} --conf $HOME/.ceph/config --id #{location[:id]} ls") do |channel, stream, data|
|
64
|
+
# stdout << data
|
65
|
+
# end
|
66
|
+
# images += stdout.split("\n").map{ |i| {:path => i, :type => 'ceph'} }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
images
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_local_working_dir(env)
|
73
|
+
@session.exec("mkdir -p #{WORKING_DIR}")
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def check_job(job_id)
|
78
|
+
oarstat = exec("oarstat --json")
|
79
|
+
oarstat = JSON.load(oarstat)
|
80
|
+
r = oarstat.select!{ |k,v| k == job_id and v["owner"] == @username }.values.first
|
81
|
+
# update the assigned hostname
|
82
|
+
# this will be used to reach the vm
|
83
|
+
if !r.nil?
|
84
|
+
@node = r["assigned_network_address"].first
|
85
|
+
end
|
86
|
+
return r
|
87
|
+
end
|
88
|
+
|
89
|
+
def check_or_reserve_subnet()
|
90
|
+
@logger.info("Checking if a subnet has been reserved")
|
91
|
+
oarstat = exec("oarstat --json")
|
92
|
+
oarstat = JSON.load(oarstat)
|
93
|
+
job = oarstat.select!{ |k,v| v["owner"] == @username && v["name"] == JOB_SUBNET_NAME }.values.first
|
94
|
+
if job.nil?
|
95
|
+
# we have to reserve a subnet
|
96
|
+
@logger.info("Reserving a subnet")
|
97
|
+
job_id = exec("oarsub -l \"slash_22=1, walltime=#{WALLTIME}\" --name #{JOB_SUBNET_NAME} \"sleep 3600\" | grep OAR_JOB_ID | cut -d '=' -f2").chomp
|
98
|
+
begin
|
99
|
+
retryable(:on => VagrantPlugins::G5K::Errors::JobNotRunning, :tries => 5, :sleep => 1) do
|
100
|
+
@logger.info("Waiting for the job to be running")
|
101
|
+
job = check_job(job_id)
|
102
|
+
if job.nil? or job["state"] != "Running"
|
103
|
+
raise VagrantPlugins::G5K::Errors::JobNotRunning
|
104
|
+
end
|
105
|
+
break
|
106
|
+
end
|
107
|
+
rescue VagrantPlugins::G5K::Errors::JobNotRunning
|
108
|
+
@logger.error("Tired of waiting")
|
109
|
+
raise VagrantPlugins::G5K::Errors::JobNotRunning
|
110
|
+
end
|
111
|
+
end
|
112
|
+
# get the macs ips addresses pool
|
113
|
+
im = exec("g5k-subnets -j #{job["Job_Id"]} -im")
|
114
|
+
@pool = im.split("\n").map{|i| i.split("\t")}
|
115
|
+
@ip, @mac = @pool[0]
|
116
|
+
@logger.info("Get the mac #{mac} and the corresponding ip #{ip} from the subnet")
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def launch_vm(env)
|
121
|
+
launcher_path = File.join(File.dirname(__FILE__), LAUNCHER_SCRIPT)
|
122
|
+
@logger.info("Launching the VM on Grid'5000")
|
123
|
+
# Checking the subnet job
|
124
|
+
subnet = check_or_reserve_subnet()
|
125
|
+
@logger.info("Uploading launcher")
|
126
|
+
# uploading the launcher
|
127
|
+
launcher_remote_path = File.join("/home", @username , WORKING_DIR, LAUNCHER_SCRIPT)
|
128
|
+
upload(launcher_path, launcher_remote_path)
|
129
|
+
# creating the params file
|
130
|
+
params_path = File.join("/home", @username, WORKING_DIR, 'params')
|
131
|
+
exec("echo #{@image_location} #{@mac} > #{params_path}")
|
132
|
+
# Submitting a new job
|
133
|
+
@logger.info("Starting a new job")
|
134
|
+
job_id = exec("oarsub -t allow_classic_ssh -l \"{virtual!=\'none\'}/nodes=1,walltime=#{WALLTIME}\" --name #{env[:machine].name} --checkpoint 60 --signal 12 --array-param-file #{params_path} #{launcher_remote_path} | grep OAR_JOB_ID | cut -d '=' -f2").chomp
|
135
|
+
|
136
|
+
|
137
|
+
begin
|
138
|
+
retryable(:on => VagrantPlugins::G5K::Errors::JobNotRunning, :tries => 5, :sleep => 1) do
|
139
|
+
@logger.info("Waiting for the job to be running")
|
140
|
+
job = check_job(job_id)
|
141
|
+
if job.nil? or job["state"] != "Running"
|
142
|
+
raise VagrantPlugins::G5K::Errors::JobNotRunning
|
143
|
+
end
|
144
|
+
# saving the id
|
145
|
+
env[:machine].id = job["Job_Id"]
|
146
|
+
break
|
147
|
+
end
|
148
|
+
rescue VagrantPlugins::G5K::Errors::JobNotRunning
|
149
|
+
@logger.error("Tired of waiting")
|
150
|
+
raise VagrantPlugins::G5K::Errors::JobNotRunning
|
151
|
+
end
|
152
|
+
@logger.info("VM booted on Grid'5000")
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def exec(cmd)
|
158
|
+
@logger.info("Executing #{cmd}")
|
159
|
+
stdout = ""
|
160
|
+
@session.exec!(cmd) do |channel, stream, data|
|
161
|
+
stdout << data
|
162
|
+
end
|
163
|
+
return stdout
|
164
|
+
end
|
165
|
+
|
166
|
+
def upload(src, dst)
|
167
|
+
@session.scp.upload!(src, dst)
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|