cloud_powers 0.2.7.23 → 1.0.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.test.env.example +6 -6
  4. data/.travis.yml +1 -1
  5. data/README +190 -0
  6. data/cloud_powers.gemspec +4 -4
  7. data/lib/cloud_powers.rb +3 -13
  8. data/lib/cloud_powers/aws_resources.rb +21 -4
  9. data/lib/cloud_powers/creatable.rb +122 -0
  10. data/lib/cloud_powers/helpers.rb +58 -0
  11. data/lib/cloud_powers/helpers/lang_help.rb +288 -0
  12. data/lib/cloud_powers/helpers/logic_help.rb +152 -0
  13. data/lib/cloud_powers/helpers/path_help.rb +90 -0
  14. data/lib/cloud_powers/node.rb +69 -68
  15. data/lib/cloud_powers/node/instance.rb +52 -0
  16. data/lib/cloud_powers/resource.rb +44 -0
  17. data/lib/cloud_powers/storage.rb +27 -14
  18. data/lib/{stubs → cloud_powers/stubs}/aws_stubs.rb +37 -14
  19. data/lib/cloud_powers/synapse/broadcast.rb +117 -0
  20. data/lib/cloud_powers/synapse/broadcast/channel.rb +44 -0
  21. data/lib/cloud_powers/synapse/pipe.rb +211 -0
  22. data/lib/cloud_powers/synapse/pipe/stream.rb +41 -0
  23. data/lib/cloud_powers/synapse/queue.rb +357 -0
  24. data/lib/cloud_powers/synapse/queue/board.rb +61 -95
  25. data/lib/cloud_powers/synapse/queue/poller.rb +29 -0
  26. data/lib/cloud_powers/synapse/synapse.rb +10 -12
  27. data/lib/cloud_powers/synapse/web_soc.rb +13 -0
  28. data/lib/cloud_powers/synapse/web_soc/soc_client.rb +52 -0
  29. data/lib/cloud_powers/synapse/web_soc/soc_server.rb +48 -0
  30. data/lib/cloud_powers/version.rb +1 -1
  31. data/lib/cloud_powers/zenv.rb +13 -12
  32. metadata +24 -13
  33. data/lib/cloud_powers/context.rb +0 -275
  34. data/lib/cloud_powers/delegator.rb +0 -113
  35. data/lib/cloud_powers/helper.rb +0 -453
  36. data/lib/cloud_powers/synapse/websocket/websocclient.rb +0 -53
  37. data/lib/cloud_powers/synapse/websocket/websocserver.rb +0 -46
  38. data/lib/cloud_powers/workflow_factory.rb +0 -160
