foreman-architect 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/architect +147 -0
- data/bin/foreman-vm +50 -0
- data/bin/worker.rb +101 -0
- data/lib/architect.rb +49 -0
- data/lib/architect/builder/physical.rb +19 -0
- data/lib/architect/builder/virtual.rb +27 -0
- data/lib/architect/config.rb +64 -0
- data/lib/architect/designer.rb +73 -0
- data/lib/architect/log.rb +28 -0
- data/lib/architect/plan.rb +41 -0
- data/lib/architect/plugin.rb +67 -0
- data/lib/architect/plugin/hello_world.rb +46 -0
- data/lib/architect/plugin/ldap_netgroup.rb +114 -0
- data/lib/architect/plugin_manager.rb +64 -0
- data/lib/architect/report.rb +67 -0
- data/lib/architect/version.rb +3 -0
- data/lib/foreman_vm.rb +409 -0
- data/lib/foreman_vm/allocator.rb +49 -0
- data/lib/foreman_vm/buildspec.rb +48 -0
- data/lib/foreman_vm/cluster.rb +83 -0
- data/lib/foreman_vm/config.rb +55 -0
- data/lib/foreman_vm/console.rb +83 -0
- data/lib/foreman_vm/domain.rb +192 -0
- data/lib/foreman_vm/foreman_api.rb +78 -0
- data/lib/foreman_vm/getopt.rb +151 -0
- data/lib/foreman_vm/hypervisor.rb +96 -0
- data/lib/foreman_vm/storage_pool.rb +104 -0
- data/lib/foreman_vm/util.rb +18 -0
- data/lib/foreman_vm/volume.rb +70 -0
- data/lib/foreman_vm/workqueue.rb +58 -0
- data/test/architect/architect_test.rb +24 -0
- data/test/architect/product_service.yaml +33 -0
- data/test/architect/tc_builder_physical.rb +13 -0
- data/test/architect/tc_config.rb +20 -0
- data/test/architect/tc_log.rb +13 -0
- data/test/architect/tc_plugin_ldap_netgroup.rb +39 -0
- data/test/architect/tc_plugin_manager.rb +27 -0
- data/test/tc_allocator.rb +61 -0
- data/test/tc_buildspec.rb +45 -0
- data/test/tc_cluster.rb +20 -0
- data/test/tc_config.rb +12 -0
- data/test/tc_foreman_api.rb +20 -0
- data/test/tc_foremanvm.rb +20 -0
- data/test/tc_hypervisor.rb +37 -0
- data/test/tc_main.rb +19 -0
- data/test/tc_storage_pool.rb +28 -0
- data/test/tc_volume.rb +22 -0
- data/test/tc_workqueue.rb +35 -0
- data/test/ts_all.rb +13 -0
- metadata +226 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
module ForemanAP
|
2
|
+
# Convenience methods for accessing the REST API of Foreman.
|
3
|
+
#
|
4
|
+
class ForemanAPI
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
require 'net/https'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
attr_accessor :log
|
11
|
+
|
12
|
+
# Create an object.
|
13
|
+
# [+uri+] The URI of the Foreman server.
|
14
|
+
# [+user+] The username to login with.
|
15
|
+
# [+password+] The password to login with.
|
16
|
+
def initialize(uri, user, password)
|
17
|
+
@uri = uri
|
18
|
+
@user = user
|
19
|
+
@password = password
|
20
|
+
@log = Logger.new(STDERR)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Send a request to the Foreman API.
|
24
|
+
# [+method+] The HTTP method; can be :post, :put, :get, or :delete
|
25
|
+
# [+path+] The path to the resource, underneath of /api/v2.
|
26
|
+
# [+payload+] The data to provide along with the request.
|
27
|
+
def request(method, path, payload)
|
28
|
+
uri = URI.parse(@uri)
|
29
|
+
path = '/api/v2/' + path
|
30
|
+
|
31
|
+
@log.debug "getting #{path}"
|
32
|
+
# Build the request
|
33
|
+
case method
|
34
|
+
when :post
|
35
|
+
req = Net::HTTP::Post.new(path, initheader = {'Content-Type' =>'application/json'})
|
36
|
+
when :put
|
37
|
+
req = Net::HTTP::Put.new(path, initheader = {'Content-Type' =>'application/json'})
|
38
|
+
when :get
|
39
|
+
req = Net::HTTP::Get.new(path, initheader = {'Content-Type' =>'application/json'})
|
40
|
+
when :delete
|
41
|
+
req = Net::HTTP::Delete.new(path, initheader = {'Content-Type' =>'application/json'})
|
42
|
+
else
|
43
|
+
raise 'Unsuported method'
|
44
|
+
end
|
45
|
+
|
46
|
+
req.basic_auth @user, @password
|
47
|
+
req.body = payload.to_json
|
48
|
+
|
49
|
+
# Submit the request
|
50
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
51
|
+
http.use_ssl = true
|
52
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE #XXX-FIXME insecure
|
53
|
+
response = http.start { |h| http.request(req) }
|
54
|
+
|
55
|
+
@log.debug "Response #{response.code} #{response.message}: #{response.body}"
|
56
|
+
|
57
|
+
if response.code != '200'
|
58
|
+
@log.error "Error #{response.code} calling the Foreman API: #{response.body}"
|
59
|
+
raise 'Error calling the Foreman API'
|
60
|
+
end
|
61
|
+
|
62
|
+
return response
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the ID of a resource in Foreman.
|
66
|
+
# [+path+] The path to the resource, underneath of /api/v2
|
67
|
+
# [+name+] The name to lookup
|
68
|
+
# [+key+] Either 'name' or 'title'. Don't ask why there is a difference.
|
69
|
+
#
|
70
|
+
def get_id(path, name, key='name')
|
71
|
+
raise 'name is required' if name.nil?
|
72
|
+
res = request(:get, "/#{path}?search=#{key}%20=%20\"#{URI.escape(name)}\"", {})
|
73
|
+
id = JSON.parse(res.body)['results'][0]['id']
|
74
|
+
raise 'Unable to get the ID for #{path}/#{key}=#{name}' if id.nil?
|
75
|
+
id
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
class ForemanVM
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
# Parse command line options
|
5
|
+
#
|
6
|
+
def parse_options
|
7
|
+
optparse = OptionParser.new do |opts|
|
8
|
+
|
9
|
+
opts.on( '--user USER', 'The username to login to Foreman with') do |arg|
|
10
|
+
@user = arg
|
11
|
+
end
|
12
|
+
|
13
|
+
opts.on( '--password PASSWORD', 'The password to login to Foreman with') do |arg|
|
14
|
+
@password = arg
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on( '--password-file FILE', 'The file containing your Foreman password') do |arg|
|
18
|
+
raise 'File does not exist' unless File.exists? arg
|
19
|
+
@password = `cat #{arg}`.chomp
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on( '--foreman-uri URI', 'The URI of the Foreman password') do |arg|
|
23
|
+
@foreman_uri = arg
|
24
|
+
#options['foreman_uri'] = arg
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
opts.separator ""
|
29
|
+
opts.separator "Specific options for building hosts:"
|
30
|
+
|
31
|
+
opts.on( '--console', 'Attach to the virtual machine console') do |arg|
|
32
|
+
@buildspec['console'] = true
|
33
|
+
@action = 'console' if @action.nil? #XXX-KLUDGE for testing
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on( '--cpus CPUS', 'The number of CPUs to assign to the VM') do |arg|
|
37
|
+
@buildspec['cpus'] = arg
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on( '--memory BYTES', 'The amount of memory, in bytes, to assign to the VM') do |arg|
|
41
|
+
@buildspec['memory'] = arg
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on( '--domain DOMAIN', 'The DNS domain name to use') do |arg|
|
45
|
+
@buildspec['domain'] = arg
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on( '--organization ORG', 'The organization name in Foreman') do |arg|
|
49
|
+
@buildspec['organization'] = arg
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on( '--owner OWNER', 'The owner name in Foreman') do |arg|
|
53
|
+
@buildspec['owner'] = arg
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on( '--hostgroup HOSTGROUP', 'The Foreman hostgroup') do |arg|
|
57
|
+
@buildspec['hostgroup'] = arg
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on( '--compute-resource RESOURCE', 'The Foreman compute resource') do |arg|
|
61
|
+
@buildspec['compute_resource'] = arg
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on( '--provision-method METHOD', 'The provisioning method (image or network)') do |arg|
|
65
|
+
@buildspec['provision_method'] = arg
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on( '--environment ENVIRONMENT', 'The Puppet environment') do |arg|
|
69
|
+
@buildspec['environment'] = arg
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on( '--network-interface INTERFACE', 'The network interface') do |arg|
|
73
|
+
@buildspec['network_interface'] = arg
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on( '--disk-capacity SIZE', 'The size of the first disk, using M or G suffixes') do |arg|
|
77
|
+
@buildspec['disk_capacity'] = arg
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on( '--disk-format FORMAT', 'The format of the disk. Can be raw or qcow2') do |arg|
|
81
|
+
@buildspec['disk_format'] = arg
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on( '--storage-pool POOL', 'The storage pool') do |arg|
|
85
|
+
@buildspec['storage_pool'] = arg
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on( '--subnet SUBNET', 'The subnet') do |arg|
|
89
|
+
@buildspec['subnet'] = arg
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO: some nice header thing
|
93
|
+
|
94
|
+
opts.separator ""
|
95
|
+
opts.separator "Actions:"
|
96
|
+
|
97
|
+
opts.on( '--rebuild', 'Rebuild the host') do |arg|
|
98
|
+
@action = 'rebuild'
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on( '--create', 'Create the host') do |arg|
|
102
|
+
@action = 'create'
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on( '--create-storage', 'Create storage and attach it to the host') do |arg|
|
106
|
+
@action = 'create-storage'
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
opts.on( '--delete', 'Delete the host') do |arg|
|
111
|
+
@action = 'delete'
|
112
|
+
end
|
113
|
+
|
114
|
+
opts.on( '--start', 'Power on the VM') do |arg|
|
115
|
+
@action = 'start'
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on( '--stop', 'Power off the VM, without doing a graceful shutdown') do |arg|
|
119
|
+
@action = 'stop'
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.on( '--dumpxml', 'Show the libvirt XML virtual machine definition') do |arg|
|
123
|
+
@action = 'dumpxml'
|
124
|
+
end
|
125
|
+
|
126
|
+
opts.on( '--monitor-boot', 'Watch the console of a VM as it boots') do |arg|
|
127
|
+
@action = 'monitor-boot'
|
128
|
+
end
|
129
|
+
|
130
|
+
opts.on( '--enable-libgfapi', 'Enable GlusterFS libgfapi') do |arg|
|
131
|
+
@action = 'enable-libgfapi'
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.on( '--disable-libgfapi', 'Disable GlusterFS libgfapi') do |arg|
|
135
|
+
@action = 'disable-libgfapi'
|
136
|
+
end
|
137
|
+
|
138
|
+
## Verbosity (DEBUG, WARN, INFO, etc.)
|
139
|
+
##log_level="DEBUG"
|
140
|
+
|
141
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
142
|
+
puts opts
|
143
|
+
exit
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
optparse.parse!
|
148
|
+
|
149
|
+
ask_password if @password.nil?
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module ForemanAP
|
2
|
+
# A host in the cluster that runs a hypervisor.
|
3
|
+
class Hypervisor
|
4
|
+
require 'foreman_vm/storage_pool'
|
5
|
+
|
6
|
+
# The total number of CPUs (physical and virtual)
|
7
|
+
def cpus
|
8
|
+
nodeinfo.cpus
|
9
|
+
end
|
10
|
+
|
11
|
+
# The total amount of memory, in bytes.
|
12
|
+
def memory
|
13
|
+
nodeinfo.memory * 1024 # Convert from KiB
|
14
|
+
end
|
15
|
+
|
16
|
+
# The amount of free memory, in bytes.
|
17
|
+
def free_memory
|
18
|
+
@conn.node_free_memory
|
19
|
+
end
|
20
|
+
|
21
|
+
# Lookup a storage pool by name.
|
22
|
+
def storage_pool(name)
|
23
|
+
StoragePool.new(@conn, name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the name of hypervisor
|
27
|
+
def hostname
|
28
|
+
@conn.hostname
|
29
|
+
end
|
30
|
+
|
31
|
+
# A guest running on the hypervisor
|
32
|
+
# [+fqdn+] the FQDN of the guest
|
33
|
+
def guest(fqdn)
|
34
|
+
ForemanAP::Domain.new(@conn, fqdn, @foreman_api)
|
35
|
+
end
|
36
|
+
|
37
|
+
# The names of all the domains defined on the hypervisor.
|
38
|
+
def domains
|
39
|
+
res = []
|
40
|
+
|
41
|
+
# Active domains
|
42
|
+
@conn.list_domains.each do |domid|
|
43
|
+
dom = @conn.lookup_domain_by_id(domid)
|
44
|
+
res.push dom.name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Inactive domains
|
48
|
+
@conn.list_defined_domains.each do |domname|
|
49
|
+
res.push domname
|
50
|
+
end
|
51
|
+
|
52
|
+
res
|
53
|
+
end
|
54
|
+
|
55
|
+
# A list of handles to all the active domains on the hypervisor
|
56
|
+
# TODO - replace #domains with this
|
57
|
+
def domains2
|
58
|
+
res = []
|
59
|
+
|
60
|
+
# Active domains
|
61
|
+
@conn.list_domains.each do |domid|
|
62
|
+
dom = @conn.lookup_domain_by_id(domid)
|
63
|
+
res.push ForemanAP::Domain.new(@conn, dom.name, @foreman_api)
|
64
|
+
end
|
65
|
+
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create an object.
|
70
|
+
#
|
71
|
+
# [+uri+] The libvirt URI. Example: qemu+tcp:///host/system
|
72
|
+
# [+user+] The username to login with.
|
73
|
+
# [+passphrase+] The passphrase to login with.
|
74
|
+
def initialize(uri, user, passphrase)
|
75
|
+
@uri = uri
|
76
|
+
@conn = Libvirt::open_auth(@uri,
|
77
|
+
[Libvirt::CRED_AUTHNAME, Libvirt::CRED_PASSPHRASE]) do |cred|
|
78
|
+
case cred["type"]
|
79
|
+
when Libvirt::CRED_AUTHNAME
|
80
|
+
user
|
81
|
+
when Libvirt::CRED_PASSPHRASE
|
82
|
+
passphrase
|
83
|
+
else
|
84
|
+
raise 'Unknown error'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# The 'nodeinfo' structure for the connection
|
92
|
+
def nodeinfo
|
93
|
+
@nodeinfo ||= @conn.node_get_info
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ForemanAP
|
2
|
+
# A storage pool containing disk volumes.
|
3
|
+
class StoragePool
|
4
|
+
require 'foreman_vm/volume'
|
5
|
+
|
6
|
+
# Scan the pool for changes. This is useful for pools that are shared
|
7
|
+
# across multiple hosts in a cluster.
|
8
|
+
def refresh
|
9
|
+
@sph.refresh
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# Lookup a volume by +name+, and return an object handle.
|
14
|
+
def volume(name)
|
15
|
+
Volume.new(@sph, name)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a new object.
|
19
|
+
#
|
20
|
+
# [+conn+] A libvirt connection handle.
|
21
|
+
# [+name+] The name of the storage pool.
|
22
|
+
#
|
23
|
+
def initialize(conn, name)
|
24
|
+
@sph = conn.lookup_storage_pool_by_name(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a new volume within the storage pool.
|
28
|
+
# [+path+] the full path to the volume
|
29
|
+
# [+capacity+] the disk capacity, in bytes
|
30
|
+
#
|
31
|
+
def create_volume(path,capacity)
|
32
|
+
xml = "<volume>
|
33
|
+
<name>#{File.basename(path)}</name>
|
34
|
+
<key>#{path}</key>
|
35
|
+
<source>
|
36
|
+
</source>
|
37
|
+
<capacity unit='bytes'>#{capacity}</capacity>
|
38
|
+
<allocation unit='bytes'>197120</allocation>
|
39
|
+
<target>
|
40
|
+
<path>#{path}</path>
|
41
|
+
<format type='raw'/>
|
42
|
+
<permissions>
|
43
|
+
<mode>0660</mode>
|
44
|
+
<owner>107</owner>
|
45
|
+
<group>107</group>
|
46
|
+
</permissions>
|
47
|
+
</target>
|
48
|
+
</volume>
|
49
|
+
"
|
50
|
+
#TODO: @log.debug "creating volume: #{xml}"
|
51
|
+
@sph.create_volume_xml(xml)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#--
|
57
|
+
## Legacy code below here
|
58
|
+
|
59
|
+
class ForemanVM
|
60
|
+
|
61
|
+
# Copy an existing disk volume to reduce the build time
|
62
|
+
#
|
63
|
+
def copy_volume
|
64
|
+
delete_volume
|
65
|
+
|
66
|
+
virsh "vol-clone --pool #{@buildspec['storage_pool']} --vol #{@buildspec['_disk_backing_file']} --newname #{fqdn()}-disk1"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clone an existing disk volume to reduce the build time
|
70
|
+
#
|
71
|
+
def clone_volume
|
72
|
+
delete_volume
|
73
|
+
|
74
|
+
# BUG: We would like to do this, but it creates the file owned by root:root
|
75
|
+
#virsh "vol-create-as --pool #{@buildspec['storage_pool']} --name #{fqdn()}-disk1 --capacity 30G --format qcow2 --backing-vol #{@buildspec['_disk_backing_file']} --backing-vol-format qcow2"
|
76
|
+
#
|
77
|
+
# WORKAROUND: use an XML volume definition to set the owner/group
|
78
|
+
#
|
79
|
+
xml = "<volume>
|
80
|
+
<name>#{fqdn}-disk1</name>
|
81
|
+
<key>/gvol/images/#{fqdn}-disk1</key>
|
82
|
+
<source>
|
83
|
+
</source>
|
84
|
+
<capacity unit='bytes'>32212254720</capacity>
|
85
|
+
<allocation unit='bytes'>197120</allocation>
|
86
|
+
<target>
|
87
|
+
<path>/gvol/images/#{fqdn}-disk1</path>
|
88
|
+
<format type='qcow2'/>
|
89
|
+
<permissions>
|
90
|
+
<mode>0660</mode>
|
91
|
+
<owner>107</owner>
|
92
|
+
<group>107</group>
|
93
|
+
</permissions>
|
94
|
+
</target>
|
95
|
+
<backingStore>
|
96
|
+
<path>#{@buildspec['_disk_backing_file']}</path>
|
97
|
+
<format type='qcow2'/>
|
98
|
+
</backingStore>
|
99
|
+
</volume>
|
100
|
+
"
|
101
|
+
@log.debug "creating volume: #{xml}"
|
102
|
+
virsh("vol-create --pool gvol --file /dev/stdin >/dev/null", xml)
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Miscellaneous utility functions
|
2
|
+
module FVMUtil
|
3
|
+
# Given an FQDN, return the shortname
|
4
|
+
def shortname(fqdn)
|
5
|
+
fqdn.sub(/\..*/, '')
|
6
|
+
end
|
7
|
+
|
8
|
+
# Given an amount in bytes, return the human-readable version in GiB
|
9
|
+
def gigabytes(s)
|
10
|
+
if s.to_i < 1073741824
|
11
|
+
mb = s.to_f / 1024 / 1024
|
12
|
+
sprintf '%0.0f MiB', mb
|
13
|
+
else
|
14
|
+
gb = s.to_f / 1024 / 1024 / 1024
|
15
|
+
sprintf '%0.0f GiB', gb
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|