vagrant-xenserver 0.0.0

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,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