@@ -0,0 +1,90 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'uri'
4
+
5
+ module Smash
6
+ module CloudPowers
7
+ module PathHelp
8
+
9
+ # Gives a common home for jobs to live so they can be easily grouped and
10
+ # found. This method will create nested directories, based on the
11
+ # <tt>#project_root()</tt> method and an additional 'lib/jobs' directory.
12
+ # If no project root has been set by the time this method is called, a new
13
+ # directory will be created relative to the gem's project root.
14
+ #
15
+ # Returns
16
+ # +String+
17
+ #
18
+ # Notes
19
+ # * # If no project root has been set by the time this method is called, a new
20
+ # directory will be created relative to the gem's project root. This might
21
+ # have deeper implications than you want to deal with so it's always a good
22
+ # idea to set your project root as soon as you can.
23
+ # * TODO: find a way to have this method figure out the actual project's
24
+ # root, as opposed to just making common <i>"good"</i> assumptions.
25
+ def job_home
26
+ string_th = FileUtils.mkdir_p("#{project_root}/lib/jobs/").first
27
+ @job_home ||= Pathname.new(string_th).realpath.to_s
28
+ end
29
+
30
+ # Gives the path from the project root to lib/jobs[/#{file}.rb]
31
+ #
32
+ # Parameters
33
+ # * file +String+ (optional) (default is '') - name of a file
34
+ #
35
+ # Returns
36
+ # * path/file +String+ if +file+ parameter is given. return has
37
+ # '.rb' extension included
38
+ # * file +String+ if +file+ parameter is not given it will return the
39
+ # <tt>#job_require_path()</tt>
40
+ #
41
+ # Notes
42
+ # * See <tt>#job_home</tt>
43
+ def job_path(file = '')
44
+ return job_home if file.empty?
45
+ Pathname.new("#{job_home}/#{file}").to_s
46
+ end
47
+
48
+
49
+ # Check if the job file exists in the job directory
50
+ #
51
+ # Parameters
52
+ # * file +String+
53
+ #
54
+ # Returns
55
+ # +Boolean+
56
+ #
57
+ # Notes
58
+ # * See +#job_home()+
59
+ def job_exist?(file)
60
+ begin
61
+ File.new("#{job_home}/#{file}")
62
+ true
63
+ rescue Errno::ENOENT
64
+ false
65
+ end
66
+ end
67
+
68
+ # Gives the path from the project root to lib/jobs[/file]
69
+ #
70
+ # Parameters String (optional)
71
+ # * file_name name of a file
72
+ #
73
+ # Returns
74
+ # * path/file +String+ if +file_name+ was given
75
+ # * path to job_directory if +file_name+ was <i>not</i> given
76
+ #
77
+ # Notes
78
+ # * Neither path nor file will have a file extension
79
+ # * See <tt>#job_home</tt>
80
+ def job_require_path(file_name = '')
81
+ begin
82
+ file_sans_extension = File.basename(file_name, '.*')
83
+ (Pathname.new(job_home) + file_sans_extension).to_s
84
+ rescue Errno::ENOENT
85
+ nil
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,36 +1,41 @@
1
- require_relative 'auth'
2
- require_relative 'helper'
3
- require_relative 'self_awareness'
4
- require_relative 'zenv'
1
+ require 'cloud_powers/auth'
2
+ require 'cloud_powers/helpers'
3
+ require 'cloud_powers/zenv'
4
+ require 'cloud_powers/node/instance'
5
5
 
6
6
  module Smash
7
7
  module CloudPowers
8
8
  module Node
9
9
  include Smash::CloudPowers::Auth
10
- include Smash::CloudPowers::Helper
11
- include Smash::CloudPowers::SelfAwareness
10
+ include Smash::CloudPowers::Helpers
12
11
  include Smash::CloudPowers::Zenv
13
12
 
14
- # These are sensible defaults that can be overriden by providing a Hash as a param.
13
+ # This method adds certain tags to an array of resource ids.
15
14
  #
16
15
  # Parameters
17
- # * opts +Hash+ (optional)
18
- # the opts Hash should have values that should be used instead of the default
19
- # configuration.
20
- def node_config(opts = {})
21
- {
22
- dry_run: zfind(:testing) || false,
23
- image_id: image('crawlbotprod').image_id, # image(:neuron).image_id
24
- instance_type: 't2.nano',
25
- min_count: opts[:max_count] || 0,
26
- max_count: 0,
27
- key_name: 'crawlBot',
28
- security_groups: ['webCrawler'],
29
- security_group_ids: ['sg-940edcf2'],
30
- placement: { availability_zone: 'us-west-2c' },
31
- disable_api_termination: 'false',
32
- instance_initiated_shutdown_behavior: 'terminate'
33
- }.merge(opts)
16
+ # * ids +Array+|+String+ - an Array or a single instance id, as an Array
17
+ # of Strings or a single String
18
+ # * tags +Array+ - an Array of key, value hash
19
+ #
20
+ # Returns
21
+ # * Returns an empty response.
22
+ #
23
+ # Examples
24
+ # create_tag('ami-2342354', tags: { key: "stack", value: "production"})
25
+ # or
26
+ # create_tag(['ami-2432342'], tags: [{ key: 'stack', value: 'production' }])
27
+ # or any permutation of those
28
+ def batch_tag(ids, tags, client: ec2)
29
+ tags_opts = { resources: ids, tags: tags }
30
+ ec2.create_tags(tags_opts)
31
+ logger.info "tags for #{ids} created"
32
+ end
33
+
34
+ def build_node(name:, client: ec2, **config)
35
+ resp = Smash::CloudPowers::Node::Instance.build(name: name, client: ec2, **config)
36
+ i_var_name = "#{name}_node"
37
+ instance_attr_accessor i_var_name
38
+ instance_variable_set(to_i_var(i_var_name), resp)
34
39
  end
