the_foreman_proxmox 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|