vagrant-g5k 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
+