bosh_cpi 1.2539.0 → 1.2546.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,7 @@ require "forwardable"
7
7
  require "cloud/config"
8
8
  require "cloud/errors"
9
9
  require "cloud/provider"
10
+ require "cloud/external_cpi"
10
11
 
11
12
  module Bosh
12
13
 
@@ -0,0 +1,140 @@
1
+ require 'membrane'
2
+ require 'open3'
3
+
4
+ module Bosh::Clouds
5
+ class ExternalCpi
6
+ ##
7
+ # Raised when the external CPI executable returns an error unknown to director
8
+ #
9
+ class UnknownError < StandardError; end
10
+
11
+ ##
12
+ # Raised when the external CPI executable returns nil or invalid JSON format to director
13
+ class InvalidResponse < StandardError; end
14
+
15
+ ##
16
+ # Raised when the external CPI bin/cpi is not executable
17
+ class NonExecutable < StandardError; end
18
+
19
+ KNOWN_RPC_ERRORS = %w(
20
+ Bosh::Clouds::VMCreationFailed
21
+ Bosh::Clouds::DiskNotFound
22
+ Bosh::Clouds::DiskNotAttached
23
+ Bosh::Clouds::NoDiskSpace
24
+ Bosh::Clouds::CloudError
25
+ Bosh::Clouds::CpiError
26
+ ).freeze
27
+
28
+ KNOWN_RPC_METHODS = %w(
29
+ current_vm_id
30
+ create_stemcell
31
+ delete_stemcell
32
+ create_vm
33
+ delete_vm
34
+ has_vm?
35
+ reboot_vm
36
+ set_vm_metadata
37
+ configure_networks
38
+ create_disk
39
+ delete_disk
40
+ attach_disk
41
+ detach_disk
42
+ snapshot_disk
43
+ delete_snapshot
44
+ get_disks
45
+ ping
46
+ ).freeze
47
+
48
+ RESPONSE_SCHEMA = Membrane::SchemaParser.parse do
49
+ {
50
+ 'result' => any,
51
+ 'error' => enum(nil,
52
+ { 'type' => String,
53
+ 'message' => String,
54
+ 'ok_to_retry' => bool
55
+ }
56
+ )
57
+ }
58
+ end
59
+
60
+ def initialize(cpi_path, director_uuid)
61
+ @cpi_path = cpi_path
62
+ @director_uuid = director_uuid
63
+ @logger = Config.logger
64
+ end
65
+
66
+ KNOWN_RPC_METHODS.each do |method_name|
67
+ define_method method_name do |*arguments|
68
+ request = JSON.dump({
69
+ 'method' => method_name.gsub(/\?$/,''),
70
+ 'arguments' => arguments,
71
+ 'context' => {
72
+ 'director_uuid' => @director_uuid
73
+ }
74
+ })
75
+
76
+ env = {'PATH' => '/usr/sbin:/usr/bin:/sbin:/bin', 'TMPDIR' => ENV['TMPDIR']}
77
+ cpi_exec_path = checked_cpi_exec_path
78
+
79
+ @logger.debug("External CPI sending request: #{request} with command: #{cpi_exec_path}")
80
+ cpi_response, stderr, exit_status = Open3.capture3(env, cpi_exec_path, stdin_data: request)
81
+ @logger.debug("External CPI got response: #{cpi_response}, err: #{stderr}, exit_status: #{exit_status}")
82
+
83
+ parsed_response = parsed_response(cpi_response)
84
+ validate_response(parsed_response)
85
+
86
+ if parsed_response['error']
87
+ handle_error(parsed_response['error'])
88
+ end
89
+
90
+ parsed_response['result']
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def checked_cpi_exec_path
97
+ unless File.executable?(@cpi_path)
98
+ raise NonExecutable, "Failed to run cpi: `#{@cpi_path}' is not executable"
99
+ end
100
+ @cpi_path
101
+ end
102
+
103
+ def handle_error(error_response)
104
+ error_type = error_response['type']
105
+ error_message = error_response['message']
106
+ unless KNOWN_RPC_ERRORS.include?(error_type)
107
+ raise UnknownError, "Received unknown error from cpi: #{error_type} with message #{error_message}"
108
+ end
109
+
110
+ error_class = constantize(error_type)
111
+
112
+ if error_class <= RetriableCloudError
113
+ error = error_class.new(error_response['ok_to_retry'])
114
+ else
115
+ error = error_class.new(error_message)
116
+ end
117
+
118
+ raise error, error_message
119
+ end
120
+
121
+ def parsed_response(input)
122
+ begin
123
+ JSON.load(input)
124
+ rescue JSON::ParserError => e
125
+ raise InvalidResponse, "Received invalid response from cpi with error #{e.message}"
126
+ end
127
+ end
128
+
129
+ def validate_response(response)
130
+ RESPONSE_SCHEMA.validate(response)
131
+ rescue Membrane::SchemaValidationError => e
132
+ raise InvalidResponse, "Received invalid response from cpi with error #{e.message}"
133
+ end
134
+
135
+ def constantize(camel_cased_word)
136
+ error_name = camel_cased_word.split('::').last
137
+ Bosh::Clouds.const_get(error_name)
138
+ end
139
+ end
140
+ end
@@ -1,6 +1,17 @@
1
1
  module Bosh::Clouds
2
2
  class Provider
3
+ def self.create(cloud_config, director_uuid)
4
+ if cloud_config.fetch('external_cpi',{}).fetch('enabled', false)
5
+ ExternalCpiProvider.create(cloud_config['external_cpi'], director_uuid)
6
+ else
7
+ PluginCloudProvider.create(cloud_config['plugin'], cloud_config['properties'])
8
+ end
9
+ end
10
+ end
3
11
 
12
+ private
13
+
14
+ class PluginCloudProvider
4
15
  def self.create(plugin, options)
5
16
  begin
6
17
  require "cloud/#{plugin}"
@@ -10,6 +21,11 @@ module Bosh::Clouds
10
21
 
11
22
  Bosh::Clouds.const_get(plugin.capitalize).new(options)
12
23
  end
24
+ end
13
25
 
26
+ class ExternalCpiProvider
27
+ def self.create(external_cpi_config, director_uuid)
28
+ ExternalCpi.new(external_cpi_config['cpi_path'], director_uuid)
29
+ end
14
30
  end
15
31
  end
@@ -1,5 +1,5 @@
1
1
  module Bosh
2
2
  module Clouds
3
- VERSION = '1.2539.0'
3
+ VERSION = '1.2546.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bosh_cpi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2539.0
4
+ version: 1.2546.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-10 00:00:00.000000000 Z
12
+ date: 2014-05-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bosh_common
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.2539.0
21
+ version: 1.2546.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,10 +26,10 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 1.2539.0
29
+ version: 1.2546.0
30
30
  description: ! 'BOSH CPI
31
31
 
32
- 69090d'
32
+ e8aedb'
33
33
  email: support@cloudfoundry.com
34
34
  executables: []
35
35
  extensions: []
@@ -44,6 +44,7 @@ files:
44
44
  - lib/cloud.rb
45
45
  - lib/cloud/config.rb
46
46
  - lib/cloud/errors.rb
47
+ - lib/cloud/external_cpi.rb
47
48
  - lib/cloud/provider.rb
48
49
  - lib/cloud/version.rb
49
50
  - README
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  version: '0'
69
70
  segments:
70
71
  - 0
71
- hash: -52753533641108904
72
+ hash: -941851252333260636
72
73
  requirements: []
73
74
  rubyforge_project:
74
75
  rubygems_version: 1.8.23