wildcloud-keeper 0.0.1

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/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