miasma-lxd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 768837675895e46e18d2454a8755dabf7a643689
4
+ data.tar.gz: 3696866fc4fae82a6c807826927acb6bd727dac2
5
+ SHA512:
6
+ metadata.gz: 9b76ccc003a7a69d5a83f1d4d0fc683ae344a7e4b9c80e121d81f05271306a7a62c89287a0dfa1cc3739380edcae548c627999a776fb9354d65799ff0cd7248e
7
+ data.tar.gz: 3c7c6f533d4c53855a5106118745356a9e77dfc060af697b18d1907d051b4fe83515b707622d9867d7753c2c6c61ca5dd9af5b4ca3a85f692ac3ffa9cd60d94f
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2015 Chris Roberts
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.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Miasma LXD
2
+
3
+ LXD API plugin for the miasma cloud library
4
+
5
+ ## Supported credential attributes:
6
+
7
+ Supported attributes used in the credentials section of API
8
+ configurations:
9
+
10
+ ```ruby
11
+ Miasma.api(
12
+ :type => :compute,
13
+ :provider => :lxd,
14
+ :credentials => {
15
+ ...
16
+ }
17
+ )
18
+ ```
19
+
20
+ ### Required attributes
21
+
22
+ * `api_endpoint` - LXD HTTPS endpoint (e.g. https://127.0.0.1:8443)
23
+ * `ssl_key` - Path to client SSL key
24
+ * `ssl_certificate` - Path to client SSL certificate
25
+
26
+ ### Initial connection required attributes
27
+
28
+ * `name` - Name of this client (defaults to hostname)
29
+ * `password` - Shared password with LXD to establish trust
30
+
31
+ ## Current support matrix
32
+
33
+ |Model |Create|Read|Update|Delete|
34
+ |--------------|------|----|------|------|
35
+ |AutoScale | | | | |
36
+ |BlockStorage | | | | |
37
+ |Compute | X | X | X | X |
38
+ |DNS | | | | |
39
+ |LoadBalancer | | | | |
40
+ |Network | | | | |
41
+ |Orchestration | | | | |
42
+ |Queues | | | | |
43
+ |Storage | | | | |
44
+
45
+ ## Info
46
+ * Repository: https://github.com/miasma-rb/miasma-lxd
@@ -0,0 +1,258 @@
1
+ require 'miasma'
2
+ require 'shellwords'
3
+ require 'bogo-websocket'
4
+
5
+ module Miasma
6
+ module Models
7
+ class Compute
8
+ # LXD compute API
9
+ class Lxd < Compute
10
+
11
+ include Contrib::Lxd::Api
12
+
13
+ # State mapping based on status
14
+ SERVER_STATE_MAP = Smash.new(
15
+ 'running' => :running,
16
+ 'stopped' => :stopped
17
+ )
18
+
19
+ # Reload a server model's data
20
+ #
21
+ # @param server [Miasma::Models::Compute::Server]
22
+ # @return [Miasma::Models::Compute::Server]
23
+ def server_reload(server)
24
+ result = request(
25
+ :path => "containers/#{server.id}",
26
+ :expects => [200, 404]
27
+ )
28
+ if(result[:response].code == 200)
29
+ result = result.get(:body, :metadata)
30
+ server.load_data(
31
+ :id => result[:name],
32
+ :name => result[:name],
33
+ :state => SERVER_STATE_MAP.fetch(result.get(:status, :status).downcase, :pending),
34
+ :status => result.fetch(:status, :status, 'unknown').downcase,
35
+ :addresses_private => (result.get(:status, :ips) || []).map{ |ip|
36
+ Server::Address.new(
37
+ :version => ip[:protocol].downcase.sub('ipv', '').to_i,
38
+ :address => ip[:address]
39
+ )
40
+ },
41
+ :image_id => 'unknown',
42
+ :flavor_id => result.get(:profiles).first,
43
+ :userdata => result.get(:userdata),
44
+ :custom => Smash.new(
45
+ :ephemeral => result[:ephemeral]
46
+ )
47
+ )
48
+ else
49
+ server.load_data(
50
+ :id => server.id,
51
+ :name => server.name,
52
+ :state => :terminated,
53
+ :status => 'terminated',
54
+ :addresses_private => [],
55
+ :image_id => 'none',
56
+ :flavor_id => 'none'
57
+ )
58
+ end
59
+ server.valid_state
60
+ end
61
+
62
+ # Destroy an existing server
63
+ #
64
+ # @param server [Miasma::Models::Compute::Server]
65
+ # @return [TrueClass, FalseClass]
66
+ def server_destroy(server)
67
+ if(server.persisted?)
68
+ if(server.state == :running)
69
+ result = request(
70
+ :path => "containers/#{server.id}/state",
71
+ :method => :put,
72
+ :expects => 202,
73
+ :json => {
74
+ :action => :stop,
75
+ :force => true
76
+ }
77
+ )
78
+ wait_for_operation(result.get(:body, :operation))
79
+ end
80
+ request(
81
+ :path => "containers/#{server.id}",
82
+ :method => :delete,
83
+ :expects => 202
84
+ )
85
+ true
86
+ else
87
+ false
88
+ end
89
+ end
90
+
91
+ # Save the server (create or update)
92
+ #
93
+ # @param server [Miasma::Models::Compute::Server]
94
+ # @return [Miasma::Models::Compute::Server]
95
+ def server_save(server)
96
+ if(server.persisted?)
97
+ else
98
+ result = request(
99
+ :path => 'containers',
100
+ :method => :post,
101
+ :expects => 202,
102
+ :json => {
103
+ :name => server.name,
104
+ :profiles => [server.flavor_id],
105
+ :ephemeral => server.custom.fetch(:ephemeral, false),
106
+ :source => {
107
+ :type => :image,
108
+ :alias => server.image_id
109
+ }
110
+ }
111
+ )
112
+ wait_for_operation(result.get(:body, :operation))
113
+ request(
114
+ :path => "containers/#{server.name}/state",
115
+ :method => :put,
116
+ :expects => 202,
117
+ :json => {
118
+ :action => :start
119
+ }
120
+ )
121
+ wait_for_operation(result.get(:body, :operation))
122
+ server.id = server.name
123
+ server
124
+ end
125
+ end
126
+
127
+ # Return all servers
128
+ #
129
+ # @return [Array<Miasma::Models::Compute::Server>]
130
+ def server_all
131
+ result = request(
132
+ :path => 'containers'
133
+ ).get(:body)
134
+ result.fetch(:metadata, []).map do |c_info|
135
+ c_name = c_info.sub("/#{version}/containers/", '')
136
+ Server.new(
137
+ self,
138
+ :id => c_name,
139
+ :name => c_name
140
+ ).valid_state
141
+ end
142
+ end
143
+
144
+ # Fetch file from server
145
+ #
146
+ # @param server [Miasma::Models::Compute::Server]
147
+ # @param path [String] remote path
148
+ # @return [IO-ish]
149
+ def server_get_file(server, path)
150
+ request(
151
+ :path => "containers/#{server.id}/files",
152
+ :params => {
153
+ :path => path
154
+ }
155
+ ).get(:body)
156
+ end
157
+
158
+ # Put file on server
159
+ #
160
+ # @param server [Miasma::Models::Compute::Server]
161
+ # @param io [IO-ish]
162
+ # @param remote_path [String]
163
+ # @return [TrueClass]
164
+ # @todo update to write in chunks when over `n` size
165
+ def server_put_file(server, io, remote_path, options={})
166
+ request(
167
+ :method => :post,
168
+ :path => "containers/#{server.id}/files",
169
+ :params => {
170
+ :path => remote_path
171
+ },
172
+ :body => io.read,
173
+ :headers => {
174
+ 'X-LXD-uid' => options.fetch(:uid, 0),
175
+ 'X-LXD-gid' => options.fetch(:gid, 0),
176
+ 'X-LXD-mode' => options.fetch(:mode, 0700)
177
+ }
178
+ )
179
+ true
180
+ end
181
+
182
+ # Execute command
183
+ #
184
+ # @param server [Miasma::Models::Compute::Server]
185
+ # @param command [String]
186
+ # @param options [Hash]
187
+ # @option options [IO] :stream write command output
188
+ # @return [TrueClass, FalseClass] command was successful
189
+ def server_execute(server, command, options={})
190
+ result = request(
191
+ :method => :post,
192
+ :path => "containers/#{server.id}/exec",
193
+ :expects => 202,
194
+ :json => Smash.new(
195
+ :command => Shellwords.shellwords(command),
196
+ :interactive => true,
197
+ 'wait-for-websocket' => true
198
+ )
199
+ )
200
+ dest = URI.parse(api_endpoint)
201
+ operation = result.get(:body, :operation).sub("/#{version}/operations/", '')
202
+ ws_common = Smash.new(
203
+ :ssl_key => ssl_key,
204
+ :ssl_certificate => ssl_certificate
205
+ )
206
+ if(options[:stream])
207
+ ws_common[:on_message] = proc{|message|
208
+ options[:stream].write(message)
209
+ }
210
+ end
211
+ websockets = Smash[
212
+ ['control', '0'].map do |fd_id|
213
+ fd_secret = result.get(:body, :metadata, :fds, fd_id)
214
+ [
215
+ fd_id,
216
+ Bogo::Websocket::Client.new(
217
+ ws_common.merge(
218
+ :destination => "wss://#{[dest.host, dest.port].compact.join(':')}",
219
+ :path => "/#{version}/operations/#{operation}/websocket",
220
+ :params => {
221
+ :secret => fd_secret
222
+ }
223
+ )
224
+ )
225
+ ]
226
+ end
227
+ ]
228
+ wait_for_operation(operation, options.fetch(:timeout, 20))
229
+ websockets.map(&:last).map(&:close)
230
+ result = request(
231
+ :path => "operations/#{operation}"
232
+ )
233
+ result.get(:body, :metadata, :metadata, :return) == 0
234
+ end
235
+
236
+ protected
237
+
238
+ # Wait for a remote operation to complete
239
+ #
240
+ # @param op_uuid [String]
241
+ # @param timeout [Integer]
242
+ # @return [TrueClass]
243
+ def wait_for_operation(op_uuid, timeout=20)
244
+ op_uuid = op_uuid.sub("/#{version}/operations/", '')
245
+ request(
246
+ :path => "operations/#{op_uuid}/wait",
247
+ :params => {
248
+ :status_code => 200,
249
+ :timeout => 20
250
+ }
251
+ )
252
+ true
253
+ end
254
+
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,103 @@
1
+ require 'miasma'
2
+
3
+ module Miasma
4
+ module Contrib
5
+ module Lxd
6
+ # API updates required for LXD communication
7
+ module Api
8
+
9
+ # Default API version to require
10
+ DEFAULT_API_VERSION = '1.0'
11
+
12
+ # Load required attributes into API class
13
+ #
14
+ # @param klass [Class]
15
+ def self.included(klass)
16
+ klass.class_eval do
17
+ attribute :name, String, :required => true, :default => Socket.gethostname
18
+ attribute :password, String
19
+ attribute :api_endpoint, String, :required => true
20
+ attribute :ssl_certificate, String, :required => true
21
+ attribute :ssl_key, String, :required => true
22
+ attribute :version, Gem::Version, :coerce => lambda{|v| Gem::Version.new(v.to_s)}, :required => true, :default => lambda{ Miasma::Contrib::Lxd::Api::DEFAULT_API_VERSION }
23
+ end
24
+ end
25
+
26
+ # @return [String] versioned endpoint
27
+ def endpoint
28
+ "#{api_endpoint}/#{version}"
29
+ end
30
+
31
+ # Clean up endpoint prior to data loading
32
+ #
33
+ # @param args [Hash]
34
+ def custom_setup(args)
35
+ if(args[:api_endpoint].to_s.end_with?('/'))
36
+ args[:api_endpoint] = args[:api_endpoint][0, endpoint.length - 1]
37
+ end
38
+ end
39
+
40
+ # Perform request
41
+ #
42
+ # @param connection [HTTP]
43
+ # @param http_method [Symbol]
44
+ # @param request_args [Array]
45
+ # @return [HTTP::Response]
46
+ def make_request(connection, http_method, request_args)
47
+ dest, options = request_args
48
+ options = Smash.new unless options
49
+ options[:ssl_context] = ssl_context
50
+ connection.send(http_method, dest, options)
51
+ end
52
+
53
+ protected
54
+
55
+ # @return [OpenSSL::SSL::SSLContext.new]
56
+ def ssl_context
57
+ memoize(:ssl_context) do
58
+ ctx = OpenSSL::SSL::SSLContext.new
59
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(ssl_certificate))
60
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(ssl_key))
61
+ ctx
62
+ end
63
+ end
64
+
65
+ # Establish connection to endpoint and register client if not
66
+ # already registered
67
+ def connect
68
+ result = request(:endpoint => api_endpoint)[:body]
69
+ unless(result[:metadata].any?{|s| s.end_with?(version.to_s)})
70
+ raise InvalidVersionError
71
+ end
72
+ result = request(:endpoint => api_endpoint, :path => "/#{version}")[:body]
73
+ if(result[:auth] != 'trusted' && password)
74
+ authenticate_connection!
75
+ end
76
+ end
77
+
78
+ # Authenticate with the endpoint to create trusted connection
79
+ def authenticate_connection!
80
+ request(
81
+ :method => :post,
82
+ :path => 'certificates',
83
+ :json => {
84
+ :type => :client,
85
+ :name => name,
86
+ :password => password
87
+ }
88
+ )
89
+ end
90
+
91
+ # Always allow retry
92
+ #
93
+ # @return [TrueClass]
94
+ def retryable_allowed?(*_)
95
+ false
96
+ end
97
+
98
+ end
99
+ end
100
+ end
101
+
102
+ Models::Compute.autoload :Lxd, 'miasma/contrib/lxd/compute'
103
+ end
@@ -0,0 +1,4 @@
1
+ module MiasmaLxd
2
+ # Current library version
3
+ VERSION = Gem::Version.new('0.1.0')
4
+ end
data/lib/miasma-lxd.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'miasma'
2
+ require 'miasma-lxd/version'
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'miasma-lxd/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'miasma-lxd'
5
+ s.version = MiasmaLxd::VERSION.version
6
+ s.summary = 'Smoggy LXD API'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'code@chrisroberts.org'
9
+ s.homepage = 'https://github.com/miasma-rb/miasma-lxd'
10
+ s.description = 'Smoggy LXD API'
11
+ s.license = 'Apache 2.0'
12
+ s.require_path = 'lib'
13
+ s.add_runtime_dependency 'bogo-websocket', '< 1.0.0'
14
+ s.add_development_dependency 'miasma', '>= 0.2.31'
15
+ s.add_development_dependency 'pry'
16
+ s.add_development_dependency 'minitest'
17
+ s.add_development_dependency 'vcr'
18
+ s.add_development_dependency 'webmock'
19
+ s.add_development_dependency 'psych', '>= 2.0.8'
20
+ s.files = Dir['lib/**/*'] + %w(miasma-lxd.gemspec README.md CHANGELOG.md LICENSE)
21
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miasma-lxd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Roberts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bogo-websocket
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: miasma
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.31
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.31
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: psych
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 2.0.8
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.0.8
111
+ description: Smoggy LXD API
112
+ email: code@chrisroberts.org
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - CHANGELOG.md
118
+ - LICENSE
119
+ - README.md
120
+ - lib/miasma-lxd.rb
121
+ - lib/miasma-lxd/version.rb
122
+ - lib/miasma/contrib/lxd.rb
123
+ - lib/miasma/contrib/lxd/compute.rb
124
+ - miasma-lxd.gemspec
125
+ homepage: https://github.com/miasma-rb/miasma-lxd
126
+ licenses:
127
+ - Apache 2.0
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.4.8
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Smoggy LXD API
149
+ test_files: []