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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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