foreman-architect 0.1.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.
Files changed (50) hide show
  1. data/bin/architect +147 -0
  2. data/bin/foreman-vm +50 -0
  3. data/bin/worker.rb +101 -0
  4. data/lib/architect.rb +49 -0
  5. data/lib/architect/builder/physical.rb +19 -0
  6. data/lib/architect/builder/virtual.rb +27 -0
  7. data/lib/architect/config.rb +64 -0
  8. data/lib/architect/designer.rb +73 -0
  9. data/lib/architect/log.rb +28 -0
  10. data/lib/architect/plan.rb +41 -0
  11. data/lib/architect/plugin.rb +67 -0
  12. data/lib/architect/plugin/hello_world.rb +46 -0
  13. data/lib/architect/plugin/ldap_netgroup.rb +114 -0
  14. data/lib/architect/plugin_manager.rb +64 -0
  15. data/lib/architect/report.rb +67 -0
  16. data/lib/architect/version.rb +3 -0
  17. data/lib/foreman_vm.rb +409 -0
  18. data/lib/foreman_vm/allocator.rb +49 -0
  19. data/lib/foreman_vm/buildspec.rb +48 -0
  20. data/lib/foreman_vm/cluster.rb +83 -0
  21. data/lib/foreman_vm/config.rb +55 -0
  22. data/lib/foreman_vm/console.rb +83 -0
  23. data/lib/foreman_vm/domain.rb +192 -0
  24. data/lib/foreman_vm/foreman_api.rb +78 -0
  25. data/lib/foreman_vm/getopt.rb +151 -0
  26. data/lib/foreman_vm/hypervisor.rb +96 -0
  27. data/lib/foreman_vm/storage_pool.rb +104 -0
  28. data/lib/foreman_vm/util.rb +18 -0
  29. data/lib/foreman_vm/volume.rb +70 -0
  30. data/lib/foreman_vm/workqueue.rb +58 -0
  31. data/test/architect/architect_test.rb +24 -0
  32. data/test/architect/product_service.yaml +33 -0
  33. data/test/architect/tc_builder_physical.rb +13 -0
  34. data/test/architect/tc_config.rb +20 -0
  35. data/test/architect/tc_log.rb +13 -0
  36. data/test/architect/tc_plugin_ldap_netgroup.rb +39 -0
  37. data/test/architect/tc_plugin_manager.rb +27 -0
  38. data/test/tc_allocator.rb +61 -0
  39. data/test/tc_buildspec.rb +45 -0
  40. data/test/tc_cluster.rb +20 -0
  41. data/test/tc_config.rb +12 -0
  42. data/test/tc_foreman_api.rb +20 -0
  43. data/test/tc_foremanvm.rb +20 -0
  44. data/test/tc_hypervisor.rb +37 -0
  45. data/test/tc_main.rb +19 -0
  46. data/test/tc_storage_pool.rb +28 -0
  47. data/test/tc_volume.rb +22 -0
  48. data/test/tc_workqueue.rb +35 -0
  49. data/test/ts_all.rb +13 -0
  50. 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