jobbie_oci 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +20 -0
- data/README.md +66 -0
- data/Rakefile +4 -0
- data/exe/jobbie +6 -0
- data/jobbie_oci.gemspec +26 -0
- data/lib/jobbie_oci/cli.rb +56 -0
- data/lib/jobbie_oci/jobs.rb +162 -0
- data/lib/jobbie_oci/oci.rb +60 -0
- data/lib/jobbie_oci/version.rb +5 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 75785a941b0a3e17af5387fbd61e775a881018a4
|
4
|
+
data.tar.gz: f9d3b9416587e292b184efb0015a6a2d5e8ccf41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 19e62e7a8ef1808507143e01cbbb97524b65b34e32485490a204c7a54eef733e1584ecb7f013f029b10e66998bd4d4e233f02a01231835af4e03c908cb06667f
|
7
|
+
data.tar.gz: 7b736413ac7b793f5111a95df6cf9d5d6a10b4625699c158b6a810bfd127a5259a46e59d7d0ec26a3c9d0d05871463e79b20080d2e3d4bb52408bf55e651bb84
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# JobbieOci
|
2
|
+
|
3
|
+
A very simple job queuing system that uses OCI Casper buckets as a backend.
|
4
|
+
|
5
|
+
Jobbie is intended to fill a gap in Oracle's OCI cloud whilst there is a lack
|
6
|
+
of a queueing service similar to Amazon's SNS/SQS. As soon as such a service
|
7
|
+
is introduced, it's unlikely that Jobbie will serve any further purpose.
|
8
|
+
|
9
|
+
Jobs are added to an 'execution group', which is used to group related
|
10
|
+
tasks together for consumption by a daemon. Multiple daemons can run in the
|
11
|
+
same execution group, but only one daemon will be able to claim the job. If
|
12
|
+
a daemon is unable to complete the job then it will eventually time out and
|
13
|
+
become claimable again.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
```
|
18
|
+
gem install jobbie_oci
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Ensure that you have a bucket in Casper and that you have permission to create
|
24
|
+
objects in it.
|
25
|
+
|
26
|
+
Jobbie can be run in one of two modes: Either client mode which can submit jobs
|
27
|
+
or list them, or daemon mode which takes jobs from the queue and executes them.
|
28
|
+
|
29
|
+
To run jobs in daemon mode, you will use:
|
30
|
+
```
|
31
|
+
jobbie daemon --exec-group=EXEC_GROUP --script-dir=SCRIPT_DIR
|
32
|
+
```
|
33
|
+
|
34
|
+
An `execution group` is used to to group related tasks together. Jobs
|
35
|
+
submitted in the same execution group will be executed in the same way. For
|
36
|
+
example, you might create a job in the "cleanup\_users" exec group, and a
|
37
|
+
jobbie daemon with a matching exec-group will be used to execute a script
|
38
|
+
to cleanup the user.
|
39
|
+
|
40
|
+
The script dir is the only directory in which scripts triggered by the
|
41
|
+
daemon are allowed to run. No fully qualified paths are allowed in the script
|
42
|
+
name.
|
43
|
+
|
44
|
+
## Example
|
45
|
+
|
46
|
+
Suppose we have a script directory called `scripts` and that we add to it a
|
47
|
+
script called `cleanup_users`.
|
48
|
+
|
49
|
+
We could add a job to the queue as follows:
|
50
|
+
```
|
51
|
+
jobbie add --bucket my-bucket --exec-group=USERMGMT --script=cleanup_users user123
|
52
|
+
```
|
53
|
+
|
54
|
+
Meanwhile we could write start a daemon as follows:
|
55
|
+
```
|
56
|
+
jobbie daemon --bucket my-bucket --exec-group=USERMGMT --script-dir=scripts
|
57
|
+
```
|
58
|
+
.. and the daemon would pick up the job and execute the script as if
|
59
|
+
someone were to manually run:
|
60
|
+
```
|
61
|
+
./scripts/cleanup_users user123
|
62
|
+
```
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jobbie_oci.
|
data/Rakefile
ADDED
data/exe/jobbie
ADDED
data/jobbie_oci.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib = File.expand_path('lib', __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'jobbie_oci/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'jobbie_oci'
|
10
|
+
spec.version = JobbieOci::VERSION
|
11
|
+
spec.authors = ['Stephen Pearson']
|
12
|
+
spec.email = ['stephen.pearson@oracle.com']
|
13
|
+
|
14
|
+
spec.summary = 'Simple noddy job queuer that uses OCI Casper backend'
|
15
|
+
spec.homepage = 'http://github.com/stephenpearson/jobbie_oci'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'oci'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
require 'jobbie_oci/jobs'
|
7
|
+
|
8
|
+
module Jobbie
|
9
|
+
module CLI
|
10
|
+
# This is the Main entry point for the CLI
|
11
|
+
class Main < Thor
|
12
|
+
class_option :region,
|
13
|
+
type: :string,
|
14
|
+
default: 'phx',
|
15
|
+
desc: 'Region in which to store jobs'
|
16
|
+
class_option :profile,
|
17
|
+
type: :string,
|
18
|
+
default: 'DEFAULT',
|
19
|
+
desc: 'OCI config profile name'
|
20
|
+
class_option :config,
|
21
|
+
type: :string,
|
22
|
+
default: OCI::ConfigFileLoader::DEFAULT_CONFIG_FILE,
|
23
|
+
desc: 'OCI config file location'
|
24
|
+
class_option :bucket,
|
25
|
+
type: :string,
|
26
|
+
required: true
|
27
|
+
class_option :verbose,
|
28
|
+
type: :boolean,
|
29
|
+
aliases: :v,
|
30
|
+
default: false
|
31
|
+
|
32
|
+
desc 'list', 'List queued jobs'
|
33
|
+
option :exec_group, type: :string, default: nil
|
34
|
+
def list
|
35
|
+
job_api = Jobbie::Jobs.new(options)
|
36
|
+
job_api.show_job_list(exec_group: options[:exec_group])
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'add', 'Add job onto the queue'
|
40
|
+
option :exec_group, type: :string, required: true
|
41
|
+
option :script, type: :string, required: true
|
42
|
+
def add(*params)
|
43
|
+
job_api = Jobbie::Jobs.new(options)
|
44
|
+
job_api.put_job(options[:exec_group], options[:script], params)
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'daemon', 'Daemon mode: Poll queue for jobs'
|
48
|
+
option :exec_group, type: :string, required: true
|
49
|
+
option :script_dir, type: :string, required: true
|
50
|
+
def daemon
|
51
|
+
job_api = Jobbie::Jobs.new(options)
|
52
|
+
job_api.daemon(options[:exec_group], options[:script_dir])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
require 'terminal-table'
|
6
|
+
|
7
|
+
require 'jobbie_oci/oci'
|
8
|
+
|
9
|
+
module Jobbie
|
10
|
+
# Process jobs from the queue
|
11
|
+
class Jobs
|
12
|
+
attr_reader :options, :oci
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
@options = options
|
16
|
+
@oci = Jobbie::OciUtil.new(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def obj_api
|
20
|
+
oci.obj_api
|
21
|
+
end
|
22
|
+
|
23
|
+
def job_list(exec_group: nil)
|
24
|
+
unordered_job_list(exec_group: exec_group).sort_by(&:first)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fqdn
|
28
|
+
Socket.gethostname
|
29
|
+
end
|
30
|
+
|
31
|
+
def job_details(job)
|
32
|
+
job_id = job.first.split('_')
|
33
|
+
params = job[1]['params']
|
34
|
+
params = params.join(' ') if params.is_a? Array
|
35
|
+
{
|
36
|
+
group: job_id.first,
|
37
|
+
id: job_id.last,
|
38
|
+
script: job[1]['script'],
|
39
|
+
params: params,
|
40
|
+
claim: job[1]['claim'],
|
41
|
+
time: Time.at(job[1]['epoch'].to_i)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def show_job_list(exec_group: nil)
|
46
|
+
rows = []
|
47
|
+
headings = if options[:verbose]
|
48
|
+
['Exec Group', 'ID', 'Command', 'Date Added', 'Claimed']
|
49
|
+
else
|
50
|
+
['Exec Group', 'Command']
|
51
|
+
end
|
52
|
+
job_list(exec_group: exec_group).each do |job|
|
53
|
+
details = job_details(job)
|
54
|
+
rows << if options[:verbose]
|
55
|
+
[details[:group], details[:id],
|
56
|
+
"#{details[:script]} #{details[:params]}",
|
57
|
+
details[:time],
|
58
|
+
details[:claim].nil? ? 'UNCLAIMED' : details[:claim]]
|
59
|
+
else
|
60
|
+
[details[:group], "#{details[:script]} #{details[:params]}"]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
puts Terminal::Table.new headings: headings, rows: rows
|
64
|
+
end
|
65
|
+
|
66
|
+
def put_job(exec_group, script, params)
|
67
|
+
obj_name = "#{exec_group}_#{Time.now.to_f}"
|
68
|
+
epoch = Time.now.to_i
|
69
|
+
obj = { script: script, params: params, epoch: epoch }
|
70
|
+
puts "Adding job #{obj_name}"
|
71
|
+
oci.add_obj(obj_name, obj.to_json)
|
72
|
+
end
|
73
|
+
|
74
|
+
def claimable_jobs?(exec_group)
|
75
|
+
job_list(exec_group: exec_group).each do |job|
|
76
|
+
obj = JSON.parse(oci.get_obj(job.first))
|
77
|
+
return true if obj['claim'].nil?
|
78
|
+
end
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def unclaimed?(obj)
|
83
|
+
if obj['claim']
|
84
|
+
# Claim expires after 10 mins
|
85
|
+
(Time.now.to_i - obj['claimed_at'].to_i) > 300
|
86
|
+
else
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def claim_job(job_id, obj)
|
92
|
+
my_ref = "#{fqdn}.#{Process.pid}"
|
93
|
+
obj[:claim] = my_ref
|
94
|
+
obj[:claimed_at] = Time.now.to_i
|
95
|
+
puts "claiming #{job_id}"
|
96
|
+
begin
|
97
|
+
oci.add_obj(job_id, obj.to_json)
|
98
|
+
rescue OCI::Errors::ServiceError
|
99
|
+
puts 'did not get claim'
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
sleep 0.3
|
103
|
+
begin
|
104
|
+
obj = JSON.parse(oci.get_obj(job_id))
|
105
|
+
rescue TypeError
|
106
|
+
puts 'Could not read back job contents, skipping ..'
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
return true if obj['claim'] == my_ref
|
110
|
+
puts 'did not get claim'
|
111
|
+
false
|
112
|
+
end
|
113
|
+
|
114
|
+
def invalid_script?(script)
|
115
|
+
(script =~ /\A[a-zA-Z0-9\._-]+\Z/).nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
def daemon(exec_group, script_dir)
|
119
|
+
dir = File.expand_path(script_dir)
|
120
|
+
loop do
|
121
|
+
puts 'Looking for jobs ..'
|
122
|
+
job_obj_list(exec_group: exec_group).map(&:name).each do |job_id|
|
123
|
+
raw = oci.get_obj(job_id)
|
124
|
+
next if raw.nil?
|
125
|
+
obj = JSON.parse(raw)
|
126
|
+
if unclaimed?(obj) && claim_job(job_id, obj)
|
127
|
+
if invalid_script?(obj['script'])
|
128
|
+
puts 'Invalid script'
|
129
|
+
else
|
130
|
+
params = obj['params']
|
131
|
+
params = params.join(' ') if params.is_a? Array
|
132
|
+
if system(File.join(dir, obj['script']), params)
|
133
|
+
puts 'Success. Removing job from queue'
|
134
|
+
oci.del_obj(job_id)
|
135
|
+
else
|
136
|
+
puts "Job #{job_id} returned non-zero exit code"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
puts 'Waiting 60 seconds ..'
|
142
|
+
sleep 60
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def job_obj_list(exec_group: nil)
|
149
|
+
if exec_group
|
150
|
+
oci.objects.select { |o| o.name.start_with? "#{exec_group}_" }
|
151
|
+
else
|
152
|
+
oci.objects
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def unordered_job_list(exec_group: nil)
|
157
|
+
job_obj_list(exec_group: exec_group).map do |job|
|
158
|
+
[job.name, JSON.parse(oci.get_obj(job.name))]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'oci'
|
4
|
+
|
5
|
+
module Jobbie
|
6
|
+
# Utility class for interacting with OCI object store
|
7
|
+
class OciUtil
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def region
|
15
|
+
OCI::Regions::REGION_SHORT_NAMES_TO_LONG_NAMES[options[:region].to_sym]
|
16
|
+
end
|
17
|
+
|
18
|
+
def client_conf
|
19
|
+
OCI::ConfigFileLoader.load_config(profile_name: options[:profile])
|
20
|
+
end
|
21
|
+
|
22
|
+
def client_args
|
23
|
+
{ config: client_conf, region: region }
|
24
|
+
end
|
25
|
+
|
26
|
+
def obj_api
|
27
|
+
OCI::ObjectStorage::ObjectStorageClient.new(client_args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def namespace
|
31
|
+
@namespace ||= obj_api.get_namespace.data
|
32
|
+
end
|
33
|
+
|
34
|
+
def bucket
|
35
|
+
options[:bucket]
|
36
|
+
end
|
37
|
+
|
38
|
+
def objects
|
39
|
+
obj_api.list_objects(namespace, bucket).flat_map do |resp|
|
40
|
+
resp.data.objects.map do |obj|
|
41
|
+
obj
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_obj(obj_name, content)
|
47
|
+
obj_api.put_object(namespace, bucket, obj_name, content)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_obj(obj_name)
|
51
|
+
obj_api.get_object(namespace, bucket, obj_name).data
|
52
|
+
rescue OCI::Errors::ServiceError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def del_obj(obj_name)
|
57
|
+
obj_api.delete_object(namespace, bucket, obj_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jobbie_oci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen Pearson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- stephen.pearson@oracle.com
|
44
|
+
executables:
|
45
|
+
- jobbie
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- ".rubocop.yml"
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- exe/jobbie
|
56
|
+
- jobbie_oci.gemspec
|
57
|
+
- lib/jobbie_oci/cli.rb
|
58
|
+
- lib/jobbie_oci/jobs.rb
|
59
|
+
- lib/jobbie_oci/oci.rb
|
60
|
+
- lib/jobbie_oci/version.rb
|
61
|
+
homepage: http://github.com/stephenpearson/jobbie_oci
|
62
|
+
licenses: []
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.6.13
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Simple noddy job queuer that uses OCI Casper backend
|
84
|
+
test_files: []
|