the_foreman_proxmox 0.3.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.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +102 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/the_foreman_proxmox/proxmox.js +110 -0
- data/app/controllers/concerns/the_foreman_proxmox/controller/parameters/compute_resource.rb +41 -0
- data/app/controllers/the_foreman_proxmox/compute_resources_controller.rb +38 -0
- data/app/helpers/proxmox_compute_helper.rb +154 -0
- data/app/helpers/proxmox_compute_selectors_helper.rb +148 -0
- data/app/models/concerns/fog_extensions/proxmox/disk.rb +29 -0
- data/app/models/concerns/fog_extensions/proxmox/server.rb +82 -0
- data/app/models/concerns/fog_extensions/proxmox/server_config.rb +49 -0
- data/app/models/concerns/fog_extensions/proxmox/volume.rb +29 -0
- data/app/models/the_foreman_proxmox/options_select.rb +36 -0
- data/app/models/the_foreman_proxmox/proxmox.rb +394 -0
- data/app/views/api/v2/compute_resources/proxmox.json.rabl +1 -0
- data/app/views/compute_resources/form/_proxmox.html.erb +24 -0
- data/app/views/compute_resources/show/_proxmox.html.erb +21 -0
- data/app/views/compute_resources_vms/form/proxmox/_base.html.erb +45 -0
- data/app/views/compute_resources_vms/form/proxmox/_config.html.erb +55 -0
- data/app/views/compute_resources_vms/form/proxmox/_network.html.erb +24 -0
- data/app/views/compute_resources_vms/form/proxmox/_volume.html.erb +23 -0
- data/app/views/compute_resources_vms/index/_proxmox.html.erb +45 -0
- data/app/views/compute_resources_vms/show/_proxmox.html.erb +36 -0
- data/app/views/dashboard/_the_foreman_proxmox_widget.erb +19 -0
- data/app/views/images/form/_proxmox.html.erb +4 -0
- data/config/routes.rb +24 -0
- data/lib/tasks/the_foreman_proxmox_tasks.rake +47 -0
- data/lib/the_foreman_proxmox.rb +23 -0
- data/lib/the_foreman_proxmox/engine.rb +92 -0
- data/lib/the_foreman_proxmox/version.rb +22 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_proxmox.po +19 -0
- data/locale/gemspec.rb +2 -0
- data/locale/the_foreman_proxmox.pot +19 -0
- data/test/factories/the_foreman_proxmox_factories.rb +33 -0
- data/test/helpers/proxmox_compute_helper_test.rb +130 -0
- data/test/test_plugin_helper.rb +15 -0
- data/test/unit/the_foreman_proxmox_test.rb +11 -0
- 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
|