fluent-plugin-google-cloud 0.6.10 → 0.6.11.pre.memory.1

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
  SHA1:
3
- metadata.gz: d1d9303278baab1ded1484eaa6c2974c5dbb3bc5
4
- data.tar.gz: 313c3e168ab212dd65e276b0e0a3562e718fcaa1
3
+ metadata.gz: fadfeb3397203502f2708012a6019ca93fbf4b28
4
+ data.tar.gz: d2ebfbc912f30cd1caa3120fe1d282570e98da6e
5
5
  SHA512:
6
- metadata.gz: 2a67095d29a49fb9b264a23f78949d98f28614428381ae4f85b886fb14d5b7d34d4974649ca8ab1c740dbc172e2e036ab64f2b788d4818d94dc3d50ba0529a8a
7
- data.tar.gz: 3ec89701d9d83e380e90eb06f4b3d9a25a7d7b223420a1384dd07b110af43c8fb4aeccd59bdfaffda4140ba74bfc4b0d5804d4dc74b627b7527d8981205a7bc5
6
+ metadata.gz: 5884686ae0eae5dad3f533d5399b171029bc03352b251c1690c20425071c141eddb34449bebcb8fa486d5fd6c6752ffe9dd207ec47889f7eed812781d01c9d12
7
+ data.tar.gz: feffa60a8bf9b93d83b9ec8762e12f74e6c2934fe57a4d10d219410bf8618ce97bc5c14a2deae8e20dbb5a1f0697f1bfec0f167b7cb9b1a2bbf61b2a916272a4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.6.10)
4
+ fluent-plugin-google-cloud (0.6.11.pre.memory.1)
5
5
  fluentd (~> 0.10)
6
6
  google-api-client (~> 0.14)
7
7
  google-cloud-logging (~> 1.2, >= 1.2.3)
@@ -16,14 +16,14 @@ GEM
16
16
  addressable (2.5.2)
17
17
  public_suffix (>= 2.0.2, < 4.0)
18
18
  ast (2.3.0)
19
- cool.io (1.5.1)
19
+ cool.io (1.5.3)
20
20
  crack (0.4.3)
21
21
  safe_yaml (~> 1.0.0)
22
22
  declarative (0.0.10)
23
23
  declarative-option (0.1.0)
24
24
  faraday (0.13.1)
25
25
  multipart-post (>= 1.2, < 3)
26
- fluentd (0.14.23)
26
+ fluentd (0.14.25)
27
27
  cool.io (>= 1.4.5, < 2.0.0)
28
28
  http_parser.rb (>= 0.5.1, < 0.7.0)
29
29
  msgpack (>= 0.7.0, < 2.0.0)
@@ -34,7 +34,7 @@ GEM
34
34
  tzinfo (~> 1.0)
35
35
  tzinfo-data (~> 1.0)
36
36
  yajl-ruby (~> 1.0)
37
- google-api-client (0.17.1)
37
+ google-api-client (0.17.4)
38
38
  addressable (~> 2.5, >= 2.5.1)
39
39
  googleauth (>= 0.5, < 0.7.0)
40
40
  httpclient (>= 2.8.1, < 3.0)
@@ -45,11 +45,11 @@ GEM
45
45
  google-cloud-env (~> 1.0)
46
46
  google-cloud-env (1.0.1)
47
47
  faraday (~> 0.11)
48
- google-cloud-logging (1.3.1)
48
+ google-cloud-logging (1.3.2)
49
49
  google-cloud-core (~> 1.1)
50
50
  google-gax (~> 0.10.1)
51
51
  stackdriver-core (~> 1.2)
52
- google-gax (0.10.1)
52
+ google-gax (0.10.2)
53
53
  google-protobuf (~> 3.2)
54
54
  googleapis-common-protos (>= 1.3.5, < 2.0)
55
55
  googleauth (~> 0.6.2)
@@ -10,7 +10,7 @@ eos
10
10
  gem.homepage =
11
11
  'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
12
12
  gem.license = 'Apache-2.0'
13
- gem.version = '0.6.10'
13
+ gem.version = '0.6.11.pre.memory.1'
14
14
  gem.authors = ['Todd Derr', 'Alex Robinson']
15
15
  gem.email = ['salty@google.com']
16
16
  gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
@@ -20,6 +20,8 @@ require 'time'
20
20
  require 'yaml'
21
21
  require 'google/apis'
22
22
  require 'google/apis/logging_v2'
23
+ require 'google/cloud/logging/v2'
24
+ require 'google/gax'
23
25
  require 'google/logging/v2/logging_pb'
24
26
  require 'google/logging/v2/logging_services_pb'
25
27
  require 'google/logging/v2/log_entry_pb'
@@ -193,7 +195,7 @@ module Fluent
193
195
  Fluent::Plugin.register_output('google_cloud', self)
194
196
 
195
197
  PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'.freeze
196
- PLUGIN_VERSION = '0.6.10'.freeze
198
+ PLUGIN_VERSION = '0.6.11.pre.memory.1'.freeze
197
199
 
198
200
  # Name of the the Google cloud logging write scope.
199
201
  LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'.freeze
@@ -391,6 +393,7 @@ module Fluent
391
393
  'The number of log entries that failed to be ingested by the'\
392
394
  ' Stackdriver output plugin due to a transient error and were'\
393
395
  ' retried')
396
+ @ok_code = @use_grpc ? 0 : 200
394
397
  end
395
398
 
396
399
  # Alert on old authentication configuration.
@@ -514,17 +517,6 @@ module Fluent
514
517
  severity = compute_severity(
515
518
  entry_level_resource.type, record, entry_level_common_labels)
516
519
 
517
- ts_secs = begin
518
- Integer ts_secs
519
- rescue ArgumentError, TypeError
520
- ts_secs
521
- end
522
- ts_nanos = begin
523
- Integer ts_nanos
524
- rescue ArgumentError, TypeError
525
- ts_nanos
526
- end
527
-
528
520
  if @use_grpc
