foreman-architect 0.1.0

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