flapjack 1.0.0rc1 → 1.0.0rc2

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.
@@ -50,9 +50,17 @@ module Flapjack
50
50
  @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
51
51
 
52
52
  @logger = opts[:logger]
53
+ @logger.debug("Jabber Initializing")
53
54
 
54
55
  @buffer = []
55
56
  @hostname = Socket.gethostname
57
+
58
+ # FIXME: i suspect the following should be in #setup so a config reload updates @identifiers
59
+ # I moved it here so the rspec passes :-/
60
+ @alias = @config['alias'] || 'flapjack'
61
+ @identifiers = ((@config['identifiers'] || []) + [@alias]).uniq
62
+ @logger.debug("I will respond to the following identifiers: #{@identifiers.join(', ')}")
63
+
56
64
  super()
57
65
  end
58
66
 
@@ -65,7 +73,9 @@ module Flapjack
65
73
  end
66
74
 
67
75
  def setup
68
- @flapjack_jid = Blather::JID.new((@config['jabberid'] || 'flapjack') + '/' + @hostname)
76
+ jid = @config['jabberid'] || 'flapjack'
77
+ jid += '/' + @hostname unless jid.include?('/')
78
+ @flapjack_jid = Blather::JID.new(jid)
69
79
 
70
80
  super(@flapjack_jid, @config['password'], @config['server'], @config['port'].to_i)
71
81
 
@@ -80,13 +90,19 @@ module Flapjack
80
90
  end
81
91
  end
82
92
 
