share-data-watcher 0.2.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a9dd6ba2eb14954eff10b7dbf83658af09a1b3e4c7a6c0ca80ea955b3bdb26d
4
+ data.tar.gz: 7a6982813617d097edaa1ca5e0369e95ee6181ac45e2d1f8d8f9488f6d630cd6
5
+ SHA512:
6
+ metadata.gz: 5ad3ab8e0b3a5928f751123c93c1528845ab64ffde519d30b714bc92494583ca606e97dee28ff1533359e091ff7656c4541686e6a4754220e5b737a3b4271cae
7
+ data.tar.gz: 3f81af461d2a95999b2c0340d5e8b7b16f522d90edfed427bbd7417b474fbfd286a10134396d8385697c8cdae9401176856b6fbca0b3bf15b4ac7ef08e4af9a1
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ # Vim
21
+ *.swp
22
+ *.swo
23
+
24
+ # ctags
25
+ tags
26
+
27
+ #Mac
28
+ .DS_Store
29
+ tmp/
30
+
31
+ .idea
32
+ reports/
33
+ target/
34
+ log
35
+ migrations/*.db
36
+ vendor
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --format Fuubar
2
+ --color
3
+ --format html
4
+ --out reports/rspec/result.html
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ # Common configuration.
2
+
3
+ require:
4
+ - rubocop-performance
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.5
8
+ Exclude:
9
+ - target/**/*
10
+
11
+
12
+ #################### Layout ###########################
13
+ Layout/LineLength:
14
+ Max: 120
15
+
16
+ #################### Metrics ###########################
17
+ Metrics/MethodLength:
18
+ Max: 20
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.5
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file
3
+
4
+
5
+ ***
6
+
7
+ ## [0.2.1]
8
+
9
+ ### Changed
10
+ * after_change hook pass builded object to proc hook
11
+
12
+
13
+ ***
14
+
15
+ ## [0.2.0]
16
+
17
+ ### Added
18
+ * Watch Type support after_change hook
19
+
20
+
21
+ ***
22
+
23
+ ## [0.1.1]
24
+
25
+ ### Added
26
+ * Regx Key support "-" And "_"
27
+
28
+
29
+ ***
30
+
31
+ ## [0.1.0]
32
+ # First Release
33
+
34
+ ### Added
35
+
36
+ ### Changed
37
+
38
+ ### Deleted
39
+
40
+ ***
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
4
+
5
+ ruby '~> 2.5'
6
+
7
+ gemspec
8
+
9
+ group :test do
10
+ gem 'as-duration'
11
+ gem 'faker'
12
+ gem 'fuubar'
13
+ gem 'rspec'
14
+ gem 'rspec-nc'
15
+ gem 'simplecov'
16
+ gem 'ulid'
17
+ end
18
+
19
+ group :development do
20
+ gem 'rubocop', require: false
21
+ gem 'rubocop-performance', require: false
22
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,124 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ share-data-watcher (0.2.1)
5
+ bundler (>= 1.16.1)
6
+ dry-struct (>= 1.2.0)
7
+ etcdv3 (~> 0.10.0)
8
+ multi_json (>= 1.14.1)
9
+ oj (>= 3.9.2)
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ specs:
14
+ as-duration (0.1.1)
15
+ ast (2.4.0)
16
+ concurrent-ruby (1.1.6)
17
+ diff-lcs (1.3)
18
+ docile (1.3.2)
19
+ dry-configurable (0.11.1)
20
+ concurrent-ruby (~> 1.0)
21
+ dry-core (~> 0.4, >= 0.4.7)
22
+ dry-equalizer (~> 0.2)
23
+ dry-container (0.7.2)
24
+ concurrent-ruby (~> 1.0)
25
+ dry-configurable (~> 0.1, >= 0.1.3)
26
+ dry-core (0.4.9)
27
+ concurrent-ruby (~> 1.0)
28
+ dry-equalizer (0.3.0)
29
+ dry-inflector (0.2.0)
30
+ dry-logic (1.0.6)
31
+ concurrent-ruby (~> 1.0)
32
+ dry-core (~> 0.2)
33
+ dry-equalizer (~> 0.2)
34
+ dry-struct (1.3.0)
35
+ dry-core (~> 0.4, >= 0.4.4)
36
+ dry-equalizer (~> 0.3)
37
+ dry-types (~> 1.3)
38
+ ice_nine (~> 0.11)
39
+ dry-types (1.3.1)
40
+ concurrent-ruby (~> 1.0)
41
+ dry-container (~> 0.3)
42
+ dry-core (~> 0.4, >= 0.4.4)
43
+ dry-equalizer (~> 0.3)
44
+ dry-inflector (~> 0.1, >= 0.1.2)
45
+ dry-logic (~> 1.0, >= 1.0.2)
46
+ etcdv3 (0.10.2)
47
+ grpc (~> 1.17)
48
+ faker (2.10.2)
49
+ i18n (>= 1.6, < 2)
50
+ fuubar (2.5.0)
51
+ rspec-core (~> 3.0)
52
+ ruby-progressbar (~> 1.4)
53
+ google-protobuf (3.11.4)
54
+ googleapis-common-protos-types (1.0.4)
55
+ google-protobuf (~> 3.0)
56
+ grpc (1.27.0)
57
+ google-protobuf (~> 3.11)
58
+ googleapis-common-protos-types (~> 1.0)
59
+ i18n (1.8.2)
60
+ concurrent-ruby (~> 1.0)
61
+ ice_nine (0.11.2)
62
+ jaro_winkler (1.5.4)
63
+ multi_json (1.14.1)
64
+ oj (3.10.2)
65
+ parallel (1.19.1)
66
+ parser (2.7.0.2)
67
+ ast (~> 2.4.0)
68
+ rainbow (3.0.0)
69
+ rexml (3.2.4)
70
+ rspec (3.9.0)
71
+ rspec-core (~> 3.9.0)
72
+ rspec-expectations (~> 3.9.0)
73
+ rspec-mocks (~> 3.9.0)
74
+ rspec-core (3.9.1)
75
+ rspec-support (~> 3.9.1)
76
+ rspec-expectations (3.9.0)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.9.0)
79
+ rspec-mocks (3.9.1)
80
+ diff-lcs (>= 1.2.0, < 2.0)
81
+ rspec-support (~> 3.9.0)
82
+ rspec-nc (0.3.0)
83
+ rspec (>= 3)
84
+ terminal-notifier (>= 1.4)
85
+ rspec-support (3.9.2)
86
+ rubocop (0.80.0)
87
+ jaro_winkler (~> 1.5.1)
88
+ parallel (~> 1.10)
89
+ parser (>= 2.7.0.1)
90
+ rainbow (>= 2.2.2, < 4.0)
91
+ rexml
92
+ ruby-progressbar (~> 1.7)
93
+ unicode-display_width (>= 1.4.0, < 1.7)
94
+ rubocop-performance (1.5.2)
95
+ rubocop (>= 0.71.0)
96
+ ruby-progressbar (1.10.1)
97
+ simplecov (0.18.2)
98
+ docile (~> 1.1)
99
+ simplecov-html (~> 0.11)
100
+ simplecov-html (0.12.0)
101
+ terminal-notifier (2.0.0)
102
+ ulid (1.2.0)
103
+ unicode-display_width (1.6.1)
104
+
105
+ PLATFORMS
106
+ ruby
107
+
108
+ DEPENDENCIES
109
+ as-duration
110
+ faker
111
+ fuubar
112
+ rspec
113
+ rspec-nc
114
+ rubocop
115
+ rubocop-performance
116
+ share-data-watcher!
117
+ simplecov
118
+ ulid
119
+
120
+ RUBY VERSION
121
+ ruby 2.5.1p57
122
+
123
+ BUNDLED WITH
124
+ 1.17.2
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # share-data-watcher
2
+
3
+ Share Data Watcher For Game Operation
4
+
5
+ Example:
6
+ - https://bitbucket.org/gamesource/share-data-watcher/src/master/example/
7
+ - https://bitbucket.org/gamesource/share-data-watcher/src/master/spec/functional/sample/
8
+
9
+ Struct Type Please Ref:
10
+ - https://dry-rb.org/gems/dry-struct/1.0/
11
+ - https://dry-rb.org/gems/dry-types/1.2/
@@ -0,0 +1,36 @@
1
+ # This is a sample build configuration for Ruby.
2
+ # Check our guides at https://confluence.atlassian.com/x/8r-5Mw for more examples.
3
+ # Only use spaces to indent your .yml configuration.
4
+ # -----
5
+ # You can specify a custom docker image from Docker Hub as your build environment.
6
+ image: ruby:2.5.5
7
+
8
+ pipelines:
9
+ default:
10
+ - step:
11
+ caches:
12
+ - bundler
13
+ script:
14
+ - bundle install
15
+ - bundle exec rspec -cfd
16
+ services:
17
+ - etcd
18
+
19
+ definitions:
20
+ caches:
21
+ bundler: ./vendor
22
+ services:
23
+ etcd:
24
+ image: quay.io/coreos/etcd:v3.3
25
+ variables:
26
+ ETCD_NAME: 'etcd-1'
27
+ ETCD_HEARTBEAT_INTERVAL: '250'
28
+ ETCD_ELECTION_TIMEOUT: '1250'
29
+ ETCD_INITIAL_CLUSTER_STATE: 'new'
30
+ ETCD_INITIAL_ADVERTISE_PEER_URLS: 'http://0.0.0.0:2380'
31
+ ETCD_INITIAL_CLUSTER: 'etcd-1=http://0.0.0.0:2380'
32
+ ETCD_INITIAL_CLUSTER_TOKEN: 'mys3cr3ttok3n'
33
+ ETCD_LISTEN_PEER_URLS: 'http://0.0.0.0:2380'
34
+ ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
35
+ ETCD_ADVERTISE_CLIENT_URLS: 'http://0.0.0.0:2379'
36
+ ETCD_AUTO_COMPACTION_RETENTION: 1
data/config.reek ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ UtilityFunction:
3
+ public_methods_only: true
4
+ TooManyStatements:
5
+ exclude:
6
+ - initialize
7
+ max_statements: 10
@@ -0,0 +1,21 @@
1
+ version: '3'
2
+
3
+ services:
4
+ etcd-1:
5
+ image: quay.io/coreos/etcd:v3.3
6
+ container_name: etcd-1
7
+ environment:
8
+ ETCD_NAME: 'etcd-1'
9
+ ETCD_HEARTBEAT_INTERVAL: '250'
10
+ ETCD_ELECTION_TIMEOUT: '1250'
11
+ ETCD_INITIAL_CLUSTER_STATE: 'new'
12
+ ETCD_INITIAL_ADVERTISE_PEER_URLS: 'http://0.0.0.0:2380'
13
+ ETCD_INITIAL_CLUSTER: 'etcd-1=http://0.0.0.0:2380'
14
+ ETCD_INITIAL_CLUSTER_TOKEN: 'mys3cr3ttok3n'
15
+ ETCD_LISTEN_PEER_URLS: 'http://0.0.0.0:2380'
16
+ ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
17
+ ETCD_ADVERTISE_CLIENT_URLS: 'http://0.0.0.0:2379'
18
+ ETCD_AUTO_COMPACTION_RETENTION: 1
19
+ ports:
20
+ - "2379:2379"
21
+ - "2380:2380"
data/example/sample ADDED
@@ -0,0 +1 @@
1
+ ../spec/functional/sample
data/example/sample.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/share-data-watcher'
4
+ require_relative 'sample/watcher'
5
+ require 'logger'
6
+ require 'ulid'
7
+ require 'json'
8
+
9
+ endpoints = ENV.fetch('ETCD_URL', 'http://0.0.0.0:2379')
10
+ healthcheck_interval = 10..30
11
+ ShareDataWatcher.logger = Logger.new(STDOUT)
12
+
13
+ watcher = Watcher.new(endpoints, healthcheck_interval: healthcheck_interval)
14
+ etcd_conn = Etcdv3.new(endpoints: endpoints, allow_reconnect: false)
15
+
16
+ Thread.new do
17
+ 5.times do
18
+ sleep 3
19
+ value = ULID.generate
20
+ key = 'go.game_sessions.force_leave'
21
+ etcd_conn.put(key, JSON.dump([value]))
22
+ puts "#{Time.now} :: update #{key} value #{value}"
23
+ end
24
+ end
25
+
26
+ watcher.start
27
+
28
+ sleep
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ # Class Methods
5
+ module ClassInterface
6
+ %i[watch fetch].each do |mesh|
7
+ define_method(mesh) do |name, key, **opts|
8
+ create_type(
9
+ type: mesh.to_sym,
10
+ name: name,
11
+ params: [
12
+ key,
13
+ opts
14
+ ]
15
+ )
16
+ end
17
+ end
18
+
19
+ def types
20
+ @types ||= []
21
+ end
22
+
23
+ protected
24
+
25
+ TypeParams = Object::Struct.new(
26
+ :name,
27
+ :type,
28
+ :params,
29
+ keyword_init: true
30
+ )
31
+
32
+ def create_type(name:, type:, params:)
33
+ types << TypeParams.new(
34
+ type: type,
35
+ name: name,
36
+ params: params
37
+ )
38
+ define_method(name) do
39
+ @types[name]
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'etcdv3'
4
+
5
+ class ShareDataWatcher
6
+ # Etcd connection client
7
+ class Connection
8
+ attr_reader :conn
9
+
10
+ DEFAULT_FETCH_TIMEOUT = 1
11
+
12
+ def initialize(endpoints)
13
+ @conn = Etcdv3.new(endpoints: endpoints, allow_reconnect: false)
14
+ end
15
+
16
+ def connecting_endpoint
17
+ conn.conn.connection.endpoint.to_s
18
+ end
19
+
20
+ def healthcheck
21
+ conn.version
22
+ true
23
+ rescue StandardError
24
+ false
25
+ end
26
+
27
+ def watch(key, prefix: true, start_revision: nil)
28
+ opt = {
29
+ timeout: Etcdv3::Watch::INFINITE_FUTURE
30
+ }
31
+ opt[:range_end] = increase_one_bit(key) if prefix
32
+ opt[:start_revision] = start_revision if start_revision
33
+ conn.watch(key, opt) do |events|
34
+ events.each do |event|
35
+ yield(event.type, event.kv)
36
+ end
37
+ end
38
+ end
39
+
40
+ def fetch(key, prefix: false, timeout: nil)
41
+ opt = {}
42
+ opt[:range_end] = increase_one_bit(key) if prefix
43
+ opt[:timeout] = timeout || DEFAULT_FETCH_TIMEOUT
44
+ res = conn.get(key, opt)
45
+ revision = res.header.revision
46
+ kvs = res.kvs
47
+ block_given? ? yield(revision, kvs) : [revision, kvs]
48
+ end
49
+
50
+ protected
51
+
52
+ def increase_one_bit(key)
53
+ str = key.dup
54
+ str[-1] = (str.getbyte(-1) + 1).chr
55
+ str
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ # Instance Methods For Shard Data Watcher
5
+ module InstanceMethods
6
+ attr_reader :endpoints
7
+ attr_reader :healthcheck_interval
8
+
9
+ def initialize(endpoints = nil, **opt)
10
+ @endpoints = endpoints || DEFAULT_ENDPOINT
11
+ @healthcheck_thread = nil
12
+ @healthcheck_interval = opt[:healthcheck_interval] || DEFAULT_HEALTHCHECK_INTERVAL
13
+ @mutex = Mutex.new
14
+ end
15
+
16
+ def start
17
+ @mutex.synchronize do
18
+ self.class.types.each do |type|
19
+ init_type(type)
20
+ end
21
+ end
22
+
23
+ until valid?; sleep 1; end
24
+ setup_healthcheck
25
+ self
26
+ end
27
+
28
+ def restart
29
+ shutdown
30
+ start
31
+ setup_healthcheck
32
+ self
33
+ end
34
+
35
+ def shutdown
36
+ types.each_value(&:shutdown)
37
+ types.clear
38
+ @healthcheck_thread.exit
39
+ self
40
+ end
41
+
42
+ def valid?
43
+ types.each do |_, value|
44
+ return false unless value.valid?
45
+ end
46
+ true
47
+ end
48
+
49
+ def to_h
50
+ types.each_with_object({}) do |(key, value), result|
51
+ result[key] = value.to_h
52
+ end
53
+ end
54
+
55
+ alias to_hash to_h
56
+
57
+ protected
58
+
59
+ def init_type(type)
60
+ klass = Type.const_get(str_camelize(type.type.to_s))
61
+ types[type.name] = klass.new(*compose_params(type.params))
62
+ end
63
+
64
+ def setup_healthcheck
65
+ @healthcheck_thread = Thread.new do
66
+ loop do
67
+ sleep rand(healthcheck_interval)
68
+ types.each do |key, value|
69
+ if value.healthcheck
70
+ debug("#{key} alive!")
71
+ else
72
+ debug("#{key} dead! restart now")
73
+ @mutex.synchronize { obj.restart }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def compose_params(params)
81
+ params.dup.unshift(endpoints)
82
+ end
83
+
84
+ def types
85
+ @types ||= {}
86
+ end
87
+
88
+ def str_camelize(str)
89
+ str.dup.split('_').map(&:capitalize).join
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ # Logger Module
5
+ module Loggable
6
+ def info(string)
7
+ ShareDataWatcher.logger&.info(string)
8
+ end
9
+
10
+ def debug(string)
11
+ ShareDataWatcher.logger&.debug(string)
12
+ end
13
+
14
+ def warn(string)
15
+ ShareDataWatcher.logger&.warn(string)
16
+ end
17
+
18
+ def error(err)
19
+ logger = ShareDataWatcher.logger
20
+ case err
21
+ when StandardError
22
+ logger&.error(
23
+ err: "#{err.class}:#{err.message}",
24
+ backtrace: err.backtrace.join("\n")
25
+ )
26
+ else
27
+ logger&.error err
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ module Struct
5
+ # Base Type
6
+ class Base < Dry::Struct
7
+ include Dry.Types()
8
+
9
+ class << self
10
+ def build(_data)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
15
+
16
+ # String Type
17
+ class String < Base
18
+ class << self
19
+ def build(data)
20
+ data.to_s
21
+ end
22
+ end
23
+ end
24
+
25
+ # Hash Type
26
+ class Hash < Base
27
+ class << self
28
+ def build(data)
29
+ MultiJson.load(
30
+ data,
31
+ symbolize_keys: true
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ module Type
5
+ # Base Type
6
+ class Base
7
+ include ShareDataWatcher::Loggable
8
+
9
+ extend Forwardable
10
+ def_delegators :connection, :connecting_endpoint, :healthcheck
11
+
12
+ attr_reader :key
13
+
14
+ def initialize(endpoints, key, opt = {})
15
+ @key = key
16
+ @struct = determine_struct_klass(opt[:struct])
17
+ @extension = opt[:extension]
18
+ @regex = extension && Regexp.new("^#{key}#{extension.gsub(/[.]/, '[.]').gsub(/%{(\w+)}/, '([a-zA-Z0-9\-\_]+)')}$")
19
+ @connection = Connection.new(endpoints)
20
+ reset
21
+ on_init
22
+ end
23
+
24
+ def get(**params)
25
+ key = fetch_key(params)
26
+ values[key.to_sym]
27
+ end
28
+
29
+ def valid?
30
+ @valid
31
+ end
32
+
33
+ def shutdown
34
+ reset
35
+ end
36
+
37
+ def restart
38
+ shutdown
39
+ on_init
40
+ end
41
+
42
+ def to_h
43
+ values.each_with_object({}) do |(key, value), result|
44
+ result[key] = value.respond_to?(:to_h) ? value.to_h : value
45
+ end
46
+ end
47
+
48
+ alias to_hash to_h
49
+
50
+ protected
51
+
52
+ def determine_struct_klass(klass)
53
+ return Struct::String if klass.nil?
54
+ raise ArgumentError, 'struct class must inherited from Struct::Base' unless klass <= Struct::Base
55
+
56
+ klass
57
+ end
58
+
59
+ def on_init; end
60
+
61
+ attr_reader :values
62
+ attr_reader :struct
63
+ attr_reader :connection
64
+ attr_reader :extension
65
+ attr_reader :regex
66
+
67
+ def fetch_key(**params)
68
+ prefix? ? format("#{key}#{extension}", params) : key
69
+ end
70
+
71
+ def prefix?
72
+ !extension.nil?
73
+ end
74
+
75
+ def put(key, value)
76
+ return unless regex_check?(key)
77
+
78
+ info("ShareDataWatcher #{key} update: #{value}")
79
+ values[key.to_sym] = struct.build(value)
80
+ rescue StandardError => e
81
+ delete(key)
82
+ error e
83
+ error "remove #{key} on ShareDataWatcher #{self.key}"
84
+ end
85
+
86
+ def regex_check?(key)
87
+ return true unless regex && regex !~ key
88
+
89
+ info "#{key} is not match #{regex}."
90
+ false
91
+ end
92
+
93
+ def delete(key)
94
+ info("ShareDataWatcher delete: #{key}")
95
+ values.delete(key.to_sym)
96
+ end
97
+
98
+ def reset
99
+ @valid = false
100
+ @values = {}
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ module Type
5
+ # Fetch Type
6
+ # For cache share watch
7
+ class Fetch < Base
8
+ attr_reader :timeout
9
+
10
+ # opt[:struct]
11
+ # opt[:extension]
12
+ # opt[:timeout]
13
+ def initialize(endpoints, key, opt = {})
14
+ @timeout = opt[:timeout]
15
+ super
16
+ end
17
+
18
+ def get(**params)
19
+ key = fetch_key(params)
20
+ values[key.to_sym] || fetch(key)
21
+ end
22
+
23
+ protected
24
+
25
+ def fetch(key)
26
+ _revision, kvs = connection.fetch(key, prefix: false, timeout: timeout)
27
+ kv = kvs.first
28
+ return nil unless kv
29
+
30
+ put(kv.key, kv.value)
31
+ values[key.to_sym]
32
+ end
33
+
34
+ def on_init
35
+ @valid = true
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ module Type
5
+ # Watch Type
6
+ # Watch key changes
7
+ class Watch < Base
8
+ attr_reader :start_revision
9
+ attr_reader :retry_interval
10
+ attr_reader :after_change
11
+
12
+ DEFAULT_RETRY_INTERVAL = 30
13
+
14
+ # opt[:struct]
15
+ # opt[:retry_interval]
16
+ # opt[:extension]
17
+ def initialize(endpoints, key, opt = {})
18
+ @retry_interval = opt[:retry_interval] || DEFAULT_RETRY_INTERVAL
19
+ @after_change = opt[:after_change] || nil
20
+
21
+ if after_change && !after_change.is_a?(Proc)
22
+ raise ArgumentError, 'after_change must be proc'
23
+ end
24
+
25
+ @thread = nil
26
+ super
27
+ end
28
+
29
+ def shutdown
30
+ @thread.exit
31
+ super
32
+ end
33
+
34
+ protected
35
+
36
+ def reset
37
+ @start_revision = -1
38
+ super
39
+ end
40
+
41
+ def on_init
42
+ @thread = Thread.new do
43
+ setup
44
+ end
45
+ end
46
+
47
+ def init_values
48
+ @start_revision, kvs = connection.fetch(key, prefix: prefix?)
49
+ info("ShareDataWatcher init #{key}, kvs: #{kvs}, start_revision: #{start_revision}")
50
+
51
+ kvs.each do |kv|
52
+ put(kv.key, kv.value)
53
+ end
54
+ @valid = true
55
+ end
56
+
57
+ def setup_watcher
58
+ connection.watch(key, prefix: prefix?, start_revision: start_revision) do |type, kv|
59
+ case type
60
+ when :PUT
61
+ put(kv.key, kv.value)
62
+ after_change&.call(type, kv.key, values[kv.key.to_sym])
63
+ when :DELETE
64
+ delete(kv.key)
65
+ after_change&.call(type, kv.key, nil)
66
+ end
67
+ end
68
+ end
69
+
70
+ def setup
71
+ init_values
72
+ setup_watcher
73
+ rescue StandardError => e
74
+ reset
75
+ error(e)
76
+ error("Reset ShareDataWatcher #{key}")
77
+ sleep retry_interval
78
+ retry
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'type/base'
4
+
5
+ # load mysql repository module stuffs in /type folder
6
+ Dir["#{File.dirname(__FILE__)}/type/**.rb"]
7
+ .each { |file| require_relative file }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ShareDataWatcher
4
+ VERSION = '0.2.1'
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'etcdv3'
4
+ require 'multi_json'
5
+ require 'oj'
6
+ require 'dry-struct'
7
+
8
+ # Watch etcd value for IAS
9
+ class ShareDataWatcher
10
+ require_relative 'share-data-watcher/loggable'
11
+
12
+ Dir["#{File.dirname(__FILE__)}/share-data-watcher/**.rb"]
13
+ .each { |file| require_relative file }
14
+
15
+ include Loggable
16
+ include InstanceMethods
17
+ extend ClassInterface
18
+
19
+ DEFAULT_ENDPOINT = 'http://localhost:2379'
20
+ DEFAULT_HEALTHCHECK_INTERVAL = (30..90).freeze
21
+
22
+ class << self
23
+ attr_accessor :logger
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('lib/share-data-watcher/version', __dir__)
4
+ # $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'share-data-watcher'
8
+ spec.version = ShareDataWatcher::VERSION
9
+ spec.authors = ['Cheok Meng Chan']
10
+ spec.email = ['cheokmeng.chan@gamesourcecloud.com']
11
+ spec.required_ruby_version = '>= 2.5'
12
+
13
+ spec.summary = 'Game Operation Data Watcher'
14
+ spec.description = 'Game Operation Data Watcher For IAS'
15
+ spec.homepage = 'https://bitbucket.org/gamesource/go-data-watcher'
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ end
22
+ spec.require_paths = ['lib']
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.add_dependency 'bundler', '>= 1.16.1'
25
+ spec.add_dependency 'dry-struct', '>= 1.2.0'
26
+ spec.add_dependency 'etcdv3', '~> 0.10.0'
27
+ spec.add_dependency 'multi_json', '>= 1.14.1'
28
+ spec.add_dependency 'oj', '>= 3.9.2'
29
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: share-data-watcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Cheok Meng Chan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-23 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.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.16.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-struct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: etcdv3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.10.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: multi_json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.14.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.14.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: oj
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.9.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.9.2
83
+ description: Game Operation Data Watcher For IAS
84
+ email:
85
+ - cheokmeng.chan@gamesourcecloud.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - ".ruby-version"
94
+ - CHANGELOG.md
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - README.md
98
+ - bitbucket-pipelines.yml
99
+ - config.reek
100
+ - docker-compose.yml
101
+ - example/sample
102
+ - example/sample.rb
103
+ - lib/share-data-watcher.rb
104
+ - lib/share-data-watcher/class_interface.rb
105
+ - lib/share-data-watcher/connection.rb
106
+ - lib/share-data-watcher/instance_methods.rb
107
+ - lib/share-data-watcher/loggable.rb
108
+ - lib/share-data-watcher/struct.rb
109
+ - lib/share-data-watcher/type.rb
110
+ - lib/share-data-watcher/type/base.rb
111
+ - lib/share-data-watcher/type/fetch.rb
112
+ - lib/share-data-watcher/type/watch.rb
113
+ - lib/share-data-watcher/version.rb
114
+ - share-data-watch.gemspec
115
+ homepage: https://bitbucket.org/gamesource/go-data-watcher
116
+ licenses: []
117
+ metadata:
118
+ allowed_push_host: https://rubygems.org
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '2.5'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.0.3.1
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Game Operation Data Watcher
138
+ test_files: []