529
521
  entry = Google::Logging::V2::LogEntry.new(
530
522
  labels: entry_level_common_labels,
@@ -583,18 +575,16 @@ module Fluent
583
575
  [k.encode('utf-8'), convert_to_utf8(v)]
584
576
  end
585
577
 
586
- write_request = Google::Logging::V2::WriteLogEntriesRequest.new(
578
+ entries_count = entries.length
579
+ client.write_log_entries(
580
+ entries,
587
581
  log_name: log_name,
588
582
  resource: Google::Api::MonitoredResource.new(
589
583
  type: group_level_resource.type,
590
584
  labels: group_level_resource.labels.to_h
591
585
  ),
592
- labels: labels_utf8_pairs.to_h,
593
- entries: entries
586
+ labels: labels_utf8_pairs.to_h
594
587
  )
595
-
596
- entries_count = entries.length
597
- client.write_log_entries(write_request)
598
588
  increment_successful_requests_count
599
589
  increment_ingested_entries_count(entries_count)
600
590
 
@@ -605,45 +595,62 @@ module Fluent
605
595
  @log.info 'Successfully sent gRPC to Stackdriver Logging API.'
606
596
  end
607
597
 
608
- rescue GRPC::Cancelled => error
609
- increment_failed_requests_count(GRPC::Core::StatusCodes::CANCELLED)
610
- increment_retried_entries_count(entries_count, error.code)
611
- # RPC cancelled, so retry via re-raising the error.
612
- @log.debug "Retrying #{entries_count} log message(s) later.",
613
- error: error.to_s, error_code: error.code.to_s
614
- raise error
615
-
616
- rescue GRPC::BadStatus => error
598
+ rescue Google::Gax::GaxError => gax_error
599
+ # GRPC::BadStatus is wrapped in error.cause.
600
+ error = gax_error.cause
617
601
  increment_failed_requests_count(error.code)
618
- case error.code
619
- when GRPC::Core::StatusCodes::CANCELLED,
620
- GRPC::Core::StatusCodes::UNAVAILABLE,
621
- GRPC::Core::StatusCodes::DEADLINE_EXCEEDED,
622
- GRPC::Core::StatusCodes::INTERNAL,
623
- GRPC::Core::StatusCodes::UNKNOWN
624
- # Server error, so retry via re-raising the error.
625
- increment_retried_entries_count(entries.length, error.code)
602
+
603
+ # See the mapping between HTTP status and gRPC status code at:
604
+ # https://github.com/grpc/grpc/blob/master/src/core/lib/transport/status_conversion.cc
605
+ case error
606
+ # Server error, so retry via re-raising the error.
607
+ when \
608
+ # HTTP status 500 (Internal Server Error).
609
+ GRPC::Internal,
610
+ # HTTP status 501 (Not Implemented).
611
+ GRPC::Unimplemented,
612
+ # HTTP status 503 (Service Unavailable).
613
+ GRPC::Unavailable,
614
+ # HTTP status 504 (Gateway Timeout).
615
+ GRPC::DeadlineExceeded
616
+ increment_retried_entries_count(entries_count, error.code)
626
617
  @log.debug "Retrying #{entries_count} log message(s) later.",
627
618
  error: error.to_s, error_code: error.code.to_s
628
619
  raise error
629
- when GRPC::Core::StatusCodes::UNIMPLEMENTED,
630
- GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED
631
- # Most client errors indicate a problem with the request itself
632
- # and should not be retried.
633
- increment_dropped_entries_count(entries_count)
634
- @log.warn "Dropping #{entries_count} log message(s)",
635
- error: error.to_s, error_code: error.code.to_s
636
- when GRPC::Core::StatusCodes::UNAUTHENTICATED
637
- # Authorization error.
638
- # These are usually solved via a `gcloud auth` call, or by
639
- # modifying the permissions on the Google Cloud project.
640
- increment_dropped_entries_count(entries_count)
620
+
621
+ # Most client errors indicate a problem with the request itself and
622
+ # should not be retried.
623
+ when \
624
+ # HTTP status 400 (Bad Request).
625
+ GRPC::InvalidArgument,
626
+ # HTTP status 401 (Unauthorized).
627
+ # These are usually solved via a `gcloud auth` call, or by
628
+ # modifying the permissions on the Google Cloud project.
629
+ GRPC::Unauthenticated,
630
+ # HTTP status 403 (Forbidden).
631
+ GRPC::PermissionDenied,
632
+ # HTTP status 404 (Not Found).
633
+ GRPC::NotFound,
634
+ # HTTP status 409 (Conflict).
635
+ GRPC::Aborted,
636
+ # HTTP status 412 (Precondition Failed).
637
+ GRPC::FailedPrecondition,
638
+ # HTTP status 429 (Too Many Requests).
639
+ GRPC::ResourceExhausted,
640
+ # HTTP status 499 (Client Closed Request).
641
+ GRPC::Cancelled,
642
+ # the remaining http codes in both 4xx and 5xx category.
643
+ # It's debatable whether to retry or drop these log entries.
644
+ # This decision is made to avoid retrying forever due to client
645
+ # errors.
646
+ GRPC::Unknown
647
+ increment_dropped_entries_count(entries_count, error.code)
641
648
  @log.warn "Dropping #{entries_count} log message(s)",
642
649
  error: error.to_s, error_code: error.code.to_s
650
+
643
651
  else
644
- # Assume this is a problem with the request itself and don't
645
- # retry.
646
- increment_dropped_entries_count(entries_count)
652
+ # Assume it is a problem with the request itself and don't retry.
653
+ increment_dropped_entries_count(entries_count, error.code)
647
654
  @log.error "Unknown response code #{error.code} from the "\
648
655
  "server, dropping #{entries_count} log message(s)",
649
656
  error: error.to_s, error_code: error.code.to_s
@@ -681,36 +688,38 @@ module Fluent
681
688
  end
682
689
 
683
690
  rescue Google::Apis::ServerError => error
684
- # Server error, so retry via re-raising the error.
685
- increment_retried_entries_count(entries.length, error.status_code)
691
+ # 5xx server errors. Retry via re-raising the error.
692
+ increment_retried_entries_count(entries_count, error.status_code)
686
693
  @log.debug "Retrying #{entries_count} log message(s) later.",
687
694
  error: error.to_s, error_code: error.status_code.to_s
688
695
  raise error
689
696
 
690
697
  rescue Google::Apis::AuthorizationError => error
691
- # Authorization error.
698
+ # 401 authorization error.
692
699
  # These are usually solved via a `gcloud auth` call, or by modifying
693
700
  # the permissions on the Google Cloud project.
694
- increment_dropped_entries_count(entries_count)
701
+ increment_dropped_entries_count(entries_count, error.status_code)
695
702
  @log.warn "Dropping #{entries_count} log message(s)",
696
703
  error_class: error.class.to_s, error: error.to_s
697
704
 
698
705
  rescue Google::Apis::ClientError => error
699
- # Most ClientErrors indicate a problem with the request itself and
700
- # should not be retried.
706
+ # 4xx client errors. Most client errors indicate a problem with the
707
+ # request itself and should not be retried.
701
708
  error_details_map = construct_error_details_map(error)
702
709
  if error_details_map.empty?
703
- increment_dropped_entries_count(entries_count)
710
+ increment_dropped_entries_count(entries_count, error.status_code)
704
711
  @log.warn "Dropping #{entries_count} log message(s)",
705
712
  error_class: error.class.to_s, error: error.to_s
706
713
  else
707
714
  error_details_map.each do |(error_code, error_message), indexes|
708
715
  partial_error_count = indexes.length
709
- increment_dropped_entries_count(partial_error_count)
716
+ increment_dropped_entries_count(partial_error_count, error_code)
717
+ entries_count -= partial_error_count
710
718
  @log.warn "Dropping #{partial_error_count} log message(s)",
711
719
  error_code: "google.rpc.Code[#{error_code}]",
712
720
  error: error_message
713
721
  end
722
+ increment_ingested_entries_count(entries_count)
714
723
  end
715
724
  end
716
725
  end
@@ -839,11 +848,9 @@ module Fluent
839
848
  # 2. If not, try to retrieve it by calling metadata server directly.
840
849
  # 3. If still not set, try to obtain it from the credentials.
841
850
  def set_project_id
851
+ @project_id ||= CredentialsInfo.project_id
842
852
  @project_id ||= fetch_gce_metadata('project/project-id') if
843
853
  @platform == Platform::GCE
844
- @project_id ||= CredentialsInfo.project_id
845
- rescue StandardError => e
846
- @log.error 'Failed to obtain project id: ', error: e
847
854
  end
848
855
 
849
856
  # 1. Return the value if it is explicitly set in the config already.
@@ -1312,7 +1319,14 @@ module Fluent
1312
1319
  instance_prefix
1313
1320
  end
1314
1321
 
1322
+ def time_or_nil(ts_secs, ts_nanos)
1323
+ Time.at((Integer ts_secs), (Integer ts_nanos) / 1_000.0)
1324
+ rescue ArgumentError, TypeError
1325
+ nil
1326
+ end
1327
+
1315
1328
  def compute_timestamp(resource_type, record, time)
1329
+ current_time = Time.now
1316
1330
  if record.key?('timestamp') &&
1317
1331
  record['timestamp'].is_a?(Hash) &&
1318
1332
  record['timestamp'].key?('seconds') &&
@@ -1320,10 +1334,12 @@ module Fluent
1320
1334
  ts_secs = record['timestamp']['seconds']
1321
1335
  ts_nanos = record['timestamp']['nanos']
1322
1336
  record.delete('timestamp')
1337
+ timestamp = time_or_nil(ts_secs, ts_nanos)
1323
1338
  elsif record.key?('timestampSeconds') &&
1324
1339
  record.key?('timestampNanos')
1325
1340
  ts_secs = record.delete('timestampSeconds')
1326
1341
  ts_nanos = record.delete('timestampNanos')
1342
+ timestamp = time_or_nil(ts_secs, ts_nanos)
1327
1343
  elsif record.key?('timeNanos')
1328
1344
  # This is deprecated since the precision is insufficient.
1329
1345
  # Use timestampSeconds/timestampNanos instead
@@ -1336,11 +1352,13 @@ module Fluent
1336
1352
  @log.warn 'timeNanos is deprecated - please use ' \
1337
1353
  'timestampSeconds and timestampNanos instead.'
1338
1354
  end
1355
+ timestamp = time_or_nil(ts_secs, ts_nanos)
1339
1356
  elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
1340
1357
  @cloudfunctions_log_match
1341
- timestamp = DateTime.parse(@cloudfunctions_log_match['timestamp'])
1342
- ts_secs = timestamp.strftime('%s').to_i
1343
- ts_nanos = timestamp.strftime('%N').to_i
1358
+ timestamp = DateTime.parse(
1359
+ @cloudfunctions_log_match['timestamp']).to_time
1360
+ ts_secs = timestamp.tv_sec
1361
+ ts_nanos = timestamp.tv_nsec
1344
1362
  elsif record.key?('time')
1345
1363
  # k8s ISO8601 timestamp
1346
1364
  begin
@@ -1355,6 +1373,44 @@ module Fluent
1355
1373
  ts_secs = timestamp.tv_sec
1356
1374
  ts_nanos = timestamp.tv_nsec
1357
1375
  end
1376
+ ts_secs = begin
1377
+ Integer ts_secs
1378
+ rescue ArgumentError, TypeError
1379
+ ts_secs
1380
+ end
1381
+ ts_nanos = begin
1382
+ Integer ts_nanos
1383
+ rescue ArgumentError, TypeError
1384
+ ts_nanos
1385
+ end
1386
+
1387
+ # Adjust timestamps from the future.
1388
+ # There are two cases:
1389
+ # 1. The parsed timestamp is later in the current year:
1390
+ # This can happen when system log lines from previous years are missing
1391
+ # the year, so the date parser assumes the current year.
1392
+ # We treat these lines as coming from last year. This could label
1393
+ # 2-year-old logs incorrectly, but this probably isn't super important.
1394
+ #
1395
+ # 2. The parsed timestamp is past the end of the current year:
1396
+ # Since the year is different from the current year, this isn't the
1397
+ # missing year in system logs. It is unlikely that users explicitly
1398
+ # write logs at a future date. This could result from an unsynchronized
1399
+ # clock on a VM, or some random value being parsed as the timestamp.
1400
+ # We reset the timestamp on those lines to the default value and let the
1401
+ # downstream API handle it.
1402
+ if timestamp
1403
+ next_year = Time.mktime(current_time.year + 1)
1404
+ one_day_later = current_time.to_datetime.next_day.to_time
1405
+ if timestamp >= next_year # Case 2.
1406
+ ts_secs = 0
1407
+ ts_nanos = 0
1408
+ elsif timestamp >= one_day_later # Case 1.
1409
+ adjusted_timestamp = timestamp.to_datetime.prev_year.to_time
1410
+ ts_secs = adjusted_timestamp.tv_sec
1411
+ # The value of ts_nanos should not change when subtracting a year.
1412
+ end
1413
+ end
1358
1414
  [ts_secs, ts_nanos]
1359
1415
  end
1360
1416
 
@@ -1454,7 +1510,7 @@ module Fluent
1454
1510
 
1455
1511
  def parse_severity(severity_str)
1456
1512
  # The API is case insensitive, but uppercase to make things simpler.
1457
- severity = severity_str.upcase.strip
1513
+ severity = severity_str.to_s.upcase.strip
1458
1514
 
1459
1515
  # If the severity is already valid, just return it.
1460
1516
  return severity if VALID_SEVERITIES.include?(severity)
@@ -1714,15 +1770,19 @@ module Fluent
1714
1770
  def api_client
1715
1771
  if @use_grpc
1716
1772
  ssl_creds = GRPC::Core::ChannelCredentials.new
1717
- authentication = Google::Auth.get_application_default
1718
- creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
1773
+ authentication = Google::Auth.get_application_default(LOGGING_SCOPE)
1774
+ creds = GRPC::Core::CallCredentials.new(lambda do |a_hash|
1775
+ authentication.apply(a_hash, use_configured_scope: true)
1776
+ end)
1719
1777
  creds = ssl_creds.compose(creds)
1720
- @client = Google::Logging::V2::LoggingServiceV2::Stub.new(
1721
- 'logging.googleapis.com', creds)
1778
+ @client = Google::Cloud::Logging::V2::LoggingServiceV2Client.new(
1779
+ channel: GRPC::Core::Channel.new(
1780
+ 'logging.googleapis.com', nil, creds))
1722
1781
  else
1723
1782
  unless @client.authorization.expired?
1724
1783
  begin
1725
- @client.authorization.fetch_access_token!
1784
+ @client.authorization.fetch_access_token!(
1785
+ use_configured_scope: true)
1726
1786
  rescue MultiJson::ParseError
1727
1787
  # Workaround an issue in the API client; just re-raise a more
1728
1788
  # descriptive error for the user (which will still cause a retry).
@@ -1873,7 +1933,7 @@ module Fluent
1873
1933
  # Increment the metric for the number of successful requests.
1874
1934
  def increment_successful_requests_count
1875
1935
  return unless @successful_requests_count
1876
- @successful_requests_count.increment(grpc: @use_grpc)
1936
+ @successful_requests_count.increment(grpc: @use_grpc, code: @ok_code)
1877
1937
  end
1878
1938
 
1879
1939
  # Increment the metric for the number of failed requests, labeled by
@@ -1887,21 +1947,22 @@ module Fluent
1887
1947
  # ingested by the Stackdriver Logging API.
1888
1948
  def increment_ingested_entries_count(count)
1889
1949
  return unless @ingested_entries_count
1890
- @ingested_entries_count.increment({}, count)
1950
+ @ingested_entries_count.increment({ grpc: @use_grpc, code: @ok_code },
1951
+ count)
1891
1952
  end
1892
1953
 
1893
1954
  # Increment the metric for the number of log entries that were dropped
1894
1955
  # and not ingested by the Stackdriver Logging API.
1895
- def increment_dropped_entries_count(count)
1956
+ def increment_dropped_entries_count(count, code)
1896
1957
  return unless @dropped_entries_count
1897
- @dropped_entries_count.increment({}, count)
1958
+ @dropped_entries_count.increment({ grpc: @use_grpc, code: code }, count)
1898
1959
  end
1899
1960
 
1900
1961
  # Increment the metric for the number of log entries that were dropped
1901
1962
  # and not ingested by the Stackdriver Logging API.
1902
1963
  def increment_retried_entries_count(count, code)
1903
1964
  return unless @retried_entries_count
1904
- @retried_entries_count.increment({ code: code }, count)
1965
+ @retried_entries_count.increment({ grpc: @use_grpc, code: code }, count)
1905
1966
  end
1906
1967
  end
1907
1968
  end
@@ -177,7 +177,6 @@ module BaseTest
177
177
  def test_ec2_metadata_requires_project_id
178
178
  setup_ec2_metadata_stubs
179
179
  exception_count = 0
180
- Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
181
180
  begin
182
181
  create_driver
183
182
  rescue Fluent::ConfigError => error
@@ -188,13 +187,15 @@ module BaseTest
188
187
  assert_equal 1, exception_count
189
188
  end
190
189
 
191
- def test_ec2_metadata_project_id_from_credentials
192
- setup_ec2_metadata_stubs
193
- [IAM_CREDENTIALS, LEGACY_CREDENTIALS].each do |creds|
194
- ENV['GOOGLE_APPLICATION_CREDENTIALS'] = creds[:path]
195
- d = create_driver
196
- d.run
197
- assert_equal creds[:project_id], d.instance.project_id
190
+ def test_project_id_from_credentials
191
+ %w(gce ec2).each do |platform|
192
+ send("setup_#{platform}_metadata_stubs")
193
+ [IAM_CREDENTIALS, LEGACY_CREDENTIALS].each do |creds|
194
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = creds[:path]
195
+ d = create_driver
196
+ d.run
197
+ assert_equal creds[:project_id], d.instance.project_id
198
+ end
198
199
  end
199
200
  end
200
201
 
@@ -216,18 +217,17 @@ module BaseTest
216
217
  d.emit('message' => log_entry(0))
217
218
  d.run
218
219
  end
219
- verify_log_entries(1, COMPUTE_PARAMS)
220
+ verify_log_entries(1, COMPUTE_PARAMS.merge(
221
+ project_id: IAM_CREDENTIALS[:project_id]))
220
222
  end
221
223
 
222
- def test_one_log_with_invalid_json_credentials
223
- setup_gce_metadata_stubs
224
- ENV['GOOGLE_APPLICATION_CREDENTIALS'] = INVALID_CREDENTIALS[:path]
225
- setup_logging_stubs do
226
- d = create_driver
227
- d.emit('message' => log_entry(0))
224
+ def test_invalid_json_credentials
225
+ %w(gce_metadata ec2_metadata no_metadata_service).each do |platform|
226
+ send("setup_#{platform}_stubs")
228
227
  exception_count = 0
228
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = INVALID_CREDENTIALS[:path]
229
229
  begin
230
- d.run
230
+ create_driver
231
231
  rescue RuntimeError => error
232
232
  assert error.message.include? 'Unable to read the credential file'
233
233
  exception_count += 1
@@ -607,7 +607,22 @@ module BaseTest
607
607
 
608
608
  def test_timestamps
609
609
  setup_gce_metadata_stubs
610
- [Time.at(123_456.789), Time.at(0), Time.now].each do |ts|
610
+ current_time = Time.now
611
+ next_year = Time.mktime(current_time.year + 1)
612
+ one_second_before_next_year = next_year - 1
613
+ adjusted_to_last_year =
614
+ one_second_before_next_year.to_datetime.prev_year.to_time
615
+ one_second_into_next_year = next_year + 1
616
+ one_day_into_next_year = next_year.to_date.next_day.to_time
617
+ {
618
+ Time.at(123_456.789) => Time.at(123_456.789),
619
+ Time.at(0) => Time.at(0),
620
+ current_time => current_time,
621
+ one_second_before_next_year => adjusted_to_last_year,
622
+ next_year => Time.at(0),
623
+ one_second_into_next_year => Time.at(0),
624
+ one_day_into_next_year => Time.at(0)
625
+ }.each do |ts, adjusted_ts|
611
626
  expected_ts = []
612
627
  emit_index = 0
613
628
  setup_logging_stubs do
@@ -615,24 +630,24 @@ module BaseTest
615
630
  d = create_driver
616
631
  # Test the "native" fluentd timestamp as well as our nanosecond tags.
617
632
  d.emit({ 'message' => log_entry(emit_index) }, ts.to_f)
618
- expected_ts.push(ts)
633
+ expected_ts.push(adjusted_ts)
619
634
  emit_index += 1
620
635
  d.emit('message' => log_entry(emit_index),
621
636
  'timeNanos' => ts.tv_sec * 1_000_000_000 + ts.tv_nsec)
622
- expected_ts.push(ts)
637
+ expected_ts.push(adjusted_ts)
623
638
  emit_index += 1
624
639
  d.emit('message' => log_entry(emit_index),
625
640
  'timestamp' => { 'seconds' => ts.tv_sec, 'nanos' => ts.tv_nsec })
626
- expected_ts.push(ts)
641
+ expected_ts.push(adjusted_ts)
627
642
  emit_index += 1
628
643
  d.emit('message' => log_entry(emit_index),
629
644
  'timestampSeconds' => ts.tv_sec, 'timestampNanos' => ts.tv_nsec)
630
- expected_ts.push(ts)
645
+ expected_ts.push(adjusted_ts)
631
646
  emit_index += 1
632
647
  d.emit('message' => log_entry(emit_index),
633
648
  'timestampSeconds' => ts.tv_sec.to_s,
634
649
  'timestampNanos' => ts.tv_nsec.to_s)
635
- expected_ts.push(ts)
650
+ expected_ts.push(adjusted_ts)
636
651
  emit_index += 1
637
652
  d.run
638
653
  verify_index = 0
@@ -644,5 +644,5 @@ module Constants
644
644
  }
645
645
  ]
