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.
@@ -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
+