83
- register_handler :message, :groupchat?, :body => /^#{@config['alias']}:\s+/ do |stanza|
93
+ body_matchers = @identifiers.inject([]) do |memo, identifier|
94
+ @logger.debug("identifier: #{identifier}, memo: #{memo}")
95
+ memo << {:body => /^#{identifier}[:\s]/}
96
+ memo
97
+ end
98
+ @logger.debug("body_matchers: #{body_matchers}")
99
+ register_handler :message, :groupchat?, body_matchers do |stanza|
84
100
  EventMachine::Synchrony.next_tick do
85
101
  on_groupchat(stanza)
86
102
  end
87
103
  end
88
104
 
89
- register_handler :message, :chat? do |stanza|
105
+ register_handler :message, :chat?, :body do |stanza|
90
106
  EventMachine::Synchrony.next_tick do
91
107
  on_chat(stanza)
92
108
  end
@@ -111,11 +127,11 @@ module Flapjack
111
127
  @logger.info("Joining room #{room}")
112
128
  presence = Blather::Stanza::Presence.new
113
129
  presence.from = @flapjack_jid
114
- presence.to = Blather::JID.new("#{room}/#{@config['alias']}")
130
+ presence.to = Blather::JID.new("#{room}/#{@alias}")
115
131
  presence << "<x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='0'></x>"
116
132
  EventMachine::Synchrony.next_tick do
117
133
  write presence
118
- say(room, "flapjack jabber gateway started at #{Time.now}, hello!", :groupchat)
134
+ say(room, "flapjack jabber gateway started at #{Time.now}, hello! Try typing 'help'.", :groupchat)
119
135
  end
120
136
  end
121
137
  end
@@ -160,7 +176,7 @@ module Flapjack
160
176
  out
161
177
  end
162
178
 
163
- def interpreter(command_raw,from)
179
+ def interpreter(command_raw, from)
164
180
  msg = nil
165
181
  action = nil
166
182
  entity_check = nil
@@ -239,7 +255,8 @@ module Flapjack
239
255
  t = Process.times
240
256
  fqdn = `/bin/hostname -f`.chomp
241
257
  pid = Process.pid
242
- msg = "Flapjack #{Flapjack::VERSION} process #{pid} on #{fqdn} \n" +
258
+ msg = "Flapjack #{Flapjack::VERSION} process #{pid} on #{fqdn}\n" +
259
+ "Identifiers: #{@identifiers.join(', ')}\n" +
243
260
  "Boot time: #{@boot_time}\n" +
244
261
  "User CPU Time: #{t.utime}\n" +
245
262
  "System CPU Time: #{t.stime}\n" +
@@ -537,18 +554,23 @@ module Flapjack
537
554
  return if @should_quit
538
555
  @logger.debug("groupchat message received: #{stanza.inspect}")
539
556
 
540
- if stanza.body =~ /^#{@config['alias']}:\s+(.*)/m
541
- command = $1
557
+ the_command = nil
558
+ @identifiers.each do |identifier|
559
+ if stanza.body =~ /^#{identifier}:?\s*(.*)/m
560
+ the_command = $1
561
+ @logger.debug("matched identifier: #{identifier}, command: #{the_command.inspect}")
562
+ break
563
+ end
542
564
  end
543
565
 
544
566
  from = stanza.from
545
567
 
546
568
  begin
547
- results = interpreter(command, from.resource.to_s)
569
+ results = interpreter(the_command, from.to_s)
548
570
  msg = results[:msg]
549
571
  action = results[:action]
550
572
  rescue => e
551
- @logger.error("Exception when interpreting command '#{command}' - #{e.class}, #{e.message}")
573
+ @logger.debug("Exception when interpreting command '#{the_command}' - #{e.class}, #{e.message}")
552
574
  msg = "Oops, something went wrong processing that command (#{e.class}, #{e.message})"
553
575
  end
554
576
 
@@ -571,8 +593,10 @@ module Flapjack
571
593
  command = stanza.body
572
594
  end
573
595
 
596
+ from = stanza.from
597
+
574
598
  begin
575
- results = interpreter(command)
599
+ results = interpreter(command, from.resource.to_s)
576
600
  msg = results[:msg]
577
601
  action = results[:action]
578
602
  rescue => e
@@ -31,6 +31,38 @@ module Flapjack
31
31
  app.helpers Flapjack::Gateways::JSONAPI::Helpers
32
32
  app.helpers Flapjack::Gateways::JSONAPI::CheckMethods::Helpers
33
33
 
34
+ app.get %r{^/checks(?:/)?(.+)?$} do
35
+ requested_checks = if params[:captures] && params[:captures][0]
36
+ params[:captures][0].split(',').uniq
37
+ else
38
+ nil
39
+ end
40
+
41
+ checks = if requested_checks
42
+ requested_checks.collect do|req_check|
43
+ Flapjack::Data::EntityCheck.for_event_id(req_check, :logger => logger, :redis => redis)
44
+ end
45
+ else
46
+ Flapjack::Data::EntityCheck.find_all(:redis => redis)
47
+ end
48
+ checks.compact!
49
+
50
+ if requested_checks && checks.empty?
51
+ raise Flapjack::Gateways::JSONAPI::ChecksNotFound.new(requested_checks)
52
+ end
53
+
54
+ linked_entity_ids = checks.empty? ? [] : checks.inject({}) do |memo, check|
55
+ memo[check.key] = check.entity.id
56
+ memo
57
+ end
58
+
59
+ checks_json = checks.collect {|check|
60
+ check.to_jsonapi(:entity_ids => linked_entity_ids[check.key])
61
+ }.join(",")
62
+
63
+ '{"checks":[' + checks_json + ']}'
64
+ end
65
+
34
66
  app.patch %r{^/checks/(.+)$} do
35
67
  checks_for_check_names(params[:captures][0].split(',')).each do |check|
36
68
  apply_json_patch('checks') do |op, property, linked, value|
@@ -27,11 +27,7 @@ module Flapjack
27
27
  end
28
28
 
29
29
  checks = if event_ids.nil?
30
- Flapjack::Data::Entity.all(:redis => redis).collect {|entity|
31
- entity.check_list.collect {|check_name|
32
- find_entity_check(entity, check_name)
33
- }
34
- }.flatten(2)
30
+ Flapjack::Data::EntityCheck.find_all(:redis => redis)
35
31
  elsif !event_ids.empty?
36
32
  event_ids.collect {|event_id| find_entity_check_by_name(*event_id.split(':', 2)) }
37
33
  else
@@ -4,7 +4,7 @@
4
4
  current_time = Time.now
5
5
  %>
6
6
  <div class="page-header">
7
- <% entity_link = u(@base_url) + "entity/" << u(@entity) %>
7
+ <% entity_link = @base_url + "entity/" << u(@entity) %>
8
8
  <h2><%= h @check %> on <a href="<%= entity_link %>" title="entity summary"><%= h @entity %></a></h2>
9
9
  </div>
10
10
 
@@ -21,7 +21,7 @@
21
21
  <tbody>
22
22
  <% @entities_sorted.each do |entity| %>
23
23
  <% row_entity = nil %>
24
- <% entity_link = u(@base_url) + "entity/" << u(entity) %>
24
+ <% entity_link = @base_url + "entity/" << u(entity) %>
25
25
  <% @states[entity].each do |check, status, summary, changed, updated, in_unscheduled_outage, in_scheduled_outage, notified| %>
26
26
  <%
27
27
  row_colour = case status
@@ -33,7 +33,7 @@
33
33
  status
34
34
  end
35
35
 
36
- check_link = u(@base_url) + "check?entity=" << u(entity) << "&amp;check=" << u(check)
36
+ check_link = @base_url + "check?entity=" << u(entity) << "&amp;check=" << u(check)
37
37
  %>
38
38
  <tr class="<%= row_colour %>">
39
39
  <% unless row_entity && entity == row_entity %>
@@ -81,7 +81,7 @@
81
81
  <td>
82
82
  <% checks.each do |entity_check| %>
83
83
  <% entity, check = entity_check.split(':', 2) %>
84
- <% check_link = "<a href=\"#{u(@base_url)}check?entity=#{u(entity)}&amp;check=#{u(check)}\" title=\"check status\">" +
84
+ <% check_link = "<a href=\"#{@base_url}check?entity=#{u(entity)}&amp;check=#{u(check)}\" title=\"check status\">" +
85
85
  h(check) + "</a>"%>
86
86
  <a href="<%= @base_url %>entity/<%= u(entity) %>" title="entity status"><%= h entity %></a> ::
87
87
  <%= check_link %> <br />
@@ -147,7 +147,7 @@
147
147
  <td><a href="<%= @base_url %>entity/<%= u(entity.name) %>" title="entity status"><%= h entity.name %></a></td>
148
148
  <td>
149
149
  <% checks.each do |check| %>
150
- <%= "<a href=\"#{u(@base_url)}check?entity=#{u(entity.name)}&amp;check=#{u(check)}\" title=\"check status\">#{ h check }</a>" %>
150
+ <%= "<a href=\"#{@base_url}check?entity=#{u(entity.name)}&amp;check=#{u(check)}\" title=\"check status\">#{ h check }</a>" %>
151
151
  <% end %>
152
152
  </td>
153
153
  </tr>
@@ -28,7 +28,7 @@
28
28
  status
29
29
  end
30
30
 
31
- check_link = u(@base_url) + "check?entity=" << u(@entity) << "&amp;check=" << u(check)
31
+ check_link = @base_url + "check?entity=" << u(@entity) << "&amp;check=" << u(check)
32
32
 
33
33
  %>
34
34
  <tr class="<%= row_colour %>">
@@ -116,3 +116,73 @@ class Redis
116
116
  end
117
117
  end
118
118
  end
119
+
120
+ module GLI
121
+ class Command
122
+ attr_accessor :passthrough
123
+ def _action
124
+ @action
125
+ end
126
+ end
127
+
128
+ class GLIOptionParser
129
+ class NormalCommandOptionParser
130
+ def parse!(parsing_result)
131
+ parsed_command_options = {}
132
+ command = parsing_result.command
133
+ arguments = nil
134
+
135
+ loop do
136
+ command._action.call if command.passthrough
137
+
138
+ option_parser_factory = OptionParserFactory.for_command(command,@accepts)
139
+ option_block_parser = CommandOptionBlockParser.new(option_parser_factory, self.error_handler)
140
+ option_block_parser.command = command
141
+ arguments = parsing_result.arguments
142
+
143
+ arguments = option_block_parser.parse!(arguments)
144
+
145
+ parsed_command_options[command] = option_parser_factory.options_hash_with_defaults_set!
146
+ command_finder = CommandFinder.new(command.commands,command.get_default_command)
147
+ next_command_name = arguments.shift
148
+
149
+ verify_required_options!(command.flags,parsed_command_options[command])
150
+
151
+ begin
152
+ command = command_finder.find_command(next_command_name)
153
+ rescue AmbiguousCommand
154
+ arguments.unshift(next_command_name)
155
+ break
156
+ rescue UnknownCommand
157
+ arguments.unshift(next_command_name)
158
+ # Although command finder could certainy know if it should use
159
+ # the default command, it has no way to put the "unknown command"
160
+ # back into the argument stack. UGH.
161
+ unless command.get_default_command.nil?
162
+ command = command_finder.find_command(command.get_default_command)
163
+ end
164
+ break
165
+ end
166
+ end
167
+
168
+ parsed_command_options[command] ||= {}
169
+ command_options = parsed_command_options[command]
170
+
171
+ this_command = command.parent
172
+ child_command_options = command_options
173
+
174
+ while this_command.kind_of?(command.class)
175
+ this_command_options = parsed_command_options[this_command] || {}
176
+ child_command_options[GLI::Command::PARENT] = this_command_options
177
+ this_command = this_command.parent
178
+ child_command_options = this_command_options
179
+ end
180
+
181
+ parsing_result.command_options = command_options
182
+ parsing_result.command = command
183
+ parsing_result.arguments = Array(arguments.compact)
184
+ parsing_result
185
+ end
186
+ end
187
+ end
188
+ end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "1.0.0rc1"
4
+ VERSION = "1.0.0rc2"
5
5
  end
6
6
 
@@ -0,0 +1,161 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+ "os"
7
+ "io/ioutil"
8
+ "time"
9
+ "net/http"
10
+ "flapjack"
11
+ "encoding/json"
12
+ "gopkg.in/alecthomas/kingpin.v1"
13
+ "github.com/go-martini/martini"
14
+ )
15
+
16
+ // State is a basic representation of a Flapjack event, with some extra field.
17
+ // The extra fields handle state expiry.
18
+ // Find more at https://github.com/flapjack/flapjack/wiki/DATA_STRUCTURES
19
+ type State struct {
20
+ flapjack.Event
21
+ TTL int64 `json:"ttl"`
22
+ }
23
+
24
+ // handler caches
25
+ func CreateState(updates chan State, w http.ResponseWriter, r *http.Request) {
26
+ var state State
27
+
28
+ body, err := ioutil.ReadAll(r.Body)
29
+ if err != nil {
30
+ message := "Error: Couldn't read request body: %s\n"
31
+ log.Printf(message, err)
32
+ fmt.Fprintf(w, message, err)
33
+ return
34
+ }
35
+
36
+ err = json.Unmarshal(body, &state)
37
+ if err != nil {
38
+ message := "Error: Couldn't read request body: %s\n"
39
+ log.Println(message, err)
40
+ fmt.Fprintf(w, message, err)
41
+ return
42
+ }
43
+
44
+ // Populate a time if none has been set.
45
+ if state.Time == 0 {
46
+ state.Time = time.Now().Unix()
47
+ }
48
+
49
+ if len(state.Type) == 0 {
50
+ state.Type = "service"
51
+ }
52
+
53
+ if state.TTL == 0 {
54
+ state.TTL = 300
55
+ }
56
+
57
+ updates <- state
58
+
59
+ json, _ := json.Marshal(state)
60
+ message := "Caching state: %s\n"
61
+ log.Printf(message, json)
62
+ fmt.Fprintf(w, message, json)
63
+ }
64
+
65
+ // cacheState stores a cache of event state to be sent to Flapjack.
66
+ // The event state is queried later when submitting events periodically
67
+ // to Flapjack.
68
+ func cacheState(updates chan State, state map[string]State) {
69
+ for ns := range updates {
70
+ key := ns.Entity + ":" + ns.Check
71
+ state[key] = ns
72
+ }
73
+ }
74
+
75
+ // submitCachedState periodically samples the cached state, sends it to Flapjack.
76
+ func submitCachedState(states map[string]State, config Config) {
77
+ transport, err := flapjack.Dial(config.Server, config.Database)
78
+ if err != nil {
79
+ fmt.Printf("Error: %s\n", err)
80
+ os.Exit(1)
81
+ }
82
+
83
+ for {
84
+ log.Printf("Number of cached states: %d\n", len(states))
85
+ for id, state := range(states) {
86
+ now := time.Now().Unix()
87
+ event := flapjack.Event{
88
+ Entity: state.Entity,
89
+ Check: state.Check,
90
+ Type: state.Type,
91
+ State: state.State,
92
+ Summary: state.Summary,
93
+ Time: now,
94
+ }
95
+
96
+ // Stale state sends UNKNOWNs
97
+ elapsed := now - state.Time
98
+ if state.TTL >= 0 && elapsed > state.TTL {
99
+ log.Printf("State for %s is stale. Sending UNKNOWN.\n", id)
100
+ event.State = "UNKNOWN"
101
+ event.Summary = fmt.Sprintf("httpbroker: Cached state is stale (%ds old, should be < %ds)", elapsed, state.TTL)
102
+ }
103
+ if config.Debug {
104
+ log.Printf("Sending event data for %s\n", id)
105
+ }
106
+ transport.Send(event)
107
+ }
108
+ time.Sleep(config.Interval)
109
+ }
110
+ }
111
+
112
+ var (
113
+ port = kingpin.Flag("port", "Address to bind HTTP server (default 3090)").Default("3090").OverrideDefaultFromEnvar("PORT").String()
114
+ server = kingpin.Flag("server", "Redis server to connect to (default localhost:6380)").Default("localhost:6380").String()
115
+ database = kingpin.Flag("database", "Redis database to connect to (default 0)").Int() // .Default("13").Int()
116
+ interval = kingpin.Flag("interval", "How often to submit events (default 10s)").Default("10s").Duration()
117
+ debug = kingpin.Flag("debug", "Enable verbose output (default false)").Bool()
118
+ )
119
+
120
+ type Config struct {
121
+ Port string
122
+ Server string
123
+ Database int
124
+ Interval time.Duration
125
+ Debug bool
126
+ }
127
+
128
+ func main() {
129
+ kingpin.Version("0.0.1")
130
+ kingpin.Parse()
131
+
132
+ config := Config{
133
+ Server: *server,
134
+ Database: *database,
135
+ Interval: *interval,
136
+ Debug: *debug,
137
+ Port: ":" + *port,
138
+ }
139
+ if config.Debug {
140
+ log.Printf("Booting with config: %+v\n", config)
141
+ }
142
+
143
+ updates := make(chan State)
144
+ state := map[string]State{}
145
+
146
+ go cacheState(updates, state)
147
+ go submitCachedState(state, config)
148
+
149
+ m := martini.Classic()
150
+ m.Group("/state", func(r martini.Router) {
151
+ r.Post("", func(res http.ResponseWriter, req *http.Request) {
152
+ CreateState(updates, res, req)
153
+ })
154
+ r.Get("", func() []byte {
155
+ data, _ := json.Marshal(state)
156
+ return data
157
+ })
158
+ })
159
+
160
+ log.Fatal(http.ListenAndServe(config.Port, m))
161
+ }