the_foreman_proxmox 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +102 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/the_foreman_proxmox/proxmox.js +110 -0
  6. data/app/controllers/concerns/the_foreman_proxmox/controller/parameters/compute_resource.rb +41 -0
  7. data/app/controllers/the_foreman_proxmox/compute_resources_controller.rb +38 -0
  8. data/app/helpers/proxmox_compute_helper.rb +154 -0
  9. data/app/helpers/proxmox_compute_selectors_helper.rb +148 -0
  10. data/app/models/concerns/fog_extensions/proxmox/disk.rb +29 -0
  11. data/app/models/concerns/fog_extensions/proxmox/server.rb +82 -0
  12. data/app/models/concerns/fog_extensions/proxmox/server_config.rb +49 -0
  13. data/app/models/concerns/fog_extensions/proxmox/volume.rb +29 -0
  14. data/app/models/the_foreman_proxmox/options_select.rb +36 -0
  15. data/app/models/the_foreman_proxmox/proxmox.rb +394 -0
  16. data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -0
  17. data/app/views/compute_resources/form/_proxmox.html.erb +24 -0
  18. data/app/views/compute_resources/show/_proxmox.html.erb +21 -0
  19. data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +45 -0
  20. data/app/views/compute_resources_vms/form/proxmox/_config.html.erb +55 -0
  21. data/app/views/compute_resources_vms/form/proxmox/_network.html.erb +24 -0
  22. data/app/views/compute_resources_vms/form/proxmox/_volume.html.erb +23 -0
  23. data/app/views/compute_resources_vms/index/_proxmox.html.erb +45 -0
  24. data/app/views/compute_resources_vms/show/_proxmox.html.erb +36 -0
  25. data/app/views/dashboard/_the_foreman_proxmox_widget.erb +19 -0
  26. data/app/views/images/form/_proxmox.html.erb +4 -0
  27. data/config/routes.rb +24 -0
  28. data/lib/tasks/the_foreman_proxmox_tasks.rake +47 -0
  29. data/lib/the_foreman_proxmox.rb +23 -0
  30. data/lib/the_foreman_proxmox/engine.rb +92 -0
  31. data/lib/the_foreman_proxmox/version.rb +22 -0
  32. data/locale/Makefile +60 -0
  33. data/locale/en/foreman_proxmox.po +19 -0
  34. data/locale/gemspec.rb +2 -0
  35. data/locale/the_foreman_proxmox.pot +19 -0
  36. data/test/factories/the_foreman_proxmox_factories.rb +33 -0
  37. data/test/helpers/proxmox_compute_helper_test.rb +130 -0
  38. data/test/test_plugin_helper.rb +15 -0
  39. data/test/unit/the_foreman_proxmox_test.rb +11 -0
  40. metadata +143 -0
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module ProxmoxComputeSelectorsHelper
21
+ def proxmox_controllers_map
22
+ [TheForemanProxmox::OptionsSelect.new(id:'ide', name: 'IDE', range: 3),
23
+ TheForemanProxmox::OptionsSelect.new(id:'sata', name: 'SATA', range: 5),
24
+ TheForemanProxmox::OptionsSelect.new(id:'scsi', name: 'SCSI', range: 13),
25
+ TheForemanProxmox::OptionsSelect.new(name:'VirtIO Block', id: 'virtio', range: 15)]
26
+ end
27
+
28
+ def proxmox_operating_systems_map
29
+ [OpenStruct.new(id: 'other', name: 'Unspecified OS'),
30
+ OpenStruct.new(id: 'wxp', name: 'Microsoft Windows XP'),
31
+ OpenStruct.new(id: 'w2k', name: 'Microsoft Windows 2000'),
32
+ OpenStruct.new(id: 'w2k3', name: 'Microsoft Windows 2003'),
33
+ OpenStruct.new(id: 'w2k8', name: 'Microsoft Windows 2008'),
34
+ OpenStruct.new(id: 'wvista', name: 'Microsoft Windows Vista'),
35
+ OpenStruct.new(id: 'win7', name: 'Microsoft Windows 7'),
36
+ OpenStruct.new(id: 'win8', name: 'Microsoft Windows 8/2012/2012r2'),
37
+ OpenStruct.new(id: 'win10', name: 'Microsoft Windows 10/2016'),
38
+ OpenStruct.new(id: 'l24', name: 'Linux 2.4 Kernel'),
39
+ OpenStruct.new(id: 'l26', name: 'Linux 2.6/3.X + Kernel'),
40
+ OpenStruct.new(id: 'solaris', name: 'Solaris/OpenSolaris/OpenIndiania kernel')]
41
+ end
42
+
43
+ def proxmox_vgas_map
44
+ [OpenStruct.new(id: 'std', name: 'Standard VGA'),
45
+ OpenStruct.new(id: 'vmware', name: 'Vmware compatible'),
46
+ OpenStruct.new(id: 'qxl', name: 'SPICE'),
47
+ OpenStruct.new(id: 'qxl2', name: 'SPICE 2 monnitors'),
48
+ OpenStruct.new(id: 'qxl3', name: 'SPICE 3 monnitors'),
49
+ OpenStruct.new(id: 'qxl4', name: 'SPICE 4 monnitors'),
50
+ OpenStruct.new(id: 'serial0', name: 'Serial terminal 0'),
51
+ OpenStruct.new(id: 'serial1', name: 'Serial terminal 1'),
52
+ OpenStruct.new(id: 'serial2', name: 'Serial terminal 2'),
53
+ OpenStruct.new(id: 'serial3', name: 'Serial terminal 3')]
54
+ end
55
+
56
+ def proxmox_keyboards_map
57
+ [OpenStruct.new(id: 'de', name: 'Deutsch'),
58
+ OpenStruct.new(id: 'de-ch', name: 'Deutsch (Swiss)'),
59
+ OpenStruct.new(id: 'da', name: 'Danish'),
60
+ OpenStruct.new(id: 'en-gb', name: 'English (UK)'),
61
+ OpenStruct.new(id: 'en-us', name: 'English (US)'),
62
+ OpenStruct.new(id: 'es', name: 'Spanish'),
63
+ OpenStruct.new(id: 'fi', name: 'Finnish'),
64
+ OpenStruct.new(id: 'fr', name: 'French'),
65
+ OpenStruct.new(id: 'fr-be', name: 'French (Belgium)'),
66
+ OpenStruct.new(id: 'fr-ca', name: 'French (Canadian)'),
67
+ OpenStruct.new(id: 'fr-ch', name: 'French (Swiss)'),
68
+ OpenStruct.new(id: 'hu', name: 'Hungarian'),
69
+ OpenStruct.new(id: 'is', name: 'Israelian'),
70
+ OpenStruct.new(id: 'it', name: 'Italian'),
71
+ OpenStruct.new(id: 'ja', name: 'Japanese'),
72
+ OpenStruct.new(id: 'lt', name: 'Lituanian'),
73
+ OpenStruct.new(id: 'mk', name: ''),
74
+ OpenStruct.new(id: 'nl', name: 'Nederland'),
75
+ OpenStruct.new(id: 'no', name: 'Norway'),
76
+ OpenStruct.new(id: 'pl', name: 'Polish'),
77
+ OpenStruct.new(id: 'pt', name: 'Portugese'),
78
+ OpenStruct.new(id: 'pt-br', name: 'Portugese (Brasilian)'),
79
+ OpenStruct.new(id: 'sv', name: 'Sv'),
80
+ OpenStruct.new(id: 'sl', name: 'Slovakian'),
81
+ OpenStruct.new(id: 'tr', name: 'Tr')]
82
+ end
83
+
84
+ def get_controller(id)
85
+ proxmox_controllers_map.find { |controller| controller.id == id}
86
+ end
87
+
88
+ def proxmox_max_device(id)
89
+ options_select = get_controller(id)
90
+ options_select ? options_select.range : 1
91
+ end
92
+
93
+ def proxmox_caches_map
94
+ [OpenStruct.new(id: 'directsync', name: 'Direct sync'),
95
+ OpenStruct.new(id: 'writethrough', name: 'Write through'),
96
+ OpenStruct.new(id: 'writeback', name: 'Write back'),
97
+ OpenStruct.new(id: 'unsafe', name: 'Write back unsafe'),
98
+ OpenStruct.new(id: 'none', name: 'No cache')]
99
+ end
100
+
101
+ def proxmox_cpus_map
102
+ [OpenStruct.new(id: '486', name: '486'),
103
+ OpenStruct.new(id: 'athlon', name: 'athlon'),
104
+ OpenStruct.new(id: 'core2duo', name: 'core2duo'),
105
+ OpenStruct.new(id: 'coreduo', name: 'coreduo'),
106
+ OpenStruct.new(id: 'kvm32', name: 'kvm32'),
107
+ OpenStruct.new(id: 'kvm64', name: '(Default) kvm64'),
108
+ OpenStruct.new(id: 'pentium', name: 'pentium'),
109
+ OpenStruct.new(id: 'pentium2', name: 'pentium2'),
110
+ OpenStruct.new(id: 'pentium3', name: 'pentium3'),
111
+ OpenStruct.new(id: 'phenom', name: 'phenom'),
112
+ OpenStruct.new(id: 'qemu32', name: 'qemu32'),
113
+ OpenStruct.new(id: 'qemu64', name: 'qemu64'),
114
+ OpenStruct.new(id: 'Conroe', name: 'Conroe'),
115
+ OpenStruct.new(id: 'Penryn', name: 'Penryn'),
116
+ OpenStruct.new(id: 'Nehalem', name: 'Nehalem'),
117
+ OpenStruct.new(id: 'Westmere', name: 'Westmere'),
118
+ OpenStruct.new(id: 'SandyBridge', name: 'SandyBridge'),
119
+ OpenStruct.new(id: 'IvyBridge', name: 'IvyBridge'),
120
+ OpenStruct.new(id: 'Haswell', name: 'Haswell'),
121
+ OpenStruct.new(id: 'Haswell-noTSX', name: 'Haswell-noTSX'),
122
+ OpenStruct.new(id: 'Broadwell', name: 'Broadwell'),
123
+ OpenStruct.new(id: 'Broadwell-noTSX', name: 'Broadwell-noTSX'),
124
+ OpenStruct.new(id: 'Skylake-Client', name: 'Skylake-Client'),
125
+ OpenStruct.new(id: 'Opteron_G1', name: 'Opteron_G1'),
126
+ OpenStruct.new(id: 'Opteron_G2', name: 'Opteron_G2'),
127
+ OpenStruct.new(id: 'Opteron_G3', name: 'Opteron_G3'),
128
+ OpenStruct.new(id: 'Opteron_G4', name: 'Opteron_G4'),
129
+ OpenStruct.new(id: 'Opteron_G5', name: 'Opteron_G5'),
130
+ OpenStruct.new(id: 'host', name: 'host')]
131
+ end
132
+
133
+ def proxmox_scsihw_map
134
+ [OpenStruct.new(id: 'lsi', name: 'lsi'),
135
+ OpenStruct.new(id: 'lsi53c810', name: 'lsi53c810'),
136
+ OpenStruct.new(id: 'megasas', name: 'megasas'),
137
+ OpenStruct.new(id: 'virtio-scsi-pci', name: 'virtio-scsi-pci'),
138
+ OpenStruct.new(id: 'virtio-scsi-single', name: 'virtio-scsi-single'),
139
+ OpenStruct.new(id: 'pvscsi', name: 'pvscsi')]
140
+ end
141
+
142
+ def proxmox_networkcards_map
143
+ [OpenStruct.new(id: 'e1000', name: 'Intel E1000'),
144
+ OpenStruct.new(id: 'virtio', name: 'VirtIO (paravirtualized)'),
145
+ OpenStruct.new(id: 'rtl8139', name: 'Realtek RTL8139'),
146
+ OpenStruct.new(id: 'vmxnet3', name: 'VMware vmxnet3')]
147
+ end
148
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of ForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Disk
23
+ extend ActiveSupport::Concern
24
+ def templated?
25
+ volid ? volid.match(/^([\w-]+)[:]base-(\d+)-disk-(\d+)/) : false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Server
23
+ extend ActiveSupport::Concern
24
+ attr_accessor :image_id, :templated
25
+ def to_s
26
+ name
27
+ end
28
+ def persisted?
29
+ !!identity && !!uptime
30
+ end
31
+ def start
32
+ action('start')
33
+ end
34
+ def stop
35
+ action('stop')
36
+ end
37
+ def reboot
38
+ stop
39
+ start
40
+ end
41
+ def reset
42
+ reboot
43
+ end
44
+ def mac
45
+ config.mac_addresses.first
46
+ end
47
+ def memory
48
+ config.memory * 1024 * 1024
49
+ end
50
+ def state
51
+ qmpstatus
52
+ end
53
+ def description
54
+ config.description
55
+ end
56
+ def vm_description
57
+ "Name=#{name}, vmid=#{vmid}"
58
+ end
59
+ def select_nic(fog_nics, nic)
60
+ fog_nics.find {|fog_nic| fog_nic.identity.to_s == nic.identifier}
61
+ end
62
+ def interfaces
63
+ config.interfaces
64
+ end
65
+ def volumes
66
+ config.disks.reject { |disk| disk.cdrom? }
67
+ end
68
+ def disks
69
+ config.disks.collect { |disk| disk.id+': '+disk.volid }
70
+ end
71
+ def vga
72
+ config.vga
73
+ end
74
+ def interfaces_attributes=(attrs); end
75
+ def volumes_attributes=(attrs); end
76
+ def config_attributes=(attrs); end
77
+ def templated?
78
+ volumes.any? { |volume| volume.templated? }
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'fog/proxmox/helpers/cpu_helper'
21
+
22
+ module FogExtensions
23
+ module Proxmox
24
+ module ServerConfig
25
+ extend ActiveSupport::Concern
26
+ def cpu_type
27
+ Fog::Proxmox::CpuHelper.extract_type(cpu)
28
+ end
29
+ def spectre
30
+ Fog::Proxmox::CpuHelper.extract_spectre(cpu)
31
+ end
32
+ def pcid
33
+ Fog::Proxmox::CpuHelper.extract_pcid(cpu)
34
+ end
35
+ def cdrom
36
+ %w[none cdrom].include?(disks.cdrom.volid) ? disks.cdrom.volid : 'image'
37
+ end
38
+ def cdrom_storage
39
+ disks.cdrom.storage
40
+ end
41
+ def cdrom_iso
42
+ disks.cdrom.volid
43
+ end
44
+ def cdrom_image
45
+ %w[none cdrom].include?(disks.cdrom.volid) ? disks.cdrom.volid : 'image'
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module FogExtensions
21
+ module Proxmox
22
+ module Volume
23
+ extend ActiveSupport::Concern
24
+ def templated?
25
+ volid ? volid.match(/^([\w-]+)[:]base-(\d+)-disk-(\d+)/) : false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheForemanProxmox.
6
+
7
+ # TheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ module TheForemanProxmox
21
+ class OptionsSelect
22
+ attr_accessor :id
23
+ attr_accessor :name
24
+ attr_accessor :range
25
+
26
+ def to_s
27
+ id
28
+ end
29
+
30
+ def initialize(args = {})
31
+ @id = args[:id]
32
+ @name = args[:name]
33
+ @range = args[:range]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2018 Tristan Robert
4
+
5
+ # This file is part of TheTheForemanProxmox.
6
+
7
+ # TheTheForemanProxmox is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+
12
+ # TheTheForemanProxmox is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with TheTheForemanProxmox. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'fog/proxmox'
21
+
22
+ module TheForemanProxmox
23
+ class Proxmox < ComputeResource
24
+ include ProxmoxComputeHelper
25
+ validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
26
+ validates :user, :format => { :with => /(\w+)[@]{1}(\w+)/ }, :presence => true
27
+ validates :password, :presence => true
28
+ before_create :test_connection
29
+ attr_accessor :node
30
+
31
+ def provided_attributes
32
+ super.merge(
33
+ :mac => :mac
34
+ )
35
+ end
36
+
37
+ def self.provider_friendly_name
38
+ "Proxmox"
39
+ end
40
+
41
+ def capabilities
42
+ [:build, :new_volume, :image]
43
+ end
44
+
45
+ def self.model_name
46
+ ComputeResource.model_name
47
+ end
48
+
49
+ def credentials_valid?
50
+ errors[:url].empty? && errors[:user].empty? && errors[:user].include?('@') && errors[:password].empty? && node
51
+ end
52
+
53
+ def test_connection(options = {})
54
+ super
55
+ credentials_valid?
56
+ rescue => e
57
+ errors[:base] << e.message
58
+ errors[:url] << e.message
59
+ end
60
+
61
+ def nodes
62
+ nodes = client.nodes.all
63
+ nodes.sort_by(&:node)
64
+ end
65
+
66
+ def pools
67
+ pools = identity_client.pools.all
68
+ pools.sort_by(&:poolid)
69
+ end
70
+
71
+ def storages
72
+ storages = node.storages.list_by_content_type 'images'
73
+ storages.sort_by(&:storage)
74
+ end
75
+
76
+ def storages_isos
77
+ storages = node.storages.list_by_content_type 'iso'
78
+ storages.sort_by(&:storage)
79
+ end
80
+
81
+ def isos(storage_id)
82
+ storage = node.storages.find_by_id storage_id if storage_id
83
+ storage.volumes.list_by_content_type('iso').sort_by(&:volid) if storage
84
+ end
85
+
86
+ def associated_host(vm)
87
+ associate_by('mac', vm.mac)
88
+ end
89
+
90
+ def bridges
91
+ node = network_client.nodes.all.first
92
+ bridges = node.networks.all(type: 'bridge')
93
+ bridges.sort_by(&:iface)
94
+ end
95
+
96
+ def available_images
97
+ templates.collect { |template| OpenStruct.new(id: template.vmid) }
98
+ end
99
+
100
+ def templates
101
+ storage = storages.first
102
+ images = storage.volumes.list_by_content_type('images')
103
+ images.select { |image| image.templated? }
104
+ end
105
+
106
+ def template(vmid)
107
+ find_vm_by_uuid(vmid)
108
+ end
109
+
110
+ def host_compute_attrs(host)
111
+ super.tap do |attrs|
112
+ ostype = host.compute_attributes['config_attributes']['ostype']
113
+ raise Foreman::Exception.new("Operating system family #{host.operatingsystem.type} is not consistent with #{ostype}") unless compute_os_types(host).include?(ostype)
114
+ end
115
+ end
116
+
117
+ def host_interfaces_attrs(host)
118
+ host.interfaces.select(&:physical?).each.with_index.reduce({}) do |hash, (nic, index)|
119
+ raise ::Foreman::Exception.new N_("Identifier interface[#{index}] required.") if nic.identifier.empty?
120
+ raise ::Foreman::Exception.new N_("Invalid identifier interface[#{index}]. Must be net[n] with n integer >= 0") unless Fog::Proxmox::ControllerHelper.valid?(Fog::Compute::Proxmox::Interface::NAME,nic.identifier)
121
+ hash.merge(index.to_s => nic.compute_attributes.merge(id: nic.identifier, ip: nic.ip, ip6: nic.ip6))
122
+ end
123
+ end
124
+
125
+ def new_volume(attr = {})
126
+ opts = volume_defaults('scsi',1).merge(attr.to_h).deep_symbolize_keys
127
+ opts[:size] = opts[:size].to_s
128
+ Fog::Compute::Proxmox::Disk.new(opts)
129
+ end
130
+
131
+ def new_interface(attr = {})
132
+ opts = interface_defaults.merge(attr.to_h).deep_symbolize_keys
133
+ Fog::Compute::Proxmox::Interface.new(opts)
134
+ end
135
+
136
+ def vm_compute_attributes(vm)
137
+ vm_attrs = vm.attributes rescue {}
138
+ vm_attrs = vm_attrs.reject{|k,v| k == :id }
139
+ vm_attrs = set_vm_volumes_attributes(vm, vm_attrs)
140
+ vm_attrs = set_vm_interfaces_attributes(vm, vm_attrs)
141
+ vm_attrs
142
+ end
143
+
144
+ def set_vm_volumes_attributes(vm, vm_attrs)
145
+ if vm.config.respond_to?(:volumes)
146
+ volumes = vm.config.volumes || []
147
+ vm_attrs[:volumes_attributes] = Hash[volumes.each_with_index.map { |volume, idx| [idx.to_s, volume.attributes] }]
148
+ end
149
+ vm_attrs
150
+ end
151
+
152
+ def set_vm_interfaces_attributes(vm, vm_attrs)
153
+ if vm.config.respond_to?(:interfaces)
154
+ interfaces = vm.config.interfaces || []
155
+ vm_attrs[:interfaces_attributes] = Hash[interfaces.each_with_index.map { |interface, idx| [idx.to_s, interface.attributes] }]
156
+ end
157
+ vm_attrs
158
+ end
159
+
160
+ def new_vm(attr = {})
161
+ vm = node.servers.new(vm_instance_defaults.merge(parse_vm(attr)))
162
+ logger.debug("new_vm() vm.config=#{vm.config.inspect}")
163
+ vm
164
+ end
165
+
166
+ def create_vm(args = {})
167
+ vmid = args[:vmid]
168
+ raise ::Foreman::Exception.new N_("invalid vmid=#{vmid}") unless node.servers.id_valid?(vmid)
169
+ image_id = args[:image_id]
170
+ node = get_cluster_node args
171
+ if image_id
172
+ logger.debug("create_vm(): clone #{image_id} in #{vmid}")
173
+ image = node.servers.get image_id
174
+ image.clone(vmid)
175
+ else
176
+ logger.debug("create_vm(): #{args}")
177
+ convert_sizes(args)
178
+ node.servers.create(parse_vm(args))
179
+ end
180
+ vm = find_vm_by_uuid(vmid)
181
+ vm
182
+ rescue => e
183
+ logger.warn "failed to create vm: #{e}"
184
+ destroy_vm vm.id if vm
185
+ raise e
186
+ end
187
+
188
+ def find_vm_by_uuid(uuid)
189
+ node.servers.get(uuid)
190
+ rescue Fog::Errors::Error => e
191
+ Foreman::Logging.exception("Failed retrieving proxmox vm by vmid=#{uuid}", e)
192
+ raise(ActiveRecord::RecordNotFound)
193
+ end
194
+
195
+ def supports_update?
196
+ true
197
+ end
198
+
199
+ def update_required?(old_attrs, new_attrs)
200
+ return true if super(old_attrs, new_attrs)
201
+
202
+ new_attrs[:interfaces_attributes].each do |key, interface|
203
+ return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' #ignore the template
204
+ end if new_attrs[:interfaces_attributes]
205
+
206
+ new_attrs[:volumes_attributes].each do |key, volume|
207
+ return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' #ignore the template
208
+ end if new_attrs[:volumes_attributes]
209
+
210
+ false
211
+ end
212
+
213
+ def editable_network_interfaces?
214
+ true
215
+ end
216
+
217
+ def user_data_supported?
218
+ true
219
+ end
220
+
221
+ def image_exists?(image)
222
+ find_vm_by_uuid(image)
223
+ end
224
+
225
+ def save_vm(uuid, attr)
226
+ vm = find_vm_by_uuid(uuid)
227
+ logger.debug("save_vm(): #{attr}")
228
+ templated = attr[:templated]
229
+ if (templated == '1' && !vm.templated?)
230
+ vm.template
231
+ else
232
+ merged = vm.config.attributes.merge!(parse_vm(attr).symbolize_keys).deep_symbolize_keys
233
+ filtered = merged.reject { |key,value| %w[node vmid].include?(key) || [:templated,:image_id].include?(key) || value.to_s.empty? }
234
+ vm.update(filtered)
235
+ end
236
+ end
237
+
238
+ def next_vmid
239
+ node.servers.next_id
240
+ end
241
+
242
+ def node
243
+ @node ||= get_cluster_node
244
+ end
245
+
246
+ def ssl_certs
247
+ self.attrs[:ssl_certs]
248
+ end
249
+
250
+ def ssl_certs=(value)
251
+ self.attrs[:ssl_certs] = value
252
+ end
253
+
254
+ def certs_to_store
255
+ return if ssl_certs.blank?
256
+ store = OpenSSL::X509::Store.new
257
+ ssl_certs.split(/(?=-----BEGIN)/).each do |cert|
258
+ x509_cert = OpenSSL::X509::Certificate.new cert
259
+ store.add_cert x509_cert
260
+ end
261
+ store
262
+ rescue => e
263
+ logger.error(e)
264
+ raise ::Foreman::Exception.new N_("Unable to store X509 certificates")
265
+ end
266
+
267
+ def ssl_verify_peer
268
+ self.attrs[:ssl_verify_peer].blank? ? false : Foreman::Cast.to_bool(self.attrs[:ssl_verify_peer])
269
+ end
270
+
271
+ def ssl_verify_peer=(value)
272
+ self.attrs[:ssl_verify_peer] = value
273
+ end
274
+
275
+ def connection_options
276
+ opts = http_proxy ? {proxy: http_proxy.full_url} : {disable_proxy: 1}
277
+ opts.store(:ssl_verify_peer, ssl_verify_peer)
278
+ opts.store(:ssl_cert_store, certs_to_store) if Foreman::Cast.to_bool(ssl_verify_peer)
279
+ opts
280
+ end
281
+
282
+ def console(uuid)
283
+ vm = find_vm_by_uuid(uuid)
284
+ if vm.config.type_console == 'vnc'
285
+ vnc_console = vm.start_console(websocket: 1)
286
+ WsProxy.start(:host => host, :host_port => vnc_console['port'], :password => vnc_console['ticket']).merge(:name => vm.name, :type => vm.config.type_console)
287
+ else
288
+ raise ::Foreman::Exception.new(N_("%s console is not supported at this time"), vm.config.type_console)
289
+ end
290
+ end
291
+
292
+ private
293
+
294
+ def fog_credentials
295
+ { pve_url: url,
296
+ pve_username: user,
297
+ pve_password: password,
298
+ connection_options: connection_options }
299
+ end
300
+
301
+ def client
302
+ @client ||= ::Fog::Compute::Proxmox.new(fog_credentials)
303
+ end
304
+
305
+ def identity_client
306
+ @identity_client ||= ::Fog::Identity::Proxmox.new(fog_credentials)
307
+ end
308
+
309
+ def network_client
310
+ @network_client ||= ::Fog::Network::Proxmox.new(fog_credentials)
311
+ end
312
+
313
+ def disconnect
314
+ client.terminate if @client
315
+ @client = nil
316
+ identity_client.terminate if @identity_client
317
+ @identity_client = nil
318
+ network_client.terminate if @network_client
319
+ @network_client = nil
320
+ end
321
+
322
+ def vm_instance_defaults
323
+ super.merge(
324
+ vmid: next_vmid,
325
+ type: 'qemu',
326
+ node: node.to_s,
327
+ cores: 1,
328
+ sockets: 1,
329
+ kvm: 0,
330
+ vga: 'std',
331
+ memory: 512 * MEGA,
332
+ ostype: 'l26',
333
+ keyboard: 'en-us',
334
+ cpu: 'kvm64',
335
+ scsihw: 'virtio-scsi-pci',
336
+ ide2: "none,media=cdrom",
337
+ templated: 0
338
+ ).merge(Fog::Proxmox::DiskHelper.flatten(volume_defaults)).merge(Fog::Proxmox::NicHelper.flatten(interface_defaults))
339
+ end
340
+
341
+ def volume_defaults(controller = 'scsi', device = 0)
342
+ id = "#{controller}#{device}"
343
+ { id: id, storage: storages.first.to_s, size: (8 * GIGA), options: { cache: 'none' } }
344
+ end
345
+
346
+ def interface_defaults(id = 'net0')
347
+ { id: id, model: 'virtio', bridge: bridges.first.to_s }
348
+ end
349
+
350
+ def get_cluster_node(args = {})
351
+ test_connection
352
+ args.empty? ? client.nodes.first : client.nodes.find_by_id(args[:node]) if errors.empty?
353
+ end
354
+
355
+ def compute_os_types(host)
356
+ os_linux_types_mapping(host).empty? ? os_windows_types_mapping(host) : os_linux_types_mapping(host)
357
+ end
358
+
359
+ def available_operating_systems
360
+ operating_systems = %w[other solaris]
361
+ operating_systems += available_linux_operating_systems
362
+ operating_systems += available_windows_operating_systems
363
+ operating_systems
364
+ end
365
+
366
+ def available_linux_operating_systems
367
+ %w[l24 l26]
368
+ end
369
+
370
+ def available_windows_operating_systems
371
+ %w[wxp w2k w2k3 w2k8 wvista win7 win8 win10]
372
+ end
373
+
374
+ def os_linux_types_mapping(host)
375
+ %w[Debian Redhat Suse Altlinux Archlinux CoreOs Gentoo].include?(host.operatingsystem.type) ? available_linux_operating_systems : []
376
+ end
377
+
378
+ def os_windows_types_mapping(host)
379
+ %w[Windows].include?(host.operatingsystem.type) ? available_windows_operating_systems : []
380
+ end
381
+
382
+ def convert_sizes(args)
383
+ args['config_attributes']['memory'] = (args['config_attributes']['memory'].to_i / MEGA).to_s
384
+ args['config_attributes']['min_memory'] = (args['config_attributes']['min_memory'].to_i / MEGA).to_s
385
+ args['config_attributes']['shares'] = (args['config_attributes']['shares'].to_i / MEGA).to_s
386
+ args['volumes_attributes'].each_value { |value| value['size'] = (value['size'].to_i / GIGA).to_s }
387
+ end
388
+
389
+ def host
390
+ URI.parse(url).host
391
+ end
392
+
393
+ end
394
+ end