vagrant-xenserver-jc 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/CHANGELOG.md +40 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE +8 -0
  6. data/README.md +167 -0
  7. data/Rakefile +14 -0
  8. data/example_box/install_wheezy.sh +128 -0
  9. data/example_box/metadata.json +3 -0
  10. data/example_box/mkbox.sh +17 -0
  11. data/lib/vagrant-xenserver/action/clone_disk.rb +30 -0
  12. data/lib/vagrant-xenserver/action/clone_vm.rb +30 -0
  13. data/lib/vagrant-xenserver/action/configure_network.rb +61 -0
  14. data/lib/vagrant-xenserver/action/connect_xs.rb +47 -0
  15. data/lib/vagrant-xenserver/action/create_template.rb +87 -0
  16. data/lib/vagrant-xenserver/action/create_vifs.rb +86 -0
  17. data/lib/vagrant-xenserver/action/create_vm.rb +95 -0
  18. data/lib/vagrant-xenserver/action/destroy_vm.rb +37 -0
  19. data/lib/vagrant-xenserver/action/download_xva.rb +101 -0
  20. data/lib/vagrant-xenserver/action/dummy.rb +16 -0
  21. data/lib/vagrant-xenserver/action/halt_vm.rb +23 -0
  22. data/lib/vagrant-xenserver/action/is_created.rb +20 -0
  23. data/lib/vagrant-xenserver/action/is_running.rb +20 -0
  24. data/lib/vagrant-xenserver/action/is_suspended.rb +20 -0
  25. data/lib/vagrant-xenserver/action/prepare_nfs_settings.rb +85 -0
  26. data/lib/vagrant-xenserver/action/prepare_nfs_valid_ids.rb +17 -0
  27. data/lib/vagrant-xenserver/action/read_ssh_info.rb +97 -0
  28. data/lib/vagrant-xenserver/action/read_state.rb +35 -0
  29. data/lib/vagrant-xenserver/action/resume_vm.rb +30 -0
  30. data/lib/vagrant-xenserver/action/set_vm_params.rb +28 -0
  31. data/lib/vagrant-xenserver/action/start_vm.rb +31 -0
  32. data/lib/vagrant-xenserver/action/suspend_vm.rb +30 -0
  33. data/lib/vagrant-xenserver/action/upload_vhd.rb +164 -0
  34. data/lib/vagrant-xenserver/action/upload_xva.rb +100 -0
  35. data/lib/vagrant-xenserver/action/validate_network.rb +112 -0
  36. data/lib/vagrant-xenserver/action/wait_himn.rb +58 -0
  37. data/lib/vagrant-xenserver/action.rb +272 -0
  38. data/lib/vagrant-xenserver/config.rb +102 -0
  39. data/lib/vagrant-xenserver/errors.rb +68 -0
  40. data/lib/vagrant-xenserver/plugin.rb +70 -0
  41. data/lib/vagrant-xenserver/provider.rb +36 -0
  42. data/lib/vagrant-xenserver/util/exnhandler.rb +49 -0
  43. data/lib/vagrant-xenserver/util/uploader.rb +215 -0
  44. data/lib/vagrant-xenserver/version.rb +6 -0
  45. data/lib/vagrant-xenserver.rb +17 -0
  46. data/locales/en.yml +38 -0
  47. data/vagrant-xenserver.gemspec +27 -0
  48. metadata +173 -0