35
40
 
36
41
  # Uses +Aws::EC2#run_instances()+ to create nodes (Neurons or Cerebrums), at
@@ -43,58 +48,54 @@ module Smash
43
48
  # * opts +Hash+ (optional)
44
49
  # an optional instance configuration hash can be passed, which will override
45
50
  # the values in the default configuration returned by #instance_config()
46
- def spin_up_neurons(opts = {}, tags = [])
47
- should_wait = opts.delete(:wait) || true
48
- ids = nil
49
- begin
50
-
51
- response = ec2.run_instances(node_config(opts))
52
- ids = response.instances.map(&:instance_id)
53
-
54
- if should_wait
55
- count = 0
56
- begin
57
- ec2.wait_until(:instance_running, instance_ids: ids) do
58
- logger.info "waiting for #{ids.count} Neurons to start..."
59
- end
60
- rescue Aws::Waiters::Errors::WaiterFailed => e
61
- # TODO: deal with failed instances
62
- # redo unless (count += 1 <=3 )
63
- end
64
- end
65
-
66
- batch_tag(ids, tags) unless tags.empty?
67
- ids
68
-
69
- rescue Aws::EC2::Errors::DryRunOperation
70
- ids = (1..(opts[:max_count] || 0)).to_a.map { |n| n.to_s }
71
- logger.info "waiting for #{ids.count} Neurons to start..."
72
- end
73
-
74
- ids
51
+ def create_node(name:, client: ec2, **config)
52
+ resp = Smash::CloudPowers::Node::Instance.create!(name: name, client: ec2, **config)
53
+ i_var_name = "#{name}_node"
54
+ instance_attr_accessor i_var_name
55
+ instance_variable_set(to_i_var(i_var_name), resp)
75
56
  end
76
57
 
77
- # This method adds certain tags to an array of resource ids.
58
+ # Uses +Aws::EC2#run_instances()+ to create nodes (Neurons or Cerebrums),
59
+ # at a rate of 0..(n <= 100) at a time, until the required number of
60
+ # instances has been started. The #instance_config() method is used to
61
+ # create instance configuration for the #run_instances method by using
62
+ # the opts hash that was provided as a parameter.
78
63
  #
79
64
  # Parameters
80
- # * ids +Array+|+String+ - an Array or a single instance id, as an Array of Strings or a single String
81
- # * tags +Array+ - an Array of key, value hash
65
+ # * opts +Hash+ (optional) - an optional instance configuration hash can
66
+ # be passed, which will override the values in the default configuration
67
+ # returned by <tt>#instance_config()</tt>
82
68
  #
83
69
  # Returns
84
- # * Returns an empty response.
85
- #
86
- # Examples
87
- # create_tag('ami-2342354', tags: { key: "stack", value: "production"})
88
- # or
89
- # create_tag(['ami-2432342'], tags: [{ key: 'stack', value: 'production' }])
90
- # or any permutation of those
91
-
92
- def batch_tag(ids, tags)
93
- tags_opts = { resources: ids, tags: tags }
94
- ec2.create_tags(tags_opts)
95
- logger.info "tags for #{ids} created"
70
+ # +Array+ of responses from
71
+ def create_nodes(name:, client: ec2, **config)
72
+ resp = ec2.run_instances(node_config(config)).instances
73
+ i_var_name = "#{name}_nodes"
74
+ instance_attr_accessor i_var_name
75
+ instance_variable_set(to_i_var(i_var_name), resp)
96
76
  end
97
77
 
