consul-templaterb 1.8.8 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c516359f510134a8471b94ba58a0a762d38b45cd7058d3b8d45ff67837047139
4
- data.tar.gz: b24b6f18c7d33561bb82b9df4ca6422052762e08b6ffb21fc76b82b55659cf3d
3
+ metadata.gz: 8efd0da9a415f6e58d2da8e712ea082efd08b51631b38c31db287cbbdf3a3bbe
4
+ data.tar.gz: 9e1fdd39625a7213dfb431dc89660fdeb0da382c4459766f5a2d34803313b9fd
5
5
  SHA512:
6
- metadata.gz: 281fa6348ebac8e6b3c143979a9b21dd182b43d3933ac6ed6f177cb4e3ab86f1db290406de8150abf77c580e17cbaaadac01673efa169c675a83e426fab165ab
7
- data.tar.gz: 86421aa3c59e9fc1073c46346cf61dde419ed389f40b549643d1f078723d3a587a4735d6c9882c44e33bd0d0958f20314572d5ead61988f0c9ac9c53b6b88b29
6
+ metadata.gz: 5f7c6e9187971dda5aadab619fe58facf7c76fe6cf7dde9a5d9c3708d525e8a3fc4bd8b8b558ccfcd8a3b2fafaf923acb7f5f673e9428635ef9b391ec50f6f79
7
+ data.tar.gz: 8031be2c311b9e1f12127a5e15922d13a12f86c86e2e0ec1ea92c1282c7f382fdab2f851c6bbb43b6d63dba91a8eb731df7825d93aaae7164407db1b1c48ebcf
data/.rubocop.yml CHANGED
@@ -16,7 +16,7 @@ Metrics/ClassLength:
16
16
  Max: 225
17
17
 
18
18
  Metrics/CyclomaticComplexity:
19
- Max: 15
19
+ Max: 20
20
20
 
21
21
  Metrics/LineLength:
22
22
  Max: 175
@@ -28,7 +28,7 @@ Metrics/ParameterLists:
28
28
  Max: 12
29
29
 
30
30
  Metrics/PerceivedComplexity:
31
- Max: 18
31
+ Max: 20
32
32
 
33
33
  Style/Documentation:
34
34
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## (UNRELEASED)
4
4
 
5
+ ## 1.9.0
6
+
7
+ OPTIMIZATIONS:
8
+ * Better network issue because of X-Consul-Index parsing bug
9
+
10
+ NEW FEATURES:
11
+ * value.endpoint.x_consul_index now works as expected
12
+
13
+ IMPROVEMENTS:
14
+ * timeline now works well behing load balancer without stickyness
15
+ * Now hide service column when filtering on a service
16
+ * apply row limits immediately
17
+ * auto-refresh duration is now configurable by `TIMELINE_REFRESH_MS`
18
+ environment variable
19
+
5
20
  ## 1.8.6 / 1.8.7 / 1.8.8 (December 20, 2018)
6
21
 
7
22
  OPTIMIZATIONS:
@@ -181,8 +181,8 @@ module Consul
181
181
  res
182
182
  end
183
183
 
184
- def find_x_consul_token(http)
185
- http.response_header['X_CONSUL_INDEX']
184
+ def find_x_consul_index(http)
185
+ http.response_header['X-Consul-Index']
186
186
  end
187
187
 
188
188
  def _handle_error(http, consul_index)
@@ -211,7 +211,8 @@ module Consul
211
211
  if !is_kv_empty && enforce_json_200 && http.response_header.status != 200 && http.response_header['Content-Type'] != 'application/json'
212
212
  _handle_error(http, consul_index) { connection = EventMachine::HttpRequest.new(conf.base_url, options) }
213
213
  else
214
- n_consul_index = find_x_consul_token(http)
214
+ n_consul_index = find_x_consul_index(http)
215
+ @x_consul_index = n_consul_index.to_i if n_consul_index
215
216
  @consecutive_errors = 0
216
217
  http_result = if is_kv_empty
217
218
  HttpResponse.new(http, default_value)