@@ -0,0 +1,28 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module XenServer
5
+ module Action
6
+ class SetVMParams
7
+ def initialize(app, env)
8
+ @app = app
9
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::set_vm_params")
10
+ end
11
+
12
+ def call(env)
13
+ myvm = env[:machine].id
14
+
15
+ if env[:machine].provider_config.pv
16
+ env[:xc].VM.set_HVM_boot_policy(myvm,"")
17
+ env[:xc].VM.set_PV_bootloader(myvm,"pygrub")
18
+ end
19
+
20
+ mem = ((env[:machine].provider_config.memory) * (1024*1024)).to_s
21
+ env[:xc].VM.set_memory_limits(myvm,mem,mem,mem,mem)
22
+
23
+ @app.call env
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require "log4r"
2
+ require "xmlrpc/client"
3
+ require "vagrant-xenserver/util/uploader"
4
+ require "rexml/document"
5
+ require "json"
6
+ require "xenapi"
7
+
8
+ module VagrantPlugins
9
+ module XenServer
10
+ module Action
11
+ class StartVM
12
+ def initialize(app, env)
13
+ @app = app
14
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::start_vm")
15
+ end
16
+
17
+ def call(env)
18
+ myvm = env[:machine].id
19
+
20
+ begin
21
+ env[:xc].VM.start(myvm,false,false)
22
+ rescue XenApi::Errors::NoHostsAvailable
23
+ raise Errors::NoHostsAvailable
24
+ end
25
+
26
+ @app.call env
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require "log4r"
2
+ require "xmlrpc/client"
3
+
4
+ module VagrantPlugins
5
+ module XenServer
6
+ module Action
7
+ class SuspendVM
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::suspend_vm")
11
+ end
12
+
13
+ def call(env)
14
+ myvm = env[:machine].id
15
+
16
+ suspend_task = env[:xc].Async.VM.suspend(myvm)
17
+ while env[:xc].task.get_status(suspend_task) == 'pending' do
18
+ sleep 1
19
+ end
20
+ suspend_result = env[:xc].task.get_status(suspend_task)
21
+ if suspend_result != "success"
22
+ raise Errors::APIError
23
+ end
24
+
25
+ @app.call env
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,164 @@
1
+ require "log4r"
2
+ require "xenapi"
3
+ require "vagrant-xenserver/util/uploader"
4
+ require "vagrant-xenserver/util/exnhandler"
5
+ require "rexml/document"
6
+ require "json"
7
+
8
+ module VagrantPlugins
9
+ module XenServer
10
+ module Action
11
+ class UploadVHD
12
+
13
+ @@lock = Mutex.new
14
+
15
+ def initialize(app, env)
16
+ @app = app
17
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::upload_vhd")
18
+ end
19
+
20
+ def get_vhd_size(box_vhd_file)
21
+ # Find out virtual size of the VHD
22
+ disk_info={}
23
+ begin
24
+ begin
25
+ disk_info=JSON.parse(IO.popen(["qemu-img", "info",box_vhd_file,"--output=json"]).read)
26
+ rescue JSON::ParserError
27
+ size=`qemu-img info #{box_vhd_file} | grep "virtual size" | cut "-d(" -f2 | cut "-d " -f1`
28
+ disk_info['virtual-size']=size.strip
29
+ end
30
+ rescue
31
+ @logger.error("Error getting virtual size of VHD: #{box_vhd_file}")
32
+ raise Errors::QemuImgError
33
+ end
34
+ return disk_info['virtual-size']
35
+ end
36
+
37
+ def call(env)
38
+ if env[:machine].provider_config.xva_url.nil?
39
+ box_vhd_file = env[:machine].box.directory.join('box.vhd').to_s
40
+
41
+ if File.exist?(box_vhd_file)
42
+ hostname = env[:machine].provider_config.xs_host
43
+ session = env[:xc].xenapi_session
44
+
45
+ @logger.info("box name=" + env[:machine].box.name.to_s)
46
+ @logger.info("box version=" + env[:machine].box.version.to_s)
47
+
48
+ md5=`dd if=#{box_vhd_file} bs=1M count=1 | md5sum | cut '-d ' -f1`.strip
49
+
50
+ @logger.info("md5=#{md5}")
51
+
52
+ # Find out if it has already been uploaded
53
+ @@lock.synchronize do
54
+
55
+ vdis = env[:xc].VDI.get_all_records
56
+
57
+ vdi_tag = "vagrant:" + env[:machine].box.name.to_s + "/" + md5
58
+
59
+ vdi_ref_rec = vdis.find { |reference,record|
60
+ @logger.info(record['tags'].to_s)
61
+ record['tags'].include?(vdi_tag)
62
+ }
63
+
64
+ if not vdi_ref_rec
65
+ virtual_size = get_vhd_size(box_vhd_file)
66
+ @logger.info("virtual_size=#{virtual_size}")
67
+ pool=env[:xc].pool.get_all
68
+ default_sr=env[:xc].pool.get_default_SR(pool[0])
69
+ @logger.info("default_SR="+default_sr)
70
+
71
+ # Verify the default SR is valid:
72
+ begin
73
+ env[:xc].SR.get_uuid(default_sr)
74
+ rescue
75
+ raise Errors::NoDefaultSR
76
+ end
77
+
78
+ vdi_record = {
79
+ 'name_label' => 'Vagrant disk',
80
+ 'name_description' => 'Base disk uploaded for the vagrant box '+env[:machine].box.name.to_s+' v'+env[:machine].box.version.to_s,
81
+ 'SR' => default_sr,
82
+ 'virtual_size' => "#{virtual_size}",
83
+ 'type' => 'user',
84
+ 'sharable' => false,
85
+ 'read_only' => false,
86
+ 'other_config' => {},
87
+ 'xenstore_data' => {},
88
+ 'sm_config' => {},
89
+ 'tags' => [] }
90
+
91
+ begin
92
+ vdi_result=env[:xc].VDI.create(vdi_record)
93
+ rescue XenApi::Errors::GenericError => e
94
+ MyUtil::Exnhandler.handle_xenapiexn("VDI.create",e,@logger)
95
+ end
96
+
97
+ @logger.info("created VDI: " + vdi_result.to_s)
98
+ vdi_uuid = env[:xc].VDI.get_uuid(vdi_result)
99
+ @logger.info("uuid: "+vdi_uuid)
100
+
101
+ # Create a task to so we can get the result of the upload
102
+ task = env[:xc].task.create("vagrant-vhd-upload",
103
+ "Task to track progress of the XVA upload from vagrant")
104
+
105
+ url = "https://#{hostname}/import_raw_vdi?session_id=#{session}&task_id=#{task}&vdi=#{vdi_result}&format=vhd"
106
+
107
+ uploader_options = {}
108
+ uploader_options[:ui] = env[:ui]
109
+ uploader_options[:insecure] = true
110
+
111
+ uploader = MyUtil::Uploader.new(box_vhd_file, url, uploader_options)
112
+
113
+ begin
114
+ uploader.upload!
115
+ rescue
116
+ env[:xc].task.cancel(task)
117
+ end
118
+
119
+ task_status = ""
120
+
121
+ begin
122
+ sleep(0.2)
123
+ task_status = env[:xc].task.get_status(task)
124
+ end while task_status == "pending"
125
+
126
+ @logger.info("task_status="+task_status)
127
+
128
+ if task_status != "success"
129
+ # Task failed - let's find out why:
130
+ error_list = env[:xc].task.get_error_info(task)
131
+ MyUtil::Exnhandler.handle("import HTTP handler", error_list)
132
+ end
133
+
134
+ task_result = env[:xc].task.get_result(task)
135
+
136
+ doc = REXML::Document.new(task_result)
137
+
138
+ doc.elements.each('value/array/data/value') do |ele|
139
+ vdi = ele.text
140
+ end
141
+
142
+ @logger.info("task_result=" + task_result)
143
+
144
+ tag_result=env[:xc].VDI.add_tags(vdi_result,vdi_tag)
145
+
146
+ @logger.info("Added tags")
147
+
148
+ env[:box_vdi] = vdi_result
149
+ else
150
+ (reference,record) = vdi_ref_rec
151
+ env[:box_vdi] = reference
152
+ @logger.info("box_vdi="+reference)
153
+
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ @app.call(env)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,100 @@
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_name = env[:machine].box.name.to_s
17
+ box_version = env[:machine].box.version.to_s
18
+
19
+ Action.getlock.synchronize do
20
+ templates = env[:xc].VM.get_all_records_where("field \"is_a_template\"=\"true\"")
21
+ template = templates.detect { |vm,vmr|
22
+ vmr["other_config"]["box_name"] == box_name &&
23
+ vmr["other_config"]["box_version"] == box_version
24
+ }
25
+
26
+ box_xva_file = env[:machine].box.directory.join('box.xva').to_s
27
+
28
+ if File.exist?(box_xva_file) && template.nil?
29
+ #box_image_file = env[:machine].box.directory.join('export.xva').to_s
30
+ hostname = env[:machine].provider_config.xs_host
31
+ session = env[:session]
32
+
33
+ @logger.info("box name=" + env[:machine].box.name.to_s)
34
+ @logger.info("box version=" + env[:machine].box.version.to_s)
35
+
36
+ # Create a task to so we can get the result of the upload
37
+ task = env[:xc].task.create("vagrant-xva-upload",
38
+ "Task to track progress of the XVA upload from vagrant")
39
+
40
+ url = "https://#{hostname}/import?session_id=#{env[:xc].xenapi_session}&task_id=#{task}"
41
+
42
+ uploader_options = {}
43
+ uploader_options[:ui] = env[:ui]
44
+ uploader_options[:insecure] = true
45
+
46
+ uploader = MyUtil::Uploader.new(box_xva_file, url, uploader_options)
47
+
48
+ begin
49
+ uploader.upload!
50
+ rescue
51
+ env[:xc].task.cancel(task)
52
+ end
53
+
54
+ task_status = ""
55
+
56
+ begin
57
+ sleep(0.2)
58
+ task_status = env[:xc].task.get_status(task)
59
+ end while task_status == "pending"
60
+
61
+ if task_status != "success"
62
+ # Task failed - let's find out why:
63
+ error_list = env[:xc].task.get_error_info(task)
64
+ MyUtil::Exnhandler.handle("VM.import", error_list)
65
+ end
66
+
67
+ task_result = env[:xc].task.get_result(task)
68
+
69
+ doc = REXML::Document.new(task_result)
70
+
71
+ @logger.debug("task_result=\"#{task_result}\"")
72
+ template_ref = doc.elements['value/array/data/value'].text
73
+
74
+ @logger.info("template_ref=" + template_ref)
75
+
76
+ # Make sure it's really a template, and add the xva_url to other_config:
77
+ env[:xc].VM.set_is_a_template(template_ref,true)
78
+ env[:xc].VM.add_to_other_config(template_ref,"box_name",box_name)
79
+ env[:xc].VM.add_to_other_config(template_ref,"box_version",box_version)
80
+
81
+ # Hackity hack: HVM booting guests don't need to set the bootable flag
82
+ # on their VBDs, but PV do. Let's set bootable=true on VBD device=0
83
+ # just in case.
84
+
85
+ vbds = env[:xc].VM.get_VBDs(template_ref)
86
+ vbds.each { |vbd|
87
+ if env[:xc].VBD.get_userdevice(vbd) == "0"
88
+ env[:xc].VBD.set_bootable(vbd, true)
89
+ end
90
+ }
91
+ env[:template] = template_ref
92
+ end
93
+ end
94
+
95
+ @app.call(env)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,112 @@
1
+ require "log4r"
2
+ require "xmlrpc/client"
3
+
4
+ module VagrantPlugins
5
+ module XenServer
6
+ module Action
7
+ class ValidateNetwork
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant::xenserver::actions::validate_network")
11
+ end
12
+
13
+ def call(env)
14
+ myvm = env[:machine].id
15
+
16
+ # only find network without :fowarded port, then sort by name label
17
+ vifs = env[:machine].config.vm.networks.reject {
18
+ |k,v| k == :forwarded_port}.sort_by {
19
+ |k,v| v[:network] || ""}
20
+
21
+ # this will hold any vifs with no device (ethX)
22
+ vifs_unknown = []
23
+
24
+ # create a placeholder hash for all vifs
25
+ # note that eth0 is already used by HIMN, so start with 1
26
+ eth = Hash.new
27
+ vifs.count.times do |x|
28
+ x += 1
29
+ eth["eth#{x}".to_sym] = {}
30
+ end
31
+
32
+ # Get All networks without HIMN
33
+ allnetworks = env[:xc].network.get_all_records.reject {
34
+ |ref,net| net['other_config']['is_host_internal_management_network'] }
35
+
36
+ # find the network type (public/external or private/single-server)
37
+ allnets = {}
38
+ allnetworks.each do |ref, params|
39
+ allnets[ref] = params
40
+ allnets[ref]["network_type"] = params["PIFs"].empty? ? "private_network" : "external_network"
41
+ end
42
+ # convert allnets to string for error message
43
+ allnets_str = allnets.map { |k,v| "#{v['name_label']} (#{v['network_type']})"}.join(", ")
44
+
45
+ # foreach vifs which has device defined, assign it to `eth` Hash
46
+ vifs.each do |k,v|
47
+ # Check if network name label in configuration matches
48
+ # the network on Xenserver Host
49
+ netrefrec = allnets.find { |ref,net| net['name_label'].upcase == v[:network].upcase }
50
+ (net_ref, net_rec) = netrefrec
51
+ if net_ref.nil?
52
+ raise Errors::InvalidNetwork, network: v[:network], allnetwork: allnets_str, vm: env[:machine].name
53
+ end
54
+
55
+ # Assign network UUID/ref
56
+ v[:net_ref] = net_ref
57
+
58
+ # Assign network type
59
+ v[:network_type] = net_rec["network_type"]
60
+
61
+ # no match, assign a device number (ethX) later
62
+ if v[:device].nil?
63
+ # vifs_unknown will contains vifs without :device defined
64
+ # SORTED BY NETWORK NAME LABEL
65
+ vifs_unknown.push(v)
66
+ else
67
+ if v[:device].start_with?("eth") and eth.include?(v[:device].to_sym)
68
+ eth[v[:device].to_sym] = v
69
+ else
70
+ raise "Configration Error in netowrk `#{v[:network]}' for device name `#{v[:device]}'"
71
+ end
72
+ end
73
+ end
74
+
75
+ # Populate `eth' hash from the rest of unconfigured vifs
76
+ vifs_unknown.each do |vif|
77
+ unconfigured_eth = eth.find {|k,v| v.empty?}[0]
78
+ vif[:device] = unconfigured_eth.to_s
79
+ eth[unconfigured_eth.to_sym] = vif
80
+ end
81
+
82
+ # Validate network settings:
83
+ # - if `ip' and dhcp is set, raise error
84
+ # - ip and netmask must be defined it dhcp is not set
85
+ # - external_network
86
+ # - can have gateway
87
+ # - private_network (internal xenserver host-only network)
88
+ # - CANNOT have gateway
89
+ # - if `ip' defined, raise error if dhcp is set
90
+ eth.each do |e, opt|
91
+ raise Errors::InvalidInterface, eth: e, opt: opt[:proto], net: opt[:network],
92
+ message: "No IP address is defined" if opt[:proto] == 'static' and opt[:ip].nil?
93
+
94
+ raise Errors::InvalidInterface, eth: e, opt: opt[:proto], net: opt[:network],
95
+ message: "No IP netmask is defined" if opt[:proto] == 'static' and opt[:netmask].nil?
96
+
97
+ raise Errors::InvalidInterface, eth: e, opt: opt[:proto], net: opt[:network],
98
+ message: "Cannot assign IP #{opt[:ip]} here" if opt[:proto] == 'dhcp' and !opt[:ip].nil?
99
+
100
+ raise Errors::InvalidInterface, eth: e, opt: opt[:network_type], net: opt[:network],
101
+ message: "Cannot assign gateway #{opt[:gateway]} here" if opt[:network_type] == "private_network" and !opt[:gateway].nil?
102
+ end
103
+
104
+ # Put eth on global env
105
+ env[:vifs] = eth
106
+
107
+ @app.call env
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,58 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module XenServer
5
+ module Action
6
+ # This action wait for SSH Communicator via HIMN interface is ready
7
+ class WaitForHIMNCommunicator
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant_xenserver::action::wait_himn")
11
+ end
12
+
13
+ def call(env)
14
+ if env[:machine].provider_config.use_himn
15
+ env[:machine_ssh_info] = wait_ssh_via_himn(env)
16
+ end
17
+
18
+ @app.call(env)
19
+ end
20
+
21
+ def wait_ssh_via_himn(env)
22
+ machine = env[:machine]
23
+ return nil if machine.id.nil?
24
+
25
+ # Find the machine
26
+ networks = env[:xc].network.get_all_records
27
+
28
+ begin
29
+ vifs = env[:xc].VM.get_VIFs(machine.id)
30
+ rescue
31
+ @logger.info("Machine couldn't be found, assuming it got destroyed.")
32
+ machine.id = nil
33
+ return nil
34
+ end
35
+
36
+ himn = networks.find { |ref,net| net['other_config']['is_host_internal_management_network'] }
37
+ (himn_ref,himn_rec) = himn
38
+
39
+ assigned_ips = himn_rec['assigned_ips']
40
+ (vif,ip) = assigned_ips.find { |vif,ip| vifs.include? vif }
41
+ command = "ssh '#{machine.provider_config.xs_host}' -l '#{machine.provider_config.xs_username}' \"true &>/dev/null </dev/tcp/#{ip.to_s}/22 && echo open || echo closed\""
42
+ max_retry = 20
43
+ begin
44
+ retries ||= 0
45
+ ssh_ready = `#{command}`.strip
46
+ sleep 1
47
+ raise if ssh_ready == "closed"
48
+ rescue
49
+ if retries == max_retry
50
+ raise Errors::HIMNCommunicatorError
51
+ end
52
+ retry if (retries += 1) <= max_retry
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end