78
+ # These are sensible defaults that can be overriden by providing a Hash as a param.
79
+ #
80
+ # Parameters
81
+ # * opts +Hash+ (optional)
82
+ # the opts Hash should have values that should be used instead of the default
83
+ # configuration.
84
+ def node_config(**config)
85
+ {
86
+ dry_run: zfind(:testing) || false,
87
+ image_id: image(zfind(:node_image)).image_id, # image(:neuron).image_id
88
+ instance_type: 't2.nano',
89
+ min_count: config[:max_count] || 0,
90
+ max_count: 0,
91
+ key_name: 'crawlBot',
92
+ security_groups: ['webCrawler'],
93
+ security_group_ids: ['sg-940edcf2'],
94
+ placement: { availability_zone: 'us-west-2c' },
95
+ disable_api_termination: 'false',
96
+ instance_initiated_shutdown_behavior: 'terminate'
97
+ }.merge(config)
98
+ end
98
99
  end
99
100
  end
100
101
  end
@@ -0,0 +1,52 @@
1
+ require 'cloud_powers/node'
2
+ require 'cloud_powers/resource'
3
+
4
+ module Smash
5
+ module CloudPowers
6
+ module Node
7
+ class Instance < Smash::CloudPowers::Resource
8
+ include Smash::CloudPowers::Node
9
+
10
+ # The name of the Aws::EC2 instance
11
+ attr_accessor :name
12
+ # An Aws::EC2::Client. See <tt>Smash::CloudPowers::AwsResources#ec2()</tt>
13
+ attr_accessor :ec2
14
+
15
+ def initialize(name:, client: ec2, **config)
16
+ super(name: name)
17
+ @ec2 = client
18
+ end
19
+
20
+ # Uses +Aws::EC2#run_instances()+ to create nodes (Neurons or Cerebrums), at
21
+ # a rate of 0..(n <= 100) at a time, until the required number of instances
22
+ # has been started. The #instance_config() method is used to create instance
23
+ # configuration for the #run_instances method by using the opts hash that was
24
+ # provided as a parameter.
25
+ #
26
+ # Parameters
27
+ # * opts +Hash+ (optional)
28
+ # an optional instance configuration hash can be passed, which will override
29
+ # the values in the default configuration returned by #instance_config()
30
+ def create_resource
31
+ # response = ec2.run_instances(
32
+ # node_config(max_count: 1, self.to_h)
33
+ # ).instances.first
34
+
35
+ instance_attr_accessor response
36
+ # id = @response[:instance_id]
37
+ begin
38
+ ec2.wait_until(:instance_running, instance_ids: [id]) do
39
+ logger.info "waiting for #{ids.count} Neurons to start..."
40
+ end
41
+ rescue Aws::Waiters::Errors::WaiterFailed => e
42
+ # TODO: retry stuff
43
+ # redo unless (count += 1 <=3 )
44
+ end
45
+
46
+ yield self if block_given?
47
+ self
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ require 'cloud_powers/aws_resources'
2
+ require 'cloud_powers/creatable'
3
+ require 'cloud_powers/helpers'
4
+ require 'cloud_powers/zenv'
5
+
6
+ module Smash
7
+ module CloudPowers
8
+ class Resource
9
+ include Smash::CloudPowers::Creatable
10
+ include Smash::CloudPowers::AwsResources
11
+ include Smash::CloudPowers::Helpers
12
+ include Smash::CloudPowers::Zenv
13
+
14
+ # client used to make HTTP requests to the cloud
15
+ attr_accessor :client
16
+ # the name this resource can be set and retrieved by
17
+ attr_accessor :call_name
18
+ # the given name for this resource
19
+ attr_accessor :name
20
+ # whether or not a call has been made to the cloud to back this resource
21
+ attr_accessor :linked
22
+ # the ID in the cloud; e.g. ARN for AWS, etc
23
+ attr_accessor :remote_id
24
+ # tracking on the remote resource that maps to this resource
25
+ attr_accessor :tags
26
+ # the type of resource this was instantiated as
27
+ attr_accessor :type
28
+
29
+ # Usually this method is called by calling +super+ in another class that
30
+ # inherits from this class. The initialize method follows the method
31
+ # signature for the active record-like pattern being followed throughout
32
+ # the code
33
+ def initialize(name:, client: nil, **config)
34
+ @linked = false
35
+ @saved = false
36
+ @client = client
37
+ @type = to_snake(self.class.name.split('::').last)
38
+ @call_name = to_snake("#{name}_#{@type}")
39
+ @name = name
40
+ @tags = Array.new
41
+ end
42
+ end
43
+ end
44
+ end
@@ -6,7 +6,11 @@ module Smash
6
6
  module Storage
