flapjack 1.0.0rc1 → 1.0.0rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }