Advertisement
Guest User

Untitled

a guest
Jan 25th, 2016
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.16 KB | None | 0 0
  1. require 'puppet_x/puppetlabs/prefetch_error'
  2. require 'puppet_x/puppetlabs/vsphere'
  3. require 'retries'
  4.  
  5. class UnableToLoadConfigurationError < StandardError
  6. end
  7.  
  8. Puppet::Type.type(:vsphere_vm).provide(:rbvmomi, :parent => PuppetX::Puppetlabs::Vsphere) do
  9. confine feature: :rbvmomi
  10. confine feature: :hocon
  11.  
  12. mk_resource_methods
  13.  
  14. [ :cpus, :memory, :extra_config, :annotation, :disks ].each do |property|
  15. define_method("#{property}=") do |v|
  16. # if @property_hash[property] != v
  17. @property_hash[property] = v
  18. @property_hash[:flush_reboot] = true
  19. # end
  20. end
  21. end
  22.  
  23. def self.instances
  24. begin
  25. result = nil
  26. benchmark(:debug, 'loaded list of VMs') do
  27. data = load_machine_info(datacenter_instance)
  28. result = data[RbVmomi::VIM::VirtualMachine].collect do |obj, machine|
  29. hash = nil
  30. benchmark(:debug, "loaded machine information for #{machine['name']}") do
  31. hash = hash_from_machine_data(obj, machine, data)
  32. end
  33. new(hash)
  34. end
  35. end
  36. result
  37. rescue Timeout::Error, StandardError => e
  38. # put error in the debug log, as re-raising it below swallows the correct stack trace
  39. Puppet.debug(e.inspect)
  40. Puppet.debug(e.backtrace)
  41.  
  42. raise PuppetX::Puppetlabs::PrefetchError.new(self.resource_type.name.to_s, e.message)
  43. end
  44. end
  45.  
  46. def self.prefetch(resources)
  47. instances.each do |prov|
  48. if resource = resources[prov.name]
  49. resource.provider = prov
  50. end
  51. end
  52. end
  53.  
  54. def self.resource_pool_from_machine_data(machine, data)
  55. path_components = []
  56. i = data[RbVmomi::VIM::ResourcePool][machine['resourcePool']]
  57. while i
  58. path_components << i['name']
  59. if i.has_key? 'parent'
  60. if data[RbVmomi::VIM::ResourcePool].has_key? i['parent']
  61. i = data[RbVmomi::VIM::ResourcePool][i['parent']]
  62. elsif data[RbVmomi::VIM::ClusterComputeResource].has_key? i['parent']
  63. # There's always a top-level "Resources" pool that we do not want to show
  64. path_components.pop
  65. i = data[RbVmomi::VIM::ClusterComputeResource][i['parent']]
  66. else
  67. i = nil
  68. end
  69. else
  70. i = nil
  71. end
  72. end
  73. '/' + path_components.reverse.join('/')
  74. end
  75.  
  76. def self.hash_from_machine_data(obj, machine, data)
  77. path_components = []
  78. i = machine
  79. while i
  80. path_components << i['name']
  81. if i.has_key? 'parent'
  82. if data[RbVmomi::VIM::Folder].has_key? i['parent']
  83. i = data[RbVmomi::VIM::Folder][i['parent']]
  84. elsif data[RbVmomi::VIM::Datacenter].has_key? i['parent']
  85. i = data[RbVmomi::VIM::Datacenter][i['parent']]
  86. else
  87. i = nil
  88. end
  89. else
  90. i = nil
  91. end
  92. end
  93. name = '/' + path_components.reverse.join('/')
  94.  
  95. property_mappings = {
  96. cpus: 'summary.config.numCpu',
  97. snapshot_disabled: 'config.flags.snapshotDisabled',
  98. snapshot_locked: 'config.flags.snapshotLocked',
  99. annotation: 'config.annotation',
  100. guest_os: 'config.guestFullName',
  101. snapshot_power_off_behavior: 'config.flags.snapshotPowerOffBehavior',
  102. memory: 'summary.config.memorySizeMB',
  103. template: 'summary.config.template',
  104. memory_reservation: 'summary.config.memoryReservation',
  105. cpu_reservation: 'summary.config.cpuReservation',
  106. number_ethernet_cards: 'summary.config.numEthernetCards',
  107. power_state: 'runtime.powerState',
  108. tools_installer_mounted: 'summary.runtime.toolsInstallerMounted',
  109. uuid: 'summary.config.uuid',
  110. instance_uuid: 'summary.config.instanceUuid',
  111. hostname: 'summary.guest.hostName',
  112. guest_ip: 'guest.ipAddress',
  113. }
  114.  
  115. api_properties = Hash[property_mappings
  116. .select { |_, v| machine.has_key? v }
  117. .collect { |key, property_name|
  118. [key, machine[property_name]]
  119. }
  120. ]
  121.  
  122. cpu_affinity = machine['config.cpuAffinity']
  123. memory_affinity = machine['config.memoryAffinity']
  124. disks = Hash.new
  125. obj.disks.each do |disk|
  126. disks[disk.deviceInfo.label] = {
  127. 'capacityInKB' => disk.capacityInKB,
  128. }
  129. end
  130.  
  131.  
  132. curated_properties = {
  133. name: name,
  134. resource_pool: resource_pool_from_machine_data(machine, data),
  135. ensure: machine_state(machine['runtime.powerState']),
  136. hostname: api_properties['hostname'] == '(none)' ? nil : api_properties['hostname'],
  137. datacenter: data[RbVmomi::VIM::Datacenter].first.last['name'],
  138. drs_behavior: drs_behavior_from_machine_data(machine, data),
  139. memory_affinity: memory_affinity.respond_to?('affinitySet') ? memory_affinity.affinitySet : [],
  140. cpu_affinity: cpu_affinity.respond_to?('affinitySet') ? cpu_affinity.affinitySet : [],
  141. object: obj,
  142. datastore_obj: obj.datastore.first,
  143. datastore: obj.datastore.first.respond_to?('name') ? obj.datastore.first.name : nil,
  144. disks_obj: obj.disks,
  145. disks: disks,
  146. }
  147.  
  148. # While the machine is booting, no extra config is available.
  149. curated_properties[:extra_config] = Hash[machine['config.extraConfig'].collect { |setting| [setting.key, setting.value] }] if machine.has_key? 'config.extraConfig'
  150.  
  151. api_properties.merge(about_info).merge(curated_properties)
  152. end
  153.  
  154. def self.cluster_compute_from_machine_data(machine, data)
  155. focus = data[RbVmomi::VIM::ResourcePool][machine['resourcePool']]
  156. while focus and focus.class != RbVmomi::VIM::ClusterComputeResource
  157. if (focus.class == Hash && focus.has_key?('parent'))
  158. focus = focus['parent']
  159. elsif focus.respond_to? 'parent'
  160. focus = focus.parent
  161. else
  162. focus = nil
  163. end
  164. end
  165. focus
  166. end
  167.  
  168. def self.drs_behavior_from_machine_data(machine, data)
  169. cluster_compute = cluster_compute_from_machine_data(machine, data)
  170. if cluster_compute
  171. config = data[RbVmomi::VIM::ClusterComputeResource][cluster_compute_from_machine_data(machine, data)]['configurationEx']
  172. override = config.drsVmConfig.find {|c| c.key.name == machine['name'] }
  173. if override
  174. override.behavior
  175. else
  176. config.drsConfig.defaultVmBehavior
  177. end
  178. end
  179. end
  180.  
  181. def self.machine_state(power_state)
  182. case power_state
  183. when 'poweredOn'
  184. :running
  185. when 'poweredOff'
  186. :stopped
  187. when 'suspended'
  188. :suspended
  189. else
  190. :unknown
  191. end
  192. end
  193.  
  194. def exists?
  195. Puppet.info("Checking if #{type_name} #{name} exists")
  196. @property_hash[:ensure] and @property_hash[:ensure] != :absent
  197. end
  198.  
  199. def create(args={})
  200. raise Puppet::Error, "Must provide a source machine, template or datastore folder to base the machine on" unless resource[:source]
  201. if resource[:source_type] == :folder
  202. create_from_folder
  203. else
  204. create_from_path(args)
  205. end
  206. end
  207.  
  208. def create_disk
  209. end
  210.  
  211. def lookup_disks(vm)
  212. disks = Hash.new
  213. vm.disks.each do |disk|
  214. disks[disk.deviceInfo.label] = {
  215. 'capacityInKB' => disk.capacityInKB.to_s,
  216. }
  217. end
  218. disks
  219. end
  220.  
  221. def disks_match?(vm)
  222. disks = lookup_disks(vm)
  223. resource[:disks] == disks
  224. end
  225.  
  226. def create_from_path(args)
  227. Puppet.info("Creating #{type_name} #{name}")
  228. base_machine = PuppetX::Puppetlabs::Vsphere::Machine.new(resource[:source])
  229. vm = datacenter_instance.find_vm(base_machine.local_path)
  230. raise Puppet::Error, "No machine found at #{base_machine.local_path}" unless vm
  231.  
  232. if resource[:resource_pool]
  233. path_components = resource[:resource_pool].split('/').select { |s| !s.empty? }
  234. compute_resource_name = path_components.shift
  235. compute_resource = datacenter_instance.find_compute_resource(compute_resource_name)
  236. raise Puppet::Error, "No compute resource found named #{compute_resource_name}" unless compute_resource
  237. if path_components.empty?
  238. pool = compute_resource.resourcePool
  239. else
  240. pool = compute_resource.resourcePool.traverse path_components.join('/')
  241. end
  242. raise Puppet::Error, "No resource pool found named #{resource[:resource_pool]}" unless pool
  243. else
  244. hosts = datacenter_instance.hostFolder.children
  245. raise Puppet::Error, "No resource pool found for default datacenter" if hosts.empty?
  246. pool = hosts.first.resourcePool
  247. end
  248.  
  249. relocate_spec = if is_linked_clone?
  250. vm.add_delta_disk_layer_on_all_disks
  251. # although we wait for the previous task to complete I was able
  252. # to sometimes trigger a race condition. I didn't find a suitable
  253. # assertion to make but a small sleep appears to aleviate the issue
  254. sleep 5
  255. RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => pool, :diskMoveType => :moveChildMostDiskBacking)
  256. else
  257. RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => pool)
  258. end
  259.  
  260. power_on = args[:stopped] == true ? false : true
  261. power_on = false if is_template?
  262. clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(
  263. :location => relocate_spec,
  264. :template => is_template?,
  265. :powerOn => power_on)
  266.  
  267. if resource[:customization_spec]
  268. begin
  269. clone_spec.customization = vim.serviceContent.customizationSpecManager.GetCustomizationSpec({name: resource[:customization_spec]}).spec
  270. rescue RbVmomi::Fault => exception
  271. if exception.message.split(':').first == 'NotFound'
  272. raise Puppet::Error, "Customization specification #{resource[:customization_spec]} not found"
  273. else
  274. raise
  275. end
  276. end
  277. end
  278.  
  279. if resource[:cpus] || resource[:memory] || resource[:annotation]
  280. Puppet.debug("adding VirtualMachineConfigSpec for #{type_name} #{name}")
  281. clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(deviceChange: [])
  282. clone_spec.config.numCPUs = resource[:cpus] if resource[:cpus] && resource[:cpus] != vm.summary.config.numCpu
  283. # Store the set values in @property_hash, so that flush can skip those values
  284. @property_hash[:cpus] = resource[:cpus]
  285. clone_spec.config.memoryMB = resource[:memory] if resource[:memory] && resource[:memory] != vm.summary.config.memorySizeMB
  286. @property_hash[:memory] = resource[:memory]
  287. clone_spec.config.annotation = resource[:annotation] if resource[:annotation] && resource[:annotation] != vm.config.annotation
  288. @property_hash[:annotation] = resource[:annotation]
  289. if resource[:extra_config]
  290. clone_spec.config.extraConfig = resource[:extra_config].map do |k,v|
  291. {:key => k, :value => v}
  292. end
  293. @property_hash[:extra_config] = resource[:extra_config].dup
  294. end
  295. end
  296.  
  297.  
  298. if resource[:disks]
  299. if disks_match?(vm)
  300. require 'pry'
  301. binding.pry
  302. @property_hash[:disks] = resource[:disks]
  303. elsif @property_hash[:disks] != nil
  304. @property_hash[:disks] = lookup_disks(vm)
  305. @missing_disks = resource[:disks].reject do |disk, capacityhash|
  306. @property_hash[:disks].has_key?(disk) && @property_hash[disks][disk] == capacityhash
  307. end
  308. else
  309. @missing_disks = resource[:disks]
  310. end
  311. end
  312.  
  313. vm.CloneVM_Task(
  314. :folder => find_or_create_folder(datacenter_instance.vmFolder, instance.folder),
  315. :name => instance.name,
  316. :spec => clone_spec).wait_for_completion
  317.  
  318. execute_command_on_machine if resource[:create_command]
  319.  
  320. @property_hash[:ensure] = :present
  321. end
  322.  
  323. def create_from_folder
  324. Puppet.info("Registering #{type_name} #{name}")
  325.  
  326. base_machine = PuppetX::Puppetlabs::Vsphere::Machine.new(resource[:name])
  327. template = resource[:template].to_s == 'true' || false
  328. vm_folder = resource[:source]
  329. vm_ext = template ? "vmtx" : "vmx"
  330.  
  331. datastore = datacenter_instance.datastore.first
  332. raise Puppet::Error, "No datastore found for default datacenter" unless datastore
  333.  
  334. if resource[:resource_pool]
  335. compute = datacenter_instance.find_compute_resource(resource[:resource_pool])
  336. raise Puppet::Error, "No resource pool found with name #{resource[:resource_pool]}" unless compute
  337. host = template ? compute.host.first : nil
  338. raise Puppet::Error, "No host system found for resource pool #{resource[:resource_pool]}" unless host
  339. pool = template ? nil : compute.resourcePool
  340. raise Puppet::Error, "No resource pool found for #{resource[:resource_pool]}" unless pool
  341. else
  342. hosts = datacenter_instance.hostFolder.children
  343. raise Puppet::Error, "No resource pool found for default datacenter" if hosts.empty?
  344. host = template ? hosts.first.host.first : nil
  345. pool = template ? nil : hosts.first.resourcePool
  346. end
  347.  
  348. folder = find_or_create_folder(datacenter_instance.vmFolder, base_machine.folder)
  349. folder.RegisterVM_Task(
  350. :path => "[#{datastore.name}] #{vm_folder}/#{vm_folder}.#{vm_ext}",
  351. :asTemplate => template,
  352. :pool => pool,
  353. :host => host
  354. ).wait_for_completion
  355. end
  356.  
  357. def execute_command_on_machine
  358. new_machine = PuppetX::Puppetlabs::Vsphere::Machine.new(name)
  359. machine = datacenter_instance.find_vm(new_machine.local_path)
  360. machine_credentials = {
  361. interactiveSession: false,
  362. username: resource[:create_command]['user'],
  363. password: resource[:create_command]['password'],
  364. }
  365. manager = vim.serviceContent.guestOperationsManager
  366. auth = RbVmomi::VIM::NamePasswordAuthentication(machine_credentials)
  367. handler = Proc.new do |exception, attempt_number, total_delay|
  368. Puppet.debug("#{exception.message}; retry attempt #{attempt_number}; #{total_delay} seconds have passed")
  369. # All exceptions in RbVmomi are RbVmomi::Fault, rather than the actual API exception
  370. # The actual exceptions come out in the message, so we parse them out
  371. case exception.message.split(':').first
  372. when 'GuestComponentsOutOfDate'
  373. raise Puppet::Error, 'VMware Tools is out of date on the guest machine'
  374. when 'InvalidGuestLogin'
  375. raise Puppet::Error, 'Incorrect credentials for the guest machine'
  376. when 'OperationDisabledByGuest'
  377. raise Puppet::Error, 'Remote access is disabled on the guest machine'
  378. when 'OperationNotSupportedByGuest'
  379. raise Puppet::Error, 'Remote access is not supported by the guest operating system'
  380. end
  381. end
  382. arguments = resource[:create_command].has_key?('arguments') ? resource[:create_command]['arguments'] : ''
  383. working_directory = resource[:create_command].has_key?('working_directory') ? resource[:create_command]['working_directory'] : '/'
  384. spec = RbVmomi::VIM::GuestProgramSpec(
  385. programPath: resource[:create_command]['command'],
  386. arguments: arguments,
  387. workingDirectory: working_directory,
  388. )
  389. with_retries(:max_tries => 10,
  390. :handler => handler,
  391. :base_sleep_seconds => 5,
  392. :max_sleep_seconds => 15,
  393. :rescue => RbVmomi::Fault) do
  394. manager.authManager.ValidateCredentialsInGuest(vm: machine, auth: auth)
  395. response = manager.processManager.StartProgramInGuest(vm: machine, auth: auth, spec: spec)
  396. Puppet.info("Ran #{resource[:create_command]['command']}, started with PID #{response}")
  397. end
  398. end
  399.  
  400. # ensure that 'is' has all key/value pairs present in 'should'
  401. def extra_config_matches?(is, should)
  402. Hash[should.keys.collect { |k| [k, is[k]] } ] == should
  403. end
  404.  
  405.  
  406. def flush
  407. if ! @property_hash.empty? and @property_hash[:flush_reboot]
  408. config_spec = RbVmomi::VIM.VirtualMachineConfigSpec
  409. config_spec.numCPUs = resource[:cpus] if resource[:cpus]
  410. config_spec.memoryMB = resource[:memory] if resource[:memory]
  411. config_spec.annotation = resource[:annotation] if resource[:annotation]
  412.  
  413. if resource[:extra_config]
  414. config_spec.extraConfig = resource[:extra_config].map do |k,v|
  415. {:key => k, :value => v}
  416. end
  417. end
  418.  
  419. if config_spec.props.count > 0
  420. do_reboot = running?
  421. if do_reboot
  422. Puppet.info("Stopping #{name} to apply configuration changes")
  423. stop
  424. end
  425. machine.ReconfigVM_Task(:spec => config_spec).wait_for_completion
  426. if do_reboot
  427. Puppet.info("Starting #{name} after applying configuration changes")
  428. start
  429. end
  430. end
  431. end
  432. end
  433.  
  434. def find_or_create_folder(root, parts)
  435. if parts.empty?
  436. root
  437. else
  438. part = parts.shift
  439. folder = root.find(part)
  440. folder = root.CreateFolder(:name => part) if folder.nil?
  441. find_or_create_folder(folder, parts)
  442. end
  443. end
  444.  
  445. def unregister
  446. Puppet.info("Unregistering #{type_name} #{name}")
  447. machine.UnregisterVM
  448. @property_hash[:ensure] = :unregistered
  449. end
  450.  
  451. def delete_from_disk
  452. Puppet.info("Deleting #{type_name} #{name}")
  453. machine.Destroy_Task.wait_for_completion
  454. @property_hash[:ensure] = :absent
  455. end
  456.  
  457. def destroy
  458. if resource[:delete_from_disk].to_s == 'false'
  459. unregister
  460. else
  461. delete_from_disk
  462. end
  463. end
  464.  
  465. def stop
  466. machine.PowerOffVM_Task.wait_for_completion
  467. @property_hash[:ensure] = :stopped
  468. end
  469.  
  470. def start
  471. machine.PowerOnVM_Task.wait_for_completion
  472. @property_hash[:ensure] = :running
  473. end
  474.  
  475. def suspend
  476. machine.SuspendVM_Task.wait_for_completion
  477. @property_hash[:ensure] = :suspended
  478. end
  479.  
  480. def reset
  481. machine.ResetVM_Task.wait_for_completion
  482. @property_hash[:ensure] = :running
  483. end
  484.  
  485. def running?
  486. current_state == :running
  487. end
  488.  
  489. def stopped?
  490. current_state == :stopped
  491. end
  492.  
  493. def suspended?
  494. current_state == :suspended
  495. end
  496.  
  497. def unknown?
  498. current_state == :unknown
  499. end
  500.  
  501. def current_state
  502. self.class.machine_state(machine['runtime.powerState'])
  503. end
  504.  
  505. private
  506. def machine
  507. unless @property_hash[:object]
  508. benchmark(:debug, "fetched #{instance.local_path} info from vSphere") do
  509. vim_machine = datacenter_instance.find_vm(instance.local_path)
  510. data = self.class.load_machine_info(vim_machine)
  511. machine = data[RbVmomi::VIM::VirtualMachine][vim_machine]
  512. hash = self.class.hash_from_machine_data(vim_machine, machine, data)
  513. @property_hash[:object] = hash[:object]
  514. end
  515. end
  516. raise Puppet::Error, "No virtual machine found at #{instance.local_path}" unless @property_hash[:object]
  517. @property_hash[:object]
  518. end
  519.  
  520. def instance
  521. @instance ||= PuppetX::Puppetlabs::Vsphere::Machine.new(name)
  522. end
  523.  
  524. def is_template?
  525. resource[:template].to_s == 'true'
  526. end
  527.  
  528. def is_linked_clone?
  529. resource[:linked_clone].to_s == 'true'
  530. end
  531.  
  532. def type_name
  533. is_template? ? "template" : "machine"
  534. end
  535.  
  536. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement