wildcloud-keeper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Wildcloud - Keeper
2
+
3
+ Responsible for managing applications on it's node - mounting, starting, managing resources.
4
+
5
+ # License
6
+
7
+ Project is licensed under the terms of the Apache 2 License.
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright 2011 Marek Jelen
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ $: << File.expand_path('../../lib', __FILE__)
18
+
19
+ require 'rubygems'
20
+
21
+ require 'eventmachine'
22
+
23
+ require 'wildcloud/keeper/runtime'
24
+
25
+ EventMachine.run do
26
+ Wildcloud::Keeper::Runtime.new
27
+ end
@@ -0,0 +1,13 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,32 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'yaml'
16
+ require 'wildcloud/keeper/logger'
17
+
18
+ module Wildcloud
19
+ module Keeper
20
+
21
+ def self.configuration
22
+ return @configuration if @configuration
23
+ file = '/etc/wildcloud/keeper.yml'
24
+ unless File.exists?(file)
25
+ file = './keeper.yml'
26
+ end
27
+ Keeper.logger.info('Configuration', "Loading from file #{file}")
28
+ @configuration = YAML.load_file(file)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,92 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'fileutils'
16
+
17
+ require 'wildcloud/keeper/configuration'
18
+ require 'wildcloud/keeper/logger'
19
+
20
+ module Wildcloud
21
+ module Keeper
22
+ module Deployers
23
+ class Aufs
24
+
25
+ def deploy(options = {})
26
+ options[:base_image] ||= 'base'
27
+
28
+ @id = options[:id]
29
+ @appid = options[:appid]
30
+
31
+ @temp_file = File.join(config['paths']['tmp'], "#{@id}.tar.gz")
32
+ @image_path = File.join(config['paths']['images'], "#{@id}")
33
+ @target_path = File.join(config['paths']['mounts'], "#{@id}")
34
+ @temp_path = File.join(config['paths']['temp'], "#{@id}")
35
+
36
+ FileUtils.mkdir_p(@image_path)
37
+ FileUtils.mkdir_p(@target_path)
38
+ FileUtils.mkdir_p(@temp_path)
39
+
40
+ @branches = "br=#{@image_path}=rw:#{File.join(config['paths']['images'], 'base')}=ro"
41
+ options[:root_path] = @image_path
42
+
43
+ unless options[:build]
44
+ run("curl -v -o #{@temp_file} -H 'X-Appid: #{config['storage']['id']}' #{config['storage']['url']}/#{@appid}.tar.gz")
45
+ run("tar -xf #{@temp_file} -C #{@image_path}")
46
+ @branches = "br=#{@temp_path}=rw:#{@image_path}=ro:#{File.join(config['paths']['images'], options[:base_image])}=ro"
47
+ options[:root_path] = @temp_path
48
+ end
49
+
50
+ run("mount -t aufs -o #{@branches} none #{@target_path}")
51
+ end
52
+
53
+ def undeploy(options)
54
+
55
+ @id ||= options[:id]
56
+ @appid ||= options[:appid]
57
+
58
+ @temp_file ||= File.join(config['paths']['tmp'], "#{@id}.tar.gz")
59
+ @image_path ||= File.join(config['paths']['images'], "#{@id}")
60
+ @target_path ||= File.join(config['paths']['mounts'], "#{@id}")
61
+ @temp_path ||= File.join(config['paths']['temp'], "#{@id}")
62
+
63
+ run("umount #{@target_path}")
64
+
65
+ if options[:persistent]
66
+ run("tar -zcf #{@temp_file} -C #{@image_path} .")
67
+ run("curl -v -X PUT -H 'X-Appid: #{config['storage']['id']}' -T #{@temp_file} #{config['storage']['url']}/#{@appid}.tar.gz")
68
+ end
69
+
70
+ FileUtils.rm_rf(@image_path)
71
+ FileUtils.rm_rf(@target_path)
72
+ FileUtils.rm_rf(@temp_path)
73
+
74
+ FileUtils.rm(@temp_file)
75
+ end
76
+
77
+ private
78
+
79
+ def config
80
+ Keeper.configuration
81
+ end
82
+
83
+ def run(command)
84
+ Keeper.logger.debug('Aufs') { command }
85
+ stdout = `#{command}`
86
+ Keeper.logger.debug('Aufs') { stdout }
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'fileutils'
16
+ require 'erb'
17
+
18
+ require 'wildcloud/keeper/configuration'
19
+ require 'wildcloud/keeper/logger'
20
+
21
+ module Wildcloud
22
+ module Keeper
23
+ module Isolators
24
+ class Lxc
25
+
26
+ def start(options)
27
+
28
+ @id = options[:id]
29
+ @target_path = File.join(config['paths']['mounts'], "#{@id}")
30
+
31
+ @config_file = File.join(config['paths']['config'], "#{@id}.config")
32
+ @fstab_file = File.join(config['paths']['config'], "#{@id}.fstab")
33
+
34
+ vm_config = ERB.new(File.read(File.expand_path('../../templates/app.config', __FILE__))).result(binding)
35
+ vm_fstab = ERB.new(File.read(File.expand_path('../../templates/app.fstab', __FILE__))).result(binding)
36
+
37
+ FileUtils.mkdir_p(config['paths']['config'])
38
+
39
+ File.open(@config_file, 'w') { |file| file.write(vm_config) }
40
+ File.open(@fstab_file, 'w') { |file| file.write(vm_fstab) }
41
+
42
+ run("lxc-create -f #{@config_file} -n #{@id}")
43
+ run("lxc-start -d -n #{@id}")
44
+
45
+ end
46
+
47
+ def stop(options)
48
+
49
+ @id ||= options[:id]
50
+ @config_file ||= File.join(config['paths']['config'], "#{@id}.config")
51
+ @fstab_file ||= File.join(config['paths']['config'], "#{@id}.fstab")
52
+
53
+
54
+ if options[:build]
55
+ build_status_file = File.join(config['paths']['images'], "#{@id}", 'var', 'build.done')
56
+ until File.exists?(build_status_file)
57
+ sleep(5)
58
+ end
59
+ end
60
+
61
+ run("lxc-stop -n #{@id}")
62
+ run("lxc-destroy -n #{@id}")
63
+
64
+ FileUtils.rm(@config_file)
65
+ FileUtils.rm(@fstab_file)
66
+ end
67
+
68
+ def resources(options)
69
+
70
+ @id ||= options[:id]
71
+
72
+ run("lxc-cgroup -n #{@id} memory.limit_in_bytes \"#{options[:memory]}\"") if options[:memory]
73
+ run("lxc-cgroup -n #{@id} memory.memsw.limit_in_bytes \"#{options[:swap]}\"") if options[:swap]
74
+ run("lxc-cgroup -n #{@id} cpuset.cpus \"#{options[:cpus]}\"") if options[:cpus]
75
+ run("lxc-cgroup -n #{@id} cpu.shares \"#{options[:cpu_share]}\"") if options[:cpu_share]
76
+ end
77
+
78
+ private
79
+
80
+ def config
81
+ Keeper.configuration
82
+ end
83
+
84
+ def run(command)
85
+ Keeper.logger.debug('LXC') { command }
86
+ stdout = `#{command}`
87
+ Keeper.logger.debug('LXC') { stdout }
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'wildcloud/logger'
16
+ require 'wildcloud/logger/middleware/console'
17
+ require 'wildcloud/logger/middleware/amqp'
18
+ require 'wildcloud/logger/middleware/json'
19
+
20
+ require 'json'
21
+
22
+ module Wildcloud
23
+ module Keeper
24
+
25
+ def self.logger
26
+ unless @logger
27
+ @logger = Wildcloud::Logger::Logger.new
28
+ @logger.application = 'wildcloud.keeper'
29
+ @logger.add(Wildcloud::Logger::Middleware::Console)
30
+ end
31
+ @logger
32
+ end
33
+
34
+ def self.add_amqp_logger(amqp)
35
+ @logger.add(Wildcloud::Logger::Middleware::Json)
36
+ @topic = AMQP::Channel.new(amqp).topic('wildcloud.logger')
37
+ @logger.add(Wildcloud::Logger::Middleware::Amqp, :exchange => @topic, :routing_key => 'wildcloud.keeper')
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,196 @@
1
+ # Copyright 2011 Marek Jelen
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'fileutils'
16
+ require 'thread'
17
+ require 'yaml'
18
+
19
+ require 'wildcloud/keeper/logger'
20
+ require 'wildcloud/keeper/configuration'
21
+
22
+ require 'wildcloud/keeper/isolators/lxc'
23
+ require 'wildcloud/keeper/deployers/aufs'
24
+
25
+ require 'wildcloud/keeper/transport/amqp'
26
+
27
+ module Wildcloud
28
+ module Keeper
29
+ class Runtime
30
+
31
+ def initialize
32
+ @repository = {}
33
+
34
+ Keeper.logger.info('Runtime') { 'Starting transport' }
35
+ @transport = Transport::Amqp.new
36
+
37
+ Keeper.logger.info('Runtime') { 'Starting thread-pool' }
38
+ @queue = Queue.new
39
+ @thread_pool = []
40
+
41
+ Keeper.configuration['node']['workers'].times do |i|
42
+ Keeper.logger.debug('Runtime', "Starting thread ##{i}")
43
+ Thread.new(i) do |id|
44
+ Thread.current.abort_on_exception = false
45
+ loop do
46
+ Keeper.logger.debug('Runtime') { "Thread ##{id} waiting for task" }
47
+ begin
48
+ @queue.pop.call
49
+ rescue Exception => exception
50
+ Keeper.logger.fatal('Runtime') { "Exception in thread #{id}: #{exception.message}" }
51
+ Keeper.logger.debug('Runtime') { exception }
52
+ Keeper.logger.debug('Runtime') { exception.backtrace }
53
+ else
54
+ Keeper.logger.debug('Runtime') { "Thread ##{id} handled task successfully" }
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ handler = self.method(:handle)
61
+ @transport.start(&handler)
62
+ @transport.send({:type => :sshkey, :node => Keeper.configuration['node']['name'], :key => File.read(File.expand_path('~/.ssh/id_rsa.pub')).strip}, :master)
63
+ heartbeat
64
+ end
65
+
66
+ def heartbeat
67
+ @transport.send({:type => 'heartbeat', :node => Keeper.configuration['node']['name']}, :master)
68
+ EM.add_timer(5, method(:heartbeat))
69
+ end
70
+
71
+ def handle(message)
72
+ Keeper.logger.debug('Runtime') { "Message received #{message.inspect}" }
73
+ method = "handle_#{message['type']}".to_sym
74
+ scope = self
75
+ @queue << proc do
76
+ scope.send(method, message)
77
+ end
78
+ rescue Exception => exception
79
+ Keeper.logger.fatal('Runtime') { "Exception in runtime: #{exception.message}" }
80
+ Keeper.logger.fatal(exception)
81
+ end
82
+
83
+ def handle_build(message)
84
+ handle_deploy(message)
85
+ handle_undeploy(message)
86
+ end
87
+
88
+ def instance(id, message)
89
+ unless @repository[id]
90
+ @repository[id] = {:isolator => Isolators::Lxc.new, :deployer => Deployers::Aufs.new}
91
+ @repository[id][:options] = {
92
+ :id => "instance_#{message['id']}",
93
+ :appid => message['appid'],
94
+ :base_image => message['image'],
95
+ :persistent => message['persistent'],
96
+ :ip_address => message['ip_address'],
97
+ :memory => message['memory'],
98
+ :swap => message['swap'],
99
+ :cpus => message['cpus'],
100
+ :cpu_share => message['cpu_share']
101
+ }
102
+ if message['type'] == 'build'
103
+ @repository[id][:options].merge!({
104
+ :id => "build_#{message['id']}",
105
+ :persistent => true,
106
+ :build => true,
107
+ :repository => message['repository'],
108
+ :revision => message['revision']
109
+ })
110
+ end
111
+ end
112
+ @repository[id]
113
+ end
114
+
115
+ def get_instance_name(message)
116
+ message['type'] == 'build' ? "build_#{message['id']}" : "instance_#{message['id']}"
117
+ end
118
+
119
+ def get_instance(message)
120
+ instance = instance(get_instance_name(message), message)
121
+ options = instance[:options]
122
+ [instance, options]
123
+ end
124
+
125
+ def handle_deploy(message)
126
+ instance, options = get_instance(message)
127
+ instance[:deployer].deploy(options)
128
+ write_system_configuration(options)
129
+ instance[:isolator].start(options)
130
+ unless options[:build]
131
+ @transport.send({:type => :deployed, :node => Keeper.configuration['node']['name'], :id => message['id']}, :master)
132
+ end
133
+ end
134
+
135
+ def handle_undeploy(message)
136
+ instance, options = get_instance(message)
137
+ instance[:isolator].stop(options)
138
+ clean_system_configuration(options)
139
+ if options[:build]
140
+ build_log = File.read(File.join(options[:root_path], 'var', 'build.log'))
141
+ end
142
+ instance[:deployer].undeploy(options)
143
+ if options[:build]
144
+ @transport.send({:type => :build_log, :node => Keeper.configuration['node']['name'], :id => message['id'], :content => build_log}, :master)
145
+ else
146
+ @transport.send({:type => :undeployed, :node => Keeper.configuration['node']['name'], :id => message['id']}, :master)
147
+ end
148
+ end
149
+
150
+ def handle_resources(message)
151
+ instance_name = get_instance_name(message)
152
+ instance = @repository[instance_name]
153
+ unless instance
154
+ return
155
+ end
156
+ options = instance[:options]
157
+ options[:memory] = message['memory']
158
+ options[:swap] = message['swap']
159
+ options[:cpus] = message['cpus']
160
+ options[:cpu_share] = message['cpu_share']
161
+ instance[:isolator].resources(options)
162
+ end
163
+
164
+ def write_system_configuration(options)
165
+ root = options[:root_path]
166
+
167
+ interfaces = ERB.new(File.read(File.expand_path('../templates/interfaces', __FILE__))).result(binding)
168
+ interfaces_path = File.join(root, 'etc', 'network', 'interfaces')
169
+ FileUtils.mkdir_p(File.dirname(interfaces_path))
170
+ File.open(interfaces_path, 'w') { |file| file.write(interfaces) }
171
+
172
+ if options[:build]
173
+ FileUtils.mkdir_p(File.join(root, 'root', '.ssh'))
174
+ FileUtils.cp(File.expand_path('~/.ssh/id_rsa'), File.join(root, 'root', '.ssh', 'id_rsa'))
175
+ FileUtils.cp(File.expand_path('~/.ssh/id_rsa.pub'), File.join(root, 'root', '.ssh', 'id_rsa.pub'))
176
+
177
+ File.open(File.join(root, 'root', 'build.yml'), 'w') { |file| file.write(YAML::dump(options)) }
178
+ end
179
+ end
180
+
181
+ def clean_system_configuration(options)
182
+ root = options[:root_path]
183
+
184
+ if options[:build]
185
+ FileUtils.rm_rf(File.join(root, 'root', '.ssh'))
186
+ end
187
+ end
188
+
189
+ def handle_quit
190
+ Keeper.logger.fatal('Runtime') { "Shutdown requested." }
191
+ EventMachine.stop_event_loop
192
+ end
193
+
194
+ end
195
+ end
196
+ end