7
7
  include Smash::CloudPowers::AwsResources
8
8
 
9
- # Searches a local task storage location for the given +file+ name
9
+ def local_job_file_exists?(file)
10
+ File.exists?(job_path(to_ruby_file_name(file)))
11
+ end
12
+
13
+ # Searches a local job storage location for the given +file+ name
10
14
  # if it exists - exit the method
11
15
  # if it does <i>not</i> exist - get the file from s3 and place it in
12
16
  # the directory that was just searched bucket using +#zfind()+
@@ -22,31 +26,32 @@ module Smash
22
26
  # project_root- |
23
27
  # |_sub_directory
24
28
  # | |_current_file.rb
25
- # |_task_storage
29
+ # |_job_storage
26
30
  # | |_demorific.rb
27
31
  # | |_foobar.rb
28
32
  # * code:
29
- # source_task('demorific')
33
+ # source_job('demorific')
30
34
  # # file tree doesn't change because this file exists
31
- # source_task('custom_greetings')
35
+ # source_job('custom_greetings')
32
36
  # # file tree now looks like this
33
37
  # * new file tree
34
38
  # project_root- |
35
39
  # |_sub_directory
36
40
  # | |_current_file.rb
37
- # |_task_storage
41
+ # |_job_storage
38
42
  # | |_demorific.rb
39
43
  # | |_foobar.rb
40
44
  # | |_custom_greetings.js # could be an after effects JS script
41
- def source_task(file)
45
+ def source_job(file)
42
46
  # TODO: better path management
43
- bucket = zfind('task storage')
44
- if task_path(to_ruby_file_name(file)).nil?
47
+ bucket = zfind('job storage')
48
+ if job_path(to_ruby_file_name(file)).nil?
45
49
  objects = s3.list_objects(bucket: bucket).contents.select do |f|
46
50
  /#{Regexp.escape file}/i =~ f.key
47
51
  end
52
+
48
53
  objects.each do |obj|
49
- s3.get_object(bucket: bucket, key: obj.key, response_target: task_path(file))
54
+ s3.get_object(bucket: bucket, key: obj.key, response_target: job_path(file))
50
55
  end
51
56
  end
52
57
  end
@@ -55,17 +60,25 @@ module Smash
55
60
  #
56
61
  # Parameters
57
62
  # * bucket +String+ - the bucket to search through in AWS
58
- # * pattern +Regex+ - the Regex pattern you want to use for the
59
- # search
63
+ # * pattern +Regexp+|+nil+ - the Regex pattern you want to use for the
64
+ # search. nil doesn't cause an exception but probably returns <tt>[]</tt>
60
65
  #
61
66
  # Example
62
- # matches = search('neuronTasks', /[Dd]emo\w*/)
67
+ # matches = search('neuronJobs', /[Dd]emo\w*/)
63
68
  # # => ['Aws::S3::Type::ListObjectOutPut','Aws::S3::Type::ListObjectOutPut',...] # anything that matched that regex
64
69
  # matches.first.contents.size
65
70
  # # => 238934 # integer representation of the file size
66
71
  def search(bucket, pattern)
67
- s3.list_objects(bucket: bucket).contents.select do |o|
68
- o.key =~ pattern
72
+ return [] if bucket.nil?
73
+
74
+ begin
75
+ pattern = /#{pattern}/ unless pattern.kind_of? Regexp
76
+ s3.list_objects(bucket: bucket).contents.select do |o|
77
+ o.key =~ pattern || /#{o.key}/ =~ pattern.to_s
78
+ end
79
+ rescue Aws::S3::Errors::NoSuchBucket => e
80
+ logger.info format_error_message e
81
+ return # log that the bucket doesn't exist but don't explode
69
82
  end
70
83
  end
71
84
 
@@ -74,7 +74,7 @@ module Smash
74
74
  # for your own custom configuration
75
75
  def self.node_stub(opts = {})
76
76
  time = opts[:launch_time] || Time.new((Time.now.utc.to_i / 86400 * 86400)) # midnight
77
- tags = [{key: 'task', value: 'test'}]
77
+ tags = [{key: 'job', value: 'test'}]
78
78
  {
79
79
  stub_responses: {
80
80
  create_tags: {},
@@ -143,18 +143,22 @@ module Smash
143
143
  stub.merge(opts.select { |k,v| stub.key? k })
144
144
  end
145
145
 
146
- # Get or create an Kinesis client and cache that client so that a Context is more well tied together
146
+ # Get or create an Kinesis client and cache that client so that a Context
147
+ # is more well tied together
147
148
  # Parameters
148
149
  # * opts <tt>Hash</tt>
149
- # * * stub_responses: defaulted to false but it can be overriden with the desired responses for local testing
150
- # * * region: defaulted to use the `#region()` method
151
- # * * AWS::Credentials object, which will also scour the context and environment for your keys
150
+ # * stub_responses: defaulted to false but it can be overriden with the
151
+ # desired responses for local testing
152
+ # * region: defaulted to use the `#region()` method
153
+ # * AWS::Credentials object, which will also scour the context and
154
+ # environment for your keys
152
155
  #
153
156
  # Returns
154
157
  # AWS::Kinesis client
155
158
  #
156
159
  # Example
157
- # # sets and gets an Kinesis client. No need to set a variable because one was
160
+ # # sets and gets an Kinesis client. No need to set a variable because
161
+ # one was
158
162
  # # just created as +@kinesis+ and is set to the client
159
163
  # kinesis(Smash::CloudPowers::AwsStubs.pipe_stub)
160
164
  #
@@ -175,6 +179,7 @@ module Smash
175
179
  },
176
180
  describe_stream: {
177
181
  stream_description: {
182
+ stream_creation_timestamp: Time.now - 10,
178
183
  stream_name: opts[:name] || 'testPipe',
179
184
  stream_arn: 'arnarnarnarnar',
180
185
  stream_status: 'ACTIVE',
@@ -223,7 +228,25 @@ module Smash
223
228
  def self.storage_stub(opts = {})
224
229
  {
225
230
  stub_responses: {
226
- head_bucket: {}
231
+ head_bucket: {
232
+
233
+ },
234
+ list_objects: {
235
+ is_truncated: false,
236
+ contents: [
237
+ key: 'testinz',
238
+ last_modified: Time.now,
239
+ etag: 'asdf1234',
240
+ size: 123,
241
+ storage_class: 'STANDARD',
242
+ owner: {display_name: 'snargle', id: 'id-bargle132'}
243
+ ],
244
+ name: 'testFile',
245
+ prefix: 'test',
246
+ delimiter: ' ',
247
+ max_keys: 1234,
248
+ common_prefixes: [{ prefix: 'test' }]
249
+ }
227
250
  }
228
251
  }
229
252
  end
@@ -250,14 +273,14 @@ module Smash
250
273
  # for your own custom configuration
251
274
  def self.queue_stub(opts = {})
252
275
  msg_body = if opts[:body]
253
- if opts[:body].kind_of? Hash
254
- opts[:body] = opts[:body].to_json
255
- elsif JSON.parse(opts[:body])
256
- begin
257
- opts[:body]
258
- rescue JSON::ParserError
259
- {foo: 'bar'}.to_json
276
+ begin
277
+ if opts[:body].kind_of? Hash
278
+ opts[:body] = opts[:body].to_json
279
+ elsif JSON.parse(opts[:body])
280
+ opts[:body]
260
281
  end
282
+ rescue JSON::ParserError
283
+ {foo: 'bar'}.to_json
261
284
  end
262
285
  end
263
286
  {