646
646
  }
647
- }.to_json
647
+ }.freeze
648
648
  end
@@ -53,24 +53,31 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
53
53
 
54
54
  def test_partial_success
55
55
  setup_gce_metadata_stubs
56
- {
57
- APPLICATION_DEFAULT_CONFIG => 4.0,
58
- PARTIAL_SUCCESS_CONFIG => 3.0
59
- }.each do |config, failed_entry_count|
60
- setup_prometheus
61
- # The API Client should not retry this and the plugin should consume
62
- # the exception.
63
- stub_request(:post, WRITE_LOG_ENTRIES_URI)
64
- .to_return(status: 400, body: PARTIAL_SUCCESS_RESPONSE_BODY)
65
- d = create_driver(PROMETHEUS_ENABLE_CONFIG + config)
66
- 4.times do |i|
67
- d.emit('message' => log_entry(i.to_s))
68
- end
69
- d.run
70
- assert_prometheus_metric_value(:stackdriver_dropped_entries_count,
71
- failed_entry_count)
56
+ setup_prometheus
57
+ # The API Client should not retry this and the plugin should consume
58
+ # the exception.
59
+ root_error_code = PARTIAL_SUCCESS_RESPONSE_BODY['error']['code']
60
+ stub_request(:post, WRITE_LOG_ENTRIES_URI)
61
+ .to_return(status: root_error_code,
62
+ body: PARTIAL_SUCCESS_RESPONSE_BODY.to_json)
63
+ d = create_driver(PROMETHEUS_ENABLE_CONFIG + PARTIAL_SUCCESS_CONFIG)
64
+ 4.times do |i|
65
+ d.emit('message' => log_entry(i.to_s))
72
66
  end
73
- assert_requested(:post, WRITE_LOG_ENTRIES_URI, times: 2)
67
+ d.run
68
+ assert_prometheus_metric_value(
69
+ :stackdriver_successful_requests_count, 0, grpc: false, code: 200)
70
+ assert_prometheus_metric_value(
71
+ :stackdriver_failed_requests_count, 1, grpc: false, code: root_error_code)
72
+ assert_prometheus_metric_value(
73
+ :stackdriver_ingested_entries_count, 1, grpc: false, code: 200)
74
+ assert_prometheus_metric_value(
75
+ :stackdriver_dropped_entries_count, 2, grpc: false, code: 3)
76
+ assert_prometheus_metric_value(
77
+ :stackdriver_dropped_entries_count, 1, grpc: false, code: 7)
78
+ assert_prometheus_metric_value(
79
+ :stackdriver_retried_entries_count, 0, grpc: false)
80
+ assert_requested(:post, WRITE_LOG_ENTRIES_URI, times: 1)
74
81
  end
75
82
 
76
83
  def test_server_error
@@ -131,16 +138,20 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
131
138
  ingested_entries_count, dropped_entries_count,
132
139
  retried_entries_count = metric_values
133
140
  assert_prometheus_metric_value(:stackdriver_successful_requests_count,
134
- successful_requests_count, grpc: false)
141
+ successful_requests_count,
142
+ grpc: false, code: 200)
135
143
  assert_prometheus_metric_value(:stackdriver_failed_requests_count,
136
144
  failed_requests_count,
137
145
  grpc: false, code: code)
