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,70 @@
|
|
1
|
+
module ForemanAP
|
2
|
+
# A disk volume attached to a virtual machine.
|
3
|
+
class Volume
|
4
|
+
# The format of the volume. Currently, only :raw is supported.
|
5
|
+
def format
|
6
|
+
case @vol.info.type
|
7
|
+
when 0
|
8
|
+
:raw
|
9
|
+
else
|
10
|
+
raise 'unknown volume type: ' + @sph.info.type
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Delete the volume.
|
15
|
+
def delete
|
16
|
+
@vol.delete
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(pool, name)
|
21
|
+
@pool = pool
|
22
|
+
@name = name
|
23
|
+
@vol = pool.lookup_volume_by_name(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#--
|
29
|
+
## Legacy code below here
|
30
|
+
|
31
|
+
class ForemanVM
|
32
|
+
# Delete the disk volume associated with the VM
|
33
|
+
#
|
34
|
+
def delete_volume
|
35
|
+
virsh "vol-delete #{self.fqdn}-disk1 --pool #{@buildspec['storage_pool']}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Wipe an existing disk volume to fix the permissions
|
39
|
+
# This is sadly needed to get the uid:gid to be qemu:qemu
|
40
|
+
#
|
41
|
+
def wipe_volume
|
42
|
+
delete_volume
|
43
|
+
|
44
|
+
# BUG: We would like to do this, but it creates the file owned by root:root
|
45
|
+
#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"
|
46
|
+
#
|
47
|
+
# WORKAROUND: use an XML volume definition to set the owner/group
|
48
|
+
#
|
49
|
+
xml = "<volume>
|
50
|
+
<name>#{fqdn}-disk1</name>
|
51
|
+
<key>/gvol/images/#{fqdn}-disk1</key>
|
52
|
+
<source>
|
53
|
+
</source>
|
54
|
+
<capacity unit='bytes'>32212254720</capacity>
|
55
|
+
<allocation unit='bytes'>197120</allocation>
|
56
|
+
<target>
|
57
|
+
<path>/gvol/images/#{fqdn}-disk1</path>
|
58
|
+
<format type='raw'/>
|
59
|
+
<permissions>
|
60
|
+
<mode>0660</mode>
|
61
|
+
<owner>107</owner>
|
62
|
+
<group>107</group>
|
63
|
+
</permissions>
|
64
|
+
</target>
|
65
|
+
</volume>
|
66
|
+
"
|
67
|
+
@log.debug "creating volume: #{xml}"
|
68
|
+
virsh("vol-create --pool gvol --file /dev/stdin >/dev/null", xml)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ForemanAP
|
2
|
+
|
3
|
+
# Allow jobs to be placed on a workqueue and processed later.
|
4
|
+
class Workqueue
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'beaneater'
|
8
|
+
require 'json'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
def initialize(tube_name = 'foreman-vm')
|
12
|
+
@tube_name = tube_name
|
13
|
+
@beanstalk = Beaneater::Pool.new(['localhost:11300'])
|
14
|
+
@tube = @beanstalk.tubes[tube_name]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add an item to the queue
|
18
|
+
def enqueue(item)
|
19
|
+
@tube.put JSON.pretty_generate(item)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Remove a job from the queue, and return the body
|
23
|
+
def dequeue
|
24
|
+
job = @tube.reserve
|
25
|
+
result = JSON.parse(job.body)
|
26
|
+
job.delete
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove all jobs from the queue
|
31
|
+
def clear
|
32
|
+
@tube.clear
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return a list of all jobs
|
37
|
+
# (TODO: try to avoid leaking beanstalkd details)
|
38
|
+
def jobs
|
39
|
+
buf = "job stats:\n\n"
|
40
|
+
buf += @tube.stats.inspect + "\n\n\n"
|
41
|
+
buf
|
42
|
+
end
|
43
|
+
|
44
|
+
# Process all jobs
|
45
|
+
def process_all_jobs
|
46
|
+
@beanstalk.jobs.register(@tube_name) do |job|
|
47
|
+
yield JSON.parse(job.body)
|
48
|
+
end
|
49
|
+
|
50
|
+
@beanstalk.jobs.process!
|
51
|
+
end
|
52
|
+
|
53
|
+
# Check if there is a worker waiting for jobs
|
54
|
+
def worker?
|
55
|
+
@tube.stats.current_watching > 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
class ArchitectTest < Minitest::Test
|
9
|
+
require 'architect'
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def architect(config = nil)
|
14
|
+
if config.nil?
|
15
|
+
Architect.new
|
16
|
+
else
|
17
|
+
f = Tempfile.new('architect-test-config.yaml')
|
18
|
+
f.write(config.to_yaml)
|
19
|
+
f.close
|
20
|
+
File.chmod(0600, f.path)
|
21
|
+
Architect.new(conffile: f.path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
---
|
2
|
+
defaults:
|
3
|
+
environment: staging
|
4
|
+
hostgroup: staging/generic
|
5
|
+
domain: brontolabs.local
|
6
|
+
subnet: staging_200
|
7
|
+
network_interface: vnet0.200
|
8
|
+
cpus: 1
|
9
|
+
memory: 2G
|
10
|
+
disk_capacity: 20G,20G
|
11
|
+
storage_pool: gvol
|
12
|
+
owner: mark.heily
|
13
|
+
instances:
|
14
|
+
- product-stg-001:
|
15
|
+
memory: 2G
|
16
|
+
- product-stg-002:
|
17
|
+
memory: 2G
|
18
|
+
- productapi-stg-001:
|
19
|
+
memory: 2G
|
20
|
+
network_interface: vnet0.202
|
21
|
+
subnet: dmz_202
|
22
|
+
- productapi-stg-002:
|
23
|
+
memory: 2G
|
24
|
+
network_interface: vnet0.202
|
25
|
+
subnet: dmz_202
|
26
|
+
- productdb-stg-001:
|
27
|
+
memory: 4G
|
28
|
+
- productsearch-stg-001:
|
29
|
+
memory: 4G
|
30
|
+
disk_capacity: 20G,40G
|
31
|
+
- productsearch-stg-002:
|
32
|
+
memory: 4G
|
33
|
+
disk_capacity: 20G,40G
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Tests for the PhysicalMachineBuilder class
|
4
|
+
#
|
5
|
+
|
6
|
+
require_relative 'architect_test'
|
7
|
+
|
8
|
+
class TestPhysicalMachineBuilder < ArchitectTest
|
9
|
+
#def test_exists?
|
10
|
+
# spec = { instance_type: 'physical' }
|
11
|
+
# architect.builder(spec).exists? spec.keys[0]
|
12
|
+
#end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'architect_test'
|
4
|
+
|
5
|
+
class TestConfig < ArchitectTest
|
6
|
+
|
7
|
+
def test_initialize_from_hash
|
8
|
+
cfg = Architect::Config.new({ 'domain' => 'foo' } )
|
9
|
+
assert_equal 'foo', cfg.domain
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_initialize_from_file
|
13
|
+
f = Tempfile.new('test_register.yaml')
|
14
|
+
f.write({ 'domain' => 'foo', 'plugins' => { 'hello_world' => {} } }.to_yaml)
|
15
|
+
f.close
|
16
|
+
File.chmod(0600, f.path)
|
17
|
+
cfg = Architect::Config.new(f.path)
|
18
|
+
assert_equal 'foo', cfg.domain
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
class TestLDAPNetGroupPlugin < MiniTest::Test
|
6
|
+
require 'architect/plugin/ldap_netgroup'
|
7
|
+
|
8
|
+
def test_name
|
9
|
+
assert_equal 'ldap_netgroup', plugin.name
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_configure
|
13
|
+
@plugin = LDAPNetgroupPlugin.new
|
14
|
+
plugin.configure({
|
15
|
+
host: 'localhost',
|
16
|
+
port: '10389',
|
17
|
+
bind_dn: 'cn=foreman-vm',
|
18
|
+
bind_password: 'password123',
|
19
|
+
base_dn: 'ou=Netgroup,dc=example,dc=com',
|
20
|
+
nis_domain: 'example.com',
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@plugin = LDAPNetgroupPlugin.new
|
26
|
+
plugin.configure({
|
27
|
+
host: nil,
|
28
|
+
port: nil,
|
29
|
+
bind_dn: nil,
|
30
|
+
bind_password: nil,
|
31
|
+
base_dn: nil,
|
32
|
+
nis_domain: nil,
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_accessor :plugin
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative 'architect_test'
|
4
|
+
|
5
|
+
class TestPluginManager < ArchitectTest
|
6
|
+
require 'architect/plugin_manager'
|
7
|
+
|
8
|
+
MOCK_CONFIG = {
|
9
|
+
'hello_world' => {
|
10
|
+
hello: 'bar',
|
11
|
+
quiet: 'true',
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
def test_initialize
|
16
|
+
pm = Architect::PluginManager.new(MOCK_CONFIG)
|
17
|
+
refute_nil pm
|
18
|
+
refute_nil pm.plugins.hello_world
|
19
|
+
end
|
20
|
+
|
21
|
+
# Test if the plugin can be configured
|
22
|
+
def test_configure
|
23
|
+
pm = Architect::PluginManager.new(MOCK_CONFIG)
|
24
|
+
pm.plugins.hello_world.configure(MOCK_CONFIG['hello_world'])
|
25
|
+
assert_equal 'bar', pm.plugins.hello_world.config.hello
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'foreman_vm/allocator'
|
5
|
+
|
6
|
+
class TestCluster < Minitest::Test
|
7
|
+
|
8
|
+
# A mockup of the class we want to write
|
9
|
+
#
|
10
|
+
# Goals:
|
11
|
+
# * Allocate new VMs on the hosts with the most free memory
|
12
|
+
# * Do not allocate all instances of a VM type
|
13
|
+
# on the same hypervisor. Example: if there is only one hypervisor
|
14
|
+
# with a VM named 'web1', do not create a VM named 'web2' here.
|
15
|
+
#
|
16
|
+
# * (Stretch goal) Try to spread out VMs across multiple hosts where possible
|
17
|
+
#
|
18
|
+
|
19
|
+
# Try to add a host
|
20
|
+
def test_add_host
|
21
|
+
alloc = ForemanAP::Allocator.new
|
22
|
+
assert alloc.add_host('host1', 1024, ['guest1', 'guest2'])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Try to add a guest
|
26
|
+
def test_alloc_guest
|
27
|
+
alloc = ForemanAP::Allocator.new
|
28
|
+
assert(alloc.add_host('host1', 1024, ['guest1', 'guest2']))
|
29
|
+
assert(alloc.add_guest('guest3', 512))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Try to add a guest exceeds free memory of host
|
33
|
+
def test_unable_to_add_guest
|
34
|
+
alloc = ForemanAP::Allocator.new
|
35
|
+
assert(alloc.add_host('host1', 1024, ['guest1', 'guest2']))
|
36
|
+
assert_nil(alloc.add_guest('guest3', 4096))
|
37
|
+
end
|
38
|
+
|
39
|
+
# Verify guest is put on host with most free memory
|
40
|
+
def test_free_memory
|
41
|
+
alloc = ForemanAP::Allocator.new
|
42
|
+
assert(alloc.add_host('host1', 2048, ['a1', 'a2']))
|
43
|
+
assert(alloc.add_host('host2', 1024, ['a1', 'a2']))
|
44
|
+
assert_equal('host1', alloc.add_guest('b1', 512))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Try to add guest that already exists
|
48
|
+
def test_duplicate_guest
|
49
|
+
alloc = ForemanAP::Allocator.new
|
50
|
+
assert(alloc.add_host('host1', 2048, ['guest1', 'guest2']))
|
51
|
+
assert_nil(alloc.add_guest('guest2', 512))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Ensure VM Instance of similar type is spread out to different hypervisors
|
55
|
+
def test_affinity
|
56
|
+
alloc = ForemanAP::Allocator.new
|
57
|
+
assert(alloc.add_host('host1', 2048, ['guest1', 'guest2']))
|
58
|
+
assert(alloc.add_host('host2', 4096, ['guest1', 'web1']))
|
59
|
+
assert_equal('host1', alloc.add_guest('web2', 512))
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
class TestBuildspec < MiniTest::Test
|
6
|
+
require 'foreman_vm/buildspec'
|
7
|
+
|
8
|
+
def test_initialize
|
9
|
+
refute ForemanAP::BuildSpec.new.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_assignment
|
13
|
+
spec = ForemanAP::BuildSpec.new
|
14
|
+
assert_equal('hostname', spec.name = 'hostname')
|
15
|
+
assert_equal(1, spec.cpus = 1)
|
16
|
+
assert_equal('4G', spec.memory = '4G')
|
17
|
+
assert_equal('20G,40G', spec.disk_capacity = '20G,40G')
|
18
|
+
assert_equal('raw', spec.disk_format = 'raw')
|
19
|
+
assert_equal('default', spec.storage_pool = 'default')
|
20
|
+
assert_equal('acme.com', spec.domain = 'acme.com')
|
21
|
+
assert_equal('vnet0.101', spec.network_interface = 'vnet0.101')
|
22
|
+
end
|
23
|
+
|
24
|
+
#def test_fancy
|
25
|
+
# spec = ForemanAP::BuildSpec.new
|
26
|
+
# spec.specify do
|
27
|
+
# hostname 'foo'
|
28
|
+
# end
|
29
|
+
# assert_equal('foo', spec.name)
|
30
|
+
#end
|
31
|
+
|
32
|
+
# Test the generation of the 'volumes_attributes' Foreman API section
|
33
|
+
def test_volumes_attributes
|
34
|
+
spec = ForemanAP::BuildSpec.new
|
35
|
+
spec.disk_format = 'raw'
|
36
|
+
spec.disk_capacity = '20G,40G'
|
37
|
+
spec.storage_pool = 'default'
|
38
|
+
assert_equal({
|
39
|
+
'volumes_attributes'=> {
|
40
|
+
'1'=>{'format_type'=>'raw', 'pool_name'=>'default', 'capacity'=>'40G'},
|
41
|
+
'0'=>{'format_type'=>'raw', 'pool_name'=>'default', 'capacity'=>'20G'}
|
42
|
+
}
|
43
|
+
}, spec.to_foreman_api)
|
44
|
+
end
|
45
|
+
end
|
data/test/tc_cluster.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
|
5
|
+
class TestCluster < MiniTest::Test
|
6
|
+
require 'foreman_vm'
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@config = ForemanAP::Config.new
|
10
|
+
@cluster = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_initialize
|
14
|
+
@cluster = ForemanAP::Cluster.new(
|
15
|
+
@config.hypervisors,
|
16
|
+
@config.libvirt_user,
|
17
|
+
@config.libvirt_password)
|
18
|
+
assert_not_nil @cluster
|
19
|
+
end
|
20
|
+
end
|