vagrant-xenserver 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ require "log4r"
2
+ require "xmlrpc/client"
3
+ require "vagrant-xenserver/util/uploader"
4
+ require "rexml/document"
5
+ require "json"
6
+
7
+ module VagrantPlugins
8
+ module XenServer
9
+ module Action
10
+ class UploadVHD
11
+ def initialize(app, env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::upload_xva")
14
+ end
15
+
16
+ def call(env)
17
+ box_vhd_file = env[:machine].box.directory.join('box.vhd').to_s
18
+
19
+ hostname = env[:machine].provider_config.xs_host
20
+ session = env[:session]
21
+
22
+ @logger.info("box name=" + env[:machine].box.name.to_s)
23
+ @logger.info("box version=" + env[:machine].box.version.to_s)
24
+
25
+ # Find out if it has already been uploaded
26
+ vdis = env[:xc].call("VDI.get_all_records", env[:session])['Value']
27
+
28
+ vdi_tag = "vagrant:" + env[:machine].box.name.to_s + "/" + env[:machine].box.version.to_s
29
+
30
+ vdi_ref_rec = vdis.find { |reference,record|
31
+ @logger.info(record['tags'].to_s)
32
+ record['tags'].include?(vdi_tag)
33
+ }
34
+
35
+ if not vdi_ref_rec
36
+
37
+ # Find out virtual size of the VHD
38
+ disk_info=JSON.parse(IO.popen(["qemu-img", "info",box_vhd_file,"--output=json"]).read)
39
+ virtual_size = disk_info['virtual-size']
40
+
41
+ pool=env[:xc].call("pool.get_all",env[:session])['Value'][0]
42
+ default_sr=env[:xc].call("pool.get_default_SR",env[:session],pool)['Value']
43
+ @logger.info("default_SR="+default_sr)
44
+ vdi_record = {
45
+ 'name_label' => 'Vagrant disk',
46
+ 'name_description' => 'Base disk uploaded for the vagrant box '+env[:machine].box.name.to_s+' v'+env[:machine].box.version.to_s,
47
+ 'SR' => default_sr,
48
+ 'virtual_size' => "#{virtual_size}",
49
+ 'type' => 'user',
50
+ 'sharable' => false,
51
+ 'read_only' => false,
52
+ 'other_config' => {},
53
+ 'xenstore_data' => {},
54
+ 'sm_config' => {},
55
+ 'tags' => [] }
56
+
57
+ vdi_result=env[:xc].call("VDI.create",env[:session],vdi_record)['Value']
58
+
59
+ @logger.info("created VDI: " + vdi_result.to_s)
60
+ vdi_uuid = env[:xc].call("VDI.get_uuid",env[:session],vdi_result)['Value']
61
+ @logger.info("uuid: "+vdi_uuid)
62
+
63
+ # Create a task to so we can get the result of the upload
64
+ task_result = env[:xc].call("task.create", env[:session], "vagrant-vhd-upload",
65
+ "Task to track progress of the XVA upload from vagrant")
66
+
67
+ if task_result["Status"] != "Success"
68
+ raise Errors::APIError
69
+ end
70
+
71
+ task = task_result["Value"]
72
+
73
+ url = "http://#{hostname}/import_raw_vdi?session_id=#{session}&task_id=#{task}&vdi=#{vdi_result}&format=vhd"
74
+
75
+ uploader_options = {}
76
+ uploader_options[:ui] = env[:ui]
77
+
78
+ uploader = MyUtil::Uploader.new(box_vhd_file, url, uploader_options)
79
+
80
+ begin
81
+ uploader.upload!
82
+ rescue Errors::UploaderInterrupted
83
+ env[:ui].info(I18n.t("vagrant.xenserver.action.upload_xva.interrupted"))
84
+ raise
85
+ end
86
+
87
+ task_status = ""
88
+
89
+ begin
90
+ sleep(0.2)
91
+ task_status_result = env[:xc].call("task.get_status",env[:session],task)
92
+ if task_status_result["Status"] != "Success"
93
+ raise Errors::APIError
94
+ end
95
+ task_status = task_status_result["Value"]
96
+ end while task_status == "pending"
97
+
98
+ @logger.info("task_status="+task_status)
99
+
100
+ if task_status != "success"
101
+ raise Errors::APIError
102
+ end
103
+
104
+ task_result_result = env[:xc].call("task.get_result",env[:session],task)
105
+ if task_result_result["Status"] != "Success"
106
+ raise Errors::APIError
107
+ end
108
+
109
+ task_result = task_result_result["Value"]
110
+
111
+ doc = REXML::Document.new(task_result)
112
+
113
+ doc.elements.each('value/array/data/value') do |ele|
114
+ vdi = ele.text
115
+ end
116
+
117
+ @logger.info("task_result=" + task_result)
118
+
119
+ tag_result=env[:xc].call("VDI.add_tags",env[:session],vdi_result,vdi_tag)
120
+ @logger.info("task_result=" + tag_result.to_s)
121
+
122
+ env[:box_vdi] = vdi_result
123
+ else
124
+ (reference,record) = vdi_ref_rec
125
+ env[:box_vdi] = reference
126
+ @logger.info("box_vdi="+reference)
127
+
128
+ end
129
+
130
+ @app.call(env)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,84 @@
1
+ require "log4r"
2
+ require "xmlrpc/client"
3
+ require "vagrant-xenserver/util/uploader"
4
+ require "rexml/document"
5
+
6
+ module VagrantPlugins
7
+ module XenServer
8
+ module Action
9
+ class UploadXVA
10
+ def initialize(app, env)
11
+ @app = app
12
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::upload_xva")
13
+ end
14
+
15
+ def call(env)
16
+ #box_image_file = env[:machine].box.directory.join('export.xva').to_s
17
+ box_image_file = "/home/jludlam/devel/vagrant-xenserver/test.xva"
18
+ hostname = env[:machine].provider_config.xs_host
19
+ session = env[:session]
20
+
21
+ @logger.info("box name=" + env[:machine].box.name.to_s)
22
+ @logger.info("box version=" + env[:machine].box.version.to_s)
23
+
24
+ # Create a task to so we can get the result of the upload
25
+ task_result = env[:xc].call("task.create", env[:session], "vagrant-xva-upload",
26
+ "Task to track progress of the XVA upload from vagrant")
27
+
28
+ if task_result["Status"] != "Success"
29
+ raise Errors::APIError
30
+ end
31
+
32
+ task = task_result["Value"]
33
+
34
+ url = "http://#{hostname}/import?session_id=#{session}&task_id=#{task}"
35
+
36
+ uploader_options = {}
37
+ uploader_options[:ui] = env[:ui]
38
+
39
+ uploader = MyUtil::Uploader.new(box_image_file, url, uploader_options)
40
+
41
+ begin
42
+ uploader.upload!
43
+ rescue Errors::UploaderInterrupted
44
+ env[:ui].info(I18n.t("vagrant.xenserver.action.upload_xva.interrupted"))
45
+ raise
46
+ end
47
+
48
+ task_status = ""
49
+
50
+ begin
51
+ sleep(0.2)
52
+ task_status_result = env[:xc].call("task.get_status",env[:session],task)
53
+ if task_status_result["Status"] != "Success"
54
+ raise Errors::APIError
55
+ end
56
+ task_status = task_status_result["Value"]
57
+ end while task_status == "pending"
58
+
59
+ @logger.info("task_status="+task_status)
60
+
61
+ if task_status != "success"
62
+ raise Errors::APIError
63
+ end
64
+
65
+ task_result_result = env[:xc].call("task.get_result",env[:session],task)
66
+ if task_result_result["Status"] != "Success"
67
+ raise Errors::APIError
68
+ end
69
+
70
+ task_result = task_result_result["Value"]
71
+
72
+ doc = REXML::Document.new(task_result)
73
+
74
+ doc.elements.each('value/array/data/value') do |ele|
75
+ @logger.info("ele=" + ele.text)
76
+ end
77
+
78
+ @logger.info("task_result=" + task_result)
79
+
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,57 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module XenServer
5
+ class Config < Vagrant.plugin("2", :config)
6
+ # The XenServer host name or IP
7
+ #
8
+ # @return [String]
9
+ attr_accessor :xs_host
10
+
11
+ # The XenServer username
12
+ #
13
+ # @return [String]
14
+ attr_accessor :xs_username
15
+
16
+ # The XenServer password
17
+ #
18
+ # @return [String]
19
+ attr_accessor :xs_password
20
+
21
+ # True if the VM should be PV
22
+ #
23
+ # @return [Bool]
24
+ attr_accessor :pv
25
+
26
+ # Memory settings
27
+ #
28
+ # @return [Int]
29
+ attr_accessor :memory
30
+
31
+ def initialize
32
+ @xs_host = UNSET_VALUE
33
+ @xs_username = UNSET_VALUE
34
+ @xs_password = UNSET_VALUE
35
+ @pv = UNSET_VALUE
36
+ @memory = UNSET_VALUE
37
+ end
38
+
39
+ def finalize!
40
+ @xs_host = nil if @xs_host == UNSET_VALUE
41
+ @xs_username = nil if @xs_username == UNSET_VALUE
42
+ @xs_password = nil if @xs_password == UNSET_VALUE
43
+ @pv = nil if @pv == UNSET_VALUE
44
+ @memory = 1024 if @memory == UNSET_VALUE
45
+ end
46
+
47
+ def validate(machine)
48
+ errors = _detected_errors
49
+ errors << I18n.t("vagrant_xenserver.config.host_required") if @xs_host.nil?
50
+ errors << I18n.t("vagrant_xenserver.config.username_required") if @xs_username.nil?
51
+ errors << I18n.t("vagrant_xenserver.config.password_required") if @xs_password.nil?
52
+
53
+ { "XenServer Provider" => errors }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module XenServer
5
+ module Errors
6
+ class VagrantXenServerError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_xenserver.errors")
8
+ end
9
+
10
+ class LoginError < VagrantXenServerError
11
+ error_key(:login_error)
12
+ end
13
+
14
+ class UploaderInterrupted < VagrantXenServerError
15
+ error_key(:uploader_interrupted)
16
+ end
17
+
18
+ class UploaderError < VagrantXenServerError
19
+ error_key(:uploader_error)
20
+ end
21
+
22
+ class APIError < VagrantXenServerError
23
+ error_key(:api_error)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,29 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The Vagrant XenServer plugin must be run within Vagrant."
5
+ end
6
+
7
+
8
+ module VagrantPlugins
9
+ module XenServer
10
+ class Plugin < Vagrant.plugin("2")
11
+ name "XenServer provider"
12
+ description <<-DESC
13
+ This plugin installs a provider that allows Vagrant to manage
14
+ virtual machines hosted on a XenServer.
15
+ DESC
16
+
17
+ config('xenserver', :provider) do
18
+ require_relative "config"
19
+ Config
20
+ end
21
+
22
+ provider('xenserver') do
23
+ require_relative "provider"
24
+ Provider
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,36 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module XenServer
5
+ class Provider < Vagrant.plugin("2", :provider)
6
+ def initialize(machine)
7
+ @machine = machine
8
+ end
9
+
10
+ def action(name)
11
+ action_method = "action_#{name}"
12
+ return Action.send(action_method) if Action.respond_to?(action_method)
13
+ nil
14
+ end
15
+
16
+ def ssh_info
17
+ env = @machine.action('read_ssh_info')
18
+ env[:machine_ssh_info]
19
+ end
20
+
21
+ def state
22
+ env = @machine.action("read_state")
23
+
24
+ state_id = env[:machine_state_id]
25
+
26
+ Vagrant::MachineState.new(state_id, state_id, state_id)
27
+ end
28
+
29
+ def to_s
30
+ id = @machine.id.nil? ? "new" : @machine.id
31
+ "XenServer (#{id})"
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,215 @@
1
+ require "uri"
2
+
3
+ require "log4r"
4
+
5
+ require "vagrant/util/busy"
6
+ require "vagrant/util/platform"
7
+ require "vagrant/util/subprocess"
8
+
9
+ module VagrantPlugins
10
+ module XenServer
11
+ module MyUtil
12
+ # This class uploads files using various protocols by subprocessing
13
+ # to cURL. cURL is a much more capable and complete upload tool than
14
+ # a hand-rolled Ruby library, so we defer to its expertise.
15
+ class Uploader
16
+ # Custom user agent provided to cURL so that requests to URL shorteners
17
+ # are properly tracked.
18
+ USER_AGENT = "VagrantXenserver/1.0"
19
+
20
+ attr_reader :source
21
+ attr_reader :destination
22
+
23
+ def initialize(source, destination, options=nil)
24
+ options ||= {}
25
+
26
+ @logger = Log4r::Logger.new("vagrant::xenserver::util::uploader")
27
+ @source = source.to_s
28
+ @destination = destination.to_s
29
+
30
+ begin
31
+ url = URI.parse(@destination)
32
+ if url.scheme && url.scheme.start_with?("http") && url.user
33
+ auth = "#{url.user}"
34
+ auth += ":#{url.password}" if url.password
35
+ url.user = nil
36
+ url.password = nil
37
+ options[:auth] ||= auth
38
+ @destination = url.to_s
39
+ end
40
+ rescue URI::InvalidURIError
41
+ # Ignore, since its clearly not HTTP
42
+ end
43
+
44
+ # Get the various optional values
45
+ @auth = options[:auth]
46
+ @ca_cert = options[:ca_cert]
47
+ @ca_path = options[:ca_path]
48
+ @continue = options[:continue]
49
+ @headers = options[:headers]
50
+ @insecure = options[:insecure]
51
+ @ui = options[:ui]
52
+ @client_cert = options[:client_cert]
53
+ end
54
+
55
+ # This executes the actual upload, uploading the source file
56
+ # to the destination with the given options used to initialize this
57
+ # class.
58
+ #
59
+ # If this method returns without an exception, the upload
60
+ # succeeded. An exception will be raised if the upload failed.
61
+ def upload!
62
+ options, subprocess_options = self.options
63
+ options += ["--output", "dummy"]
64
+ options << @destination
65
+ options += ["-T", @source]
66
+
67
+ # This variable can contain the proc that'll be sent to
68
+ # the subprocess execute.
69
+ data_proc = nil
70
+
71
+ if @ui
72
+ # If we're outputting progress, then setup the subprocess to
73
+ # tell us output so we can parse it out.
74
+ subprocess_options[:notify] = :stderr
75
+
76
+ progress_data = ""
77
+ progress_regexp = /(\r(.+?))\r/
78
+
79
+ # Setup the proc that'll receive the real-time data from
80
+ # the uploader.
81
+ data_proc = Proc.new do |type, data|
82
+ # Type will always be "stderr" because that is the only
83
+ # type of data we're subscribed for notifications.
84
+
85
+ # Accumulate progress_data
86
+ progress_data << data
87
+
88
+ while true
89
+ # If we have a full amount of column data (two "\r") then
90
+ # we report new progress reports. Otherwise, just keep
91
+ # accumulating.
92
+ match = progress_regexp.match(progress_data)
93
+ break if !match
94
+ data = match[2]
95
+ progress_data.gsub!(match[1], "")
96
+
97
+ # Ignore the first \r and split by whitespace to grab the columns
98
+ columns = data.strip.split(/\s+/)
99
+
100
+ # COLUMN DATA:
101
+ #
102
+ # 0 - % total
103
+ # 1 - Total size
104
+ # 2 - % received
105
+ # 3 - Received size
106
+ # 4 - % transferred
107
+ # 5 - Transferred size
108
+ # 6 - Average download speed
109
+ # 7 - Average upload speed
110
+ # 9 - Total time
111
+ # 9 - Time spent
112
+ # 10 - Time left
113
+ # 11 - Current speed
114
+
115
+ output = "Progress: #{columns[0]}% (Rate: #{columns[11]}/s, Estimated time remaining: #{columns[10]})"
116
+ @ui.clear_line
117
+ @ui.detail(output, new_line: false)
118
+ end
119
+ end
120
+ end
121
+
122
+ @logger.info("Uploader starting upload: ")
123
+ @logger.info(" -- Source: #{@source}")
124
+ @logger.info(" -- Destination: #{@destination}")
125
+
126
+ begin
127
+ execute_curl(options, subprocess_options, &data_proc)
128
+ ensure
129
+ # If we're outputting to the UI, clear the output to
130
+ # avoid lingering progress meters.
131
+ if @ui
132
+ @ui.clear_line
133
+
134
+ # Windows doesn't clear properly for some reason, so we just
135
+ # output one more newline.
136
+ # @ui.detail("") if Platform.windows?
137
+ end
138
+ end
139
+
140
+ # Everything succeeded
141
+ true
142
+ end
143
+
144
+ def execute_curl(options, subprocess_options, &data_proc)
145
+ options = options.dup
146
+ options << subprocess_options
147
+
148
+ # Create the callback that is called if we are interrupted
149
+ interrupted = false
150
+ int_callback = Proc.new do
151
+ @logger.info("Uploader interrupted!")
152
+ interrupted = true
153
+ end
154
+
155
+ # Execute!
156
+ result = Vagrant::Util::Busy.busy(int_callback) do
157
+ Vagrant::Util::Subprocess.execute("curl", *options, &data_proc)
158
+ end
159
+
160
+ # If the upload was interrupted, then raise a specific error
161
+ raise Errors::UploaderInterrupted if interrupted
162
+
163
+ # If it didn't exit successfully, we need to parse the data and
164
+ # show an error message.
165
+ if result.exit_code != 0
166
+ @logger.warn("Uploader exit code: #{result.exit_code}")
167
+ parts = result.stderr.split(/\n*curl:\s+\(\d+\)\s*/, 2)
168
+ parts[1] ||= ""
169
+ raise Errors::UploaderError, message: parts[1].chomp
170
+ end
171
+
172
+ result
173
+ end
174
+
175
+ # Returns the varoius cURL and subprocess options.
176
+ #
177
+ # @return [Array<Array, Hash>]
178
+ def options
179
+ # Build the list of parameters to execute with cURL
180
+ options = [
181
+ "--fail",
182
+ "--location",
183
+ "--max-redirs", "10",
184
+ "--user-agent", USER_AGENT,
185
+ ]
186
+
187
+ options += ["--cacert", @ca_cert] if @ca_cert
188
+ options += ["--capath", @ca_path] if @ca_path
189
+ options += ["--continue-at", "-"] if @continue
190
+ options << "--insecure" if @insecure
191
+ options << "--cert" << @client_cert if @client_cert
192
+ options << "-u" << @auth if @auth
193
+
194
+ if @headers
195
+ Array(@headers).each do |header|
196
+ options << "-H" << header
197
+ end
198
+ end
199
+
200
+ # Specify some options for the subprocess
201
+ subprocess_options = {}
202
+
203
+ # If we're in Vagrant, then we use the packaged CA bundle
204
+ if Vagrant.in_installer?
205
+ subprocess_options[:env] ||= {}
206
+ subprocess_options[:env]["CURL_CA_BUNDLE"] =
207
+ File.expand_path("cacert.pem", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"])
208
+ end
209
+
210
+ return [options, subprocess_options]
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end