@@ -1,5 +1,5 @@
1
1
  module Consul
2
2
  module Async
3
- VERSION = '1.8.8'.freeze
3
+ VERSION = '1.9.0'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  <% datasource = ENV['TIMELINE_DATASOURCE'] || 'timeline.json'
2
2
  # Time to wait before reloading configuration again in seconds (0 = never)
3
- refresh = ENV['REFRESH'] || '3600' %><%= render_file('common/header.html.erb', title: 'Services Timeline') %>
3
+ refresh = ENV['TIMELINE_REFRESH_MS'] || '10000' %><%= render_file('common/header.html.erb', title: 'Services Timeline') %>
4
4
  <div class="main">
5
5
  <div class="row mx-0">
6
6
  <div id="filter-menu" class="col-2 col-m-3 px-4 pt-4">
@@ -27,7 +27,10 @@
27
27
  <span id="numRowsDisplayed" class="lookup badge badge-pill badge-primary">loading&hellip;</span>
28
28
  </span>
29
29
  </div>
30
- <input id="maxRows" title="max rows to display" placeholder="max rows" style="width: 5em !important;" type="number" min="100" max="<%= (ENV['CONSUL_TIMELINE_BUFFER'] || 10000) %>" step="25" value="5000" class="form-control"/>
30
+ <button class="btn btn-primary btn-sm" id="firstPage" title="Newest entries" disabled="disabled" onclick="serviceTimeline.firstPage()">⇤</button>
31
+ <button class="btn btn-primary btn-sm" id="prevPage" title="Newer entries" disabled="disabled" onclick="serviceTimeline.prevPage()">←</button>
32
+ <input id="maxRows" title="max rows to display" placeholder="max rows" style="width: 5.5em" type="number" min="25" max="<%= (ENV['CONSUL_TIMELINE_BUFFER'] || 10000) %>" step="25" value="250" class="form-control" onchange="serviceTimeline.displayEvents(false)"/>
33
+ <button class="btn btn-primary btn-sm" id="nextPage" disabled="disabled" onclick="serviceTimeline.nextPage()">→</button>
31
34
  </div>
32
35
  </div>
33
36
  </div>
@@ -22,6 +22,7 @@
22
22
  }
23
23
  .checkName {
24
24
  padding-left: 5px;
25
+ max-width: 12em;
25
26
  overflow: hidden;
26
27
  white-space: nowrap;
27
28
  text-overflow: ellipsis;
@@ -93,11 +94,11 @@ td.serviceName {
93
94
  border-left: 0px;
94
95
  border-right: 0px;
95
96
  }
96
- .service-status {
97
- width: 10em;
97
+ td.evt-service-status {
98
+ min-width: 10em;
98
99
  }
99
- .all-instances {
100
- width: 14em;
100
+ td.all-instances {
101
+ min-width: 10em;
101
102
  text-align: right;
102
103
  }
103
104
  #node-statuses span {
@@ -221,4 +222,4 @@ pre, code {
221
222
  }
222
223
  .connect-disabled{
223
224
  display: none;
224
- }
225
+ }
@@ -17,13 +17,17 @@ class ServiceTimeline {
17
17
  this.lastEntryLoaded = null;
18
18
  this.presentServices = {}
19
19
  this.currentlyUpdating = false;
20
+ this.start = null;
21
+ this.next = null;
22
+ this.prev = null;
20
23
  var sT = this;
21
24
  }
22
25
 
23
26
  fetchRessource(firstReload) {
24
- $.ajax({url: this.ressourceURL, cache: false, dataType: "json", sourceObject: this,
27
+ $.ajax({url: this.ressourceURL, cache: true, dataType: "json", sourceObject: this,
25
28
  success: function(result){
26
29
  serviceTimeline.initRessource(result, firstReload);
30
+ serviceTimeline.currentlyUpdating = false;
27
31
  },
28
32
  error: function(err) {
29
33
  console.log("Error updating data", err);
@@ -32,7 +36,69 @@ class ServiceTimeline {
32
36
  });
33
37
  }
34
38
 
39
+ nextPage() {
40
+ this.start = this.next;
41
+ console.log("nextPage() at ", this.start);
42
+ this.doFilter();
43
+ }
44
+
45
+ prevPage() {
46
+ this.start = this.prev;
47
+ console.log("prevPage() at ", this.start);
48
+ this.doFilter();
49
+ }
50
+
51
+ firstPage() {
52
+ this.start = null;
53
+ this.doFilter();
54
+ }
55
+
35
56
  initRessource(data, firstReload) {
57
+ if (!data || data.length < 1) {
58
+ console.log("No new data to load ", data);
59
+ return;
60
+ }
61
+ if (this.data != null) {
62
+ var previousCount = this.data.length;
63
+ if (previousCount > 0) {
64
+ var previousMaxIndex = indexOfTimelineEvent(this.data[previousCount - 1]);
65
+ var newIndex = indexOfTimelineEvent(data[data.length - 1]);
66
+ if (newIndex < previousMaxIndex) {
67
+ console.log("New index " + newIndex + " is lower than previous one", previousMaxIndex, " no need to reload");
68
+ return;
69
+ } else if (newIndex == previousMaxIndex) {
70
+ if (this.data.length >= data.length) {
71
+ //console.log("new := (idx=", newIndex, ",len=", data.length, ") old := (idx:=", previousMaxIndex, this.data.length, "), no need to reload");
72
+ return;
73
+ }
74
+ } else {
75
+ // Lets merge old data with new one
76
+ if (this.data.length > data.length) {
77
+ var diff = this.data.length - data.length;
78
+ var firstNewItem = indexOfTimelineEvent(data[0]);
79
+ var maxItemsInMemory = 50000;
80
+ if (maxItemsInMemory > data.length) {
81
+ console.log("Looking for matches with ", firstNewItem);
82
+ for (var i = 0 ; i < this.data.length; i++){
83
+ var oldVal = indexOfTimelineEvent(this.data[i]);
84
+ if (oldVal == firstNewItem) {
85
+ // We found the common start
86
+ var maxItemsToAdd = maxItemsInMemory - data.length;
87
+ var start = 0;
88
+ if (i > maxItemsToAdd) {
89
+ start = i - maxItemsToAdd;
90
+ }
91
+ console.log("Did preprend ", (i - start), "items to data");
92
+ data = this.data.slice(start, i).concat(data);
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ console.log("Loading", data.length, "items...");
36
102
  this.data = data;
37
103
  this.reloadTimeline(firstReload);
38
104
  }
@@ -132,7 +198,8 @@ class ServiceTimeline {
132
198
  if (!found) {
133
199
  sT.selectService($('#anyService', false));
134
200
  }
135
- setInterval(sT.reloadDataFromJSON, 10000);
201
+ console.log("Starting autorefresh every ", serviceTimeline.refresh, "ms.")
202
+ setInterval(sT.reloadDataFromJSON, serviceTimeline.refresh);
136
203
  }, 150);
137
204
  }
138
205
  }
@@ -232,13 +299,16 @@ class ServiceTimeline {
232
299
  var filterValue = $('#instance-filter')[0].value;
233
300
  var serviceName = serviceTimeline.serviceInstanceFilter;
234
301
  var serviceEvaluator = function(){return true};
302
+ var stylesheetText = '';
235
303
  if (serviceName != '' && serviceName != 'All') {
236
- serviceEvaluator = function(e){ return e.service === serviceName }
304
+ stylesheetText = '.serviceCol { display: none; }';
305
+ serviceEvaluator = function(e){ return e.service === serviceName }
237
306
  }
307
+ // Show / hide .serviceCol content
308
+ document.getElementById('serviceCol').textContent = stylesheetText;
238
309
  var maxRows = document.getElementById("maxRows").value;
239
310
  //$("#service-title").html(service['name']);
240
311
  var tableBody = $('#all-events > tbody');
241
- var startIndex = 0;
242
312
  var newDoc = document.createDocumentFragment();
243
313
  var frag = document.createElement('tbody');
244
314
  newDoc.appendChild(frag);
@@ -289,12 +359,56 @@ class ServiceTimeline {
289
359
  return false;
290
360
  }
291
361
  }
292
- for (var i = this.data.length - 1 ; i >= 0 && count <= maxRows; i--) {
362
+ var startIdx = this.start;
363
+ if (startIdx == null) {
364
+ startIdx = (this.data[this.data.length - 1].idx);
365
+ } else {
366
+ //console.log("startIdx := ", startIdx);
367
+ }
368
+ var previousElements = [];
369
+ document.getElementById('prevPage').setAttribute('disabled', 'disabled');
370
+ this.next = null;
371
+ document.getElementById('firstPage').setAttribute('disabled', 'disabled');
372
+ this.prev = null;
373
+ document.getElementById('nextPage').setAttribute('disabled', 'disabled');
374
+ var totalSkipped = 0;
375
+ for (var i = this.data.length - 1 ; i >= 0; i--) {
376
+ if (count >= maxRows) {
377
+ this.next = this.data[i].idx;
378
+ //console.log("next is ", this.data[i].ts, ' at ', this.next);
379
+ var nextTs = this.data[i].ts;
380
+ setTimeout(function(){
381
+ nextPage = document.getElementById('nextPage');
382
+ nextPage.removeAttribute('disabled');
383
+ nextPage.setAttribute('title', 'Older entries at ' + nextTs);
384
+ }, 1);
385
+ break;
386
+ }
293
387
  var e = this.data[i];
294
388
  if (!serviceEvaluator(e)) {
295
389
  continue;
296
390
  }
391
+ if (startIdx < e.idx) {
392
+ totalSkipped++;
393
+ previousElements.push(e);
394
+ var previousCounter = previousElements.length;
395
+ if (previousCounter == 1) {
396
+ setTimeout(function(){
397
+ document.getElementById('prevPage').removeAttribute('disabled');
398
+ document.getElementById('firstPage').removeAttribute('disabled');
399
+ }, 1);
400
+ } else if (previousCounter >= maxRows){
401
+ previousElements.shift();
402
+ }
403
+ continue;
404
+ }
297
405
  count++;
406
+ if (count == 1) {
407
+ if (previousElements.length > 0) {
408
+ //console.log("Current is ", e.ts, ' aka ', e.idx, ", Had ", previousElements.length, 'elements before, skipped := ', totalSkipped, "skipped elements", previousElements);
409
+ this.prev = previousElements.shift().idx;
410
+ }
411
+ }
298
412
  var row = document.createElement('tr');
299
413
  row.setAttribute("class", 'srv-' + e.service);
300
414
  var timestamp;
@@ -312,7 +426,7 @@ class ServiceTimeline {
312
426
  {
313
427
  var timeElement = this.appChild('time', document.createTextNode(timestamp));
314
428
  timeElement.setAttribute('datetime', fullTimestamp);
315
- timeElement.setAttribute('title', fullTimestamp);
429
+ timeElement.setAttribute('title', fullTimestamp + '\nX-Consul-Index: ' +e.idx);
316
430
  this.buildCell(row, 'td', 'ts', timeElement);
317
431
  }
318
432
  this.buildCell(row, 'td', 'lookup serviceName serviceCol', document.createTextNode(e.service));
@@ -352,10 +466,10 @@ class ServiceTimeline {
352
466
  }
353
467
  {
354
468
  if (e.new_state == e.old_state) {
355
- var td = this.buildCell(row, 'td', 'service-status', this.createBadge(e['new_state']));
469
+ var td = this.buildCell(row, 'td', 'evt-service-status', this.createBadge(e['new_state']));
356
470
  } else {
357
471
  var old = e.old_state == null ? this.createBadge('new', 'primary') : this.createBadge(e.old_state);
358
- var td = this.buildCell(row, 'td', 'service-status', old);
472
+ var td = this.buildCell(row, 'td', 'evt-service-status', old);
359
473
  td.appendChild(document.createTextNode('→'));
360
474
  var newState = (e.new_state == null) ? this.createBadge('removed', 'dark') : this.createBadge(e.new_state);
361
475
  td.appendChild(newState);
@@ -375,25 +489,28 @@ class ServiceTimeline {
375
489
  allInstances.appendChild(elem);
376
490
  }
377
491
  var elem = this.createBadge(nCritical);
378
- var percent = Math.round(nSuccess * 100 / e['stats']['total']);
379
- var clazz = 'secondary'
380
- if (percent > 90) {
381
- clazz = 'success';
382
- } else if (percent < 20) {
383
- clazz = 'danger';
384
- } else if (percent < 50) {
385
- clazz = 'warning';
492
+ if (e['stats']['total'] > 0) {
493
+ var percent = Math.round(nSuccess * 100 / e['stats']['total']);
494
+ var clazz = 'secondary'
495
+ if (percent > 90) {
496
+ clazz = 'success';
497
+ } else if (percent < 20) {
498
+ clazz = 'danger';
499
+ } else if (percent < 50) {
500
+ clazz = 'warning';
501
+ }
502
+ this.buildCell(row, 'td', 'ipercents', this.createBadge(percent + " %", clazz));
503
+ } else {
504
+ this.buildCell(row, 'td', 'ipercents', this.createBadge('none', 'danger'));
386
505
  }
387
- this.buildCell(row, 'td', 'ipercents', this.createBadge(percent + " %", clazz));
388
506
  }
389
507
  frag.append(row);
390
508
  }
391
- $('#numRowsDisplayed').html(count + ' / ' + this.data.length);
509
+ $('#numRowsDisplayed').html(totalSkipped + '…' + (totalSkipped + count) + ' / ' + this.data.length);
392
510
  var tbody = tableBody[0];
393
511
  tbody.parentNode.replaceChild(frag, tbody);
394
512
  if (this.data.length > 1) {
395
513
  this.lastEntryLoaded = this.data[this.data.length - 1];
396
- console.log("Last entry loaded: ", indexOfTimelineEvent(this.lastEntryLoaded));
397
514
  }
398
515
  }
399
- }
516
+ }
@@ -4,6 +4,8 @@ require 'set'
4
4
  services_blacklist = (ENV['EXCLUDE_SERVICES'] || 'consul-agent-http,mesos-slave,mesos-agent-watcher,mesos-exporter-slave').split(',')
5
5
 
6
6
  @current_time = Time.now.utc.iso8601
7
+ @last_service_info = {}
8
+ @newest_index = 0 unless @newest_index
7
9
  cur_state = services.map do |service_name, _tags|
8
10
  next if services_blacklist.include?(service_name)
9
11
 
@@ -14,6 +16,9 @@ cur_state = services.map do |service_name, _tags|
14
16
  'critical' => 0,
15
17
  'total' => snodes.count
16
18
  }
19
+ cur_index = snodes.endpoint.x_consul_index.to_i
20
+ @newest_index = cur_index if @newest_index < cur_index
21
+ @last_service_info[service_name] = { 'idx' => cur_index, 'stats' => cur_stats }
17
22
  snodes.each do |snode|
18
23
  case snode.status.downcase
19
24
  when 'passing'
@@ -32,9 +37,9 @@ cur_state = services.map do |service_name, _tags|
32
37
  'address' => instance.service_address,
33
38
  'node' => instance['Node']['Node'],
34
39
  'port' => instance['Service']['Port'],
35
- 'idx' => instance['Service']['ModifyIndex'],
40
+ 'idx' => (instance['Service']['ModifyIndex'] || cur_index).to_i,
36
41
  'status' => instance.status,
37
- 'stats' => cur_stats,
42
+ 'stats' => cur_stats,
38
43
  'checks' => instance['Checks'].map { |check| [check['CheckID'], { 'name' => check['Name'], 'status' => check['Status'], 'output' => check['Output'] }] }.to_h
39
44
  }]
40
45
  end.to_h
@@ -54,26 +59,27 @@ class RingBuffer < Array
54
59
 
55
60
  def initialize(max_size:, enum: nil)
56
61
  @max_size = max_size
62
+ warn "Ringbuffer initialized with #{max_size}"
57
63
  enum&.each { |e| self << e }
58
64
  end
59
65
 
60
66
  def <<(element)
61
- if size < @max_size || @max_size.nil?
62
- super
63
- else
67
+ return unless element
68
+ if size >= @max_size
64
69
  shift
70
+ end
71
+ previous_e = last
72
+ after = []
73
+ while !previous_e.nil? && (previous_e['idx'] > element['idx'])
74
+ after.insert(0, pop)
65
75
  previous_e = last
66
- after = []
67
- while !previous_e.nil? && previous_e['idx'] > element['idx']
68
- after << pop
69
- previous_e = last
70
- end
71
- push(element)
72
- push after
73
76
  end
77
+ push(element)
78
+ after.each do |x|
79
+ push(x)
80
+ end
81
+ self
74
82
  end
75
-
76
- alias push <<
77
83
  end
78
84
 
79
85
  def diff(old, new_e)
@@ -92,16 +98,12 @@ def log_event(line)
92
98
  puts "#{Time.now.to_i} #{line}" if ENV['DEBUG_TIMELINE']
93
99
  end
94
100
 
95
- def store_event(service: service_name, instance: nil, old_state: nil, new_state: nil, instance_info: nil, checks: [])
96
- STDERR.puts "empty instance_info for #{service} ; #{instance} ; #{new_state}" unless instance_info
97
- @new_events << { 'service' => service, 'instance' => instance, 'idx' => instance_info['idx'], 'old_state' => old_state, 'new_state' => new_state,
101
+ def store_event(service: nil, instance: nil, old_state: nil, new_state: nil, instance_info: nil, checks: [])
102
+ @new_events << { 'service' => service, 'instance' => instance, 'old_state' => old_state, 'new_state' => new_state,
98
103
  'ts' => @current_time, 'instance_info' => instance_info }.tap do |ev|
99
104
  ev['checks'] = checks if checks
100
- ev['stats'] = if instance_info
101
- instance_info['stats']
102
- else
103
- {}
104
- end
105
+ ev['stats'] = instance_info['stats']
106
+ ev['idx'] = instance_info['idx']
105
107
  end
106
108
  end
107
109
 
@@ -133,11 +135,19 @@ service_diff = diff(old_state.keys, cur_state.keys)
133
135
  service_diff.disappeared.each do |service_name|
134
136
  old_state[service_name].each do |instance_name, instance_info|
135
137
  checks = compute_checks(old_state, cur_state, service_name, instance_name)
138
+ the_info = {}.merge(instance_info)
139
+ the_info['idx'] = @newest_index
140
+ the_info['stats'] = {
141
+ 'passing' => 0,
142
+ 'warning' => 0,
143
+ 'critical' => 0,
144
+ 'total' => 0,
145
+ }
136
146
  store_event(service: service_name,
137
147
  instance: instance_name,
138
148
  old_state: old_state[service_name][instance_name]['status'],
139
149
  new_state: nil,
140
- instance_info: instance_info,
150
+ instance_info: the_info,
141
151
  checks: checks)
142
152
  end
143
153
  end
@@ -156,11 +166,14 @@ end
156
166
 
157
167
  instance_diff.disappeared.each do |instance_name|
158
168
  checks = compute_checks(old_state, cur_state, service_name, instance_name)
169
+ the_info = {}.merge(old_state[service_name][instance_name])
170
+ the_info['idx'] = @last_service_info[service_name]['idx']
171
+ the_info['stats'] = @last_service_info[service_name]['stats']
159
172
  store_event(service: service_name,
160
173
  old_state: old_state[service_name][instance_name]['status'],
161
174
  new_state: nil,
162
175
  instance: instance_name,
163
- instance_info: old_state[service_name][instance_name],
176
+ instance_info: the_info,
164
177
  checks: checks)
165
178
  end
166
179
  instance_diff.appeared.each do |instance_name|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: consul-templaterb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.8
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SRE Core Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-20 00:00:00.000000000 Z
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-http-request