138
146
  assert_prometheus_metric_value(:stackdriver_ingested_entries_count,
139
- ingested_entries_count)
147
+ ingested_entries_count,
148
+ grpc: false, code: 200)
140
149
  assert_prometheus_metric_value(:stackdriver_dropped_entries_count,
141
- dropped_entries_count)
150
+ dropped_entries_count,
151
+ grpc: false, code: code)
142
152
  assert_prometheus_metric_value(:stackdriver_retried_entries_count,
143
- retried_entries_count, code: code)
153
+ retried_entries_count,
154
+ grpc: false, code: code)
144
155
  end
145
156
  end
146
157
 
@@ -206,6 +217,8 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
206
217
  assert_equal(100, test_obj.parse_severity(' 105'))
207
218
  assert_equal(100, test_obj.parse_severity(' 105 '))
208
219
 
220
+ assert_equal(100, test_obj.parse_severity(100))
221
+
209
222
  assert_equal('DEFAULT', test_obj.parse_severity('-100'))
210
223
  assert_equal('DEFAULT', test_obj.parse_severity('105 100'))
211
224
 
@@ -243,6 +256,11 @@ class GoogleCloudOutputTest < Test::Unit::TestCase
243
256
  assert_equal('DEFAULT', test_obj.parse_severity('ER ROR'))
244
257
 
245
258
  # anything else should translate to 'DEFAULT'
259
+ assert_equal('DEFAULT', test_obj.parse_severity(nil))
260
+ assert_equal('DEFAULT', test_obj.parse_severity(Object.new))
261
+ assert_equal('DEFAULT', test_obj.parse_severity({}))
262
+ assert_equal('DEFAULT', test_obj.parse_severity([]))
263
+ assert_equal('DEFAULT', test_obj.parse_severity(100.0))
246
264
  assert_equal('DEFAULT', test_obj.parse_severity(''))
247
265
  assert_equal('DEFAULT', test_obj.parse_severity('garbage'))
248
266
  assert_equal('DEFAULT', test_obj.parse_severity('er'))
@@ -28,12 +28,19 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
28
28
 
29
29
  def test_client_error
30
30
  setup_gce_metadata_stubs
31
- { 8 => 'ResourceExhausted',
32
- 12 => 'Unimplemented',
33
- 16 => 'Unauthenticated' }.each_with_index do |(code, message), index|
31
+ {
32
+ GRPC::Core::StatusCodes::CANCELLED => 'Cancelled',
33
+ GRPC::Core::StatusCodes::UNKNOWN => 'Unknown',
34
+ GRPC::Core::StatusCodes::INVALID_ARGUMENT => 'InvalidArgument',
35
+ GRPC::Core::StatusCodes::NOT_FOUND => 'NotFound',
36
+ GRPC::Core::StatusCodes::PERMISSION_DENIED => 'PermissionDenied',
37
+ GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED => 'ResourceExhausted',
38
+ GRPC::Core::StatusCodes::FAILED_PRECONDITION => 'FailedPrecondition',
39
+ GRPC::Core::StatusCodes::ABORTED => 'Aborted',
40
+ GRPC::Core::StatusCodes::UNAUTHENTICATED => 'Unauthenticated'
41
+ }.each_with_index do |(code, message), index|
34
42
  setup_logging_stubs(true, code, message) do
35
- d = create_driver(USE_GRPC_CONFIG, 'test',
36
- GRPCLoggingMockFailingService.rpc_stub_class)
43
+ d = create_driver(USE_GRPC_CONFIG, 'test')
37
44
  # The API Client should not retry this and the plugin should consume the
38
45
  # exception.
39
46
  d.emit('message' => log_entry(0))
@@ -45,23 +52,20 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
45
52
 
46
53
  def test_server_error
47
54
  setup_gce_metadata_stubs
48
- { 1 => 'Cancelled',
49
- 2 => 'Unknown',
50
- 4 => 'DeadlineExceeded',
51
- 13 => 'Internal',
52
- 14 => 'Unavailable' }.each_with_index do |(code, message), index|
55
+ {
56
+ GRPC::Core::StatusCodes::DEADLINE_EXCEEDED => 'DeadlineExceeded',
57
+ GRPC::Core::StatusCodes::UNIMPLEMENTED => 'Unimplemented',
58
+ GRPC::Core::StatusCodes::INTERNAL => 'Internal',
59
+ GRPC::Core::StatusCodes::UNAVAILABLE => 'Unavailable'
60
+ }.each_with_index do |(code, message), index|
53
61
  exception_count = 0
54
62
  setup_logging_stubs(true, code, message) do
55
- d = create_driver(USE_GRPC_CONFIG, 'test',
56
- GRPCLoggingMockFailingService.rpc_stub_class)
63
+ d = create_driver(USE_GRPC_CONFIG, 'test')
57
64
  # The API client should retry this once, then throw an exception which
58
65
  # gets propagated through the plugin
59
66
  d.emit('message' => log_entry(0))
60
67
  begin
61
68
  d.run
62
- rescue GRPC::Cancelled
63
- # No need to check the message -- we already know the code.
64
- exception_count += 1
65
69
  rescue GRPC::BadStatus => error
66
70
  assert_equal "#{code}:#{message}", error.message
67
71
  exception_count += 1
@@ -93,8 +97,7 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
93
97
  setup_prometheus
94
98
  (1..request_count).each do
95
99
  setup_logging_stubs(should_fail, code, 'SomeMessage') do
96
- d = create_driver(USE_GRPC_CONFIG + PROMETHEUS_ENABLE_CONFIG, 'test',
97
- GRPCLoggingMockFailingService.rpc_stub_class)
100
+ d = create_driver(USE_GRPC_CONFIG + PROMETHEUS_ENABLE_CONFIG, 'test')
98
101
  (1..entry_count).each do |i|
99
102
  d.emit('message' => log_entry(i.to_s))
100
103
  end
@@ -110,16 +113,20 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
110
113
  ingested_entries_count, dropped_entries_count,
111
114
  retried_entries_count = metric_values
112
115
  assert_prometheus_metric_value(:stackdriver_successful_requests_count,
113
- successful_requests_count, grpc: true)
116
+ successful_requests_count,
117
+ grpc: true, code: 0)
114
118
  assert_prometheus_metric_value(:stackdriver_failed_requests_count,
115
119
  failed_requests_count,
116
120
  grpc: true, code: code)
117
121
  assert_prometheus_metric_value(:stackdriver_ingested_entries_count,
118
- ingested_entries_count)
122
+ ingested_entries_count,
123
+ grpc: true, code: 0)
119
124
  assert_prometheus_metric_value(:stackdriver_dropped_entries_count,
120
- dropped_entries_count)
125
+ dropped_entries_count,
126
+ grpc: true, code: code)
121
127
  assert_prometheus_metric_value(:stackdriver_retried_entries_count,
122
- retried_entries_count, code: code)
128
+ retried_entries_count,
129
+ grpc: true, code: code)
123
130
  end
124
131
  end
125
132
 
@@ -212,82 +219,58 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
212
219
  use_grpc true
213
220
  ).freeze
214
221
 
215
- @@mock_port = 0 # rubocop:disable Style/ClassVars
216
-
217
- def generate_mock_host
218
- # rubocop:disable Style/ClassVars
219
- @@mock_port = (@@mock_port + 1) % 10_000 + 50_000
220
- "localhost:#{@@mock_port}"
221
- # rubocop:enable Style/ClassVars
222
- end
223
-
224
222
  # Create a Fluentd output test driver with the Google Cloud Output plugin with
225
223
  # grpc enabled. The signature of this method is different between the grpc
226
224
  # path and the non-grpc path. For grpc, an additional grpc stub class can be
227
225
  # passed in to construct the mock used by the test driver.
228
- def create_driver(conf = APPLICATION_DEFAULT_CONFIG, tag = 'test',
229
- grpc_stub = GRPCLoggingMockService.rpc_stub_class)
226
+ def create_driver(conf = APPLICATION_DEFAULT_CONFIG, tag = 'test')
230
227
  conf += USE_GRPC_CONFIG
231
228
  Fluent::Test::BufferedOutputTestDriver.new(
232
- GoogleCloudOutputWithGRPCMock.new(grpc_stub, @mock_host),
233
- tag).configure(conf, true)
229
+ GoogleCloudOutputWithGRPCMock.new(@grpc_stub), tag).configure(conf, true)
234
230
  end
235
231
 
236
232
  # Google Cloud Fluent output stub with grpc mock.
237
233
  class GoogleCloudOutputWithGRPCMock < Fluent::GoogleCloudOutput
238
- def initialize(grpc_stub, mock_host)
234
+ def initialize(grpc_stub)
239
235
  super()
240
236
  @grpc_stub = grpc_stub
241
- @mock_host = mock_host
242
237
  end
243
238
 
244
239
  def api_client
245
240
  ssl_creds = GRPC::Core::ChannelCredentials.new
246
- authentication = Google::Auth.get_application_default
247
- creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
241
+ authentication = Google::Auth.get_application_default(LOGGING_SCOPE)
242
+ creds = GRPC::Core::CallCredentials.new(lambda do |a_hash|
243
+ authentication.apply(a_hash, use_configured_scope: true)
244
+ end)
248
245
  ssl_creds.compose(creds)
249
246
 
250
- # Here we have obtained the creds, but for the mock, we will leave the
251
- # channel insecure.
252
- @grpc_stub.new(@mock_host, :this_channel_is_insecure)
247
+ @grpc_stub
253
248
  end
254
249
  end
255
250
 
256
251
  # GRPC logging mock that successfully logs the records.
257
- class GRPCLoggingMockService < Google::Logging::V2::LoggingServiceV2::Service
252
+ class GRPCLoggingMockService <
253
+ Google::Cloud::Logging::V2::LoggingServiceV2Client
258
254
  def initialize(requests_received)
259
255
  super()
260
256
  @requests_received = requests_received
261
257
  end
262
258
 
263
- def write_log_entries(request, _call)
259
+ def write_log_entries(entries, log_name: nil, resource: nil, labels: nil)
260
+ request = Google::Apis::LoggingV2::WriteLogEntriesRequest.new(
261
+ log_name: log_name,
262
+ resource: resource,
263
+ labels: labels,
264
+ entries: entries
265
+ )
264
266
  @requests_received << request
265
267
  WriteLogEntriesResponse.new
266
268
  end
267
-
268
- # TODO(lingshi) Remove these dummy methods when grpc/9033 is fixed.
269
- #
270
- # These methods should never be called, so they will just fail the tests
271
- # with "unimplemented" errors..
272
- def _undefined
273
- raise "Method #{__callee__} is unimplemented and needs to be overridden."
274
- end
275
-
276
- alias list_logs _undefined
277
- alias list_log_entries _undefined
278
- alias list_log_services _undefined
279
- alias list_log_service_indexes _undefined
280
- alias list_monitored_resource_descriptors _undefined
281
- alias delete_log _undefined
282
- undef_method :_undefined
283
269
  end
284
270
 
285
271
  # GRPC logging mock that fails and returns server side or client side errors.
286
272
  class GRPCLoggingMockFailingService <
287
- Google::Logging::V2::LoggingServiceV2::Service
288
- # 'code_sent' and 'message_sent' are references of external variables. We
289
- # will assert the values of them later. 'code_value' and 'message_value'
290
- # are actual error code and message we expect this mock to return.
273
+ Google::Cloud::Logging::V2::LoggingServiceV2Client
291
274
  def initialize(code, message, failed_attempts)
292
275
  @code = code
293
276
  @message = message
@@ -295,66 +278,42 @@ class GoogleCloudOutputGRPCTest < Test::Unit::TestCase
295
278
  super()
296
279
  end
297
280
 
298
- def write_log_entries(_request, _call)
281
+ # rubocop:disable Lint/UnusedMethodArgument
282
+ def write_log_entries(entries, log_name: nil, resource: nil, labels: nil)
299
283
  @failed_attempts << 1
300
- raise GRPC::BadStatus.new(@code, @message)
301
- end
302
-
303
- # TODO(lingshi) Remove these dummy methods when grpc/9033 is fixed.
304
- #
305
- # These methods should never be called, so they will just fail the tests
306
- # with "unimplemented" errors..
307
- def _undefined
308
- raise "Method #{__callee__} is unimplemented and needs to be overridden."
284
+ begin
285
+ raise GRPC::BadStatus.new_status_exception(@code, @message)
286
+ rescue
287
+ # Google::Gax::GaxError will wrap the latest thrown exception as @cause.
288
+ raise Google::Gax::GaxError, @message
289
+ end
309
290
  end
310
-
311
- alias list_logs _undefined
312
- alias list_log_entries _undefined
313
- alias list_log_services _undefined
314
- alias list_log_service_indexes _undefined
315
- alias list_monitored_resource_descriptors _undefined
316
- alias delete_log _undefined
317
- undef_method :_undefined
291
+ # rubocop:enable Lint/UnusedMethodArgument
318
292
  end
319
293
 
320
294
  # Set up grpc stubs to mock the external calls.
321
- # TODO(qingling128): Remove this comment after grpc/grpc#12506 is resolved.
322
- # Due to a gRPC load balancing issue (grpc/grpc#12506), we have to use a
323
- # different port each time we create a gRPC mock as a temporary workaround.
324
- # Thus we can only create one driver in each setup_logging_stubs context.
325
- def setup_logging_stubs(should_fail = false, code = 0, message = 'Ok')
326
- # Save the mock host in an instance variable, so later on when creating the
327
- # logging driver, we can refer to this host with exactly the same port.
328
- @mock_host = generate_mock_host
329
- srv = GRPC::RpcServer.new
330
- @failed_attempts = []
331
- @requests_sent = []
295
+ def setup_logging_stubs(should_fail = false, code = nil, message = nil)
332
296
  if should_fail
333
- grpc = GRPCLoggingMockFailingService.new(code, message, @failed_attempts)
297
+ @failed_attempts = []
298
+ @grpc_stub = GRPCLoggingMockFailingService.new(
299
+ code, message, @failed_attempts)
334
300
  else
335
- grpc = GRPCLoggingMockService.new(@requests_sent)
336
- end
337
- srv.handle(grpc)
338
- srv.add_http2_port(@mock_host, :this_port_is_insecure)
339
- t = Thread.new { srv.run }
340
- srv.wait_till_running
341
- begin
342
- yield
343
- rescue Test::Unit::Failure, StandardError => e
344
- srv.stop
345
- t.join
346
- raise e
301
+ @requests_sent = []
302
+ @grpc_stub = GRPCLoggingMockService.new(@requests_sent)
347
303
  end
348
- srv.stop
349
- t.join
350
- @mock_host = nil
304
+ yield
351
305
  end
352
306
 
353
307
  # Verify the number and the content of the log entries match the expectation.
354
308
  # The caller can optionally provide a block which is called for each entry.
355
309
  def verify_log_entries(n, params, payload_type = 'textPayload', &block)
356
310
  @requests_sent.each do |request|
357
- @logs_sent << JSON.parse(request.to_json)
311
+ @logs_sent << {
312
+ 'entries' => request.entries.map { |entry| JSON.parse(entry.to_json) },
313
+ 'labels' => request.labels,
314
+ 'resource' => request.resource,
315
+ 'logName' => request.log_name
316
+ }
358
317
  end
359
318
  verify_json_log_entries(n, params, payload_type, &block)
360
319
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-google-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.10
4
+ version: 0.6.11.pre.memory.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Derr
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-11-19 00:00:00.000000000 Z
12
+ date: 2017-12-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd
@@ -243,9 +243,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
243
243
  version: '2.0'
244
244
  required_rubygems_version: !ruby/object:Gem::Requirement
245
245
  requirements:
246
- - - ">="
246
+ - - ">"
247
247
  - !ruby/object:Gem::Version
248
- version: '0'
248
+ version: 1.3.1
249
249
  requirements: []
250
250
  rubyforge_project:
251
251
  rubygems_version: 2.4.8