the-maestro 0.2.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.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/LICENSE +23 -0
- data/README.rdoc +378 -0
- data/Rakefile +116 -0
- data/VERSION +1 -0
- data/lib/maestro.rb +354 -0
- data/lib/maestro/cloud.rb +384 -0
- data/lib/maestro/cloud/aws.rb +1231 -0
- data/lib/maestro/dsl_property.rb +15 -0
- data/lib/maestro/log4r/console_formatter.rb +18 -0
- data/lib/maestro/log4r/file_formatter.rb +24 -0
- data/lib/maestro/node.rb +123 -0
- data/lib/maestro/operating_system.rb +53 -0
- data/lib/maestro/operating_system/cent_os.rb +23 -0
- data/lib/maestro/operating_system/debian.rb +40 -0
- data/lib/maestro/operating_system/fedora.rb +23 -0
- data/lib/maestro/operating_system/ubuntu.rb +100 -0
- data/lib/maestro/role.rb +36 -0
- data/lib/maestro/tasks.rb +52 -0
- data/lib/maestro/validator.rb +32 -0
- data/rails/init.rb +1 -0
- data/test/integration/base_aws.rb +156 -0
- data/test/integration/fixtures/config/maestro/cookbooks/emacs/metadata.json +41 -0
- data/test/integration/fixtures/config/maestro/cookbooks/emacs/metadata.rb +3 -0
- data/test/integration/fixtures/config/maestro/cookbooks/emacs/recipes/default.rb +21 -0
- data/test/integration/fixtures/config/maestro/roles/default.json +9 -0
- data/test/integration/fixtures/config/maestro/roles/web.json +9 -0
- data/test/integration/helper.rb +8 -0
- data/test/integration/test_aws_cloud.rb +805 -0
- data/test/integration/test_cent_os.rb +104 -0
- data/test/integration/test_debian.rb +119 -0
- data/test/integration/test_fedora.rb +104 -0
- data/test/integration/test_ubuntu.rb +149 -0
- data/test/unit/fixtures/invalid-clouds-not-a-directory/config/maestro/clouds +1 -0
- data/test/unit/fixtures/invalid-cookbooks-not-a-directory/config/maestro/cookbooks +0 -0
- data/test/unit/fixtures/invalid-maestro-not-a-directory/config/maestro +0 -0
- data/test/unit/fixtures/invalid-missing-cookbooks/config/maestro/clouds/valid.yml +21 -0
- data/test/unit/fixtures/invalid-missing-roles/config/maestro/clouds/valid.yml +21 -0
- data/test/unit/fixtures/invalid-roles-not-a-directory/config/maestro/roles +1 -0
- data/test/unit/fixtures/ssh/id_rsa-maestro-test-keypair +27 -0
- data/test/unit/helper.rb +6 -0
- data/test/unit/test_aws_cloud.rb +133 -0
- data/test/unit/test_aws_ec2_node.rb +76 -0
- data/test/unit/test_aws_elb_node.rb +221 -0
- data/test/unit/test_aws_rds_node.rb +380 -0
- data/test/unit/test_cent_os.rb +28 -0
- data/test/unit/test_cloud.rb +142 -0
- data/test/unit/test_debian.rb +62 -0
- data/test/unit/test_fedora.rb +28 -0
- data/test/unit/test_invalid_mode.rb +11 -0
- data/test/unit/test_maestro.rb +140 -0
- data/test/unit/test_node.rb +50 -0
- data/test/unit/test_operating_system.rb +19 -0
- data/test/unit/test_rails_mode.rb +77 -0
- data/test/unit/test_role.rb +59 -0
- data/test/unit/test_standalone_mode.rb +75 -0
- data/test/unit/test_ubuntu.rb +95 -0
- data/the-maestro.gemspec +150 -0
- metadata +228 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "base_aws"
|
3
|
+
|
4
|
+
# Integration tests for CentOs
|
5
|
+
class TestCentOs < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include BaseAws
|
8
|
+
|
9
|
+
context "Maestro::OperatingSystem::CentOs" do
|
10
|
+
|
11
|
+
#######################
|
12
|
+
# Setup
|
13
|
+
#######################
|
14
|
+
|
15
|
+
setup do
|
16
|
+
credentials = @credentials
|
17
|
+
@cloud = aws_cloud :maestro_centos_itest do
|
18
|
+
keypair_name credentials[:keypair_name]
|
19
|
+
keypair_file credentials[:keypair_file]
|
20
|
+
aws_account_id credentials[:aws_account_id]
|
21
|
+
aws_access_key credentials[:aws_access_key]
|
22
|
+
aws_secret_access_key credentials[:aws_secret_access_key]
|
23
|
+
chef_bucket credentials[:chef_bucket]
|
24
|
+
|
25
|
+
roles do
|
26
|
+
end
|
27
|
+
|
28
|
+
nodes do
|
29
|
+
ec2_node "centos-itest" do
|
30
|
+
instance_type "m1.small"
|
31
|
+
availability_zone "us-east-1b"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@node = @cloud.nodes["centos-itest"]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#######################
|
40
|
+
# Teardown
|
41
|
+
#######################
|
42
|
+
|
43
|
+
teardown do
|
44
|
+
# terminate instances
|
45
|
+
instances = @ec2.describe_instances
|
46
|
+
to_be_terminated = Array.new
|
47
|
+
to_be_watched = Array.new
|
48
|
+
@cloud.ec2_nodes.each_pair do |node_name, node|
|
49
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
50
|
+
if !instance.nil?
|
51
|
+
to_be_terminated << instance.instanceId
|
52
|
+
to_be_watched << node_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if !to_be_terminated.empty?
|
56
|
+
puts "Terminating CentOS integration test EC2 instances"
|
57
|
+
@ec2.terminate_instances(:instance_id => to_be_terminated)
|
58
|
+
end
|
59
|
+
STDOUT.sync = true
|
60
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
61
|
+
while !to_be_watched.empty?
|
62
|
+
instances = @ec2.describe_instances()
|
63
|
+
to_be_watched.each do |node_name|
|
64
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
65
|
+
if instance.nil?
|
66
|
+
puts ""
|
67
|
+
puts "Node #{node_name} terminated"
|
68
|
+
to_be_watched.delete(node_name)
|
69
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
70
|
+
else
|
71
|
+
print "."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
sleep 5 if !to_be_watched.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# delete ec2 security groups
|
78
|
+
cloud_security_groups = @cloud.ec2_security_groups
|
79
|
+
cloud_security_groups.each do |group_name|
|
80
|
+
@ec2.delete_security_group(:group_name => group_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
#######################
|
86
|
+
# Tests
|
87
|
+
#######################
|
88
|
+
|
89
|
+
context "CentOS 5.4" do
|
90
|
+
should "install chef-solo" do
|
91
|
+
@node.ami "ami-0859bb61"
|
92
|
+
@node.ssh_user "root"
|
93
|
+
assert_nothing_raised do
|
94
|
+
@cloud.connect!
|
95
|
+
@cloud.start
|
96
|
+
@cloud.get_configurable_node_hostnames
|
97
|
+
assert !@cloud.chef_solo_installed?[0]
|
98
|
+
@cloud.install_chef_solo
|
99
|
+
assert @cloud.chef_solo_installed?[0]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "base_aws"
|
3
|
+
|
4
|
+
# Integration tests for Debian
|
5
|
+
class TestDebian < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include BaseAws
|
8
|
+
|
9
|
+
context "Maestro::OperatingSystem::Debian" do
|
10
|
+
|
11
|
+
#######################
|
12
|
+
# Setup
|
13
|
+
#######################
|
14
|
+
|
15
|
+
setup do
|
16
|
+
credentials = @credentials
|
17
|
+
@cloud = aws_cloud :maestro_debian_itest do
|
18
|
+
keypair_name credentials[:keypair_name]
|
19
|
+
keypair_file credentials[:keypair_file]
|
20
|
+
aws_account_id credentials[:aws_account_id]
|
21
|
+
aws_access_key credentials[:aws_access_key]
|
22
|
+
aws_secret_access_key credentials[:aws_secret_access_key]
|
23
|
+
chef_bucket credentials[:chef_bucket]
|
24
|
+
|
25
|
+
roles do
|
26
|
+
end
|
27
|
+
|
28
|
+
nodes do
|
29
|
+
ec2_node "debian-itest" do
|
30
|
+
instance_type "m1.small"
|
31
|
+
availability_zone "us-east-1b"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@node = @cloud.nodes["debian-itest"]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#######################
|
40
|
+
# Teardown
|
41
|
+
#######################
|
42
|
+
|
43
|
+
teardown do
|
44
|
+
# terminate instances
|
45
|
+
instances = @ec2.describe_instances
|
46
|
+
to_be_terminated = Array.new
|
47
|
+
to_be_watched = Array.new
|
48
|
+
@cloud.ec2_nodes.each_pair do |node_name, node|
|
49
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
50
|
+
if !instance.nil?
|
51
|
+
to_be_terminated << instance.instanceId
|
52
|
+
to_be_watched << node_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if !to_be_terminated.empty?
|
56
|
+
puts "Terminating Debian integration test EC2 instances"
|
57
|
+
@ec2.terminate_instances(:instance_id => to_be_terminated)
|
58
|
+
end
|
59
|
+
STDOUT.sync = true
|
60
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
61
|
+
while !to_be_watched.empty?
|
62
|
+
instances = @ec2.describe_instances()
|
63
|
+
to_be_watched.each do |node_name|
|
64
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
65
|
+
if instance.nil?
|
66
|
+
puts ""
|
67
|
+
puts "Node #{node_name} terminated"
|
68
|
+
to_be_watched.delete(node_name)
|
69
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
70
|
+
else
|
71
|
+
print "."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
sleep 5 if !to_be_watched.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# delete ec2 security groups
|
78
|
+
cloud_security_groups = @cloud.ec2_security_groups
|
79
|
+
cloud_security_groups.each do |group_name|
|
80
|
+
@ec2.delete_security_group(:group_name => group_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
#######################
|
86
|
+
# Tests
|
87
|
+
#######################
|
88
|
+
|
89
|
+
context "Debian 6.0" do
|
90
|
+
should "install chef-solo" do
|
91
|
+
@node.ami "ami-daf615b3"
|
92
|
+
@node.ssh_user "root"
|
93
|
+
assert_nothing_raised do
|
94
|
+
@cloud.connect!
|
95
|
+
@cloud.start
|
96
|
+
@cloud.get_configurable_node_hostnames
|
97
|
+
assert !@cloud.chef_solo_installed?[0]
|
98
|
+
@cloud.install_chef_solo
|
99
|
+
assert @cloud.chef_solo_installed?[0]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "Debian 5.0" do
|
105
|
+
should "install chef-solo" do
|
106
|
+
@node.ami "ami-dcf615b5"
|
107
|
+
@node.ssh_user "root"
|
108
|
+
assert_nothing_raised do
|
109
|
+
@cloud.connect!
|
110
|
+
@cloud.start
|
111
|
+
@cloud.get_configurable_node_hostnames
|
112
|
+
assert !@cloud.chef_solo_installed?[0]
|
113
|
+
@cloud.install_chef_solo
|
114
|
+
assert @cloud.chef_solo_installed?[0]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "base_aws"
|
3
|
+
|
4
|
+
# Integration tests for Fedora
|
5
|
+
class TestFedora < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include BaseAws
|
8
|
+
|
9
|
+
context "Maestro::OperatingSystem::Fedora" do
|
10
|
+
|
11
|
+
#######################
|
12
|
+
# Setup
|
13
|
+
#######################
|
14
|
+
|
15
|
+
setup do
|
16
|
+
credentials = @credentials
|
17
|
+
@cloud = aws_cloud :maestro_fedora_itest do
|
18
|
+
keypair_name credentials[:keypair_name]
|
19
|
+
keypair_file credentials[:keypair_file]
|
20
|
+
aws_account_id credentials[:aws_account_id]
|
21
|
+
aws_access_key credentials[:aws_access_key]
|
22
|
+
aws_secret_access_key credentials[:aws_secret_access_key]
|
23
|
+
chef_bucket credentials[:chef_bucket]
|
24
|
+
|
25
|
+
roles do
|
26
|
+
end
|
27
|
+
|
28
|
+
nodes do
|
29
|
+
ec2_node "fedora-itest" do
|
30
|
+
instance_type "m1.small"
|
31
|
+
availability_zone "us-east-1b"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@node = @cloud.nodes["fedora-itest"]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#######################
|
40
|
+
# Teardown
|
41
|
+
#######################
|
42
|
+
|
43
|
+
teardown do
|
44
|
+
# terminate instances
|
45
|
+
instances = @ec2.describe_instances
|
46
|
+
to_be_terminated = Array.new
|
47
|
+
to_be_watched = Array.new
|
48
|
+
@cloud.ec2_nodes.each_pair do |node_name, node|
|
49
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
50
|
+
if !instance.nil?
|
51
|
+
to_be_terminated << instance.instanceId
|
52
|
+
to_be_watched << node_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if !to_be_terminated.empty?
|
56
|
+
puts "Terminating Fedora integration test EC2 instances"
|
57
|
+
@ec2.terminate_instances(:instance_id => to_be_terminated)
|
58
|
+
end
|
59
|
+
STDOUT.sync = true
|
60
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
61
|
+
while !to_be_watched.empty?
|
62
|
+
instances = @ec2.describe_instances()
|
63
|
+
to_be_watched.each do |node_name|
|
64
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
65
|
+
if instance.nil?
|
66
|
+
puts ""
|
67
|
+
puts "Node #{node_name} terminated"
|
68
|
+
to_be_watched.delete(node_name)
|
69
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
70
|
+
else
|
71
|
+
print "."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
sleep 5 if !to_be_watched.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# delete ec2 security groups
|
78
|
+
cloud_security_groups = @cloud.ec2_security_groups
|
79
|
+
cloud_security_groups.each do |group_name|
|
80
|
+
@ec2.delete_security_group(:group_name => group_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
#######################
|
86
|
+
# Tests
|
87
|
+
#######################
|
88
|
+
|
89
|
+
context "Fedora 8" do
|
90
|
+
should "install chef-solo" do
|
91
|
+
@node.ami "ami-f51aff9c"
|
92
|
+
@node.ssh_user "root"
|
93
|
+
assert_nothing_raised do
|
94
|
+
@cloud.connect!
|
95
|
+
@cloud.start
|
96
|
+
@cloud.get_configurable_node_hostnames
|
97
|
+
assert !@cloud.chef_solo_installed?[0]
|
98
|
+
@cloud.install_chef_solo
|
99
|
+
assert @cloud.chef_solo_installed?[0]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "base_aws"
|
3
|
+
|
4
|
+
# Integration tests for Ubuntu
|
5
|
+
class TestUbuntu < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include BaseAws
|
8
|
+
|
9
|
+
context "Maestro::OperatingSystem::Ubuntu" do
|
10
|
+
|
11
|
+
#######################
|
12
|
+
# Setup
|
13
|
+
#######################
|
14
|
+
|
15
|
+
setup do
|
16
|
+
credentials = @credentials
|
17
|
+
@cloud = aws_cloud :maestro_ubuntu_itest do
|
18
|
+
keypair_name credentials[:keypair_name]
|
19
|
+
keypair_file credentials[:keypair_file]
|
20
|
+
aws_account_id credentials[:aws_account_id]
|
21
|
+
aws_access_key credentials[:aws_access_key]
|
22
|
+
aws_secret_access_key credentials[:aws_secret_access_key]
|
23
|
+
chef_bucket credentials[:chef_bucket]
|
24
|
+
|
25
|
+
roles do
|
26
|
+
end
|
27
|
+
|
28
|
+
nodes do
|
29
|
+
ec2_node "ubuntu-itest" do
|
30
|
+
instance_type "m1.small"
|
31
|
+
availability_zone "us-east-1b"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@node = @cloud.nodes["ubuntu-itest"]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#######################
|
40
|
+
# Teardown
|
41
|
+
#######################
|
42
|
+
|
43
|
+
teardown do
|
44
|
+
# terminate instances
|
45
|
+
instances = @ec2.describe_instances
|
46
|
+
to_be_terminated = Array.new
|
47
|
+
to_be_watched = Array.new
|
48
|
+
@cloud.ec2_nodes.each_pair do |node_name, node|
|
49
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
50
|
+
if !instance.nil?
|
51
|
+
to_be_terminated << instance.instanceId
|
52
|
+
to_be_watched << node_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if !to_be_terminated.empty?
|
56
|
+
puts "Terminating Ubuntu integration test EC2 instances"
|
57
|
+
@ec2.terminate_instances(:instance_id => to_be_terminated)
|
58
|
+
end
|
59
|
+
STDOUT.sync = true
|
60
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
61
|
+
while !to_be_watched.empty?
|
62
|
+
instances = @ec2.describe_instances()
|
63
|
+
to_be_watched.each do |node_name|
|
64
|
+
instance = @cloud.find_ec2_node_instance(node_name, instances)
|
65
|
+
if instance.nil?
|
66
|
+
puts ""
|
67
|
+
puts "Node #{node_name} terminated"
|
68
|
+
to_be_watched.delete(node_name)
|
69
|
+
print "Waiting for Nodes #{to_be_watched.inspect} to terminate..." if !to_be_watched.empty?
|
70
|
+
else
|
71
|
+
print "."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
sleep 5 if !to_be_watched.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# delete ec2 security groups
|
78
|
+
cloud_security_groups = @cloud.ec2_security_groups
|
79
|
+
cloud_security_groups.each do |group_name|
|
80
|
+
@ec2.delete_security_group(:group_name => group_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
#######################
|
86
|
+
# Tests
|
87
|
+
#######################
|
88
|
+
|
89
|
+
context "Ubuntu 9.10" do
|
90
|
+
should "install chef-solo" do
|
91
|
+
@node.ami "ami-bb709dd2"
|
92
|
+
@node.ssh_user "ubuntu"
|
93
|
+
assert_nothing_raised do
|
94
|
+
@cloud.connect!
|
95
|
+
@cloud.start
|
96
|
+
@cloud.get_configurable_node_hostnames
|
97
|
+
assert !@cloud.chef_solo_installed?[0]
|
98
|
+
@cloud.install_chef_solo
|
99
|
+
assert @cloud.chef_solo_installed?[0]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "Ubuntu 9.04" do
|
105
|
+
should "install chef-solo" do
|
106
|
+
@node.ami "ami-ccf615a5"
|
107
|
+
@node.ssh_user "root"
|
108
|
+
assert_nothing_raised do
|
109
|
+
@cloud.connect!
|
110
|
+
@cloud.start
|
111
|
+
@cloud.get_configurable_node_hostnames
|
112
|
+
assert !@cloud.chef_solo_installed?[0]
|
113
|
+
@cloud.install_chef_solo
|
114
|
+
assert @cloud.chef_solo_installed?[0]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "Ubuntu 8.10" do
|
120
|
+
should "install chef-solo" do
|
121
|
+
@node.ami "ami-c0f615a9"
|
122
|
+
@node.ssh_user "root"
|
123
|
+
assert_nothing_raised do
|
124
|
+
@cloud.connect!
|
125
|
+
@cloud.start
|
126
|
+
@cloud.get_configurable_node_hostnames
|
127
|
+
assert !@cloud.chef_solo_installed?[0]
|
128
|
+
@cloud.install_chef_solo
|
129
|
+
assert @cloud.chef_solo_installed?[0]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "Ubuntu 8.04" do
|
135
|
+
should "install chef-solo" do
|
136
|
+
@node.ami "ami-c4f615ad"
|
137
|
+
@node.ssh_user "root"
|
138
|
+
assert_nothing_raised do
|
139
|
+
@cloud.connect!
|
140
|
+
@cloud.start
|
141
|
+
@cloud.get_configurable_node_hostnames
|
142
|
+
assert !@cloud.chef_solo_installed?[0]
|
143
|
+
@cloud.install_chef_solo
|
144
|
+
assert @cloud.chef_solo_installed?[0]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|