rogue_machines 0.1.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 +7 -0
- data/CHANGELOG.md +15 -0
- data/Cargo.lock +286 -0
- data/Cargo.toml +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +211 -0
- data/ext/rogue_machines_native/core/Cargo.toml +24 -0
- data/ext/rogue_machines_native/core/src/armed.rs +78 -0
- data/ext/rogue_machines_native/core/src/experiment.rs +84 -0
- data/ext/rogue_machines_native/core/src/lib.rs +30 -0
- data/ext/rogue_machines_native/core/src/rogue.rs +135 -0
- data/ext/rogue_machines_native/extconf.rb +3 -0
- data/ext/rogue_machines_native/ffi/Cargo.toml +19 -0
- data/ext/rogue_machines_native/ffi/extconf.rb +21 -0
- data/ext/rogue_machines_native/ffi/src/injection.rs +44 -0
- data/ext/rogue_machines_native/ffi/src/lib.rs +173 -0
- data/ext/rogue_machines_native/ffi/src/registry.rs +288 -0
- data/lib/rogue_machines/version.rb +5 -0
- data/lib/rogue_machines.rb +296 -0
- data/sig/rogue_machines.rbs +146 -0
- metadata +98 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Fault injection: making a dependency fail, stall or lie on purpose.
|
|
2
|
+
module RogueMachines
|
|
3
|
+
VERSION: String
|
|
4
|
+
|
|
5
|
+
# Nanoseconds in a second.
|
|
6
|
+
NS_PER_SECOND: Integer
|
|
7
|
+
|
|
8
|
+
# Millionths in a whole, the unit a share is held in.
|
|
9
|
+
PER_MILLION: Integer
|
|
10
|
+
|
|
11
|
+
# Experiments a process can arm at once, the width of the armed mask.
|
|
12
|
+
EXPERIMENTS: Integer
|
|
13
|
+
|
|
14
|
+
# Attempts at one consistent reading before giving up on a clock being
|
|
15
|
+
# replaced faster than it can be read.
|
|
16
|
+
READINGS: Integer
|
|
17
|
+
|
|
18
|
+
class Error < StandardError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Raised by a `:raise` fault in place of the real call.
|
|
22
|
+
class Injected < Error
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The fault to perform for one call.
|
|
26
|
+
class Injection < ::Data
|
|
27
|
+
# `:raise`, `:delay` or `:garble`.
|
|
28
|
+
attr_reader fault: Symbol
|
|
29
|
+
|
|
30
|
+
# How long a `:delay` waits, and zero for the others.
|
|
31
|
+
attr_reader delay_ns: Integer
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Anything that reports monotonic nanoseconds.
|
|
35
|
+
interface _Clock
|
|
36
|
+
def now_ns: () -> Integer
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Anything that draws below a bound, which `Random` satisfies.
|
|
40
|
+
interface _Random
|
|
41
|
+
def rand: (Integer bound) -> Integer
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The experiments a process is running, by name. Reach it through the module.
|
|
45
|
+
class Rogue
|
|
46
|
+
def self.new: () -> instance
|
|
47
|
+
|
|
48
|
+
# Whether any experiment is armed as of the last sweep, which is the whole
|
|
49
|
+
# of the decision a disarmed process takes. A sweep happens on every
|
|
50
|
+
# `decide` and every `armed`, so this trails an expiry by one call.
|
|
51
|
+
def any_armed?: () -> bool
|
|
52
|
+
|
|
53
|
+
# Sweeps expired experiments at `now_ns`, then counts what is left.
|
|
54
|
+
# Reports 0 without sweeping for a superseded generation. Every seam
|
|
55
|
+
# taking a reading refuses a Float rather than truncating it.
|
|
56
|
+
def armed_count: (Integer now_ns, Integer generation) -> Integer
|
|
57
|
+
|
|
58
|
+
# Arms `name`, replacing any experiment already there. Raises
|
|
59
|
+
# `ArgumentError` for an unknown fault, a share outside a whole or below
|
|
60
|
+
# one millionth, a duration that is non-positive, below a nanosecond or
|
|
61
|
+
# longer than the cap, a `now_ns` that is not an Integer or is outside the
|
|
62
|
+
# range, or a `:delay`
|
|
63
|
+
# with no `delay:`. Raises `Error` when every slot is taken. Nothing is
|
|
64
|
+
# armed and no slot is taken when it raises.
|
|
65
|
+
def arm: (String name, fault: String, share: Float, expires_in: Float,
|
|
66
|
+
now_ns: Integer, generation: Integer, ?delay: Float) -> void
|
|
67
|
+
|
|
68
|
+
# Reports whether `name` was still running, which an experiment that
|
|
69
|
+
# expired on its own is not.
|
|
70
|
+
def disarm: (String name, Integer now_ns, Integer generation) -> bool
|
|
71
|
+
|
|
72
|
+
def disarm_all: () -> void
|
|
73
|
+
|
|
74
|
+
# Which clock the armed expiries were computed against. Every reading is
|
|
75
|
+
# only meaningful for one of these, and a call carrying another one is
|
|
76
|
+
# refused rather than acted on, since sweeping is destructive.
|
|
77
|
+
def generation: () -> Integer
|
|
78
|
+
|
|
79
|
+
# Disarms everything and starts a generation, retiring readings already
|
|
80
|
+
# taken against the outgoing clock. Returns the new generation.
|
|
81
|
+
def invalidate: () -> Integer
|
|
82
|
+
|
|
83
|
+
# The names still running at `now_ns`, in no particular order, and none
|
|
84
|
+
# for a superseded generation.
|
|
85
|
+
def armed: (Integer now_ns, Integer generation) -> Array[String]
|
|
86
|
+
|
|
87
|
+
# The fault to perform, or `nil` to make the real call. Raises
|
|
88
|
+
# `ArgumentError` for a `draw` or `now_ns` that is not an Integer, is
|
|
89
|
+
# outside its range, or is a `draw` at or above `PER_MILLION`. A Float is
|
|
90
|
+
# refused rather than truncated, because a truncated draw becomes 0,
|
|
91
|
+
# which every share covers.
|
|
92
|
+
def decide: (String name, Integer now_ns, Integer draw, Integer generation) -> Injection?
|
|
93
|
+
|
|
94
|
+
def inspect: () -> String
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Delegates to `DeadlineMachines.clock` when unset, rather than memoising
|
|
98
|
+
# it, so replacing that clock moves every expiry with it.
|
|
99
|
+
def self.clock: () -> _Clock
|
|
100
|
+
|
|
101
|
+
# Disarms everything first: an armed expiry was computed against the
|
|
102
|
+
# outgoing clock and is not meaningful against another one.
|
|
103
|
+
def self.clock=: (_Clock?) -> void
|
|
104
|
+
def self.random: () -> _Random
|
|
105
|
+
def self.random=: (_Random?) -> void
|
|
106
|
+
# The registry itself. Reach for the module methods instead: arming
|
|
107
|
+
# through this bypasses the substitute a `:garble` returns, and freezing
|
|
108
|
+
# it does not prevent arming, since the native side holds its own state.
|
|
109
|
+
def self.registry: () -> Rogue
|
|
110
|
+
|
|
111
|
+
# Arms an experiment under `name`. `expires_in` counts from now and is
|
|
112
|
+
# required, so no experiment outlives the person who armed it.
|
|
113
|
+
#
|
|
114
|
+
# Raises `ArgumentError` for a blank name, a name that is neither ASCII nor
|
|
115
|
+
# valid UTF-8, or any argument the registry refuses. Raises `Error` when
|
|
116
|
+
# every slot is taken, when the clock was replaced faster than a reading
|
|
117
|
+
# could be taken, and when the clock returned something that is not a
|
|
118
|
+
# non-negative Integer of nanoseconds.
|
|
119
|
+
#
|
|
120
|
+
# `share`, `expires_in` and `delay` go through `Float()`, so a String that
|
|
121
|
+
# parses as a number is accepted, one that does not raises `ArgumentError`,
|
|
122
|
+
# and a value with no numeric conversion at all raises `TypeError`.
|
|
123
|
+
def self.arm: (Symbol | String name, fault: Symbol | String, share: Numeric | String,
|
|
124
|
+
expires_in: Numeric | String, ?delay: (Numeric | String)?,
|
|
125
|
+
?garbled: untyped) -> (Symbol | String)
|
|
126
|
+
|
|
127
|
+
def self.disarm: (Symbol | String name) -> bool
|
|
128
|
+
def self.disarm_all: () -> void
|
|
129
|
+
|
|
130
|
+
# With no name, whether anything at all is armed.
|
|
131
|
+
def self.armed?: (?(Symbol | String)?) -> bool
|
|
132
|
+
|
|
133
|
+
# The experiments still running, which excludes any that has expired.
|
|
134
|
+
def self.armed: () -> Array[Symbol]
|
|
135
|
+
|
|
136
|
+
# Runs the block, or the fault armed under `name` instead. Not the block's
|
|
137
|
+
# own type: a `:garble` returns the substitute it was armed with, which is
|
|
138
|
+
# whatever the caller passed as `garbled:`. Makes the real call when the
|
|
139
|
+
# clock was replaced faster than a reading could be taken.
|
|
140
|
+
#
|
|
141
|
+
# Raises `Injected` for a `:raise` fault, which is the point of the gem,
|
|
142
|
+
# `DeadlineMachines::Error` when a `:delay` spends the ambient budget,
|
|
143
|
+
# `ArgumentError` for a name it cannot key on or a draw the random gave it,
|
|
144
|
+
# and `Error` for a clock returning an unusable reading.
|
|
145
|
+
def self.run: (Symbol | String name) { () -> untyped } -> untyped
|
|
146
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rogue_machines
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Abdelkader Boudih
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: deadline_machines
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rb_sys
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.9'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.9'
|
|
40
|
+
description: 'Fault injection for chaos engineering: arms a named experiment that
|
|
41
|
+
makes a dependency fail, stall or lie for a share of calls, so the breakers, retries
|
|
42
|
+
and budgets around it can be shown to work. Bound to Ruby from the zero-dependency
|
|
43
|
+
rogue-machines crate. Disarmed by default, and every experiment carries an expiry
|
|
44
|
+
it cannot outlive.'
|
|
45
|
+
email:
|
|
46
|
+
- oss@seuros.com
|
|
47
|
+
executables: []
|
|
48
|
+
extensions:
|
|
49
|
+
- ext/rogue_machines_native/extconf.rb
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- Cargo.lock
|
|
54
|
+
- Cargo.toml
|
|
55
|
+
- LICENSE.txt
|
|
56
|
+
- README.md
|
|
57
|
+
- ext/rogue_machines_native/core/Cargo.toml
|
|
58
|
+
- ext/rogue_machines_native/core/src/armed.rs
|
|
59
|
+
- ext/rogue_machines_native/core/src/experiment.rs
|
|
60
|
+
- ext/rogue_machines_native/core/src/lib.rs
|
|
61
|
+
- ext/rogue_machines_native/core/src/rogue.rs
|
|
62
|
+
- ext/rogue_machines_native/extconf.rb
|
|
63
|
+
- ext/rogue_machines_native/ffi/Cargo.toml
|
|
64
|
+
- ext/rogue_machines_native/ffi/extconf.rb
|
|
65
|
+
- ext/rogue_machines_native/ffi/src/injection.rs
|
|
66
|
+
- ext/rogue_machines_native/ffi/src/lib.rs
|
|
67
|
+
- ext/rogue_machines_native/ffi/src/registry.rs
|
|
68
|
+
- lib/rogue_machines.rb
|
|
69
|
+
- lib/rogue_machines/version.rb
|
|
70
|
+
- sig/rogue_machines.rbs
|
|
71
|
+
homepage: https://github.com/seuros/rogue_machines
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
source_code_uri: https://github.com/seuros/rogue_machines
|
|
76
|
+
changelog_uri: https://github.com/seuros/rogue_machines/blob/master/CHANGELOG.md
|
|
77
|
+
bug_tracker_uri: https://github.com/seuros/rogue_machines/issues
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
cargo_crate_name: rogue_machines_native
|
|
80
|
+
cargo_manifest_path: ext/rogue_machines_native/ffi/Cargo.toml
|
|
81
|
+
rdoc_options: []
|
|
82
|
+
require_paths:
|
|
83
|
+
- lib
|
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: 4.0.0
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
requirements: []
|
|
95
|
+
rubygems_version: 4.0.20
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: Fault injection and chaos engineering for Ruby
|
|
98
|
+
test_files: []
|