Compare commits

...
421 Commits
Author SHA1 Message Date
Bill Oley 2b38b55718 tag version 2.2.5 2026-10-19 20:53:18 +00:00
Bill Oley 6ad1127989 Ensure that NonSslForbiddenHandler and NotFoundHandler return content length 0 and conection close headers to prevent clients hanging 2026-10-19 20:53:12 +00:00
Bill Oley e72ca910dc 2.2.4-SNAPSHOT 2026-10-19 14:40:08 +00:00
Bill Oley 70672972b2 tag version 2.2.3 2026-10-19 14:19:43 +00:00
Bill Oley 276e5e8fb2 log4j dependency to 2.26.1 2026-10-19 14:15:10 +00:00
Bill Oley 673ae360e8 remove StaticFileServer, add NotFoundHandler, add NonSslForbiddenHandler, add HttpStaticFileServerHandler to return HSTS http header on all https requests from server and balancer 2026-10-19 14:00:26 +00:00
Bill Oley bb3386925c 2.2.3-SNAPSHOT 2026-05-13 12:12:04 -04:00
Bill Oley ecf481440c tag version 2.2.2 2026-05-13 12:12:04 -04:00
Bill Oley 7582b62391 remove collect-parent-plugin module that had been refactored into collectd 2026-05-13 12:12:04 -04:00
Bill Oley 97edb406bc update dependencies 2026-05-13 11:06:51 -04:00
Bill Oley 86a5e58683 update grafana-plugin-sdk-for-go version, recompile datasource executables 2026-05-12 11:52:30 -04:00
Bill Oley 4e8a7dbfb2 Change docker-compose.yaml to use grafana 13.0.1 and correct version in package-lock.json 2026-05-12 10:54:38 -04:00
Bill Oley 03f11ca11c Corrections to CollectDPluginParent and WriteNSQPlugin 2026-05-11 13:22:29 -04:00
Bill Oley 849e265536 Enable connection, socket, and response timeouts for balancer http connections 2026-04-15 15:46:02 -04:00
Bill Oley 6d83aa98cd Changes to balancer yml files 2026-04-14 15:43:59 -04:00
Bill Oley 00b36ed7b8 Fix to MetaCache begin range 2026-04-14 15:43:34 -04:00
Bill Oley f19779ce13 remove unused ability to have multiple tcp threadpools 2026-04-14 15:43:04 -04:00
Bill Oley fe7daa3faa Corrections to MetaCache scanning 2026-04-13 16:26:08 -04:00
Bill Oley 739d3a4726 Use a Scanner instead of BatchScanner in MetaCache so we can short-circuit when maxTagValues is reached for a metric 2026-04-13 16:10:13 -04:00
Bill Oley d7b747ef79 catch Exception instead of IOException 2026-04-13 16:09:04 -04:00
Bill Oley 0f891b28b2 add necessary dependencies to collectd 2026-04-13 16:08:44 -04:00
Bill Oley ca86bddba1 install collectd-api jar and pom to local repository 2026-04-13 16:08:08 -04:00
Ivan Bella 6e9ab8e533 Log a message when hitting a key that would have failed previously (#251)
* Log a message when hitting a key that would have failed previously

* spelling
2026-04-13 13:04:56 -04:00
Bill Oley 6c52ee5715 Remove automatic ReferenceCountUtil.release from HttpRelayHandler, avoid NPE in exception handling 2026-04-13 12:51:16 -04:00
Bill Oley 52ebc1856a Use maven.multiModuleProjectDirectory to find collectd jar 2026-04-13 12:51:16 -04:00
Bill Oley ac390d5bed Add GenericKeyedObjectPoolConfiguration class to supply AbandonedConfig and millisecond setters 2026-04-13 12:51:12 -04:00
Bill Oley 80fd98d304 Refactor CollectDPluginParent and subclasses for easier testing, better handle malformed statsd etsy format metrics 2026-04-13 12:50:36 -04:00
Bill Oley 6467283cbb MetaCache should use the autowired AccumuloClient and a BatchScanner 2026-03-25 13:01:50 -04:00
Bill Oley cdc277067a AccumuloConfiguration should set the default zookeeper timeout and default scan threads for AccumuloClient 2026-03-25 13:01:01 -04:00
Bill Oley c27a3d2bb6 HttpRequest implement ReferenceCounted and pass through to FullHttpRequest to avoid memory leaks 2026-03-25 12:59:45 -04:00
Bill Oley 3498c5e546 Remove tycho-compiler-jdt from analytics pom 2026-03-25 12:58:06 -04:00
Bill Oley 01ecbce346 Maintain collectd-api jar as it is not available from maven central 2026-03-25 12:57:02 -04:00
Ivan Bella 1e746df912 2.2.2-SNAPSHOT 2025-12-30 16:01:28 +00:00
Ivan Bella d9214ed406 2.2.1 2025-12-30 16:00:56 +00:00
Ivan Bella 9a1c0e6e2f Merge pull request #250 from NationalSecurityAgency/bugfix/out-of-order-age-off
Fixed the metric age off iterator to ensure forward progress when seeking past keys for ageoff
2025-12-30 10:59:47 -05:00
Ivan Bella 95f3e3d596 imports 2025-12-30 15:22:55 +00:00
Ivan Bella aaa0d91f11 review comment 2025-12-30 15:15:20 +00:00
Ivan Bella 48509e747b Format, and added test to verify timestamp handling with mutations 2025-12-29 21:52:07 +00:00
Ivan Bella 286bb85cab Fixed the metric age off iterator to ensure forward progress when
seeking past keys to ageoff
2025-12-29 20:46:45 +00:00
Bill Oley abfebeca60 Set version to 2.2.1-SNAPSHOT 2025-03-28 11:21:29 -04:00
Bill Oley e335eeb3b4 Release version 2.2.0 2025-03-28 11:21:29 -04:00
Bill Oley 12d3695f10 Improve recoverabiliy and logging of TcpClient 2025-03-28 11:21:29 -04:00
Bill Oley 61df23e6ba Fix intermittent test failures; use ports in server range, fix incorrect call of setup vs cleanup, don't use zk leaderLatch in tests, syncronization in TestDataStore 2025-03-28 11:21:29 -04:00
Bill Oley 2640cbaf80 Fix CollectDPluginParent handling of ethstat patterns, implement Exclusions class to filter metrics and tags in collectd and DataStore 2025-03-28 11:21:28 -04:00
Bill Oley 11a417c890 Refactor collectd plugins into one module 2025-03-28 11:21:22 -04:00
Bill Oley 780e8f37b8 Fix Tag.hashCode() to use HashCodeBuilder instead of a hash of toString 2025-03-25 11:38:08 -04:00
Bill Oley 5ce377b15c Combine the use of multiple RegexFilters to a single TagFilter that also uses a cache to speed up comparisons 2025-03-25 11:38:03 -04:00
Bill Oley 4ed6eb98de Update grafana datasource plugin to grafana version 11.5.x 2025-03-19 12:47:40 -04:00
Bill Oley 4d08db3fbc Set version to 2.1.6-SNAPSHOT 2024-04-15 10:54:17 -04:00
Bill Oley 16d73a1845 Release version 2.1.5 2024-04-15 10:54:17 -04:00
Bill Oley 42b06cf60c Prevent maven plugins from using log4j:log4j 2024-04-15 10:54:11 -04:00
Bill Oley e772728159 Correctly handle load, dwquery, hadoop, accumulo2, metrics in CollectDPluginParent 2024-04-10 16:25:45 -04:00
Bill Oley ebce93684a Save FileSystem instead of constantly re-creating, ensure CsvReader closed 2024-04-10 16:25:44 -04:00
Bill Oley ff7a75c7c4 Fix extraction of native libraries in scripts, make indentation consistent 2024-04-10 16:25:38 -04:00
Bill Oley ad61f2cc85 Set version to 2.1.5-SNAPSHOT 2024-03-29 16:52:06 -04:00
Bill Oley e8beb77cb9 Release version 2.1.4 2024-03-29 16:50:04 -04:00
Bill Oley d6839fbc6c Set client to null on exception, don't try to return it to pool 2024-03-29 16:46:21 -04:00
Bill Oley d53079a996 Allow users to specify an interval to calculate rates over, default downsample interval if set or 1 second otherwise 2024-03-29 13:23:03 -04:00
Bill Oley 2e7489afef Modifications to CollectDPluginParent to support dwquery metrics and to correctly identify rack for a host 2024-03-29 12:24:12 -04:00
Bill Oley ee71a2c74f Improve scripts to extract native libraries only when they are not present 2024-03-29 12:23:23 -04:00
Bill Oley 89a374f9a2 Fix util scripts and use BIN_DIR instead of THIS_DIR 2024-03-21 10:25:21 -04:00
Bill Oley f145d0d1a4 Set version to 2.1.4-SNAPSHOT 2024-03-13 15:41:00 -04:00
Bill Oley 387140722c Release version 2.1.3 2024-03-13 15:39:21 -04:00
Bill Oley b3617820f7 Modifications to CollectDPluginParent to support period delimited metric and tag.value pairs of micrometer metrics ETSY format 2024-03-13 15:32:57 -04:00
Bill Oley ac2d9463dc Set version to 2.1.3-SNAPSHOT 2024-03-07 13:14:13 -05:00
Bill Oley c32867685f Release version 2.1.2 2024-03-07 13:14:13 -05:00
Bill Oley 08057e7d67 Add jcl-over-slf4j dependency for collectd-nsq-plugin to supply logging via commons-logging and slf4j 2024-03-07 13:14:12 -05:00
Bill Oley a058e5a816 Use only netty dependencies that are necessary, ensure extraction of netty-tcnative-boringssl-static and netty-transport-native-epoll (for dynamic linking) for linux OS 2024-03-06 17:49:40 -05:00
Bill Oley 99b81986a4 Remove requiredAuths, requiredRoles from balancer ymland env (not used) and log information about the RemoteAuthenticationProvider when created 2024-03-06 17:06:51 -05:00
Bill Oley d7ecbd345f Maintain auths and roles in sorted order in TimelyUser 2024-03-06 17:04:29 -05:00
Bill Oley 0f9059b6b6 Default ciphers to null in SslClientProperties 2024-03-06 17:03:46 -05:00
Bill Oley fcbdd9296b Set version to 2.1.2-SNAPSHOT 2024-03-01 12:21:26 -05:00
Bill Oley df3ade7929 Release version 2.1.1 2024-03-01 12:19:42 -05:00
Bill Oley 823a6fa08f Ensure that WebClient.Builder has client certificate before creating RemoteAuthenticationProvider, add logging 2024-03-01 12:15:18 -05:00
Bill Oley 89ab2635fd Log format change, async logger setting, remove create-timely-profiles.sh 2024-02-29 14:59:07 -05:00
Bill Oley 13d8db5ab7 Set version to 2.1.1-SNAPSHOT 2024-02-27 14:29:46 -05:00
Bill Oley 8db5358b50 Release version 2.1.0 2024-02-27 14:29:45 -05:00
Bill Oley 3e376e106b Update dependencies spring-boot:2.7.18, spring-cloud:3.1.8, spring:5.3.31, netty:4.1.106.Final, google-flatbuffers:23.5.26, classgraph:4.8.165, spotbugs:4.8.3.0, and others 2024-02-27 14:29:44 -05:00
Bill Oley 18eecfd7b0 Refactor services to better use spring boot configuration, service, tests, use Java 11, set version to 2.1.0-SNAPSHOT 2024-02-27 14:29:32 -05:00
Bill Oley 065d8a9a9f Use either deprecated or newer property for context classloader in TieredCompactionStrategy 2024-02-02 16:44:41 -05:00
Bill Oley 09c2ef7e11 Bump version to 2.0.3-SNAPSHOT 2024-01-31 12:04:08 -05:00
Bill Oley cb875819ff Release version 2.0.2 2024-01-31 11:49:57 -05:00
Bill Oley fcc5f0f716 Fix OneWaySSL tests by creating a PKCS12 file from SelfSignedCertificate 2024-01-29 17:33:13 -05:00
Bill Oley c87543f7d7 Code formatting via autoformat profile 2024-01-29 16:28:06 -05:00
Bill Oley d7f5a3b20b Add codestyle config, Pom changes to support code formatting, import sort, pom sort 2024-01-29 16:24:24 -05:00
Bill Oley 9601cdc927 curator version to 5.5.0 2024-01-29 16:23:27 -05:00
Bill Oley 60c2588c79 Bump version to 2.0.2-SNAPSHOT 2024-01-26 13:12:18 -05:00
Bill Oley f3275c6a34 Release version 2.0.1 2024-01-26 13:10:20 -05:00
Bill Oley 7486b6b761 Add RemoteUserDetailsService, fix MiniAccumulo test, simplify SSL configuration and allow more types of SSL keyStores and trustStores 2024-01-26 13:07:26 -05:00
Bill Oley b1a968590c Bump version to 2.0.1-SNAPSHOT 2024-01-17 10:42:09 -05:00
Bill Oley 8f86b20c02 Release version 2.0.0 2024-01-17 10:42:09 -05:00
Bill Oley a3620a3f24 Fixes for intermittent test failures, version to 2.0.0-SNAPSHOT 2024-01-17 10:42:09 -05:00
Bill Oley 9c99e74958 Use InMemoryAccumulo instead of MiniAccumulo, test fixes 2024-01-17 10:42:09 -05:00
Bill Oley 91ec36e819 Changes to support accumulo2 2024-01-17 10:42:08 -05:00
Bill Oley 9ad1b18b74 Delete grafana-auth module 2024-01-17 10:42:08 -05:00
Bill Oley 82c45d4dd4 Bump version to 1.3.7-SNAPSHOT 2023-10-13 10:28:19 -04:00
Bill Oley a0e47d0b48 Release version 1.3.6 2023-10-13 10:25:43 -04:00
dependabot[bot] d3dc324d84 Bump golang.org/x/net from 0.7.0 to 0.17.0 in /grafana/timely-app (#246)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-13 10:18:12 -04:00
Bill Oley 4871b3a4a9 Update dependencies, protect against NPE in AuthCache, AuthorizationsUtil 2023-10-13 10:02:33 -04:00
dependabot[bot] 8d5d5eeca8 Bump terser from 4.8.0 to 4.8.1 in /grafana/timely-app (#235)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 14:26:51 -04:00
dependabot[bot] a006a44372 Bump decode-uri-component from 0.2.0 to 0.2.2 in /grafana/timely-app (#240)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 14:26:07 -04:00
dependabot[bot] 6cfcdc1030 Bump golang.org/x/net in /grafana/timely-app (#241)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20210614182718-04defd469f4e to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 14:25:46 -04:00
dependabot[bot] 4eb7ceff8c Bump semver from 5.7.1 to 5.7.2 in /grafana/timely-app (#244)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 14:25:13 -04:00
dependabot[bot] 6ad8da2b99 Bump word-wrap from 1.2.3 to 1.2.4 in /grafana/timely-app (#245)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 14:24:12 -04:00
dependabot[bot] 7f56a36906 Bump guava from 29.0-jre to 32.0.0-jre (#243)
Bumps [guava](https://github.com/google/guava) from 29.0-jre to 32.0.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-15 12:53:29 -04:00
Christopher Tubbs 1443fdceb9 Bump formatter and impsort plugins for newer Maven 2023-06-07 11:01:29 -04:00
Christopher Tubbs 3cff14eb03 Bump NPM/Yarn deps
* This avoids warnings about CVEs from GitHub in the referenced
  dependencies
* This required relaxing the node version constraint, since I didn't
  have version 12, only 16
2022-04-11 15:49:13 -04:00
dependabot[bot] 8fa5b56e62 Bump hadoop-common from 2.7.7 to 2.10.1 (#230)
Bumps hadoop-common from 2.7.7 to 2.10.1.

---
updated-dependencies:
- dependency-name: org.apache.hadoop:hadoop-common
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 14:22:59 -04:00
dependabot[bot] b2f2cef834 Bump follow-redirects from 1.14.1 to 1.14.8 in /grafana/timely-app (#232)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 14:22:18 -04:00
dependabot[bot] c6a2988083 Bump accumulo-core from 1.9.3 to 1.10.1 (#231)
Bumps [accumulo-core](https://github.com/apache/accumulo) from 1.9.3 to 1.10.1.
- [Release notes](https://github.com/apache/accumulo/releases)
- [Commits](https://github.com/apache/accumulo/compare/rel/1.9.3...rel/1.10.1)

---
updated-dependencies:
- dependency-name: org.apache.accumulo:accumulo-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 14:21:50 -04:00
Bill Oley d54817cc9a Bump version to 1.3.6-SNAPSHOT 2022-01-07 14:43:04 -05:00
Bill Oley c4ee035584 Release version 1.3.5 2022-01-07 14:40:06 -05:00
Chromico Rek d4bfc1a3aa Update log4j to 2.17.1 to address CVE-2021-44832 (#227) 2021-12-30 13:50:34 -05:00
Chromico Rek fb315148a1 Update log4j to 2.17 to address CVE-2021-45105. (#226) 2021-12-20 10:44:47 -05:00
dependabot[bot] e8ae575086 Bump @grafana/data from 8.2.2 to 8.2.3 in /grafana/timely-app (#222)
Bumps [@grafana/data](https://github.com/grafana/grafana/tree/HEAD/packages/grafana-data) from 8.2.2 to 8.2.3.
- [Release notes](https://github.com/grafana/grafana/releases)
- [Changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md)
- [Commits](https://github.com/grafana/grafana/commits/v8.2.3/packages/grafana-data)

---
updated-dependencies:
- dependency-name: "@grafana/data"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-15 16:59:06 -05:00
Christopher Tubbs da0146c741 Bump log4j and other cleanup
* Update log4j2 to 2.16.0 to address CVEs
* Update impsort and sortpom plugins
* Update spotbugs plugin to build on newer JDKs and suppress common
  minor warnings from that
* Fix a few minor warnings from spotbugs that are easy: remove hamcrest
  matcher, fix null checks that are always or never null
* Suppress module-info.class from shaded jars
2021-12-15 16:53:24 -05:00
Bill Oley 882b4e2587 Bump version to 1.3.5-SNAPSHOT 2021-11-01 10:17:49 -04:00
Bill Oley 8048862f2c Release version 1.3.4 2021-11-01 10:15:01 -04:00
Bill Oley c2f00c3d19 Convert line endings in pom files from dos->unix 2021-11-01 10:11:06 -04:00
Bill Oley 61b1a245e2 Bump version to 1.3.4-SNAPSHOT 2021-10-26 16:07:30 -04:00
Bill Oley a3c33115c4 Release version 1.3.3 2021-10-26 15:57:25 -04:00
Bill Oley bed1ceb9ee Remove yarn-error.log and add to .gitignore 2021-10-26 15:56:12 -04:00
Bill Oley 06e2699a44 Update to [email protected], [email protected], [email protected] 2021-10-26 14:59:25 -04:00
Bill Oley d09faaa087 Correct log message formatting 2021-10-22 14:21:49 -04:00
dependabot[bot] 088f1a5cab Bump jsoup from 1.11.3 to 1.14.2 (#216)
Bumps [jsoup](https://github.com/jhy/jsoup) from 1.11.3 to 1.14.2.
- [Release notes](https://github.com/jhy/jsoup/releases)
- [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES)
- [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.11.3...jsoup-1.14.2)

---
updated-dependencies:
- dependency-name: org.jsoup:jsoup
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-22 13:47:11 -04:00
dependabot[bot] cf6d4ada3e Bump guava from 27.1-jre to 29.0-jre (#214)
Bumps [guava](https://github.com/google/guava) from 27.1-jre to 29.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-22 13:46:44 -04:00
Bill Oley e464a8c9e0 fixes #217 - Improve ageoff iterator remove/add logic in DataStoreImpl (#218)
* fixes #217 - Improve ageoff iterator remove/add logic in DataStoreImpl

* fixes #217 - Use curator LeaderLatch to elect a leader server which will remove/add the age off iterators
2021-10-22 13:13:00 -04:00
Bill Oley 8e4e61c373 fixes #219 Create scripts to help with running and testing multiple instances of server and balancer 2021-10-22 13:11:41 -04:00
dependabot[bot] 729dca23ba Bump httpclient from 4.5.6 to 4.5.13 (#215)
Bumps httpclient from 4.5.6 to 4.5.13.

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents:httpclient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-03 23:49:36 -04:00
Bill Oley 104d20ad1b Bump version to 1.3.3-SNAPSHOT 2020-11-06 13:30:04 -05:00
Bill Oley f0f005f8fa Release version 1.3.2 2020-11-06 13:23:36 -05:00
Bill Oley 8593218b89 Logging fixes 2020-11-06 12:24:22 -05:00
Bill Oley cbfe6a439f closes #213 Release reference counted netty objects to avoid memory errors 2020-11-06 11:50:24 -05:00
Bill Oley 455616d568 closes #213 Release reference counted netty objects to avoid memory errors 2020-11-05 14:17:01 -05:00
Bill Oley 9d3172cc75 Close objects when TcpClient has problem connecting to Timely 2020-11-04 16:06:08 -05:00
Bill Oley 7aa7b0da2a Remove entries from metricMap when metric is no longer cached on a particular server 2020-11-04 16:04:52 -05:00
Bill Oley 6c61861d83 Fix NPE in BalancedMetricResolver.rebalance where a metric exists in metricMap but not in metricToHostMap 2020-10-29 16:49:55 -04:00
Bill Oley 91882ba363 closes #212 Use writeLock for DataStoreCache.ageOffGorillaStores to avoid ConcurrentModificationException 2020-10-28 15:58:33 -04:00
Bill Oley ed61b5f305 Fix css inconsistent rendering in grafana datasource QueryEditor, remove overridden/unusd css 2020-10-27 14:10:58 -04:00
Bill Oley a083c2d632 Fix oauth issues - intersect TimelyUser auths with Accumulo auths when creating from oauthToken, allow X.509 (PEM) jwt signature check key, increase size of httpHeader max size 2020-10-23 14:41:46 -04:00
Jonathan SteinandJonathan Stein 2891794174 Fixing Nondeterministic Tests (#211)
* Fix MetricAdapterTest and QueryResponseTest flakiness

* change MetricAdapterTest and QueryResponseTest flakiness fix by defining Json key order

* Removed .swp file

* Fix SearchLookupResponse testResponse1

* Fixed flaky AuthCacheTest

Co-authored-by: Jonathan Stein <[email protected]>
2020-10-19 14:01:17 -04:00
Bill Oley c99b2471cf Bump version to 1.3.2-SNAPSHOT 2020-10-14 14:58:07 -04:00
Bill Oley bb479a58d7 Release version 1.3.1 2020-10-14 14:54:59 -04:00
Bill Oley ff37d40801 datasource logging fix, combine datasource golang code for readability 2020-10-14 14:46:09 -04:00
Bill Oley a7d980975e closes #210 Only reload cert and CA when changed
Only reload cert and CA when changed
2020-10-14 13:12:11 -04:00
dependabot[bot] 30f5a0c1b2 Bump junit from 4.12 to 4.13.1 (#209)
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-13 00:54:58 -04:00
Bill Oley 2f9e6ca2d7 Bump version to 1.3.1-SNAPSHOT 2020-09-25 13:04:54 -04:00
Bill Oley 954528a460 Release version 1.3.0 2020-09-25 12:39:33 -04:00
Bill Oley 0f7b514b30 Update authentication configuration and error handling, Grafana depenancies to 7.2.0
Grafana depenancies to 7.2.0
2020-09-25 12:19:08 -04:00
Bill Oley a6ca46e1c1 Rewrite grafana plugin for grafana 7 using typescript, react. Use new backend plugin feature to enable alerts
More work - has errors

more work

more work

Add addOauthPassThru setting

CS fixes, OAuthPassThru setting, Alias fix, Auto Focus on new tag

Logging cleanup and make aliases work

Delete old version of plugin

remove extra files

Accept JWT authentication and test fixes

Accept JWT authentication

Fix assembly

Add JWT settings to sample configs
2020-09-15 16:26:29 -04:00
Bill Oley 487d1d4a5c Use MetaCache for suggest api and implement tagk and tagv types of suggest 2020-09-15 16:26:18 -04:00
Christopher Tubbs e3c26f815c Merge pull request #206 from NationalSecurityAgency/dependabot/maven/commons-beanutils-commons-beanutils-1.9.4
Bump commons-beanutils from 1.9.3 to 1.9.4
2020-07-02 17:16:58 -04:00
Christopher Tubbs bbc1946204 Update plugins for newer JDKs
* Bump formatter and impsort to latest to stay current
* Bump spotbugs to support up to at least JDK 14
2020-07-02 17:09:06 -04:00
dependabot[bot] 991412f99b Bump commons-beanutils from 1.9.3 to 1.9.4
Bumps commons-beanutils from 1.9.3 to 1.9.4.

Signed-off-by: dependabot[bot] <[email protected]>
2020-07-02 20:44:34 +00:00
Christopher Tubbs 5428e93cb5 Merge pull request #205 from NationalSecurityAgency/dependabot/maven/version.log4j-2.13.3
Bump version.log4j from 2.11.2 to 2.13.3
2020-07-02 16:44:06 -04:00
dependabot[bot] c18aae5a89 Bump version.log4j from 2.11.2 to 2.13.3
Bumps `version.log4j` from 2.11.2 to 2.13.3.

Updates `log4j-api` from 2.11.2 to 2.13.3

Updates `log4j-core` from 2.11.2 to 2.13.3

Updates `log4j-slf4j-impl` from 2.11.2 to 2.13.3

Signed-off-by: dependabot[bot] <[email protected]>
2020-07-01 18:20:00 +00:00
Bill Oley 6972c46a1a Merge pull request #204 from matthpeterson/issue-203
Closes issue #203 - avoid calling sizeInBytes if it's not expected to exist
2020-04-17 10:42:14 -04:00
matthpeterson 4d7bee7566 Closes issue #203 - Stop calling sizeInBytes if it's not expected to exist 2020-04-16 15:17:08 +00:00
Bill Oley 4b11b3d50f closes #201 Create balancer assignment file if it doesn't exist 2020-04-10 10:27:18 -04:00
Bill Oley 84b9856ec7 closes #199 Fix issues when using a generated (self-signed) server certificate 2020-04-10 10:20:25 -04:00
Bill Oley c9548a9b9a closes #198 Cleaner shutdown of resources for Timely server, balancer, grafana-auth 2020-04-10 10:01:47 -04:00
Bill Oley aec7bd4c38 closes #197 Fix scan range in MetaCacheImpl 2020-04-09 11:58:30 -04:00
Bill Oley e2b8b643f2 closes #200 Initialize non-cached metrics stored in zookeeper 2020-04-09 11:32:03 -04:00
Bill Oley c19e1d8b2f Set version to 1.2.8-SNAPSHOT 2020-02-21 10:20:48 -05:00
Bill Oley cb73e8e75c Tag version 1.2.7 2020-02-21 10:02:31 -05:00
Bill Oley 4c508c1a7d Merge pull request #195 from dtspence/TIMELY-194
Fixed #194, corrected datasource.js to look at wildcard tag value
2020-02-21 08:55:37 -05:00
dtspence c6c2c0d41e Fixed #194, corrected datasource.js to look at wildcard tag value
The alignment process to match tag query results with the metric definition
looks at the tag k/v pairs and makes a special consideration for a wildcard tag value.
The change adjusts datasource.js so that the '.*' notation will match a query result
and metric definition.
2020-02-05 19:56:03 +00:00
Christopher Tubbs a24cef78b3 Update jackson (multiple CVEs) (#193) 2019-11-07 23:05:38 -05:00
Bill Oley 08f99f6540 Set version to 1.2.7-SNAPSHOT 2019-10-30 12:33:49 -04:00
Bill Oley be11da7d15 Tag version 1.2.6 2019-10-30 12:30:33 -04:00
Bill Oley 4bc5753328 fixes #192 Thread deadlock in BalancedMetricResolver 2019-10-29 12:24:10 -04:00
Bill Oley 2ccb89a06d re #191 Correct typos 2019-10-18 09:16:03 -04:00
Bill Oley dfc3840ad4 re #191 Delete cached metrics when they are no longer being updated 2019-10-17 17:49:24 -04:00
Bill Oley 6235034d8b BalancedMetricResolver in the non-leader balancer should read assignments at intervals 2019-10-17 17:49:24 -04:00
Bill Oley d8de6ac06f Make zookeeper timeout configurable 2019-10-17 17:49:24 -04:00
Christopher Tubbs f381946abe Merge pull request #190 from NationalSecurityAgency/dependabot/maven/io.netty-netty-all-4.1.42.Final
Bump netty-all from 4.1.35.Final to 4.1.42.Final
2019-10-17 09:35:13 -07:00
dependabot[bot] 48bbe6a561 Bump netty-all from 4.1.35.Final to 4.1.42.Final
Bumps [netty-all](https://github.com/netty/netty) from 4.1.35.Final to 4.1.42.Final.
- [Release notes](https://github.com/netty/netty/releases)
- [Commits](https://github.com/netty/netty/compare/netty-4.1.35.Final...netty-4.1.42.Final)

Signed-off-by: dependabot[bot] <[email protected]>
2019-10-11 18:58:57 +00:00
Christopher Tubbs 0f6110ea59 Update jackson to 2.9.10
* Fix CVE-2019-16335
* Fix CVE-2019-14540
2019-09-26 23:40:08 -04:00
Bill Oley 461b5e7e78 Pom version to 1.2.6-SNAPSHOT 2019-08-22 12:17:45 -04:00
Bill Oley 4ec7d94c28 Tag version 1.2.5 2019-08-22 12:12:49 -04:00
Bill Oley 77e910ae06 Use BufferedOutputWriter instead of PrintWriter, pass Exceptions instead of having to check error condition 2019-08-22 10:09:27 -04:00
Bill Oley a4998af887 Move jackson-databind version to the properties section 2019-08-08 13:50:08 -04:00
Bill Oley 2858549ede Merge pull request #188 from ctubbsii/jdk11-build-support
Bump versions / permit build with JDK11
2019-08-08 13:35:54 -04:00
Christopher Tubbs 57f0e1408f Bump versions / permit build with JDK11
Support builds on newer JDKs
* Update tycho-compiler-jdt in analytics/pom.xml
* Ignore spotbugs RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE for JDK11
* Bump several plugins to latest (maven-compiler-plugin, etc.)
* Add profile for using -release flag in Eclipse and with JDK9 and later

Fix recently added warnings
* Add try-with-resources for TreeCache objects to remove recent warnings
* Suppress generics warning in MetricCompactionStrategy
* Update TabletMetadataConsole to avoid deprecated Accumulo
  ClientConfiguration API

Other
* Bump jackson-databind for CVE-2019-14439, CVE-2019-14379
* Bump hadoop 2.7 to latest in series (2.7.3->2.7.7) for build
2019-08-06 16:14:13 -04:00
Bill Oley 151adc025a Merge pull request #185 from dtspence/TIMELY-184
Fixes #184 Metric compaction strategy and summary implementation
2019-08-06 15:15:45 -04:00
Bill Oley 5abd9d7200 closes #187 Prevent deadlock in balancer distributed read/write lock 2019-08-06 12:00:57 -04:00
Bill Oley fa2e211c72 closes #186 Make balancer websocket incomingBufferSize its own setting 2019-08-06 11:47:36 -04:00
dtspence a21438976e TIMELY-184: Metric compaction strategy and summary implementation. 2019-08-01 14:05:35 +00:00
Christopher Tubbs ba37047f3a Update jackson-databind (CVE-2019-12814) 2019-07-23 20:38:46 -04:00
Bill Oley 9305470c69 re #183 Check tagKey not null or blank in Meta.toKeys 2019-06-19 09:08:20 -04:00
Bill Oley 200233542e re #183 Add timestamps to meta tags to allow proper age off 2019-06-17 15:36:51 -04:00
Bill Oley 3cd78dc624 Merge branch 'drewfarris-config-fixup' 2019-05-30 12:55:35 -04:00
Bill Oley 9027cac6e3 Merge branch 'config-fixup' of https://github.com/drewfarris/timely into drewfarris-config-fixup
Conflicts:
	server/conf/timely-docker.yml
	server/conf/timely-standalone.yml
	server/conf/timely.yml
2019-05-30 12:50:31 -04:00
Bill Oley 3663b4e6d5 Pom version to 1.2.5-SNAPSHOT 2019-05-30 12:38:42 -04:00
Bill Oley 64973d0674 Merge pull request #182 from ctubbsii/fix-jackson
Fix most compiler warnings and many other issues
2019-05-30 12:25:00 -04:00
Christopher Tubbs 5512306f1e Fix most compiler warnings and other issues
Build:
* Fix netty-tcnative-boringssl-static version specs in pom.xml
* Bump the version of accumulo to latest release (1.9.3)
* Update jackson to 2.9.9 (CVE-2019-12086)

Warnings:
* Remove all use of deprecated netty stuffs and use their replacements
  in the netty APIs
* Add missing generics parameters everywhere
* Remove unused code (unused private fields, such as loggers)
* Remove standard javadoc tags that were missing descriptions (these do
  not add value to the javadocs, since default `@param`, `@throws`, and
  `@return` tags are provided by javadoc)
* Add missing serialVersionUid fields to Serializable classes
* Fix a resource leak warning about not closing ScannerOptionsHelper
  (NOTE: 3 other resource leak warnings exist, but did not attempt to
  fix those, since they were non-trivial)
* Remove deprecated annotations from hibernate and replace with their
  non-deprecated counterparts from javax.validation.constraints
* Remove use of deprecated Accumulo ClientConfiguration constructor

Other:
* Use lowercase header names, for HTTP/2 compatibility (and to stop
  incorrectly assuming case-sensitivity for HTTP/1)
* Narrow the scope of necessary `@SuppressWarnings("unchecked")`
  occurrences to a variable instead of suppressing for an entire method or
  an entire class
* IDE added some missing overrides annotations
* (style consistency) Replace tabs with spaces
* Set log4j2 root logger level for tests to WARN instead of TRACE
2019-05-24 12:12:00 -04:00
Bill Oley d35ae08188 Tag version 1.2.4 2019-05-22 17:14:42 -04:00
Bill Oley e448657afb Correction in websocket pipeline from Netty API change 2019-05-22 16:49:11 -04:00
Bill Oley 1dcd893711 Add age off iterator for meta table
Add age off iterator for meta table
2019-05-22 16:49:08 -04:00
Bill Oley 409f5375d1 Change pom version to 1.2.4-SNAPSHOT 2019-05-22 16:48:09 -04:00
Bill Oley c5df8139ca Correct typos 2019-05-22 16:46:55 -04:00
Bill Oley dcd23d4fc8 fixes #181 Use netty-tcnative-boringssl-static 2019-04-18 11:56:54 -04:00
Bill Oley cc7326fc19 Tag version 1.2.2 2019-04-12 11:24:53 -04:00
Bill Oley 0d2b6e61c8 Correct typos 2019-04-12 10:55:04 -04:00
Bill Oley b7cd8765c5 Correct typos and minor changed to MetaCache 2019-04-10 15:31:53 -04:00
Bill Oley a9d27d5b1b re #179 Allow AuthCache to periodically refresh credentials, cache meta table up to a configurable number of tags 2019-04-08 10:50:14 -04:00
Bill Oley 7d67eb218d Update netty-tcnative dependency, remove x509 principal extractor from AuthenticationService 2019-04-03 15:52:19 -04:00
Bill Oley 5fb7e3dd2b Bump pom versions to 1.2.2-SNAPSHOT 2019-04-02 12:57:22 -04:00
Bill Oley 5a89861cec Tag version 1.2.1 2019-04-02 12:53:04 -04:00
Bill Oley a3bc9a01ee Update dependency versions
Update dependencies

Update dependency versions
2019-04-02 12:43:36 -04:00
Bill Oley dba494b06d Bump pom versions to 1.2.1-SNAPSHOT 2019-02-25 15:41:31 -05:00
Bill Oley 5826884c3c Tag version 1.2.0 2019-02-25 15:11:57 -05:00
Bill Oley f94c731bbc Remove unused modeule directory introduced in error 2019-02-25 15:11:12 -05:00
Bill Oley 4e042bcb94 Allow all http methods in grafana auth proxy, disable cookies in HttpPools 2019-02-22 16:17:36 -05:00
Bill Oley ab7f499134 closes #180 Allow a configurable default visibility for metrics 2019-02-21 15:33:25 -05:00
Bill Oley c098f9f3e4 re #179 Create an authentication app for grafana that uses Timely's authentication framework and grafana's auth-proxy
re #179 Create authentication proxy for grafana
2019-02-21 13:28:12 -05:00
Bill Oley 94cfcbc0c4 Allow authentication provider to return roles, check users against requiredRoles and requiredAuths for access
index on master: 674e1b0 Improve synchronization in AuthenticationService

Check validated users against requiredRoles and requiredAuths

Conflicts:
	server/conf/security.xml
	server/src/main/java/timely/auth/AuthenticationService.java

More work
2019-02-15 11:24:07 -05:00
Bill Oley 674e1b0ab5 Improve synchronization in AuthenticationService 2019-02-08 16:53:35 -05:00
Bill Oley 4eedb84547 Add synchronization to AuthenticationService to prevent simultaneous authentications of the same user by multiple threads 2019-02-07 17:17:08 -05:00
Bill Oley bef86eac37 Fix websocket proxying with a client certificate, add ability to configure client cert in python websocket client 2019-02-07 17:16:00 -05:00
Bill Oley 27bb16e403 re #178 Allow proxying of http and ws requests
More work

Correct errors
2019-02-05 11:41:35 -05:00
Drew Farris e2bbe4e212 Minor fix-ups to default configuration files.
. Replace ssl with server-ssl in timely-standalone.yml and timely-docker.yml
  . Add a value for cache.enabled in timely.yml
2019-01-19 22:07:07 -05:00
Bill Oley a95d2d7ea1 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-16 13:55:47 -05:00
Bill Oley f751175190 Update version of collectd 2019-01-16 13:53:43 -05:00
Bill Oley 648199f330 Revert "Bump pom versions to 1.1.1-SNAPSHOT"
This reverts commit fcfc3f7bc0.
2019-01-16 13:53:26 -05:00
Bill Oley fcfc3f7bc0 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-15 15:48:43 -05:00
Bill Oley 200bc39902 Tag version 1.1.0
Grafana app changes
2019-01-15 15:47:14 -05:00
Bill Oley f854c9e394 Add build profile for python client package 2019-01-15 15:08:30 -05:00
Bill Oley 73ddae1f8d Correct typo 2019-01-15 14:15:20 -05:00
Bill Oley cb7184503e Allow Websocket requests to take advantage of cached metrics 2019-01-11 16:12:26 -05:00
Christopher Tubbs 3a357b50ef Update jackson-databind dependency
(Also, remove redundant test dependency on timely-common.)
2019-01-10 16:07:25 -05:00
Bill Oley 689aee0678 Fix deadlock in tracking nonCachedMetrics across processes 2019-01-08 12:06:41 -05:00
Bill Oley 32f6b9d4c9 Include the bin/testing directory in assembly 2019-01-04 11:14:11 -05:00
Bill Oley 512f318387 Add distributed tracking of nonCachedMetrics between the timely servers and the balancers
work in progress

Add distributed tracking of noncachedMetrics
2019-01-04 11:04:24 -05:00
Bill Oley e04cfb310b Add execute to bash scripts 2019-01-03 11:40:15 -05:00
Bill Oley 0cecc3b191 Fix copy error and imports 2019-01-02 11:02:25 -05:00
Bill Oley b3d75e4c68 Fixes to synchronizing assignments between Balancer copies, reduce number of Timer threads 2018-12-27 17:14:19 -05:00
Bill Oley 004c344153 Fix to oldestTimestamp tracking in GorillaStore and additional trace logging 2018-12-21 14:52:59 -05:00
Bill Oley 2590effeee Add actions for BalancedMetricResolver leaderLatch 2018-12-19 13:58:29 -05:00
Bill Oley f49c83601a Fix merge issues 2018-12-19 10:50:18 -05:00
Bill Oley df035259c9 Auto-discovery of timely-servers, run multiple balancers, refactor configuration
Changes in progress

Changes in progress

Work in progress
2018-12-18 16:01:10 -05:00
Bill Oley 3a8cbd3378 Use StampedLocks in BalancedMetricResolver, recalibrate ArrivalRate calculations, bug fixes 2018-11-26 16:30:17 -05:00
Bill Oley 39bea1da8d Synchronize access to balancer assignments, save & restore assignments on restart, correct access to native netty libraries 2018-11-20 12:13:55 -05:00
Bill Oley 1d3f8b7d3a Reformatting and fix PooledSocketFactory validate 2018-11-16 15:14:06 -05:00
Bill Oley 0fbbb86768 re #176 Create TCP/UDP/HTTP/Websocket balancer for timely 2018-11-06 13:22:19 -05:00
Bill Oley 8b807e5ac7 re #163 Create in-memory cache capability 2018-11-06 13:12:08 -05:00
Christopher Tubbs 710c789984 More POM cleanup
* More consolidation of plugin configuration to parent POM
* Fix grafana (timely-app) module config (not a jar, so skip jar plugins)
* Migrate to spotbugs from findbugs
* Remove unneeded bundling of flatbuffers-java library
* Keep exclude filter files out of the jar artifacts by moving out of src/main/resources
2018-10-18 20:30:52 -04:00
Christopher Tubbs cf0d63a4e7 Apply formatting changes to java source files 2018-10-18 18:41:23 -04:00
Christopher Tubbs bfc67db185 Update poms
* use sortpom
* remove redundancy in child poms
* add missing versions
* update versions to fix CVE issues
* update formatter and add impsort
2018-10-18 18:40:12 -04:00
femtotrader f604314640 Update README.md (#175) 2018-10-03 01:28:16 -04:00
Christopher Tubbs 25c9e93888 Update lodash dependency to 4.17.11
Update dependency due to https://nvd.nist.gov/vuln/detail/CVE-2018-3721
2018-09-27 16:23:06 -04:00
Bill Oley a323d198e9 fixes #173 Allow gaps in data to be interpolated or filled with value 2018-09-21 14:48:06 -04:00
Bill Oley 8e6cc4e0b4 closes #171 TimelyWebsocket should handle json return of a TimelyException 2018-09-14 14:35:37 -04:00
Bill Oley 3a3d98ec81 closes #172 WebSocketClient typo in constructor (uri vs url) 2018-09-14 14:34:07 -04:00
Bill Oley 7599a2ac60 closes #170 TimeDateRange python code is mis-calculating begin/end date 2018-09-14 14:33:14 -04:00
Bill Oley 3d33ba2e31 re #163 send and accept metric name in query paramater for load
balancing by metric name
2018-06-15 15:54:32 -04:00
Bill Oley bbbb453818 closes #168 - Websocket MetricScanner sometimes does not return completed=true element 2018-05-25 15:04:01 -04:00
Bill Oley 54951c05e6 re #165 - Aggregation buckets in AggregationIterator should be cleared once their values are returned 2018-03-26 10:00:50 -04:00
Bill Oley b63914e02c Remove unnecessary debug logging 2018-03-22 11:04:19 -04:00
Bill Oley 1f386db00a closes #167 - Python client and analytic code should retain metrics with duplicate timestamps 2018-03-22 10:52:23 -04:00
Bill Oley 49222ac76a Bump version to 1.0.1-SNAPSHOT 2018-03-22 10:52:17 -04:00
Bill Oley 38f2c6cdfa Release version 1.0.0 2018-03-22 10:52:03 -04:00
Bill Oley 3926e6f066 Revert "closes #167 - Python client and analytic code should retain metrics with duplicate timestamps"
This reverts commit 30800a0b5c.
2018-03-22 10:47:18 -04:00
Bill Oley 30800a0b5c closes #167 - Python client and analytic code should retain metrics with duplicate timestamps 2018-03-20 13:31:57 -04:00
Bill Oley 92b23a4c51 closes #166 : Gracefully handle tag values with delimiters and handle bad data
closes #166 : Gracefully handle tag values with delimiters and handle bad data
2018-03-15 16:13:11 -04:00
Bill Oley 8030ea59a5 TIMELY-165: Refactor key structure of metrics table for better query performance
refactor key structure for better performance

refactor key structure - fix tests

encode timestamp in colQual, fix range creator in DataSourceImpl

Add setup.py to .gitignore, add logging for metrics that fail to parse

TIMELY-165: cleanup
2018-03-08 16:57:30 -05:00
Bill Oley a4c8158003 closes #164 - DownsampleMemoryEstimator should be able to handle situation where start of scan has no samples and correctly estimate memory usage anyway 2018-02-13 13:13:47 -05:00
Bill Oley acba454ee3 Merge commit '3079060' 2018-01-26 10:39:06 -05:00
Bill Oley 30790608ca Bump version to 0.0.7-SNAPSHOT 2018-01-26 10:38:16 -05:00
Bill Oley f2f3788f99 tag version 0.0.6 2018-01-26 10:35:56 -05:00
Bill Oley 326b9dc8f1 closes #164 - Limit memory consumed by DownsampleIterator and return iteratively on time bucket boundaries
Initial work

More work

cwImproved implementation of DownsampleIterator.MemoryEstimator

closes #164 - Limit memory consumed by DownsampleIterator and return iteratively on time bucket boundaries
2018-01-24 15:23:20 -05:00
Bill Oley 3f3b605c63 re #160 - Close and invalidate CloseableHttpClient so that we can recover from bad connections 2017-11-17 10:19:11 -05:00
Bill Oley 5580a2fa66 re #160 - refactor config for WriteNSQPlugin, add socket timeouts to WriteTimelyPlugin 2017-11-09 16:08:08 -05:00
Bill Oley 1a54c3ada3 closes #161 - Add handling of a min and max percentage of a rolling average to python analytic 2017-11-03 12:20:13 -04:00
Bill Oley 2080764e0d closes #162 - Python analytic should be able to convert a counter metric into a rate 2017-11-03 09:57:38 -04:00
Bill Oley e7dade04ee closes #160 - Improve reliability and error recovery of WriteNSQPlugin and WriteTimelyPlugin 2017-10-31 14:52:49 -04:00
Bill Oley 7ed337483e fixes #159 TimelyAnalytic error when analyticConfig.groupByColumn is None 2017-10-18 12:21:28 -04:00
Bill Oley b070bc3c47 closes #158 Package python code so that it can be deployed and managed with pip 2017-10-18 11:10:41 -04:00
Bill Oley 51f9103741 closes #156: Use object pools in WriteNSQPlugin and WriteTimelyPlugin to support multithreaded use 2017-10-02 13:56:16 -04:00
Bill Oley a97fa83e2f closes #157 - Add query metrics to internal metrics 2017-09-26 12:59:59 -04:00
Drew Farris 0b5c3544db Merge pull request #154 from drewfarris/docker-update
Docker & Grafana updates for 0.0.6-SNAPSHOT
2017-08-15 16:21:15 -04:00
Dave Marion 186b60756a fixes #155 - log more information on ssl error 2017-08-14 09:18:32 -04:00
Drew Farris 133a6bf5cf Bumped grafana plugin reference to 0.0.6-SNAPSHOT 2017-08-12 12:24:51 -04:00
Drew Farris b684d7eefe Updated Docker config for 0.0.6-SNAPSHOT and grafana 4.4.3 2017-08-12 12:21:34 -04:00
Dave Marion 73f1603f1e Bump version to 0.0.6-SNAPSHOT 2017-08-11 11:24:36 -04:00
Dave Marion 9267e0c915 Change pom versions for 0.0.5 tag 2017-08-11 11:21:44 -04:00
Dave Marion 1ce49628e5 Closes #123 - Updates to Timely App and Dashboards for latest Grafana 2017-08-08 10:30:38 -04:00
Bill Oley acad3af083 fixes #149: Add funtionality to CollectDPluginParent to handle GenericJMX metrics 2017-08-01 16:39:32 -04:00
Dave Marion b4635f1a55 re #123 - update pairs() to toPairs() 2017-07-12 14:15:00 -04:00
alerman 4c1ede04df Update datasource.js for grafana 4 (#147)
fixes #123 - Update _.all to _.every as the newer version of lodash has changed the term. This fixes error seen with Timely Standalon and Grafana >4.2
2017-07-12 13:44:49 -04:00
Dave Marion 2841b0a3e9 closes #143 : set sock to null in close, fixed some warnings 2017-05-04 16:09:57 -04:00
Dave Marion 6efa13909f closes #144: Update to Netty 4.0.46.Final 2017-05-04 12:45:42 -04:00
Dave Marion 368c572c67 closes #141 - build collectd plugins only when collectd profile is activated 2017-04-14 09:34:17 -04:00
Dave Marion 554baf6af7 Updated readme to new markdown syntax 2017-04-13 12:59:50 -04:00
Dave Marion 1ef11799be fixes #138 - don't close source until all subscriptions have completed. 2017-03-24 08:52:51 -04:00
Dave Marion 16b664297d fixes #138 - synchronize the response list and exit the subscription source when the last message is received 2017-03-23 16:35:16 -04:00
Bill Oley 4277b63bc3 re #130 - Improvements to python code 2017-03-22 10:57:42 -04:00
Bill Oley 5a9ad3cb27 re #130 - Corrections to python code 2017-03-20 08:22:34 -04:00
Bill Oley f990e89ab3 re #130 - Improvements to python code 2017-03-17 17:05:19 -04:00
Bill Oley c01ac80f23 re #130 - Corrections to python code 2017-03-16 08:59:20 -04:00
Bill Oley 35e8f99d09 re #130 - Additional development on Python client and analytics 2017-03-15 18:21:43 -04:00
Bill Oley 847144d236 re #130 - Update to seaborn graphing for python client 2017-02-13 17:55:22 -05:00
Ivan Bella b3bfbc1e1f fixes #136 removed the encoding and decoding of tag names and values from the tag parser 2017-02-06 13:48:07 -05:00
Bill Oley 8d86e3894a re #130 - Try seaborn graphing to generate png file programmatically - in progress 2017-02-03 16:43:47 -05:00
Dave Marion 99b9181902 Bumped version to 0.0.5-SNAPSHOT 2017-02-02 10:48:56 -05:00
Dave Marion 3c0bf87b79 0.0.4 release 2017-02-02 10:43:02 -05:00
Bill Oley d2ba7f635e re #130 - Refactor python code in preparation to automate 2017-01-23 15:08:42 -05:00
Ivan Bella 92510fdc83 Aggregation (#135)
fixes #108 - Implemented the aggregation across time series separate after downsampling
2017-01-23 10:33:04 -05:00
Dave Marion 4eac17fbb5 re #73 - change map in accumulator to a concurrent map, it was failing to serialize with a ConcurrentModificationException from the task manager to the job manager 2017-01-20 12:13:36 -05:00
Dave Marion 328ae075f4 fixes #134 - remove log4j2 transitive dependency so that flink job logging works 2017-01-19 13:15:27 -05:00
Ivan Bella cf12295ac8 fixes 133 - added the buildnumber-maven-plugin, and put the buildNumber and timestamp into each of the jar's MANIFEST 2017-01-18 14:37:28 -05:00
Ivan Bella df180504f2 fixes #132 - Ensure we do not seek past the end key in the initial range when skipping aged off data. 2017-01-18 13:15:00 -05:00
Dave Marion 021ff832ef fixes #131 - bring back the old ageoff filter and apply to meta table, use new iterator on metrics table 2017-01-18 09:10:40 -05:00
Drew Farris 6fc9066e70 Closes #128 modify server pom for netty-transport-native-epoll dep
- remove runtime scope
2017-01-10 21:14:38 -05:00
Bill Oley f11b82b662 re #130 - Create analytics library - correct errors and add ability to display all series regardless of detected conditions 2017-01-09 17:29:15 -05:00
Bill Oley dcfd165a9b re #130 - Add sample analytic script 2017-01-06 18:32:39 -05:00
Bill Oley fd98558218 re #130 - Create analytics library and graphing capability using plot.ly 2017-01-06 18:26:02 -05:00
Dave Marion f47cd66ec2 re #128 - use no classifier for the native transport epoll dependency, use one for the linux environments though 2017-01-05 16:49:58 -05:00
Dave Marion ec4ba6e7f5 closes #129 - update Apache Flink version to 1.1.4 2017-01-05 15:14:29 -05:00
Dave Marion e698734e4b closes #126: Added udp and tcp client 2016-12-21 16:53:11 -05:00
Dave Marion 78b49fe44e closes #124 - change ageoff filter to an iterator so that it can seek smartly 2016-12-13 12:25:23 -05:00
Dave Marion f109f04162 closes #125 - use a smarter start time when zero is supplied by using the age off configuration 2016-12-08 09:59:35 -05:00
Dave Marion 4bf68fe328 closes #122: use netty native epoll 2016-12-07 15:03:05 -05:00
Dave Marion 9eb8da8e80 re #73 - changes from testing Flink summarization job
closes #121 - made metric scanner options configurable
2016-11-30 15:23:45 -05:00
Bill Oley f54705374e Add more robust begin/end time options 2016-11-29 17:16:52 -05:00
Dave Marion 5e166f0044 re #120 - add task to send ping to server at interval 2016-11-21 09:41:50 -05:00
Bill Oley 17527af6b4 re #119 - Create python websocket client 2016-11-16 16:31:50 -05:00
Dave Marion 64f7890f8b re #73 - created Flink job to summarize metrics and re-insert into Timely 2016-11-10 16:03:11 -05:00
Ivan Bella e7c058ef56 closes #116: Simplified the endOfPeriod calculation 2016-11-10 14:11:13 -05:00
Dave Marion 6415d100fc re #118 - send last message at end of subscription with complete flag set to true. Refactor client classes. 2016-11-10 13:25:22 -05:00
Dave Marion 046000c16a closes #118 - don't close connection when metric scanner is done. There might be multiple on one connection, but do inform the Subscription object that the thread is done 2016-11-10 11:25:15 -05:00
Dave Marion d0f6534296 re #118 - close web socket connection on error or when done sending subscription data 2016-11-10 11:05:19 -05:00
Dave Marion 07c04425e1 closes #117 - added endTime to client AddSubscription object 2016-11-09 17:08:28 -05:00
Dave Marion 113b6464f3 closes #116 - aligned the end timestamp to the end of the last downsample period 2016-11-09 15:38:14 -05:00
Dave Marion e2eda4c74a closes #116 - baseline query start to the start of the first downsample window based on the epoch 2016-11-09 10:54:42 -05:00
Dave Marion 68b8d6cd51 closes #115 = added viz tags to metric response on subscriptions 2016-11-08 09:48:52 -05:00
Dave Marion 458028d5df re #111 - remove unused import 2016-11-04 12:05:27 -04:00
Dave Marion 320f291785 closes #113 - fallback to log4j2 2.6.2 2016-11-04 11:27:01 -04:00
Dave Marion 95c2654357 Merge branch 'master' of https://github.com/NationalSecurityAgency/timely 2016-11-03 16:08:57 -04:00
Dave Marion 045964c01d closes #112 - added Java WebSocket client 2016-11-03 14:42:29 -04:00
Ivan Bella 2f8759e32b re #111: Added some useful trace messages 2016-11-03 13:28:28 -04:00
Dave Marion 451f001b9f closes #109 - added endTime to add subscription request 2016-11-03 11:22:03 -04:00
Ivan Bella 044f6576fd close #111: Reimplemented the counter concept for rates. 2016-11-02 17:18:33 -04:00
Dave Marion 520edee16f closes #110 - created client request objects for login and subscription API 2016-11-02 11:09:49 -04:00
Dave Marion 8034a673fc Updated haproxy parsing logic 2016-10-31 12:53:29 -04:00
Dave Marion eb9e874a57 closes #105 - updated most dependencies to latest release 2016-10-27 16:51:20 -04:00
Dave Marion 1658312512 Bumped version for next dev cycle 2016-10-27 13:44:12 -04:00
Dave Marion 2fdfed7554 0.0.3 release 2016-10-27 13:40:22 -04:00
Brian Loss e90340be09 fixes #107 - Fix bad regex in timely Grafana app 2016-10-27 13:21:17 -04:00
Ivan Bella 9770dce1cd re #106: Significant performance enhancement where valid answers are preserved in a separate list avoiding excessive passes through the entire set of captured metrics on each round. 2016-10-26 15:48:56 -04:00
Ivan Bella 8f98199a37 re #101: Removing the first element from the list after computing. Also added some efficiencies in the metrics comparator and the setTopKeyValue method. 2016-10-26 14:52:20 -04:00
Dave Marion 425616d536 Update location of log4j configuration 2016-10-26 13:28:55 -04:00
Dave Marion 158ee4fd55 re #102 - sparse time series optimization 2016-10-26 12:04:25 -04:00
Dave Marion 89e8fd7ebd closes #103 - return rate of zero if no time has elapsed between data points 2016-10-24 11:05:44 -04:00
Dave Marion cb5794491a closes #102 - changes to handle sparse time series. New test. 2016-10-21 16:09:25 -04:00
Dave Marion 8b4f0d5764 closes #99 - re-implemented SlidingWindowFilter and added:
1. Multiple Time Series
2. Ability to handle when a time series drops off
3. Ability to handle when a time series appears
4. New tests
2016-10-20 15:42:52 -04:00
Dave Marion c90b86c4c9 re #91 : remove code added in dd30d54e (#77) because of downsample iterator changes. 2016-10-19 16:47:33 -04:00
Dave Marion f104d02dc6 closes #91 :
1. The DownsampleIterator no longer creates an array of downsamples upon initialization. Instead, it creates downsample buckets as needed.
2. The DownsampleIterator no longer interpolates for missing values.
3. The DownsampleIterator no longer converts counters to rates, this is done with a different iterator. The counter reset value and counter max value are no longer used.
2016-10-14 12:22:03 -04:00
Dave Marion ac85ad1aa7 closes #94 - Fixed rate calculations so that the result is divided by the time difference. Added test for non-constant sample time (added jitter). 2016-10-14 11:53:05 -04:00
Dave Marion 21f3c20a5f closes #93 : pushed common pom things up to the parent pom, removed System.out's in a test, created common module. 2016-10-12 16:07:42 -04:00
klucar b43d877035 final docker fixes, removed unused aggregator from query template 2016-10-10 20:06:22 -04:00
Jim Klucar 8f46ee052c Merge pull request #85 from trucemop/docker
Docker example update
2016-10-10 19:54:07 -04:00
klucar 6cda9c4a60 fixes so templated queries use regex instead of globs 2016-10-10 19:50:05 -04:00
Dave Marion cfc3e491d6 fixes #86 - removed rpm profile from server pom 2016-09-29 13:21:42 -04:00
Dave Marion 23668b294f fixes #84 - calculating rate before downsample iterator using the sliding window iterator 2016-09-29 13:07:41 -04:00
Dave Marion beceb678ea Merge branch 'master' of github.com:NationalSecurityAgency/timely 2016-09-28 14:07:46 -04:00
Dave Marion 4257e28233 Fixup warnings 2016-09-28 14:07:24 -04:00
Brian Loss fb007c170e Ensure server is shut down if there is an error during server.run in order to fix #89. 2016-09-28 14:01:56 -04:00
Dave Marion cabeb2c0d7 re #90 - fix script 2016-09-28 14:00:04 -04:00
Brian Loss e4a2b011a4 Merge branch 'master' of github.com:NationalSecurityAgency/timely 2016-09-28 13:19:13 -04:00
Brian Loss 8f6dfd1644 Update log4j2 configuration to fix #90
moved log4j2.xml to log4j2-spring.xml (recommended by spring boot)
	moved log4j2 config files to conf dir (since it's on the classpath)
	don't specify log4j config file location for spring boot apps (timely server/standalone) since the file is found on the classpath
2016-09-28 13:17:16 -04:00
Dave Marion 33fb3dc857 re #84 - added sliding window iterator which takes a filter. Tests added for moving average and rate calculations 2016-09-28 11:37:15 -04:00
John Highcock 186edeb0f2 Remove extra standalone_test.json 2016-09-26 11:26:39 -04:00
John Highcock a034223376 Install bouncycastle via epel-release 2016-09-25 11:58:51 -04:00
John Highcock 9c75c75aa5 Addressing some of the comments. JAVA_HOME specific version, script quotes, env: prefix for pass. 2016-09-25 09:36:10 -04:00
John Highcock c8646a8d59 Star the timely dashboard 2016-09-24 23:00:18 -04:00
John Highcock b28cb4451b Merge branch 'master' into docker 2016-09-24 21:57:24 -04:00
John Highcock a4b9a1034c Add the grafana bits so that datasource and dashboard are setup. 2016-09-24 21:51:45 -04:00
John Highcock 12e64316eb Can now just run 'docker-compose up' 2016-09-24 19:41:29 -04:00
John Highcock a2389435d4 Working again. 2016-09-24 17:29:17 -04:00
Jim Klucar 6a0455b1cd Merge pull request #82 from klucar/newapi
Timely Client jar and Test speed improvements
2016-09-23 10:19:09 -04:00
Jim Klucar 7abc9e68e4 Standardized on Jackson Json annotations over the XmlElement style
Updated test cases to use compact Json serialization format.
2016-09-22 10:28:34 -04:00
Jim Klucar e45dcb0450 standardized to all Jackson annotations 2016-09-22 09:57:34 -04:00
Jim Klucar a8ce05000e Fixed Metric::equals to ignore ordering of List.
Flattened Json format.
2016-09-22 09:51:57 -04:00
Jim Klucar 747d3e7ad1 removed TODO reminder comment that was fixed 2016-09-19 11:24:05 -04:00
Jim Klucar fcf0ec0b87 removed commented out plugin 2016-09-19 11:22:12 -04:00
Jim Klucar 976c6da07c fixed check to ensure correct number of tags of each type was found 2016-09-19 11:21:44 -04:00
Jim Klucar ed96a0a8e8 made shutdown quiet period configurable. 5 seconds by default 2016-09-19 10:17:48 -04:00
Jim Klucar d5c3bbbcc5 removed old commented out code 2016-09-19 09:22:39 -04:00
John Highcock ec1ac9af73 Remove TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 exception in Dockerfile. 2016-09-16 15:55:45 -04:00
Jim Klucar 2a2d11d01b Made sleep times configurable from the TestConfiguration. 2016-09-16 13:02:24 -04:00
Jim Klucar 14eba0fb93 added exclusion for Server.shutdown. Findbugs doesn't see Collection.forEach as a use and calls the collection of Futures useless 2016-09-16 13:01:57 -04:00
Jim Klucar 418afe8f87 fast server shutdown 2016-09-16 12:31:31 -04:00
Jim Klucar 2b39ec3ab5 Tests now re-use Mini Accumulo Cluster instance 2016-09-16 12:31:31 -04:00
Jim Klucar 2ff895b147 changed core module to client module 2016-09-16 12:31:14 -04:00
Jim Klucar 16a6223c48 Extracted former api classes into a client jar. 2016-09-15 15:40:08 -04:00
Jim Klucar e928ac3746 breaking out models into core module 2016-09-08 14:51:39 -04:00
Dave Marion 8f5d54ebd4 fixes #79 - disable console output from utility class 2016-09-07 10:13:11 -04:00
Dave Marion 123f63972e fixes #78 - changes for Timely to work with Accumulo 1.7 and 1.8 2016-09-06 11:43:42 -04:00
Dave Marion dd30d54ed9 closes #77 - return error when there are too many downsample buckets 2016-09-01 15:47:32 -04:00
Dave Marion d37bf1c824 #16 - per metric ageoff (#76)
* fixes #16, added age off map to configuration to specify ageoff, in days, per metric. "default" accepted. Remove all ageoff iterators on server startup and re-setup with current configuration
2016-08-31 15:48:41 -04:00
Dave Marion 6cd4a8bd67 closes #65: Added support for sending metrics in String and binary form to UDP socket 2016-08-30 15:51:20 -04:00
Dave Marion 1fa71e0c6a fixes #66 - allow metric data to be sent to Timely TCP port in Google Flatbuffers encoded binary form (#74) 2016-08-30 10:06:49 -04:00
Dave Marion c5efeb5be9 closes #69, removed double lexicoder for metric value encoding 2016-08-29 15:07:22 -04:00
Brian Loss 2c8fa5da44 fixes #71: Pass IPs stored in configuration through to tcp, query, and websocket servers. 2016-08-26 14:52:03 -04:00
Dave Marion 9cbc94eb58 turn formatter off so that whitespace changes are not lost 2016-08-26 12:26:07 -04:00
Jim Klucar 4ce4482764 changed whitespace for clarity 2016-08-25 14:29:54 -04:00
Jim Klucar 49a8d970fe minor pom consistency tweaks 2016-08-25 14:18:55 -04:00
brianloss 1be6f24676 Use Spring Configuration (#68)
* Initial pass at converting timely configuration to Spring Boot for type-safe config that is a lot more flexible than standard properties file configuration.

* Change banner color to red.

* Update Configuration class to support fluent API.

* Re-work configuration to group properties in a more sensible manner.

* Use NotBlank everywhere instead of NotEmpty for validations.
2016-08-25 13:19:59 -04:00
Dave Marion 82b94f1a71 fixes #40 - add regex support for lookup requests 2016-08-23 09:26:56 -04:00
Jim Klucar 019c5789ce fixed bug where timely.ssl.trust.store.file property is defined but is empty string 2016-08-05 11:27:43 -04:00
klucar 4336defc14 docker update version 2016-08-05 07:45:56 -04:00
klucar 712a2100bb added instances to metrics 2016-08-05 07:44:57 -04:00
klucar bc46f54604 updated to 0.0.3-SNAPSHOT 2016-08-03 13:54:42 -04:00
Dave Marion 1b713e7874 re #56: fixed some scanner handling, log messages 2016-08-02 10:38:25 -04:00
Dave Marion 3f5dfd2219 fixes #56: Added a Netty handler in the web socket pipeline to get the TSESSIONID cookie value before the web socket upgrade and store it in the channel attribute map. Replaced sessionId property on subscription objects with subscriptionId, which will now allow multiple subscriptions per channel. Modified MetricResponse to contain the subscriptionId in the returned data. Updated tests and example. 2016-08-02 09:29:49 -04:00
Dave Marion ea707fda42 fixes #57, moved plugin/dependency versions to parent pom 2016-08-01 13:02:29 -04:00
klucar b245be476a fixed display name of dashboard 2016-07-28 09:25:24 -04:00
klucar 9619530814 making dashboards available for #59 2016-07-28 09:23:21 -04:00
klucar adf617e59f First cut at #59 2016-07-28 09:20:05 -04:00
Dave Marion 3f49855840 Set version to 0.0.3-SNAPSHOT 2016-07-27 08:23:50 -04:00
721 changed files with 80334 additions and 17175 deletions

No files matched your search

+6
View File
@@ -4,3 +4,9 @@
target/
.idea/
*.iml
logs/
server/pki/
client/src/main/python/setup.py
grafana/timely-app/yarn-error.log
grafana/timely-app/data/
+1
View File
@@ -3,3 +3,4 @@ Eric Newton ([email protected])
Jim Klucar ([email protected])
Charlie Williams ([email protected])
Drew Farris ([email protected])
Brian Loss ([email protected])
+2 -2
View File
@@ -1,7 +1,7 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
httpProperties://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@@ -193,7 +193,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
httpProperties://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
File renamed without changes.
+2 -2
View File
@@ -2,7 +2,7 @@
[![Apache License][li]][ll]
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo] (http://accumulo.apache.org/) and [Grafana] (http://www.grafana.org). Documentation is located [here] (https://nationalsecurityagency.github.io/timely/).
Timely is a time series database application that provides secure access to time series data. Timely is written in Java and designed to work with [Apache Accumulo](https://accumuloProperties.apache.org/) and [Grafana](https://www.grafana.com). Documentation is located [here](https://nationalsecurityagency.github.io/timely/).
[li]: http://img.shields.io/badge/license-ASL-blue.svg
[li]: https://img.shields.io/badge/license-ASL-blue.svg
[ll]: https://www.apache.org/licenses/LICENSE-2.0
+2
View File
@@ -0,0 +1,2 @@
/bin/
/target
+20
View File
@@ -0,0 +1,20 @@
timelyHostname=
timelyTcpPort=
timelyHttpsPort=
timelyWssPort=
doLogin=
#timelyUsername=
#timelyPassword=
keyStoreFile=
keyStoreType=
keyStorePass=
trustStoreFile=
trustStoreType=
trustStorePass=
hostVerificationEnabled=false
bufferSize=10485760
metrics=
startTime=0
endTime=0
interval=1
intervalUnits=HOURS
+252
View File
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>2.2.5</version>
</parent>
<artifactId>timely-analytics</artifactId>
<!--
Execute "mvn clean package -Pbuild-jar"
to build a jar file out of this project!
How to use the Flink Quickstart pom:
a) Adding new dependencies:
You can add dependencies to the list below.
Please check if the maven-shade-plugin below is filtering out your dependency
and remove the exclude from there.
b) Build a jar for running on the cluster:
There are two options for creating a jar from this project
b.1) "mvn clean package" -> this will create a fat jar which contains all
dependencies necessary for running the jar created by this pom in a cluster.
The "maven-shade-plugin" excludes everything that is provided on a running Flink cluster.
b.2) "mvn clean package -Pbuild-jar" -> This will also create a fat-jar, but with much
nicer dependency exclusion handling. This approach is preferred and leads to
much cleaner jar files.
-->
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<!-- We use the maven-shade plugin to create a fat jar that contains all dependencies
except flink and it's transitive dependencies. The resulting fat-jar can be executed
on a cluster. Change the value of Program-Class if your program entry point changes. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactSet>
<excludes>
<!-- This list contains all dependencies of flink-dist
Everything else will be packaged into the fat-jar
-->
<exclude>org.apache.flink:flink-annotations</exclude>
<exclude>org.apache.flink:flink-shaded-hadoop1_2.11</exclude>
<exclude>org.apache.flink:flink-shaded-hadoop2</exclude>
<exclude>org.apache.flink:flink-shaded-curator-recipes</exclude>
<exclude>org.apache.flink:flink-core</exclude>
<exclude>org.apache.flink:flink-java</exclude>
<exclude>org.apache.flink:flink-scala_2.11</exclude>
<exclude>org.apache.flink:flink-runtime_2.11</exclude>
<exclude>org.apache.flink:flink-optimizer_2.11</exclude>
<exclude>org.apache.flink:flink-clients_2.11</exclude>
<exclude>org.apache.flink:flink-avro_2.11</exclude>
<exclude>org.apache.flink:flink-examples-batch_2.11</exclude>
<exclude>org.apache.flink:flink-examples-streaming_2.11</exclude>
<exclude>org.apache.flink:flink-streaming-java_2.11</exclude>
<!-- Also exclude very big transitive dependencies of Flink
WARNING: You have to remove these excludes if your code relies on other
versions of these dependencies.
-->
<exclude>org.scala-lang:scala-library</exclude>
<exclude>org.scala-lang:scala-compiler</exclude>
<exclude>org.scala-lang:scala-reflect</exclude>
<exclude>com.typesafe.akka:akka-actor_*</exclude>
<exclude>com.typesafe.akka:akka-remote_*</exclude>
<exclude>com.typesafe.akka:akka-slf4j_*</exclude>
<exclude>io.netty:netty-all</exclude>
<exclude>io.netty:netty</exclude>
<exclude>commons-fileupload:commons-fileupload</exclude>
<exclude>org.apache.avro:avro</exclude>
<exclude>commons-collections:commons-collections</exclude>
<exclude>org.codehaus.jackson:jackson-core-asl</exclude>
<exclude>org.codehaus.jackson:jackson-mapper-asl</exclude>
<exclude>com.thoughtworks.paranamer:paranamer</exclude>
<exclude>org.xerial.snappy:snappy-java</exclude>
<exclude>org.apache.commons:commons-compress</exclude>
<exclude>org.tukaani:xz</exclude>
<exclude>com.esotericsoftware.kryo:kryo</exclude>
<exclude>com.esotericsoftware.minlog:minlog</exclude>
<exclude>org.objenesis:objenesis</exclude>
<exclude>com.twitter:chill_*</exclude>
<exclude>com.twitter:chill-java</exclude>
<exclude>commons-lang:commons-lang</exclude>
<exclude>junit:junit</exclude>
<exclude>org.apache.commons:commons-lang3</exclude>
<exclude>org.slf4j:slf4j-api</exclude>
<exclude>org.slf4j:slf4j-log4j12</exclude>
<exclude>log4j:log4j</exclude>
<exclude>org.apache.commons:commons-math</exclude>
<exclude>org.apache.sling:org.apache.sling.commons.json</exclude>
<exclude>commons-logging:commons-logging</exclude>
<exclude>commons-codec:commons-codec</exclude>
<exclude>com.fasterxml.jackson.core:jackson-core</exclude>
<exclude>com.fasterxml.jackson.core:jackson-databind</exclude>
<exclude>com.fasterxml.jackson.core:jackson-annotations</exclude>
<exclude>stax:stax-api</exclude>
<exclude>com.typesafe:config</exclude>
<exclude>org.uncommons.maths:uncommons-maths</exclude>
<exclude>com.github.scopt:scopt_*</exclude>
<exclude>commons-io:commons-io</exclude>
<exclude>commons-cli:commons-cli</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/module-info.class</exclude>
</excludes>
</filter>
<filter>
<artifact>org.apache.flink:*</artifact>
<excludes>
<!-- exclude shaded google but include shaded curator -->
<exclude>org/apache/flink/shaded/com/**</exclude>
<exclude>web-docs/**</exclude>
</excludes>
</filter>
<filter>
<!-- Do not copy the signatures in the META-INF folder.
Otherwise, this might cause SecurityExceptions when using the JAR.
-->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<!-- If you want to use ./bin/flink run <quickstart jar> uncomment the following lines.
This will add a Main-Class entry to the manifest file -->
<!--
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>jar.StreamingJob</mainClass>
</transformer>
</transformers>
-->
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Profile for packaging correct JAR files -->
<id>build-jar</id>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- disable the exclusion rules -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<artifactSet>
<excludes combine.self="override" />
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/module-info.class</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,169 @@
package timely.analytics.flink;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.flink.streaming.util.serialization.SerializationSchema;
import timely.model.Tag;
public class MetricHistogram implements Serializable, SerializationSchema<MetricHistogram> {
private static final long serialVersionUID = 1L;
private static final String SPACE = " ";
private static final String AVG = " sample=avg\n";
private static final String COUNT = " sample=count\n";
private static final String MAX = " sample=max\n";
private static final String MIN = " sample=min\n";
private static final String SUM = " sample=sum\n";
private static final String p50 = " sample=50p\n";
private static final String p75 = " sample=75p\n";
private static final String p90 = " sample=90p\n";
private static final String p99 = " sample=99p\n";
private String metric = null;
private List<Tag> tags = null;
private long timestamp = 0L;
private boolean initialized = false;
private List<Double> values = new ArrayList<>();
public void initialize(String metric, List<Tag> tags) {
this.metric = metric;
this.tags = tags;
initialized = true;
}
public boolean isInitialized() {
return initialized;
}
public String getMetric() {
return metric;
}
public List<Tag> getTags() {
return tags;
}
public long getTimestamp() {
return timestamp;
}
public void update(Double d, long timestamp) {
values.add(d);
this.timestamp = timestamp;
}
public void done() {
Collections.sort(values);
}
public Double avg() {
if (values.size() == 0) {
return 0.0D;
}
return sum() / count();
}
public Double min() {
if (values.size() == 0) {
return 0.0D;
}
return values.get(0);
}
public Double max() {
if (values.size() == 0) {
return 0.0D;
}
return values.get(values.size() - 1);
}
public Double getPercentile(int p) {
if (values.size() == 0) {
return 0.0D;
}
int position = Math.round((p * 1.0f / 100) * values.size());
position = (0 == position) ? 0 : position - 1;
return values.get(position);
}
public Double count() {
return values.size() * 1.0D;
}
public Double sum() {
double sum = 0.0D;
for (Double d : values) {
sum += d;
}
return sum;
}
@Override
public String toString() {
done();
StringBuilder buf = new StringBuilder();
buf.append("metric: ").append(metric);
buf.append(" tags: ").append(tags);
buf.append(" timestamp: ").append(timestamp);
buf.append(" count: ").append(count());
buf.append(" min: ").append(min());
buf.append(" max: ").append(max());
buf.append(" sum: ").append(sum());
buf.append(" 50p: ").append(getPercentile(50));
buf.append(" 75p: ").append(getPercentile(75));
buf.append(" 99p: ").append(getPercentile(99));
return buf.toString();
}
public byte[] serialize(MetricHistogram histo) {
StringBuilder buf = new StringBuilder();
String tags = histo.getTags().stream().map(t -> t.join()).collect(Collectors.joining(" "));
// @formatter:off
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.min())
.append(SPACE).append(tags).append(MIN);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.max())
.append(SPACE).append(tags).append(MAX);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.count())
.append(SPACE).append(tags).append(COUNT);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.sum())
.append(SPACE).append(tags).append(SUM);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.avg())
.append(SPACE).append(tags).append(AVG);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(50))
.append(SPACE).append(tags).append(p50);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(75))
.append(SPACE).append(tags).append(p75);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(90))
.append(SPACE).append(tags).append(p90);
buf.append("put ").append(histo.getMetric() + "_summarized")
.append(SPACE).append(histo.getTimestamp())
.append(SPACE).append(histo.getPercentile(99))
.append(SPACE).append(tags).append(p99);
// @formatter:off
return buf.toString().getBytes();
}
}
@@ -0,0 +1,19 @@
package timely.analytics.flink;
import org.apache.flink.api.java.functions.KeySelector;
import timely.api.response.MetricResponse;
/**
* Uniquely defines a time series in a Flink stream. A unique time series is the metric name and all of its tags, to include visibility if it exists.
*/
public class MetricKeySelector implements KeySelector<MetricResponse,String> {
private static final long serialVersionUID = 1L;
@Override
public String getKey(MetricResponse m) throws Exception {
return m.getMetric() + m.getTags().toString();
}
}
@@ -0,0 +1,55 @@
package timely.analytics.flink;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.commons.lang3.mutable.MutableLong;
import org.apache.flink.api.common.accumulators.Accumulator;
/**
* Custom accumulator that retains a sorted map of strings and their counts
*
*/
public class SortedStringAccumulator implements Accumulator<String,ConcurrentSkipListMap<String,MutableLong>> {
private static final long serialVersionUID = 1L;
private ConcurrentSkipListMap<String,MutableLong> values = new ConcurrentSkipListMap<>();
@Override
public void add(String value) {
if (values.containsKey(value)) {
values.get(value).increment();
} else {
values.put(value, new MutableLong(1));
}
}
@Override
public ConcurrentSkipListMap<String,MutableLong> getLocalValue() {
return values;
}
@Override
public void resetLocal() {
values.clear();
}
@Override
public void merge(Accumulator<String,ConcurrentSkipListMap<String,MutableLong>> other) {
other.getLocalValue().forEach((k, v) -> {
if (values.containsKey(k)) {
values.get(k).add(v.longValue());
} else {
values.put(k, v);
}
});
}
@Override
public Accumulator<String,ConcurrentSkipListMap<String,MutableLong>> clone() {
SortedStringAccumulator a = new SortedStringAccumulator();
a.getLocalValue().putAll(this.values);
return a;
}
}
@@ -0,0 +1,215 @@
package timely.analytics.flink;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import org.apache.flink.api.common.accumulators.LongCounter;
import org.apache.flink.api.common.functions.StoppableFunction;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import org.apache.flink.streaming.api.watermark.Watermark;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.response.MetricResponse;
import timely.api.response.MetricResponses;
import timely.client.websocket.ClientHandler;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.serialize.JsonSerializer;
public class SubscriptionSource extends RichSourceFunction<MetricResponse> implements Serializable, StoppableFunction {
private static class SubscriptionThreadFactory implements ThreadFactory {
private static final String NAME = "Subscription Source";
private AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, NAME + " " + threadNum.getAndIncrement());
}
}
private static final Logger log = LoggerFactory.getLogger(SubscriptionSource.class);
private static final long serialVersionUID = 1L;
private transient WebSocketSubscriptionClient client = null;
private long start = 0L;
private long end = 0L;
private final String[] metrics;
private final SummarizationJobParameters jp;
private final long window;
private final AtomicInteger subscriptionsRemaining;
public SubscriptionSource(SummarizationJobParameters jp) {
this.jp = jp;
start = jp.getStartTime();
end = jp.getEndTime();
metrics = jp.getMetrics();
window = jp.getSummarizationInterval().toMilliseconds();
subscriptionsRemaining = new AtomicInteger(metrics.length);
}
@Override
public void open(Configuration parameters) throws Exception {
log.info("Opening summarization job.");
super.open(parameters);
client = new WebSocketSubscriptionClient(jp.getTimelyHostname(), jp.getTimelyHttpsPort(), jp.getTimelyWssPort(), jp.isClientAuth(), jp.isDoLogin(),
jp.getKeyStoreFile(), jp.getKeyStoreType(), jp.getKeyStorePass(), jp.getTrustStoreFile(), jp.getTrustStoreType(),
jp.getTrustStorePass(), jp.isHostVerificationEnabled(), jp.getBufferSize());
}
@Override
public synchronized void close() throws Exception {
super.close();
if (null != client) {
client.close();
}
}
@Override
public void run(SourceContext<MetricResponse> ctx) throws Exception {
log.info("Running summarization job");
final SortedStringAccumulator dateTimeAccumulator = new SortedStringAccumulator();
this.getRuntimeContext().addAccumulator("source hourly count", dateTimeAccumulator);
final LongCounter sourceInputs = new LongCounter();
this.getRuntimeContext().addAccumulator("source input counter", sourceInputs);
final LongCounter inputProcessedInMainThread = new LongCounter();
this.getRuntimeContext().addAccumulator("input processed in main thread", inputProcessedInMainThread);
// Use only one thread to ensure that time is still ordered
final ExecutorService svc = Executors.newFixedThreadPool(1, new SubscriptionThreadFactory());
ClientHandler handler = new ClientHandler() {
@Override
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(String.class, new MessageHandler.Whole<String>() {
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
private long lastWatermarkTime = 0L;
@Override
public void onMessage(final String message) {
log.info("Message received on Websocket session {}, length: {}", session.getId(), message.length());
try {
// Deserialize in this thread
final MetricResponses responses = JsonSerializer.getObjectMapper().readValue(message, MetricResponses.class);
// Process in the other
final Runnable r = () -> {
responses.getResponses().forEach(response -> {
if (response.isComplete()) {
log.info("Received last message.");
ctx.emitWatermark(new Watermark(Long.MAX_VALUE));
subscriptionsRemaining.getAndDecrement();
return;
}
log.trace("Received metric: {}", response);
long time = response.getTimestamp();
ctx.collectWithTimestamp(response, time);
dateTimeAccumulator.add(formatter.format(new Date(response.getTimestamp())));
sourceInputs.add(1);
// Emit a watermark every second of event
// time
if (lastWatermarkTime == 0) {
lastWatermarkTime = time;
} else if ((time - lastWatermarkTime) > window) {
lastWatermarkTime = time;
ctx.emitWatermark(new Watermark(time - 1));
}
});
};
try {
svc.execute(r);
} catch (RejectedExecutionException e) {
// Run it in this thread
r.run();
inputProcessedInMainThread.add(1);
}
} catch (IOException e) {
log.error("Error deserializing metric response, closing", e);
try {
close();
} catch (Exception e1) {
log.error("Error closing client", e1);
}
}
}
});
}
@Override
public void onClose(Session session, CloseReason reason) {
super.onClose(session, reason);
try {
close();
} catch (Exception e1) {
log.error("Error closing client", e1);
}
// Signal done sending data
ctx.emitWatermark(new Watermark(Long.MAX_VALUE));
}
@Override
public void onError(Session session, Throwable error) {
super.onError(session, error);
try {
close();
} catch (Exception e1) {
log.error("Error closing client", e1);
}
}
};
try {
client.open(handler);
for (String m : metrics) {
log.info("Adding subscription for {}", m);
client.addSubscription(m, null, start, end, 5000);
}
while (!client.isClosed() && subscriptionsRemaining.get() > 0) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
log.error("Sleep interrupted.");
}
}
} finally {
svc.shutdown();
}
}
@Override
public void cancel() {
log.info("Cancelling subscription source.");
try {
close();
} catch (Exception e) {
log.error("Error closing web socket client");
}
}
@Override
public void stop() {
log.info("Stopping subscription source.");
try {
close();
} catch (Exception e) {
log.error("Error closing web socket client");
}
}
}
@@ -0,0 +1,109 @@
package timely.analytics.flink;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.flink.api.common.accumulators.LongCounter;
import org.apache.flink.api.common.functions.FoldFunction;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.SocketClientSink;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.response.MetricResponse;
public class SummarizationJob {
private static final Logger log = LoggerFactory.getLogger(SummarizationJob.class);
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: timely.analytics.flink.SummarizationJob <path_to_properties_file>");
System.exit(1);
}
log.info("Loading properties from: {}", args[0]);
ParameterTool params = ParameterTool.fromPropertiesFile(args[0]);
final SummarizationJobParameters jp = new SummarizationJobParameters(params);
log.info("Properties: {}", jp);
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// @formatter:off
env.getConfig().setGlobalJobParameters(jp);
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
env.addSource(new SubscriptionSource(jp), "Timely Metrics Subscription")
// partition by unique metric
.keyBy(new MetricKeySelector())
.window(TumblingEventTimeWindows.of(jp.getSummarizationInterval()))
.fold(new MetricHistogram(), new FoldFunction<MetricResponse, MetricHistogram>() {
private final Logger log = LoggerFactory
.getLogger("timely.analytics.SummarizationJob.FoldFunction@");
private static final long serialVersionUID = 1L;
@Override
public MetricHistogram fold(MetricHistogram histo, MetricResponse metric) throws Exception {
if (!histo.isInitialized()) {
histo.initialize(metric.getMetric(), metric.getTags());
} else {
if (!histo.getMetric().equals(metric.getMetric())) {
throw new RuntimeException("Metric name does not match - partitioning problem");
}
if (!histo.getTags().equals(metric.getTags())) {
throw new RuntimeException("Tags don't not match - partitioning problem");
}
}
log.trace("Updating histogram for {} with value {} at {} for metric {}", histo.getMetric()
+ histo.getTags(), metric.getValue(), metric.getTimestamp(), metric.getMetric()
+ metric.getTags());
histo.update(metric.getValue(), metric.getTimestamp());
return histo;
}
})
.name("Metric Windows")
.addSink(
new SocketClientSink<MetricHistogram>(jp.getTimelyHostname(), jp.getTimelyTcpPort(),
new MetricHistogram(), 2, true) {
private final Logger log = LoggerFactory
.getLogger("timely.analytics.SummarizationJob.SocketClientSink");
private static final long serialVersionUID = 1L;
private final LongCounter sinkCounter = new LongCounter();
private final SortedStringAccumulator dateTimeAccumulator = new SortedStringAccumulator();
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
this.getRuntimeContext().addAccumulator("sink counter", this.sinkCounter);
this.getRuntimeContext().addAccumulator("sink hourly count", dateTimeAccumulator);
log.info("Sink opened");
}
@Override
public void invoke(MetricHistogram histo) throws Exception {
histo.done();
super.invoke(histo);
this.sinkCounter.add(1);
this.dateTimeAccumulator.add(formatter.format(new Date(histo.getTimestamp())));
}
@Override
public void close() throws Exception {
super.close();
log.info("Sink closed");
}
}).name("Timely Metric Sink");
// @formatter:on
env.execute("Metric Summarization");
// Looks like there is no way to stop Flink job if underlying web socket
// is closed.
}
}
@@ -0,0 +1,215 @@
package timely.analytics.flink;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.flink.api.common.ExecutionConfig.GlobalJobParameters;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.windowing.time.Time;
public class SummarizationJobParameters extends GlobalJobParameters implements Serializable {
private static final long serialVersionUID = 1L;
private final String timelyHostname;
private final int timelyTcpPort;
private final int timelyHttpsPort;
private final int timelyWssPort;
private final boolean clientAuth;
private final boolean doLogin;
private final String timelyUsername;
private final String timelyPassword;
private final String keyStoreFile;
private final String keyStoreType;
private final String keyStorePass;
private final String trustStoreFile;
private final String trustStoreType;
private final String trustStorePass;
private final boolean hostVerificationEnabled;
private final int bufferSize;
private final String[] metrics;
private final long startTime;
private final long endTime;
private final String interval;
private final String intervalUnits;
public SummarizationJobParameters(ParameterTool params) {
timelyHostname = params.getRequired("timelyHostname");
timelyTcpPort = params.getInt("timelyTcpPort", 4241);
timelyHttpsPort = params.getInt("timelyHttpsPort", 4242);
timelyWssPort = params.getInt("timelyWssPort", 4243);
clientAuth = params.getBoolean("clientAuth", false);
doLogin = params.getBoolean("doLogin", false);
timelyUsername = params.get("timelyUsername", null);
timelyPassword = params.get("timelyPassword", null);
keyStoreFile = params.getRequired("keyStoreFile");
keyStoreType = params.get("keyStoreType", "JKS");
keyStorePass = params.getRequired("keyStorePass");
trustStoreFile = params.getRequired("trustStoreFile");
trustStoreType = params.get("trustStoreType", "JKS");
trustStorePass = params.getRequired("trustStorePass");
hostVerificationEnabled = params.getBoolean("hostVerificationEnabled", true);
bufferSize = params.getInt("bufferSize", 10485760);
String metricNames = params.getRequired("metrics");
if (null != metricNames) {
metrics = metricNames.split(",");
} else {
metrics = null;
}
startTime = params.getLong("startTime", 0L);
endTime = params.getLong("endTime", 0L);
interval = params.getRequired("interval");
intervalUnits = params.getRequired("intervalUnits");
}
public SummarizationJobParameters(Configuration conf) {
System.out.println("Creating job parameters from configuration: " + conf);
timelyHostname = conf.getString("timelyHostname", null);
timelyTcpPort = conf.getInteger("timelyTcpPort", 4241);
timelyHttpsPort = conf.getInteger("timelyHttpsPort", 4242);
timelyWssPort = conf.getInteger("timelyWssPort", 4243);
clientAuth = conf.getBoolean("clientAuth", false);
doLogin = conf.getBoolean("doLogin", false);
timelyUsername = conf.getString("timelyUsername", null);
timelyPassword = conf.getString("timelyPassword", null);
keyStoreFile = conf.getString("keyStoreFile", null);
keyStoreType = conf.getString("keyStoreType", "JKS");
keyStorePass = conf.getString("keyStorePass", null);
trustStoreFile = conf.getString("trustStoreFile", null);
trustStoreType = conf.getString("trustStoreType", "JKS");
trustStorePass = conf.getString("trustStorePass", null);
hostVerificationEnabled = conf.getBoolean("hostVerificationEnabled", true);
bufferSize = conf.getInteger("bufferSize", 10485760);
String metricNames = conf.getString("metrics", null);
if (null != metricNames) {
metrics = metricNames.split(",");
} else {
metrics = null;
}
startTime = conf.getLong("startTime", 0L);
endTime = conf.getLong("endTime", 0L);
interval = conf.getString("interval", null);
intervalUnits = conf.getString("intervalUnits", null);
}
public Time getSummarizationInterval() {
return Time.of(Long.parseLong(interval), TimeUnit.valueOf(intervalUnits));
}
public String getTimelyHostname() {
return timelyHostname;
}
public int getTimelyTcpPort() {
return timelyTcpPort;
}
public int getTimelyHttpsPort() {
return timelyHttpsPort;
}
public int getTimelyWssPort() {
return timelyWssPort;
}
public boolean isClientAuth() {
return clientAuth;
}
public boolean isDoLogin() {
return doLogin;
}
public String getTimelyUsername() {
return timelyUsername;
}
public String getTimelyPassword() {
return timelyPassword;
}
public String getKeyStoreFile() {
return keyStoreFile;
}
public String getKeyStoreType() {
return keyStoreType;
}
public String getKeyStorePass() {
return keyStorePass;
}
public String getTrustStoreFile() {
return trustStoreFile;
}
public String getTrustStoreType() {
return trustStoreType;
}
public String getTrustStorePass() {
return trustStorePass;
}
public boolean isHostVerificationEnabled() {
return hostVerificationEnabled;
}
public int getBufferSize() {
return bufferSize;
}
public String[] getMetrics() {
return metrics;
}
public long getStartTime() {
return startTime;
}
public long getEndTime() {
return endTime;
}
public String getInterval() {
return interval;
}
public String getIntervalUnits() {
return intervalUnits;
}
@Override
public Map<String,String> toMap() {
Map<String,String> m = new HashMap<>();
m.put("timelyHostname", this.timelyHostname);
m.put("timelyHttpsPort", Integer.toString(this.timelyHttpsPort));
m.put("timelyWssPort", Integer.toString(this.timelyWssPort));
m.put("doLogin", Boolean.toString(this.doLogin));
m.put("timelyUsername", this.timelyUsername);
m.put("timelyPassword", this.timelyPassword);
m.put("keyStoreFile", this.keyStoreFile);
m.put("keyStoreType", this.keyStoreType);
m.put("keyStorePass", this.keyStorePass);
m.put("trustStoreFile", this.trustStoreFile);
m.put("trustStoreType", this.trustStoreType);
m.put("trustStorePass", this.trustStorePass);
m.put("hostVerificationEnabled", Boolean.toString(this.hostVerificationEnabled));
m.put("bufferSize", Integer.toString(this.bufferSize));
m.put("metrics", String.join(",", this.metrics));
m.put("startTime", Long.toString(this.startTime));
m.put("endTime", Long.toString(this.endTime));
m.put("interval", this.interval);
m.put("intervalUnits", this.intervalUnits);
return m;
}
@Override
public String toString() {
return toMap().toString();
}
}
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="timely.analytics.flink.MetricHistogram" />
<Method name="serialize" />
<Bug pattern="DM_DEFAULT_ENCODING" />
</Match>
<Match>
<Class name="timely.analytics.flink.SortedStringAccumulator" />
<Method name="clone" />
<Bug pattern="CN_IDIOM_NO_SUPER_CALL" />
</Match>
<Match>
<Class name="timely.analytics.flink.SummarizationJobParameters" />
<Method name="getMetrics" />
<Bug pattern="EI_EXPOSE_REP" />
</Match>
<Match>
<!-- these are probably avoidable, but needs further triage to fix -->
<Bug pattern="EI_EXPOSE_REP" />
</Match>
<Match>
<!-- these are probably avoidable, but needs further triage to fix -->
<Bug pattern="EI_EXPOSE_REP2" />
</Match>
</FindBugsFilter>
@@ -0,0 +1,106 @@
package timely.analytics.flink;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import timely.model.Metric;
import timely.model.Tag;
import timely.model.parse.MetricParser;
public class MetricHistogramTest {
private MetricHistogram m = null;
private long timestamp = System.currentTimeMillis();
@Before
public void createMetric() throws Exception {
m = new MetricHistogram();
for (double d = 1.0D; d <= 100.0; d += 1.0) {
timestamp += 1000;
m.update(d, timestamp);
}
m.toString();
Assert.assertEquals(timestamp, m.getTimestamp());
}
@Test
public void testMin() throws Exception {
Assert.assertEquals(1.0D, m.min(), 0.0D);
}
@Test
public void testMax() throws Exception {
Assert.assertEquals(100.0D, m.max(), 0.0D);
}
@Test
public void testAvg() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
Assert.assertEquals((sum / 100.0D), m.avg(), 0.0D);
}
@Test
public void testCount() throws Exception {
Assert.assertEquals(100.0D, m.count(), 0.0D);
}
@Test
public void test50thPercentile() throws Exception {
Assert.assertEquals(50.0D, m.getPercentile(50), 0.0D);
}
@Test
public void test75thPercentile() throws Exception {
Assert.assertEquals(75.0D, m.getPercentile(75), 0.0D);
}
@Test
public void test90thPercentile() throws Exception {
Assert.assertEquals(90.0D, m.getPercentile(90), 0.0D);
}
@Test
public void test99thPercentile() throws Exception {
Assert.assertEquals(99.0D, m.getPercentile(99), 0.0D);
}
@Test
public void testSerialization() throws Exception {
MetricParser metricParser = new MetricParser();
Tag t1 = new Tag("tag1=value1");
Tag t2 = new Tag("tag2=value2");
Tag avg = new Tag("sample=avg");
Tag min = new Tag("sample=min");
Tag max = new Tag("sample=max");
Tag sum = new Tag("sample=sum");
Tag count = new Tag("sample=count");
Tag p50 = new Tag("sample=50p");
Tag p75 = new Tag("sample=75p");
Tag p90 = new Tag("sample=90p");
Tag p99 = new Tag("sample=99p");
List<Tag> tags = new ArrayList<>();
tags.add(t1);
tags.add(t2);
m.initialize("sys.cpu.user", tags);
byte[] bytes = m.serialize(m);
String puts = new String(bytes);
for (String put : puts.split("\n")) {
Metric metric = metricParser.parse(put);
Assert.assertEquals("sys.cpu.user_summarized", metric.getName());
metric.getTags().forEach(t -> {
Assert.assertTrue(t.equals(t1) || t.equals(t2) || t.equals(avg) || t.equals(min) || t.equals(max) || t.equals(sum) || t.equals(count)
|| t.equals(p50) || t.equals(p75) || t.equals(p90) || t.equals(p99));
});
}
}
}
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
else
THIS_SCRIPT=`readlink -f $0`
fi
THIS_DIR="${THIS_SCRIPT%/*}"
BASE_DIR=$(cd $THIS_DIR/.. && pwd)
BIN_DIR=${BASE_DIR}/bin
. ${BIN_DIR}/timely-balancer-env.sh
for i in $(seq 1 $NUM_INSTANCES); do
echo ${BIN_DIR}/timely-balancer.sh $i
${BIN_DIR}/timely-balancer.sh $i
done
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
else
THIS_SCRIPT=`readlink -f $0`
fi
THIS_DIR="${THIS_SCRIPT%/*}"
BASE_DIR=$(cd $THIS_DIR/.. && pwd)
BIN_DIR=${BASE_DIR}/bin
. ${BIN_DIR}/timely-balancer-env.sh
for i in $(seq 1 $NUM_INSTANCES); do
PID=`jps -m | grep -E 'timely-balancer-.*-exec.jar' | grep "instance=${i}" | awk '{print $1}'`
if [ "$PID" != "" ]; then
echo "stopping timely-balancer instance ${i} [${PID}]"
kill -9 $PID
fi
done
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
NUM_INSTANCES=1
ACCUMULO_ZOOKEEPERS=localhost:2181
HADOOP_CONF_DIR=${HADOOP_CONF_DIR}
HADOOP_DEFAULT_FS=hdfs://localhost:8020
TCP_BASE_PORT=4242
HTTP_BASE_PORT=4243
WEBSOCKET_BASE_PORT=4244
UDP_BASE_PORT=4245
HOST_IP=0.0.0.0
HOST=$(hostname -s)
HOST_VERIFICATION_ENABLED=true
GENERATE_KEYS=true
TRUST_STORE_TYPE=
TRUST_STORE_FILE=
TRUST_STORE_PASSWORD=
KEY_STORE_TYPE=
KEY_STORE_FILE=
KEY_STORE_PASSWORD=
AUTHORIZATION_URL=
AUTHORIZATION_CERTIFICATE_TYPE=
AUTHORIZATION_CERTIFICATE_FILE=
ANONYMOUS_HTTP=true
ANONYMOUS_WEBSOCKET=true
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
if [[ $(uname) == "Darwin" ]]; then
THIS_SCRIPT=$(python -c 'import os,sys; print os.path.realpath(sys.argv[1])' "$0")
else
THIS_SCRIPT=$(readlink -f "$0")
HW=`uname -m`
OS_PATH=linux-${HW}
fi
if [ -z "$1" ]; then
export INSTANCE="1"
else
export INSTANCE="$1"
fi
PID=$(jps -m | grep -E 'timely-balancer-.*-exec.jar' | grep "instance=${INSTANCE}" | awk '{print $1}')
if [ "$PID" == "" ]; then
THIS_DIR="${THIS_SCRIPT%/*}"
BASE_DIR=$(cd "$THIS_DIR"/.. && pwd)
LIB_DIR="${BASE_DIR}/lib"
BIN_DIR="${BASE_DIR}/bin"
export CONF_DIR="${BASE_DIR}/conf"
NATIVE_DIR="${BIN_DIR}/META-INF/native"
set -a
. "${BIN_DIR}/timely-balancer-env.sh"
set +a
# use either a value from timely-balancer-env.sh or the default
export LOG_DIR="${TIMELY_LOG_DIR:-${BASE_DIR}/logs}"
if [[ ! -e ${NATIVE_DIR} ]]; then
mkdir -p "${NATIVE_DIR}"
fi
if [[ ! -e ${LOG_DIR} ]]; then
mkdir "${LOG_DIR}"
fi
JVM_ARGS="-Xmx2G -Xms2G -XX:NewSize=1G -XX:MaxNewSize=1G"
JVM_ARGS="${JVM_ARGS} -Dlogging.config=${CONF_DIR}/log4j2.yml"
JVM_ARGS="${JVM_ARGS} -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -XX:+UseG1GC -XX:+UseStringDeduplication"
JVM_ARGS="${JVM_ARGS} -Djava.net.preferIPv4Stack=true"
JVM_ARGS="${JVM_ARGS} -XX:+UseNUMA"
# for linux varieties of OS, extract native libraries if not already present
if [[ -n "$OS_PATH" ]]; then
if [[ ! -d ${NATIVE_DIR} || -z "$(ls -A ${NATIVE_DIR})" ]]; then
pushd "${BASE_DIR}/bin" || exit
"${JAVA_HOME}"/bin/jar xf "${LIB_DIR}"/netty-tcnative-boringssl-static-*.Final-"${OS_PATH}".jar META-INF/native/libnetty_tcnative_linux_"${HW}".so
"${JAVA_HOME}"/bin/jar xf "${LIB_DIR}"/netty-transport-native-epoll-*-"${OS_PATH}".jar META-INF/native/libnetty_transport_native_epoll_"${HW}".so
popd || exit
fi
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
fi
echo "${JAVA_HOME}/bin/java ${JVM_ARGS} -jar ${BIN_DIR}/timely-balancer-*-exec.jar --instance=${INSTANCE}"
nohup "${JAVA_HOME}"/bin/java ${JVM_ARGS} -jar "${BIN_DIR}"/timely-balancer-*-exec.jar --instance="${INSTANCE}" >> "${LOG_DIR}/timely-balancer${INSTANCE}.out" 2>&1 &
else
echo "timely-balancer --instance=${INSTANCE} already running with pid ${PID}"
fi
+36
View File
@@ -0,0 +1,36 @@
Configuration:
status: warn
monitorInterval: 60
Properties:
Property:
- name: logDir
value: ${env:LOG_DIR}
- name: PID
value: "????"
- name: LOG_PATTERN
value: "%d{ISO8601} %-5p [%-40.40t] [%-40.40c{1.}] %m%n%wEx"
Appenders:
RollingFile:
- name: File
fileName: "${sys:logDir}/timely-balancer${env:INSTANCE}.log"
filePattern: "${sys:logDir}/timely-balancer${env:INSTANCE}.log.%d{yyyy-MM-dd}-%i.gz"
append: true
bufferedIO: true
bufferSize: 8192
Policies:
TimeBasedTriggeringPolicy:
interval: 1
SizeBasedTriggeringPolicy:
size: 250MB
DefaultRolloverStrategy:
max: 10
PatternLayout:
pattern: "${LOG_PATTERN}"
Loggers:
Root:
level: info
AppenderRef:
- ref: File
+168
View File
@@ -0,0 +1,168 @@
timely:
instance: ${INSTANCE}
zookeeper:
servers: ${ACCUMULO_ZOOKEEPERS}
timeout: 30s
security:
session-max-age: 86400
cache-expiration-minutes: 60
cache-refresh-minutes: 5
authorization-url: ${AUTHORIZATION_URL}
allow-anonymous-http-access: ${ANONYMOUS_HTTP:true}
allow-anonymous-ws-access: ${ANONYMOUS_WEBSOCKET:true}
server-ssl:
key-store-type: ${KEY_STORE_TYPE}
key-store-file: ${KEY_STORE_FILE}
key-store-password: ${KEY_STORE_PASSWORD}
use-generated-keypair: ${GENERATE_KEYS}
ignore-ssl-handshake-errors: true
trust-store-type: ${TRUST_STORE_TYPE}
trust-store-file: ${TRUST_STORE_FILE}
trust-store-password: ${TRUST_STORE_PASSWORD}
client-ssl:
key-store-type: ${KEY_STORE_TYPE}
key-store-file: ${KEY_STORE_FILE}
key-store-password: ${KEY_STORE_PASSWORD}
trust-store-type: ${TRUST_STORE_TYPE}
trust-store-file: ${TRUST_STORE_FILE}
trust-store-password: ${TRUST_STORE_PASSWORD}
hostVerificationEnabled: ${HOST_VERIFICATION_ENABLED:true}
jwt-ssl:
key-store-type: ${AUTHORIZATION_CERTIFICATE_TYPE}
key-store-file: ${AUTHORIZATION_CERTIFICATE_FILE}
key-store-password: ${AUTHORIZATION_CERTIFICATE_PASSWORD}
cache:
enabled: false
metric-age-off-hours:
default: 26
non-cached-metrics:
balancer:
check-server-health-interval: 10000
server-failures-before-down: 6
server-successes-before-up: 1
default-fs: ${HADOOP_DEFAULT_FS}
fs-config-resources:
- ${HADOOP_CONF_DIR}/core-site.xml
- ${HADOOP_CONF_DIR}/hdfs-site.xml
assignment-file: hdfs:///timely/balancer/assignments.csv
server:
ip: ${HOST_IP}
tcp-base-port: ${TCP_BASE_PORT}
tcp-buffer-size: 100
tcp-client-pool:
min-idle-per-key: 5
max-idle-per-key: 5
max-total-per-key: 5
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
udp-base-port: ${UDP_BASE_PORT}
udp-client-pool:
min-idle-per-key: 5
max-idle-per-key: 5
max-total-per-key: 5
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
http:
host: ${HOST}
ip: ${HOST_IP}
base-port: ${HTTP_BASE_PORT}
redirect-path: /secure-me
strict-transport-max-age: 604800
connection-request-timeout: 5000
connect-timeout: 5000
socketTimeout: 300000
request-timeout: 300000
cors:
allow-any-origin: false
allow-null-origin: false
allow-credentials: true
allowed-origins:
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
allowed-headers: content-type
httpClientPool:
min-idle-per-key: 10
max-idle-per-key: 40
max-total-per-key: 40
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
websocket:
ip: ${HOST_IP}
base-port: ${WEBSOCKET_BASE_PORT}
timeout: 60
subscription-lag: 120
scanner-batch-size: 10000
flush-interval-seconds: 30
scanner-read-ahead: 1
subscription-batch-size: 5000
incoming-buffer-size: 50000000
ws-client-pool:
min-idle-per-key: 0
max-idle-per-key: 40
max-total-per-key: 40
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
logging:
level:
ROOT: info
timely: debug
+356
View File
@@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>2.2.5</version>
</parent>
<artifactId>timely-balancer</artifactId>
<description>Timely Balancer</description>
<properties>
<start-class>timely.balancer.TimelyBalancer</start-class>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.google.flatbuffers</groupId>
<artifactId>flatbuffers-java</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-classes-epoll</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.javacsv</groupId>
<artifactId>javacsv</artifactId>
</dependency>
<dependency>
<groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-client</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<classifier>linux-aarch_64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<classifier>linux-x86_64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<classifier>linux-aarch_64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<classifier>linux-x86_64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minicluster</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>src/main/dist/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<excludeArtifactIds>junit</excludeArtifactIds>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-native</id>
<goals>
<goal>unpack</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/native</outputDirectory>
<includes>**/*.so</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifest>
<!--suppress UnresolvedMavenProperty, MavenModelInspection -->
<mainClass>${start-class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<goals>
<goal>add-source</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<sources>
<source>src/main/generated-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<extensions>
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
</extension>
</extensions>
</build>
<profiles>
<profile>
<id>docker</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
+50
View File
@@ -0,0 +1,50 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>netty-tcnative-boringssl-static*linux-x86_64.jar</include>
<include>netty-tcnative-boringssl-static*linux-aarch_64.jar</include>
<include>netty-transport-native-epoll*linux-x86_64.jar</include>
<include>netty-transport-native-epoll*linux-aarch_64.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>timely-balancer-*-exec.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/conf</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
</fileSets>
</assembly>
+11
View File
@@ -0,0 +1,11 @@
FROM azul/zulu-openjdk-alpine:11
LABEL version=${project.version} \
run="docker run ${docker.image.prefix}${project.artifactId}:latest" \
description="${project.description}"
ADD ${project.build.finalName}-exec.jar /app.jar
RUN apk add libc6-compat curl
EXPOSE 54322 54323 54324 54325
ENTRYPOINT ["java","-jar","app.jar"]
@@ -0,0 +1,121 @@
package timely.balancer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.google.common.util.concurrent.AtomicDouble;
public class ArrivalRate {
private AtomicLong shortTermArrivals = new AtomicLong(0);
private AtomicDouble shortTermRate = new AtomicDouble(0);
private AtomicLong shortTermLastReset = new AtomicLong(0);
private AtomicLong mediumTermArrivals = new AtomicLong(0);
private AtomicDouble mediumTermRate = new AtomicDouble(0);
private AtomicLong mediumTermLastReset = new AtomicLong(0);
private AtomicLong longTermArrivals = new AtomicLong(0);
private AtomicDouble longTermRate = new AtomicDouble(0);
private AtomicLong longTermLastReset = new AtomicLong(0);
public ArrivalRate(ScheduledExecutorService arrivalRateExecutor) {
long now = System.currentTimeMillis();
shortTermLastReset.set(now);
mediumTermLastReset.set(now);
longTermLastReset.set(now);
// 2 minute
arrivalRateExecutor.scheduleAtFixedRate(() -> {
resetShort();
}, 2, 2, TimeUnit.MINUTES);
// 5 minute
arrivalRateExecutor.scheduleAtFixedRate(() -> {
resetMedium();
}, 5, 5, TimeUnit.MINUTES);
// 15 minute
arrivalRateExecutor.scheduleAtFixedRate(() -> {
resetLong();
}, 15, 15, TimeUnit.MINUTES);
}
private void resetShort() {
long now = System.currentTimeMillis();
long lastReset = shortTermLastReset.get();
if (now == lastReset) {
shortTermRate.set(0);
} else {
shortTermRate.set(shortTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
shortTermArrivals.set(0);
shortTermLastReset.set(now);
}
private void resetMedium() {
long now = System.currentTimeMillis();
long lastReset = mediumTermLastReset.get();
if (now == lastReset) {
mediumTermRate.set(0);
} else {
mediumTermRate.set(mediumTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
mediumTermArrivals.set(0);
mediumTermLastReset.set(now);
}
private void resetLong() {
long now = System.currentTimeMillis();
long lastReset = longTermLastReset.get();
if (now == lastReset) {
longTermRate.set(0);
} else {
longTermRate.set(longTermArrivals.get() / (((double) (now - lastReset)) / 1000));
}
longTermArrivals.set(0);
longTermLastReset.set(now);
}
public void arrived() {
shortTermArrivals.incrementAndGet();
mediumTermArrivals.incrementAndGet();
longTermArrivals.incrementAndGet();
}
private double getShortRate(long now) {
double rate = shortTermRate.get();
long lastReset = shortTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return shortTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
private double getMediumRate(long now) {
double rate = mediumTermRate.get();
long lastReset = mediumTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return mediumTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
private double getLongRate(long now) {
double rate = longTermRate.get();
long lastReset = longTermLastReset.get();
if (rate > 0 || now == lastReset) {
return rate;
} else {
return longTermArrivals.get() / (((double) (now - lastReset)) / 1000);
}
}
public double getRate() {
long now = System.currentTimeMillis();
return 0.10 * getLongRate(now) + 0.10 * getMediumRate(now) + 0.80 * getShortRate(now);
}
}
@@ -0,0 +1,489 @@
package timely.balancer;
import static org.apache.accumulo.core.conf.ConfigurationTypeHelper.getTimeInMillis;
import static timely.Constants.NON_CACHED_METRICS;
import static timely.Constants.NON_CACHED_METRICS_LOCK_PATH;
import static timely.Constants.SERVICE_DISCOVERY_PATH;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryForever;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import io.netty.bootstrap.AbstractBootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.OpenSslServerContext;
import io.netty.handler.ssl.OpenSslServerSessionContext;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime;
import io.netty.util.internal.SystemPropertyUtil;
import timely.auth.JWTTokenHandler;
import timely.balancer.configuration.BalancerHttpProperties;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.configuration.BalancerWebsocketProperties;
import timely.balancer.connection.http.HttpClientPool;
import timely.balancer.connection.tcp.TcpClientPool;
import timely.balancer.connection.udp.UdpClientPool;
import timely.balancer.connection.ws.WsClientPool;
import timely.balancer.netty.http.HttpRelayHandler;
import timely.balancer.netty.tcp.TcpRelayHandler;
import timely.balancer.netty.udp.UdpRelayHandler;
import timely.balancer.netty.ws.WsRelayHandler;
import timely.common.component.AuthenticationService;
import timely.common.configuration.SecurityProperties;
import timely.common.configuration.SslClientProperties;
import timely.common.configuration.SslServerProperties;
import timely.common.configuration.ZookeeperProperties;
import timely.netty.http.HttpStrictTransportSecurityHandler;
import timely.netty.http.HttpVersionRequestHandler;
import timely.netty.http.NonSslForbiddenHandler;
import timely.netty.http.NotFoundHandler;
import timely.netty.http.TimelyExceptionHandler;
import timely.netty.http.auth.X509LoginRequestHandler;
import timely.netty.tcp.MetricsBufferDecoder;
import timely.netty.tcp.TcpDecoder;
import timely.netty.tcp.TcpVersionHandler;
import timely.netty.udp.UdpDecoder;
import timely.netty.udp.UdpPacketToByteBuf;
import timely.netty.websocket.WebSocketFullRequestHandler;
import timely.netty.websocket.WebSocketRequestDecoder;
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
public class Balancer {
final public static String LEADER_LATCH_PATH = "/timely/balancer/leader";
final public static String ASSIGNMENTS_LOCK_PATH = "/timely/balancer/assignments/lock";
final public static String ASSIGNMENTS_LAST_UPDATED_PATH = "/timely/balancer/assignments/lastUpdated";
private static final Logger log = LoggerFactory.getLogger(Balancer.class);
private static final String EPOLL_MIN_VERSION = "2.7";
private static final String OS_NAME = "os.name";
private static final String OS_VERSION = "os.version";
private static final String WS_PATH = "/websocket";
private BalancerProperties balancerProperties;
private SecurityProperties securityProperties;
private SslServerProperties sslServerProperties;
private SslClientProperties sslClientProperties;
private ZookeeperProperties zookeeperProperties;
private BalancerServerProperties balancerServerProperties;
private BalancerHttpProperties balancerHttpProperties;
private BalancerWebsocketProperties balancerWebsocketProperties;
private ApplicationContext applicationContext;
private AuthenticationService authenticationService;
private CuratorFramework curatorFramework;
private int quietPeriod;
private SslContext serverSslContext;
private SSLContext clientSSLContext;
private EventLoopGroup tcpWorkerGroup = null;
private EventLoopGroup tcpBossGroup = null;
private EventLoopGroup httpWorkerGroup = null;
private EventLoopGroup httpBossGroup = null;
private EventLoopGroup wsWorkerGroup = null;
private EventLoopGroup wsBossGroup = null;
private EventLoopGroup udpBossGroup = null;
private EventLoopGroup udpWorkerGroup = null;
private MetricResolver metricResolver;
protected Channel tcpChannelHandle = null;
protected Channel httpChannelHandle = null;
protected Channel wsChannelHandle = null;
protected List<Channel> udpChannelHandleList = new ArrayList<>();
protected int DEFAULT_EVENT_LOOP_THREADS;
private String[] zkPaths = new String[] {LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH, SERVICE_DISCOVERY_PATH,
NON_CACHED_METRICS, NON_CACHED_METRICS_LOCK_PATH};
private static boolean useEpoll() {
final String osName = SystemPropertyUtil.get(OS_NAME).toLowerCase().trim();
final String osVersion = SystemPropertyUtil.get(OS_VERSION).toLowerCase();
// split at periods, keep the first two, join with a period
final String majMinVers = StringUtils.join(Arrays.stream(osVersion.split("\\.")).limit(2).collect(Collectors.toList()), ".");
if (osName.startsWith("linux")) {
try {
Runtime.Version currentVersion = Runtime.Version.parse(majMinVers);
Runtime.Version epollMinVersion = Runtime.Version.parse(EPOLL_MIN_VERSION);
return currentVersion.compareTo(epollMinVersion) > 0;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return false;
}
public Balancer(ApplicationContext applicationContext, SslContext serverSslContext, SSLContext clientSSLContext,
AuthenticationService authenticationService, MetricResolver metricResolver, BalancerProperties balancerProperties,
ZookeeperProperties zookeeperProperties, SecurityProperties securityProperties, SslServerProperties sslServerProperties,
SslClientProperties sslClientProperties, BalancerServerProperties balancerServerProperties, BalancerHttpProperties balancerHttpProperties,
BalancerWebsocketProperties balancerWebsocketProperties) {
this.serverSslContext = serverSslContext;
this.clientSSLContext = clientSSLContext;
this.quietPeriod = balancerServerProperties.getShutdownQuietPeriod();
this.applicationContext = applicationContext;
this.authenticationService = authenticationService;
this.metricResolver = metricResolver;
this.balancerProperties = balancerProperties;
this.zookeeperProperties = zookeeperProperties;
this.securityProperties = securityProperties;
this.sslServerProperties = sslServerProperties;
this.sslClientProperties = sslClientProperties;
this.balancerServerProperties = balancerServerProperties;
this.balancerHttpProperties = balancerHttpProperties;
this.balancerWebsocketProperties = balancerWebsocketProperties;
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
public void start() {
log.info("Starting {}", this.getClass().getSimpleName());
try {
// start curator framework
RetryPolicy retryPolicy = new RetryForever(1000);
int timeout = Long.valueOf(getTimeInMillis(zookeeperProperties.getTimeout())).intValue();
curatorFramework = CuratorFrameworkFactory.newClient(zookeeperProperties.getServers(), timeout, 10000, retryPolicy);
curatorFramework.start();
ensureZkPaths(curatorFramework, zkPaths);
JWTTokenHandler.init(securityProperties, null);
final boolean useEpoll = useEpoll();
Class<? extends ServerSocketChannel> channelClass;
Class<? extends Channel> datagramChannelClass;
if (useEpoll) {
tcpWorkerGroup = new EpollEventLoopGroup();
tcpBossGroup = new EpollEventLoopGroup();
httpWorkerGroup = new EpollEventLoopGroup();
httpBossGroup = new EpollEventLoopGroup();
wsWorkerGroup = new EpollEventLoopGroup();
wsBossGroup = new EpollEventLoopGroup();
udpWorkerGroup = new EpollEventLoopGroup();
udpBossGroup = new EpollEventLoopGroup();
channelClass = EpollServerSocketChannel.class;
datagramChannelClass = EpollDatagramChannel.class;
} else {
tcpWorkerGroup = new NioEventLoopGroup();
tcpBossGroup = new NioEventLoopGroup();
httpWorkerGroup = new NioEventLoopGroup();
httpBossGroup = new NioEventLoopGroup();
wsWorkerGroup = new NioEventLoopGroup();
wsBossGroup = new NioEventLoopGroup();
udpWorkerGroup = new NioEventLoopGroup();
udpBossGroup = new NioEventLoopGroup();
channelClass = NioServerSocketChannel.class;
datagramChannelClass = NioDatagramChannel.class;
}
log.info("Using channel class {}", channelClass.getSimpleName());
final ServerBootstrap tcpServer = new ServerBootstrap();
tcpServer.group(tcpBossGroup, tcpWorkerGroup);
tcpServer.channel(channelClass);
tcpServer.handler(new LoggingHandler());
TcpClientPool tcpClientPool = new TcpClientPool(balancerServerProperties);
tcpServer.childHandler(setupTcpChannel(metricResolver, tcpClientPool));
tcpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
tcpServer.option(ChannelOption.SO_BACKLOG, 128);
tcpServer.childOption(ChannelOption.SO_KEEPALIVE, true);
final int tcpPort = balancerServerProperties.getTcpPort();
final String tcpIp = balancerServerProperties.getIp();
tcpChannelHandle = bind(tcpServer, tcpIp, tcpPort);
final String tcpAddress = ((InetSocketAddress) tcpChannelHandle.localAddress()).getAddress().getHostAddress();
final int httpPort = balancerHttpProperties.getPort();
final String httpIp = balancerHttpProperties.getIp();
if (serverSslContext instanceof OpenSslServerContext) {
OpenSslServerContext openssl = (OpenSslServerContext) serverSslContext;
String application = "Timely_" + httpPort;
OpenSslServerSessionContext opensslCtx = openssl.sessionContext();
opensslCtx.setSessionCacheEnabled(true);
opensslCtx.setSessionCacheSize(128);
opensslCtx.setSessionIdContext(application.getBytes(StandardCharsets.UTF_8));
opensslCtx.setSessionTimeout(securityProperties.getSessionMaxAge());
}
final ServerBootstrap httpServer = new ServerBootstrap();
httpServer.group(httpBossGroup, httpWorkerGroup);
httpServer.channel(channelClass);
httpServer.handler(new LoggingHandler());
HttpClientPool httpClientPool = new HttpClientPool(balancerHttpProperties, clientSSLContext, sslClientProperties);
httpServer.childHandler(setupHttpChannel(balancerHttpProperties, sslServerProperties, serverSslContext, metricResolver, httpClientPool));
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
httpServer.option(ChannelOption.SO_BACKLOG, 128);
httpServer.childOption(ChannelOption.SO_KEEPALIVE, true);
httpChannelHandle = bind(httpServer, httpIp, httpPort);
final String httpAddress = ((InetSocketAddress) httpChannelHandle.localAddress()).getAddress().getHostAddress();
final int wsPort = balancerWebsocketProperties.getPort();
final String wsIp = balancerWebsocketProperties.getIp();
final ServerBootstrap wsServer = new ServerBootstrap();
wsServer.group(wsBossGroup, wsWorkerGroup);
wsServer.channel(channelClass);
wsServer.handler(new LoggingHandler());
WsClientPool wsClientPool = new WsClientPool(balancerProperties, balancerWebsocketProperties, sslClientProperties, clientSSLContext);
wsServer.childHandler(setupWSChannel(balancerWebsocketProperties, securityProperties, serverSslContext, metricResolver, wsClientPool));
wsServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
wsServer.option(ChannelOption.SO_BACKLOG, 128);
wsServer.childOption(ChannelOption.SO_KEEPALIVE, true);
wsChannelHandle = bind(wsServer, wsIp, wsPort);
final String wsAddress = ((InetSocketAddress) wsChannelHandle.localAddress()).getAddress().getHostAddress();
final int udpPort = balancerServerProperties.getUdpPort();
final String udpIp = balancerServerProperties.getIp();
UdpClientPool udpClientPool = new UdpClientPool(balancerServerProperties);
for (int n = 0; n < DEFAULT_EVENT_LOOP_THREADS; n++) {
final Bootstrap udpServer = new Bootstrap();
udpServer.group(udpBossGroup);
udpServer.channel(datagramChannelClass);
udpServer.handler(setupUdpChannel(metricResolver, udpClientPool));
udpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
udpServer.option(EpollChannelOption.SO_REUSEADDR, true);
udpServer.option(EpollChannelOption.SO_REUSEPORT, true);
udpChannelHandleList.add(bind(udpServer, udpIp, udpPort));
}
log.info("Balancer started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic",
tcpAddress, tcpPort, httpAddress, httpPort, wsAddress, wsPort, wsAddress, udpPort);
} catch (Exception e) {
log.error(e.getMessage(), e);
SpringApplication.exit(applicationContext, () -> 0);
}
}
protected Channel bind(AbstractBootstrap server, String ip, int port) {
Channel channel = null;
try {
channel = server.bind(ip, port).sync().channel();
} catch (InterruptedException e) {
log.error("Failed to bind to {}:{} -- {}", ip, port, e.getMessage());
}
if (channel == null) {
throw new IllegalStateException("Failed to bind to port:" + ip + ":" + port);
}
return channel;
}
public void shutdown() {
List<ChannelFuture> channelFutures = new ArrayList<>();
if (tcpChannelHandle != null) {
log.info("Closing tcpChannelHandle");
channelFutures.add(tcpChannelHandle.close());
}
if (httpChannelHandle != null) {
log.info("Closing httpChannelHandle");
channelFutures.add(httpChannelHandle.close());
}
if (wsChannelHandle != null) {
log.info("Closing wsChannelHandle");
channelFutures.add(wsChannelHandle.close());
}
int udpChannel = 1;
for (Channel c : udpChannelHandleList) {
log.info("Closing udpChannelHandle #" + udpChannel++);
channelFutures.add(c.close());
}
// wait for the channels to shutdown
channelFutures.forEach(f -> {
try {
f.get();
} catch (final Exception e) {
log.error("Channel:" + f.channel().config() + " -> " + e.getMessage(), e);
}
});
List<Future<?>> groupFutures = new ArrayList<>();
if (tcpBossGroup != null) {
log.info("Shutting down tcpBossGroup");
groupFutures.add(tcpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (tcpWorkerGroup != null) {
log.info("Shutting down tcpWorkerGroup");
groupFutures.add(tcpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (httpBossGroup != null) {
log.info("Shutting down httpBossGroup");
groupFutures.add(httpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (httpWorkerGroup != null) {
log.info("Shutting down httpWorkerGroup");
groupFutures.add(httpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (wsBossGroup != null) {
log.info("Shutting down wsBossGroup");
groupFutures.add(wsBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (wsWorkerGroup != null) {
log.info("Shutting down wsWorkerGroup");
groupFutures.add(wsWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (udpBossGroup != null) {
log.info("Shutting down udpBossGroup");
groupFutures.add(udpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (udpWorkerGroup != null) {
log.info("Shutting down udpWorkerGroup");
groupFutures.add(udpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
groupFutures.parallelStream().forEach(f -> {
try {
f.get();
} catch (final Exception e) {
log.error("Group:" + f.toString() + " -> " + e.getMessage(), e);
}
});
try {
log.info("Closing WebSocketRequestDecoder");
WebSocketRequestDecoder.close();
} catch (Exception e) {
log.error("Error closing WebSocketRequestDecoder during shutdown", e);
}
log.info("{} shut down.", this.getClass().getSimpleName());
}
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
for (String s : paths) {
try {
Stat stat = curatorFramework.checkExists().forPath(s);
if (stat == null) {
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(s);
}
} catch (Exception e) {
log.info(e.getMessage());
}
}
}
protected ChannelHandler setupHttpChannel(BalancerHttpProperties balancerHttpProperties, SslServerProperties sslServerProperties, SslContext sslCtx,
MetricResolver metricResolver, HttpClientPool httpClientPool) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast("nonsslforbidden", new NonSslForbiddenHandler(sslCtx));
ch.pipeline().addLast("encoder", new HttpResponseEncoder());
ch.pipeline().addLast("strict", new HttpStrictTransportSecurityHandler(balancerHttpProperties));
ch.pipeline().addLast("decoder", new HttpRequestDecoder(4096, 32768, 8192, true, 128));
ch.pipeline().addLast("compressor", new HttpContentCompressor());
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
ch.pipeline().addLast("queryDecoder",
new timely.netty.http.HttpRequestDecoder(authenticationService, securityProperties, balancerHttpProperties));
ch.pipeline().addLast("notfound", new NotFoundHandler());
ch.pipeline().addLast("login", new X509LoginRequestHandler(authenticationService, securityProperties, balancerHttpProperties));
ch.pipeline().addLast("version", new HttpVersionRequestHandler());
ch.pipeline().addLast("httpRelay", new HttpRelayHandler(balancerHttpProperties, metricResolver, httpClientPool));
ch.pipeline().addLast("error", new TimelyExceptionHandler().setIgnoreSslHandshakeErrors(sslServerProperties.isIgnoreSslHandshakeErrors()));
}
};
}
protected ChannelHandler setupUdpChannel(MetricResolver metricResolver, UdpClientPool udpClientPool) {
return new ChannelInitializer<DatagramChannel>() {
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
ch.pipeline().addLast("logger", new LoggingHandler());
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new UdpDecoder());
ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool));
}
};
}
protected ChannelHandler setupTcpChannel(MetricResolver metricResolver, TcpClientPool tcpClientPool) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new TcpDecoder());
ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPool));
ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
}
};
}
protected ChannelHandler setupWSChannel(BalancerWebsocketProperties balancerWebsocketProperties, SecurityProperties securityProperties, SslContext sslCtx,
MetricResolver metricResolver, WsClientPool wsClientPool) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
ch.pipeline().addLast("httpServer", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler(authenticationService));
ch.pipeline().addLast("idle-handler", new IdleStateHandler(balancerWebsocketProperties.getTimeout(), 0, 0));
ch.pipeline().addLast("ws-protocol", new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, false, true));
ch.pipeline().addLast("wsDecoder", new WebSocketRequestDecoder(authenticationService, securityProperties));
ch.pipeline().addLast("httpRelay", new WsRelayHandler(balancerWebsocketProperties, metricResolver, wsClientPool));
ch.pipeline().addLast("error", new WSTimelyExceptionHandler());
}
};
}
}
@@ -0,0 +1,72 @@
package timely.balancer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.tcp.TcpClientPool;
import timely.balancer.healthcheck.TCPServerHealthCheck;
public class HealthChecker {
private static final Logger log = LoggerFactory.getLogger(HealthChecker.class);
private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
private TCPServerHealthCheck tcpServerHealthCheck;
private BalancerProperties balancerProperties;
private BalancerServerProperties balancerServerProperties;
protected List<TimelyBalancedHost> timelyHosts = new ArrayList<>();
public HealthChecker(BalancerProperties balancerProperties, BalancerServerProperties balancerServerProperties) {
this.balancerProperties = balancerProperties;
this.balancerServerProperties = balancerServerProperties;
}
public void start() {
TcpClientPool tcpClientPool = new TcpClientPool(balancerServerProperties);
this.tcpServerHealthCheck = new TCPServerHealthCheck(tcpClientPool);
this.executorService.scheduleAtFixedRate(() -> {
synchronized (timelyHosts) {
for (TimelyBalancedHost h : timelyHosts) {
try {
if (tcpServerHealthCheck.isServerHealthy(h)) {
h.reportSuccess();
} else {
h.reportFailure();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
h.reportFailure();
}
}
}
}, 0, balancerProperties.getCheckServerHealthInterval(), TimeUnit.MILLISECONDS);
}
public void shutdown() {
this.executorService.shutdown();
try {
this.executorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
} finally {
if (!this.executorService.isTerminated()) {
this.executorService.shutdownNow();
}
}
}
public void setTimelyHosts(List<TimelyBalancedHost> timelyHosts) {
synchronized (timelyHosts) {
this.timelyHosts.clear();
this.timelyHosts.addAll(timelyHosts);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,14 @@
package timely.balancer;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication(scanBasePackages = {"timely.balancer", "timely.common"}, exclude = {ErrorMvcAutoConfiguration.class})
public class TimelyBalancer {
public static void main(String[] args) {
new SpringApplicationBuilder(TimelyBalancer.class).web(WebApplicationType.NONE).run(args);
}
}
@@ -0,0 +1,58 @@
package timely.balancer.configuration;
import javax.net.ssl.SSLContext;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.netty.handler.ssl.SslContext;
import timely.balancer.Balancer;
import timely.balancer.HealthChecker;
import timely.balancer.MetricResolver;
import timely.common.component.AuthenticationService;
import timely.common.configuration.CacheProperties;
import timely.common.configuration.SecurityProperties;
import timely.common.configuration.SslClientProperties;
import timely.common.configuration.SslServerProperties;
import timely.common.configuration.ZookeeperProperties;
@Configuration
@EnableConfigurationProperties({BalancerProperties.class, BalancerServerProperties.class, BalancerWebsocketProperties.class, BalancerHttpProperties.class})
public class BalancerConfiguration {
@Bean
@ConditionalOnMissingBean
public Balancer balancer(ApplicationContext applicationContext, @Qualifier("nettySslContext") SslContext serverSslContext,
@Qualifier("outboundJDKSslContext") SSLContext clientSSLContext, AuthenticationService authenticationService, MetricResolver metricResolver,
BalancerProperties balancerProperties, ZookeeperProperties zookeeperProperties, SecurityProperties securityProperties,
SslServerProperties sslServerProperties, SslClientProperties sslClientProperties, BalancerServerProperties balancerServerProperties,
BalancerHttpProperties balancerHttpProperties, BalancerWebsocketProperties balancerWebsocketProperties) {
Balancer balancer = new Balancer(applicationContext, serverSslContext, clientSSLContext, authenticationService, metricResolver, balancerProperties,
zookeeperProperties, securityProperties, sslServerProperties, sslClientProperties, balancerServerProperties, balancerHttpProperties,
balancerWebsocketProperties);
balancer.start();
return balancer;
}
@Bean
@ConditionalOnMissingBean
public HealthChecker healthChecker(BalancerProperties balancerProperties, BalancerServerProperties balancerServerProperties) {
HealthChecker healthChecker = new HealthChecker(balancerProperties, balancerServerProperties);
healthChecker.start();
return healthChecker;
}
@Bean
@ConditionalOnMissingBean
public MetricResolver metricResolver(CuratorFramework curatorFramework, BalancerProperties balancerProperties, CacheProperties cacheProperties,
HealthChecker healthChecker) throws Exception {
MetricResolver metricResolver = new MetricResolver(curatorFramework, balancerProperties, cacheProperties, healthChecker);
metricResolver.start();
return metricResolver;
}
}
@@ -0,0 +1,60 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import timely.common.configuration.HttpProperties;
@RefreshScope
@ConfigurationProperties(prefix = "timely.balancer.http")
public class BalancerHttpProperties extends HttpProperties {
private int connectionRequestTimeout = 5000;
private int connectTimeout = 5000;
private int socketTimeout = 300000;
private int requestTimeout = 300000;
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfiguration<CloseableHttpClient> httpClientPool = new GenericKeyedObjectPoolConfiguration<>();
public GenericKeyedObjectPoolConfiguration<CloseableHttpClient> getHttpClientPool() {
return httpClientPool;
}
public void setConnectionRequestTimeout(final int connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public int getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectTimeout(final int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setSocketTimeout(final int socketTimeout) {
this.socketTimeout = socketTimeout;
}
public int getSocketTimeout() {
return socketTimeout;
}
public void setRequestTimeout(int requestTimeout) {
this.requestTimeout = requestTimeout;
}
public int getRequestTimeout() {
return requestTimeout;
}
}
@@ -0,0 +1,93 @@
package timely.balancer.configuration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Validated
@Component
@ConfigurationProperties(prefix = "timely.balancer")
public class BalancerProperties {
private boolean loginRequired = false;
private String assignmentFile;
private String defaultFs;
private List<String> fsConfigResources;
private Double controlBandPercentage = 0.05;
private long checkServerHealthInterval = 10000;
private int serverFailuresBeforeDown = 3;
private int serverSuccessesBeforeUp = 3;
public void setLoginRequired(boolean loginRequired) {
this.loginRequired = loginRequired;
}
public boolean isLoginRequired() {
return loginRequired;
}
public String getAssignmentFile() {
return assignmentFile;
}
public void setAssignmentFile(String assignmentFile) {
this.assignmentFile = assignmentFile;
}
public String getDefaultFs() {
return defaultFs;
}
public void setDefaultFs(String defaultFs) {
this.defaultFs = defaultFs;
}
public List<String> getFsConfigResources() {
return fsConfigResources;
}
public void setFsConfigResources(List<String> fsConfigResources) {
this.fsConfigResources = fsConfigResources;
}
public void setControlBandPercentage(Double controlBandPercentage) {
this.controlBandPercentage = controlBandPercentage;
}
public Double getControlBandPercentage() {
return controlBandPercentage;
}
public void setCheckServerHealthInterval(long checkServerHealthInterval) {
this.checkServerHealthInterval = checkServerHealthInterval;
}
public long getCheckServerHealthInterval() {
return checkServerHealthInterval;
}
public int getServerFailuresBeforeDown() {
return serverFailuresBeforeDown;
}
public void setServerFailuresBeforeDown(int serverFailuresBeforeDown) {
this.serverFailuresBeforeDown = serverFailuresBeforeDown;
}
public int getServerSuccessesBeforeUp() {
return serverSuccessesBeforeUp;
}
public void setServerSuccessesBeforeUp(int serverSuccessesBeforeUp) {
this.serverSuccessesBeforeUp = serverSuccessesBeforeUp;
}
}
@@ -0,0 +1,42 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import timely.client.tcp.TcpClient;
import timely.client.udp.UdpClient;
import timely.common.configuration.ServerProperties;
@RefreshScope
@ConfigurationProperties(prefix = "timely.balancer.server")
public class BalancerServerProperties extends ServerProperties {
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfiguration<UdpClient> udpClientPool = new GenericKeyedObjectPoolConfiguration<>();
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfiguration<TcpClient> tcpClientPool = new GenericKeyedObjectPoolConfiguration<>();
private int tcpBufferSize = -1;
public GenericKeyedObjectPoolConfiguration<UdpClient> getUdpClientPool() {
return udpClientPool;
}
public GenericKeyedObjectPoolConfiguration<TcpClient> getTcpClientPool() {
return tcpClientPool;
}
public int getTcpBufferSize() {
return tcpBufferSize;
}
public void setTcpBufferSize(int tcpBufferSize) {
this.tcpBufferSize = tcpBufferSize;
}
}
@@ -0,0 +1,33 @@
package timely.balancer.configuration;
import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.common.configuration.WebsocketProperties;
@RefreshScope
@ConfigurationProperties(prefix = "timely.balancer.websocket")
public class BalancerWebsocketProperties extends WebsocketProperties {
@Valid
@NestedConfigurationProperty
private GenericKeyedObjectPoolConfiguration<WebSocketSubscriptionClient> wsClientPool = new GenericKeyedObjectPoolConfiguration<>();
public GenericKeyedObjectPoolConfiguration<WebSocketSubscriptionClient> getWsClientPool() {
return wsClientPool;
}
private int incomingBufferSize = 50000000;
public int getIncomingBufferSize() {
return incomingBufferSize;
}
public void setIncomingBufferSize(int incomingBufferSize) {
this.incomingBufferSize = incomingBufferSize;
}
}
@@ -0,0 +1,83 @@
package timely.balancer.configuration;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
public class GenericKeyedObjectPoolConfiguration<T> extends GenericKeyedObjectPoolConfig<T> {
private Duration removeAbandonedTimeout = null;
private boolean removeAbandonedLogging = false;
private boolean removeAbandonedOnBorrow = false;
private boolean removeAbandonedOnMaintenance = false;
public void setMaxWaitMillis(long maxWaitMillis) {
super.setMaxWait(Duration.ofMillis(maxWaitMillis));
}
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
super.setMinEvictableIdleTime(Duration.ofMillis(minEvictableIdleTimeMillis));
}
public void setEvictorShutdownTimeoutMillis(long evictorShutdownTimeoutMillis) {
super.setEvictorShutdownTimeout(Duration.ofMillis(evictorShutdownTimeoutMillis));
}
public void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) {
super.setSoftMinEvictableIdleTime(Duration.ofMillis(softMinEvictableIdleTimeMillis));
}
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
super.setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis));
}
public void setRemoveAbandonedTimeoutMillis(long removeAbandonedTimeoutMillis) {
this.removeAbandonedTimeout = Duration.ofMillis(removeAbandonedTimeoutMillis);
}
public Duration getRemoveAbandonedTimeout() {
return removeAbandonedTimeout;
}
public void setRemoveAbandonedOnBorrow(boolean removeAbandonedOnBorrow) {
this.removeAbandonedOnBorrow = removeAbandonedOnBorrow;
}
public boolean getRemoveAbandonedOnBorrow() {
return removeAbandonedOnBorrow;
}
public void setRemoveAbandonedOnMaintenance(boolean removeAbandonedOnMaintenance) {
this.removeAbandonedOnMaintenance = removeAbandonedOnMaintenance;
}
public boolean getRemoveAbandonedOnMaintenance() {
return removeAbandonedOnMaintenance;
}
public void setRemoveAbandonedLogging(boolean removeAbandonedLogging) {
this.removeAbandonedLogging = removeAbandonedLogging;
}
public boolean getRemoveAbandonedLogging() {
return removeAbandonedLogging;
}
public AbandonedConfig getAbandonedConfig() {
AbandonedConfig abandonedConfig = null;
if (removeAbandonedTimeout != null) {
abandonedConfig = new AbandonedConfig();
abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout);
if (removeAbandonedLogging) {
abandonedConfig.setLogAbandoned(removeAbandonedLogging);
abandonedConfig.setLogWriter(new PrintWriter(System.out, true, StandardCharsets.UTF_8));
}
abandonedConfig.setRemoveAbandonedOnBorrow(removeAbandonedOnBorrow);
abandonedConfig.setRemoveAbandonedOnMaintenance(removeAbandonedOnMaintenance);
}
return abandonedConfig;
}
}
@@ -0,0 +1,182 @@
package timely.balancer.connection;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.ArrivalRate;
import timely.balancer.configuration.BalancerProperties;
public class TimelyBalancedHost {
private static final Logger log = LoggerFactory.getLogger(TimelyBalancedHost.class);
private String host;
private int tcpPort;
private int httpPort;
private int wsPort;
private int udpPort;
private boolean isUp = true;
private BalancerProperties balancerProperties;
private int failures = 0;
private int successes = 0;
private ArrivalRate arrivalRate = null;
private TimelyBalancedHost() {
}
private TimelyBalancedHost(String host, int tcpPort, int httpPort, int wsPort, int udpPort, ArrivalRate arrivalRate) {
this.host = host;
this.tcpPort = tcpPort;
this.httpPort = httpPort;
this.wsPort = wsPort;
this.udpPort = udpPort;
this.arrivalRate = arrivalRate;
}
static public TimelyBalancedHost of(String host, int tcpPort, int httpPort, int wsPort, int udpPort, ArrivalRate arrivalRate) {
return new TimelyBalancedHost(host, tcpPort, httpPort, wsPort, udpPort, arrivalRate);
}
public void setBalancerProperties(BalancerProperties balancerProperties) {
this.balancerProperties = balancerProperties;
}
synchronized public boolean isUp() {
return isUp;
}
public void reportSuccess() {
int serverSuccessesBeforeUp = balancerProperties.getServerSuccessesBeforeUp();
String h = host;
int p = tcpPort;
synchronized (this) {
if (log.isTraceEnabled()) {
log.trace("success reported host:{} port:{} isUp:{} successes:{} serverSuccessesBeforeUp:{}", h, p, isUp, (successes + 1),
serverSuccessesBeforeUp);
}
if (!isUp) {
if (++successes >= serverSuccessesBeforeUp) {
isUp = true;
successes = 0;
log.info("host up host:{} port:{}", h, p);
}
}
}
}
public void reportFailure() {
int serverFailuresBeforeDown = balancerProperties.getServerFailuresBeforeDown();
String h = host;
int p = tcpPort;
synchronized (this) {
if (log.isTraceEnabled()) {
log.trace("failure reported host:{} port:{} isUp:{} failures:{} serverFailuresBeforeUp:{}", h, p, isUp, (failures + 1),
serverFailuresBeforeDown);
}
if (isUp) {
if (++failures >= serverFailuresBeforeDown) {
isUp = false;
failures = 0;
log.info("host down host:{} port:{}", h, p);
}
}
}
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getUdpPort() {
return udpPort;
}
public void setUdpPort(int udpPort) {
this.udpPort = udpPort;
}
public int getTcpPort() {
return tcpPort;
}
public void setTcpPort(int tcpPort) {
this.tcpPort = tcpPort;
}
public int getHttpPort() {
return httpPort;
}
public void setHttpPort(int httpPort) {
this.httpPort = httpPort;
}
public int getWsPort() {
return wsPort;
}
public void setWsPort(int wsPort) {
this.wsPort = wsPort;
}
public void arrived() {
if (this.arrivalRate != null) {
this.arrivalRate.arrived();
}
}
public double getArrivalRate() {
if (this.arrivalRate == null) {
return 0;
} else {
return this.arrivalRate.getRate();
}
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(host);
builder.append(tcpPort);
builder.append(httpPort);
builder.append(wsPort);
builder.append(udpPort);
return builder.toHashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof TimelyBalancedHost) {
TimelyBalancedHost other = (TimelyBalancedHost) o;
EqualsBuilder builder = new EqualsBuilder();
builder.append(host, other.host);
builder.append(tcpPort, other.tcpPort);
builder.append(httpPort, other.httpPort);
builder.append(wsPort, other.wsPort);
builder.append(udpPort, other.udpPort);
return builder.isEquals();
} else {
return false;
}
}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
builder.append(host);
builder.append(tcpPort);
builder.append(httpPort);
builder.append(wsPort);
builder.append(udpPort);
builder.append(isUp);
return builder.toString();
}
}
@@ -0,0 +1,50 @@
package timely.balancer.connection.http;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.http.impl.client.CloseableHttpClient;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.http.HttpClient;
import timely.common.configuration.SslClientProperties;
public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost,CloseableHttpClient> {
private final SSLContext sslContext;
private SslClientProperties sslClientProperties;
public HttpClientFactory(SSLContext sslContext, SslClientProperties sslClientProperties) {
this.sslContext = sslContext;
this.sslClientProperties = sslClientProperties;
}
@Override
public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception {
// disable cookie management because we are sharing connections
return new DefaultPooledObject<>(
HttpClient.get(this.sslContext, null, sslClientProperties.isHostVerificationEnabled(), sslClientProperties.isUseClientCert()));
}
@Override
public void destroyObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
}
@Override
public boolean validateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) {
return true;
}
@Override
public void activateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
}
@Override
public void passivateObject(TimelyBalancedHost k, PooledObject<CloseableHttpClient> o) throws Exception {
}
}
@@ -0,0 +1,17 @@
package timely.balancer.connection.http;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.http.impl.client.CloseableHttpClient;
import timely.balancer.configuration.BalancerHttpProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.common.configuration.SslClientProperties;
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost,CloseableHttpClient> {
public HttpClientPool(BalancerHttpProperties config, SSLContext sslContext, SslClientProperties sslClientProperties) {
super(new HttpClientFactory(sslContext, sslClientProperties), config.getHttpClientPool(), config.getHttpClientPool().getAbandonedConfig());
}
}
@@ -0,0 +1,57 @@
package timely.balancer.connection.tcp;
import java.io.IOException;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.tcp.TcpClient;
public class TcpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost,TcpClient> {
private int bufferSize;
public TcpClientFactory(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public PooledObject<TcpClient> makeObject(TimelyBalancedHost k) throws Exception {
return new DefaultPooledObject<>(new TcpClient(k.getHost(), k.getTcpPort(), bufferSize));
}
@Override
public void destroyObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
try {
o.getObject().flush();
o.getObject().close();
} catch (IOException e) {
throw new IOException("Error closing connection to " + k.getHost() + ":" + k.getTcpPort());
}
}
@Override
public boolean validateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) {
return true;
}
@Override
public void activateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
try {
o.getObject().open();
} catch (Exception e) {
throw new IOException("Unable to connect to " + k.getHost() + ":" + k.getTcpPort(), e);
}
}
@Override
public void passivateObject(TimelyBalancedHost k, PooledObject<TcpClient> o) throws Exception {
try {
o.getObject().flush();
} catch (Exception e) {
throw new IOException("Error flushing connection to " + k.getHost() + ":" + k.getTcpPort());
}
}
}
@@ -0,0 +1,14 @@
package timely.balancer.connection.tcp;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.tcp.TcpClient;
public class TcpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost,TcpClient> {
public TcpClientPool(BalancerServerProperties config) {
super(new TcpClientFactory(config.getTcpBufferSize()), config.getTcpClientPool(), config.getTcpClientPool().getAbandonedConfig());
}
}
@@ -0,0 +1,53 @@
package timely.balancer.connection.udp;
import java.io.IOException;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.udp.UdpClient;
public class UdpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost,UdpClient> {
public UdpClientFactory() {}
@Override
public PooledObject<UdpClient> makeObject(TimelyBalancedHost k) throws Exception {
return new DefaultPooledObject<>(new UdpClient(k.getHost(), k.getUdpPort()));
}
@Override
public void destroyObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
try {
o.getObject().flush();
o.getObject().close();
} catch (IOException e) {
throw new IOException("Error closing connection to " + k.getHost() + ":" + k.getUdpPort());
}
}
@Override
public boolean validateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) {
return true;
}
@Override
public void activateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
try {
o.getObject().open();
} catch (Exception e) {
throw new IOException("Unable to connect to " + k.getHost() + ":" + k.getUdpPort(), e);
}
}
@Override
public void passivateObject(TimelyBalancedHost k, PooledObject<UdpClient> o) throws Exception {
try {
o.getObject().flush();
} catch (Exception e) {
throw new IOException("Error flushing connection to " + k.getHost() + ":" + k.getUdpPort());
}
}
}
@@ -0,0 +1,14 @@
package timely.balancer.connection.udp;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.udp.UdpClient;
public class UdpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost,UdpClient> {
public UdpClientPool(BalancerServerProperties config) {
super(new UdpClientFactory(), config.getUdpClientPool(), config.getUdpClientPool().getAbandonedConfig());
}
}
@@ -0,0 +1,59 @@
package timely.balancer.connection.ws;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerWebsocketProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.common.configuration.SslClientProperties;
public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost,WebSocketSubscriptionClient> {
private final SSLContext sslContext;
private BalancerProperties balancerProperties;
private BalancerWebsocketProperties balancerWebsocketProperties;
private SslClientProperties sslClientProperties;
public WsClientFactory(BalancerProperties balancerProperties, BalancerWebsocketProperties balancerWebsocketProperties,
SslClientProperties sslClientProperties, SSLContext sslContext) {
this.balancerProperties = balancerProperties;
this.balancerWebsocketProperties = balancerWebsocketProperties;
this.sslClientProperties = sslClientProperties;
this.sslContext = sslContext;
}
@Override
public PooledObject<WebSocketSubscriptionClient> makeObject(TimelyBalancedHost k) throws Exception {
int bufferSize = balancerWebsocketProperties.getIncomingBufferSize();
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(), k.getWsPort(),
sslClientProperties.isUseClientCert(), balancerProperties.isLoginRequired(), sslClientProperties.isHostVerificationEnabled(),
bufferSize);
return new DefaultPooledObject<>(client);
}
@Override
public void destroyObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
}
@Override
public boolean validateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) {
return true;
}
@Override
public void activateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
}
@Override
public void passivateObject(TimelyBalancedHost k, PooledObject<WebSocketSubscriptionClient> o) throws Exception {
}
}
@@ -0,0 +1,20 @@
package timely.balancer.connection.ws;
import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerWebsocketProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.common.configuration.SslClientProperties;
public class WsClientPool extends GenericKeyedObjectPool<TimelyBalancedHost,WebSocketSubscriptionClient> {
public WsClientPool(BalancerProperties balancerProperties, BalancerWebsocketProperties config, SslClientProperties sslClientProperties,
SSLContext sslContext) {
super(new WsClientFactory(balancerProperties, config, sslClientProperties, sslContext), config.getWsClientPool(),
config.getWsClientPool().getAbandonedConfig());
}
}
@@ -0,0 +1,8 @@
package timely.balancer.healthcheck;
import timely.balancer.connection.TimelyBalancedHost;
public interface ServerHealthCheck {
public boolean isServerHealthy(TimelyBalancedHost timelyBalancedHost);
}
@@ -0,0 +1,33 @@
package timely.balancer.healthcheck;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.tcp.TcpClientPool;
import timely.client.tcp.TcpClient;
public class TCPServerHealthCheck implements ServerHealthCheck {
final private TcpClientPool tcpClientPool;
public TCPServerHealthCheck(TcpClientPool tcpClientPool) {
this.tcpClientPool = tcpClientPool;
}
@Override
public boolean isServerHealthy(TimelyBalancedHost timelyBalancedHost) {
boolean healthy = true;
TcpClient client = null;
try {
client = tcpClientPool.borrowObject(timelyBalancedHost);
client.open();
} catch (Exception e) {
healthy = false;
} finally {
if (client != null) {
tcpClientPool.returnObject(timelyBalancedHost, client);
}
}
return healthy;
}
}
@@ -0,0 +1,230 @@
package timely.balancer.netty.http;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;
import com.google.common.base.Throwables;
import com.google.common.collect.Multimap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import timely.api.request.AuthenticatedRequest;
import timely.api.request.HttpRequest;
import timely.api.request.MetricRequest;
import timely.api.request.timeseries.QueryRequest;
import timely.api.response.TimelyException;
import timely.auth.TimelyAuthenticationToken;
import timely.auth.util.HttpHeaderUtils;
import timely.auth.util.ProxiedEntityUtils;
import timely.balancer.MetricResolver;
import timely.balancer.configuration.BalancerHttpProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.http.HttpClientPool;
import timely.netty.http.TimelyHttpHandler;
public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
private static final Logger log = LoggerFactory.getLogger(HttpRelayHandler.class);
private BalancerHttpProperties balancerHttpProperties;
private final HttpClientPool httpClientPool;
private MetricResolver metricResolver;
private ExecutorService requestExecutor = Executors.newCachedThreadPool();
public HttpRelayHandler(BalancerHttpProperties balancerHttpProperties, MetricResolver metricResolver, HttpClientPool httpClientPool) {
this.balancerHttpProperties = balancerHttpProperties;
this.metricResolver = metricResolver;
this.httpClientPool = httpClientPool;
}
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
CloseableHttpClient client = null;
TimelyBalancedHost k = null;
CloseableHttpResponse relayedResponse = null;
FullHttpRequest request = null;
try {
String relayURI = "";
try {
request = msg.getHttpRequest();
String originalURI = request.uri();
originalURI = encodeURI(originalURI);
String metric = null;
if (msg instanceof QueryRequest) {
Collection<QueryRequest.SubQuery> subqueries = ((QueryRequest) msg).getQueries();
if (subqueries != null) {
Iterator<QueryRequest.SubQuery> itr = subqueries.iterator();
if (itr.hasNext()) {
metric = itr.next().getMetric();
}
}
k = metricResolver.getHostPortKey(metric);
} else if (msg instanceof MetricRequest) {
metric = ((MetricRequest) msg).getMetric().getName();
k = metricResolver.getHostPortKeyIngest(metric);
} else {
k = metricResolver.getHostPortKey(null);
}
client = httpClientPool.borrowObject(k);
Multimap<String,String> headers = HttpHeaderUtils.toMultimap(request.headers());
if (msg instanceof AuthenticatedRequest) {
AuthenticatedRequest authenticatedRequest = (AuthenticatedRequest) msg;
TimelyAuthenticationToken token = authenticatedRequest.getToken();
if (token != null) {
if (token.getClientCert() != null) {
ProxiedEntityUtils.addProxyHeaders(headers, token.getClientCert());
}
}
}
List<Header> relayedHeaderList = new ArrayList<>();
for (Map.Entry<String,String> h : headers.entries()) {
if (!h.getKey().equals(CONTENT_LENGTH.toString())) {
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
}
}
Header[] headerArray = new Header[relayedHeaderList.size()];
relayedHeaderList.toArray(headerArray);
relayURI = "https://" + k.getHost() + ":" + k.getHttpPort() + originalURI;
HttpRequestBase relayedRequest = null;
if (request.method().equals(HttpMethod.GET)) {
relayedRequest = new HttpGet(relayURI);
} else if (request.method().equals(HttpMethod.POST)) {
relayedRequest = new HttpPost(relayURI);
String content = request.content().toString(StandardCharsets.UTF_8);
StringEntity entity = new StringEntity(content);
((HttpPost) relayedRequest).setEntity(entity);
} else {
throw new IllegalArgumentException("Unsupported HTTP method: " + request.method());
}
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = executeHttpRequest(client, relayedRequest, balancerHttpProperties.getRequestTimeout());
} catch (IOException e) {
String message = String.format("%s calling %s", e.getMessage() == null ? "" : e.getMessage(), relayURI);
log.error(message, e);
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.GATEWAY_TIMEOUT.code(), e.getMessage(), e.getLocalizedMessage(), e));
return;
} catch (Exception e) {
String message = e.getMessage();
if (message != null && message.contains("No matching tags")) {
log.trace(message);
} else {
message = String.format("%s calling %s", e.getMessage() == null ? "" : e.getMessage(), relayURI);
log.error(message, e);
}
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), e.getMessage(), e.getLocalizedMessage(), e));
return;
}
FullHttpResponse response;
if (relayedResponse == null) {
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(), "Method not allowed", "Method not allowed"));
} else {
ByteArrayOutputStream baos = new MyByteArrayOutputStream();
StreamUtils.copy(relayedResponse.getEntity().getContent(), baos);
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(relayedResponse.getStatusLine().getStatusCode()),
Unpooled.copiedBuffer(baos.toByteArray()));
for (Header h : relayedResponse.getAllHeaders()) {
response.headers().add(h.getName(), h.getValue());
}
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (relayedResponse != null) {
relayedResponse.close();
}
if (client != null && k != null) {
httpClientPool.returnObject(k, client);
} else {
log.error("NOT RETURNING CONNECTION! " + msg.getHttpRequest().uri());
}
}
}
static public class MyByteArrayOutputStream extends ByteArrayOutputStream {
public MyByteArrayOutputStream() {}
public MyByteArrayOutputStream(int size) {
super(size);
}
public int getCount() {
return count;
}
public byte[] getBuf() {
return buf;
}
}
static public String encodeURI(String s) {
try {
s = s.replaceAll("\\{", URLEncoder.encode("{", "UTF-8"));
s = s.replaceAll("\\}", URLEncoder.encode("}", "UTF-8"));
} catch (Exception e) {
}
return s;
}
private CloseableHttpResponse executeHttpRequest(CloseableHttpClient httpClient, HttpRequestBase relayedRequest, long timeout) throws Exception {
try {
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
requestConfigBuilder.setConnectionRequestTimeout(this.balancerHttpProperties.getConnectionRequestTimeout());
requestConfigBuilder.setConnectTimeout(this.balancerHttpProperties.getConnectTimeout());
requestConfigBuilder.setSocketTimeout(this.balancerHttpProperties.getSocketTimeout());
RequestConfig requestConfig = requestConfigBuilder.build();
relayedRequest.setConfig(requestConfig);
Future<CloseableHttpResponse> f = requestExecutor.submit(() -> httpClient.execute(relayedRequest));
return f.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Throwable cause = Throwables.getRootCause(e);
throw new IOException(cause.getMessage(), cause);
} catch (Exception e) {
throw e;
}
}
}
@@ -0,0 +1,92 @@
package timely.balancer.netty.tcp;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import timely.api.request.MetricRequest;
import timely.api.request.TcpRequest;
import timely.balancer.MetricResolver;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.tcp.TcpClientPool;
import timely.client.tcp.TcpClient;
import timely.netty.Constants;
public class TcpRelayHandler extends SimpleChannelInboundHandler<TcpRequest> {
private static final Logger log = LoggerFactory.getLogger(TcpRelayHandler.class);
private static final String LOG_ERR_MSG = "Error storing put metric: {}";
private static final String ERR_MSG = "Error storing put metric: ";
private MetricResolver metricResolver;
private TcpClientPool tcpClientPool;
public TcpRelayHandler(MetricResolver metricResolver, TcpClientPool tcpClientPool) {
this.metricResolver = metricResolver;
this.tcpClientPool = tcpClientPool;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TcpRequest msg) throws Exception {
log.trace("Received {}", msg);
try {
String line;
Pair<TimelyBalancedHost,TcpClient> keyClientPair = null;
try {
if (msg instanceof MetricRequest) {
String metricName = ((MetricRequest) msg).getMetric().getName();
line = ((MetricRequest) msg).getLine();
keyClientPair = getClient(metricName, true);
} else {
// Version request
line = "version";
keyClientPair = getClient(null, false);
}
keyClientPair.getRight().write(line + "\n");
} finally {
if (keyClientPair != null && keyClientPair.getLeft() != null && keyClientPair.getRight() != null) {
tcpClientPool.returnObject(keyClientPair.getLeft(), keyClientPair.getRight());
}
}
} catch (Exception e) {
log.error(LOG_ERR_MSG, msg, e);
ChannelFuture cf = ctx.writeAndFlush(Unpooled.copiedBuffer((ERR_MSG + e.getMessage() + "\n").getBytes(StandardCharsets.UTF_8)));
if (!cf.isSuccess()) {
Throwable t = cf.cause();
String message = (t == null) ? "" : t.getMessage();
log.error(String.format(Constants.ERR_WRITING_RESPONSE, message), cf.cause());
}
}
}
private Pair<TimelyBalancedHost,TcpClient> getClient(String metric, boolean metricRequest) {
TcpClient client = null;
TimelyBalancedHost k = null;
int failures = 0;
while (client == null) {
try {
k = (metricRequest == true) ? metricResolver.getHostPortKeyIngest(metric) : metricResolver.getHostPortKey(metric);
client = tcpClientPool.borrowObject(k);
} catch (Exception e1) {
failures++;
client = null;
if (failures % 10 == 0) {
log.error(e1.getMessage(), e1);
}
try {
Thread.sleep(failures < 10 ? 500 : 60000);
} catch (InterruptedException e2) {
// nothing
}
}
}
return Pair.of(k, client);
}
}
@@ -0,0 +1,84 @@
package timely.balancer.netty.udp;
import java.nio.charset.StandardCharsets;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import timely.api.request.MetricRequest;
import timely.api.request.UdpRequest;
import timely.balancer.MetricResolver;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.udp.UdpClientPool;
import timely.client.udp.UdpClient;
import timely.netty.Constants;
public class UdpRelayHandler extends SimpleChannelInboundHandler<UdpRequest> {
private static final Logger log = LoggerFactory.getLogger(UdpRelayHandler.class);
private static final String LOG_ERR_MSG = "Error storing put metric: {}";
private static final String ERR_MSG = "Error storing put metric: ";
private MetricResolver metricResolver;
private UdpClientPool udpClientPool;
public UdpRelayHandler(MetricResolver metricResolver, UdpClientPool udpClientPool) {
this.metricResolver = metricResolver;
this.udpClientPool = udpClientPool;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, UdpRequest msg) throws Exception {
log.trace("Received {}", msg);
try {
MetricRequest metricRequest = (MetricRequest) msg;
String metricName = metricRequest.getMetric().getName();
Pair<TimelyBalancedHost,UdpClient> keyClientPair = null;
try {
keyClientPair = getClient(metricName, true);
keyClientPair.getRight().write(metricRequest.getLine() + "\n");
keyClientPair.getRight().flush();
} finally {
if (keyClientPair != null && keyClientPair.getLeft() != null && keyClientPair.getRight() != null) {
udpClientPool.returnObject(keyClientPair.getLeft(), keyClientPair.getRight());
}
}
} catch (Exception e) {
log.error(LOG_ERR_MSG, msg, e);
ChannelFuture cf = ctx.writeAndFlush(Unpooled.copiedBuffer((ERR_MSG + e.getMessage() + "\n").getBytes(StandardCharsets.UTF_8)));
if (!cf.isSuccess()) {
Throwable t = cf.cause();
String message = (t == null) ? "" : t.getMessage();
log.error(String.format(Constants.ERR_WRITING_RESPONSE, message), cf.cause());
}
}
}
private Pair<TimelyBalancedHost,UdpClient> getClient(String metric, boolean metricRequest) {
UdpClient client = null;
TimelyBalancedHost k = null;
int failures = 0;
while (client == null) {
try {
k = (metricRequest == true) ? metricResolver.getHostPortKeyIngest(metric) : metricResolver.getHostPortKey(metric);
client = udpClientPool.borrowObject(k);
} catch (Exception e1) {
failures++;
client = null;
log.error(e1.getMessage(), e1);
try {
Thread.sleep(failures < 10 ? 500 : 60000);
} catch (InterruptedException e2) {
// nothing
}
}
}
return Pair.of(k, client);
}
}
@@ -0,0 +1,94 @@
package timely.balancer.netty.ws;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.ScheduledFuture;
import timely.api.response.TimelyException;
import timely.auth.TimelyAuthenticationToken;
import timely.auth.util.ProxiedEntityUtils;
import timely.client.websocket.ClientHandler;
public class WsClientHandler extends ClientHandler {
private static final Logger log = LoggerFactory.getLogger(WsClientHandler.class);
private final ChannelHandlerContext ctx;
private final TimelyAuthenticationToken token;
private ScheduledFuture<?> ping;
public WsClientHandler(ChannelHandlerContext ctx, TimelyAuthenticationToken token, int pingRate) {
this.ctx = ctx;
this.token = token;
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
log.trace("Sending ping on channel {}", ctx.channel());
ctx.writeAndFlush(new PingWebSocketFrame());
}, pingRate, pingRate, TimeUnit.SECONDS);
}
@Override
public void beforeRequest(Map<String,List<String>> headers) {
if (token.getClientCert() != null) {
Multimap<String,String> proxyRequestHeaders = HashMultimap.create();
ProxiedEntityUtils.addProxyHeaders(proxyRequestHeaders, token.getClientCert());
for (String s : proxyRequestHeaders.keySet()) {
headers.put(s, new ArrayList<>(proxyRequestHeaders.get(s)));
}
}
Multimap<String,String> originalRequestHeaders = token.getHttpHeaders();
for (String s : originalRequestHeaders.keySet()) {
if (!headers.containsKey(s)) {
// add pre-existing values if key does not exist in proxyRequest
headers.put(s, new ArrayList<>(originalRequestHeaders.get(s)));
}
}
}
@Override
public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(String.class, message -> {
ctx.writeAndFlush(new TextWebSocketFrame(message));
log.debug("Message received on Websocket session {}: {}", session.getId(), message);
});
}
@Override
public void onClose(Session session, CloseReason reason) {
super.onClose(session, reason);
if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) {
log.error("Abnormal close: " + reason.getReasonPhrase());
Exception e = new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), reason.getReasonPhrase(), "");
WsRelayHandler.sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e);
}
if (!ping.isCancelled()) {
ping.cancel(false);
}
}
@Override
public void onError(Session session, Throwable t) {
super.onError(session, t);
log.error(t.getMessage(), t);
Exception e = new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), t.getMessage(), "");
WsRelayHandler.sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e);
if (!ping.isCancelled()) {
ping.cancel(false);
}
}
}
@@ -0,0 +1,43 @@
package timely.balancer.netty.ws;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.ws.WsClientPool;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
public class WsClientHolder {
private static final Logger log = LoggerFactory.getLogger(WsClientHolder.class);
private WebSocketSubscriptionClient client;
private TimelyBalancedHost host;
public WsClientHolder(TimelyBalancedHost host, WebSocketSubscriptionClient client) {
this.host = host;
this.client = client;
}
synchronized public WebSocketSubscriptionClient getClient() {
return client;
}
synchronized public TimelyBalancedHost getHost() {
return host;
}
synchronized public void close(WsClientPool wsClientPool) {
if (client != null && host != null) {
try {
client.close();
} catch (IOException e) {
log.error("Error closing web socket client", e);
}
wsClientPool.returnObject(host, client);
client = null;
host = null;
}
}
}
@@ -0,0 +1,204 @@
package timely.balancer.netty.ws;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import timely.api.request.websocket.AddSubscription;
import timely.api.request.websocket.CloseSubscription;
import timely.api.request.websocket.CreateSubscription;
import timely.api.request.websocket.RemoveSubscription;
import timely.api.request.websocket.SubscriptionRequest;
import timely.api.response.TimelyException;
import timely.auth.util.ProxiedEntityUtils;
import timely.balancer.MetricResolver;
import timely.balancer.configuration.BalancerWebsocketProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.ws.WsClientPool;
import timely.client.websocket.subscription.WebSocketSubscriptionClient;
import timely.netty.http.TimelyHttpHandler;
public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequest> implements TimelyHttpHandler {
private static final Logger log = LoggerFactory.getLogger(WsRelayHandler.class);
private BalancerWebsocketProperties balancerWebsocketProperties;
private WsClientPool wsClientPool;
private MetricResolver metricResolver;
static private ObjectMapper mapper;
Map<String,Map<String,WsClientHolder>> wsClients = new ConcurrentHashMap<>();
Map<String,ChannelHandlerContext> wsSubscriptions = new ConcurrentHashMap<>();
static {
mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(SerializationFeature.WRAP_EXCEPTIONS, true);
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}
public WsRelayHandler(BalancerWebsocketProperties balancerWebsocketProperties, MetricResolver metricResolver, WsClientPool wsClientPool) {
this.balancerWebsocketProperties = balancerWebsocketProperties;
this.metricResolver = metricResolver;
this.wsClientPool = wsClientPool;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, SubscriptionRequest msg) throws Exception {
TimelyBalancedHost k;
ChannelHandlerContext origContext = null;
String subscriptionId;
try {
String metric = null;
Multimap<String,String> headers = msg.getRequestHeaders();
ProxiedEntityUtils.addProxyHeaders(headers, msg.getToken().getClientCert());
if (msg instanceof CreateSubscription) {
CreateSubscription create = (CreateSubscription) msg;
final String currentSubscriptionId = create.getSubscriptionId();
subscriptionId = currentSubscriptionId;
wsSubscriptions.put(subscriptionId, ctx);
// WebSocketSubscriptionClient sends the createSubscription when
// we call open
// We don't know where to send it to yet anyway because that
// depends on the metric.
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Map<String,WsClientHolder> metricToClientMap = wsClients.get(currentSubscriptionId);
if (metricToClientMap != null) {
synchronized (metricToClientMap) {
// close all clients for this subscriptionId
log.debug("Channel closed, closing subscriptions for subscriptionId:{}", currentSubscriptionId);
for (Map.Entry<String,WsClientHolder> entry : metricToClientMap.entrySet()) {
entry.getValue().close(wsClientPool);
}
wsClients.remove(currentSubscriptionId);
wsSubscriptions.remove(currentSubscriptionId);
}
}
}
});
} else if (msg instanceof AddSubscription) {
AddSubscription add = (AddSubscription) msg;
metric = add.getMetric();
subscriptionId = add.getSubscriptionId();
origContext = wsSubscriptions.get(subscriptionId);
if (origContext == null) {
// send error because user never sent a CreateSubscription
// and we have nowhere to send the results
log.info("ChannelHandlerContext not found for subscriptionId:{} - createSubscription not called?", subscriptionId);
sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, new IllegalArgumentException("Must call create first"));
}
Map<String,WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
if (metricToClientMap == null) {
metricToClientMap = new HashMap<>();
wsClients.put(subscriptionId, metricToClientMap);
}
WebSocketSubscriptionClient client = null;
synchronized (metricToClientMap) {
WsClientHolder hostClientHolder = metricToClientMap.get(metric);
if (hostClientHolder == null) {
k = metricResolver.getHostPortKey(metric);
client = wsClientPool.borrowObject(k);
client.open(new WsClientHandler(origContext, add.getToken(), (balancerWebsocketProperties.getTimeout() / 2)));
metricToClientMap.put(metric, new WsClientHolder(k, client));
} else {
client = hostClientHolder.getClient();
}
}
Map<String,String> tags = null;
Long startTime = 0L;
Long endTime = 0L;
Long delayTime = 5000L;
if (add.getTags().isPresent()) {
tags = add.getTags().get();
}
if (add.getStartTime().isPresent()) {
startTime = add.getStartTime().get();
}
if (add.getEndTime().isPresent()) {
endTime = add.getEndTime().get();
}
if (add.getDelayTime().isPresent()) {
delayTime = add.getDelayTime().get();
}
client.addSubscription(add.getMetric(), tags, startTime, endTime, delayTime);
} else if (msg instanceof RemoveSubscription) {
RemoveSubscription remove = (RemoveSubscription) msg;
metric = remove.getMetric();
subscriptionId = remove.getSubscriptionId();
Map<String,WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
if (metricToClientMap != null) {
synchronized (metricToClientMap) {
WsClientHolder hostClientHolder = metricToClientMap.get(metric);
if (hostClientHolder != null) {
hostClientHolder.getClient().removeSubscription(metric);
} else {
log.info("client not found for subscriptionId:{} metric:{}", subscriptionId, metric);
}
}
}
} else if (msg instanceof CloseSubscription) {
CloseSubscription close = (CloseSubscription) msg;
subscriptionId = close.getSubscriptionId();
Map<String,WsClientHolder> metricToClientMap = wsClients.get(subscriptionId);
if (metricToClientMap != null) {
synchronized (metricToClientMap) {
// close all clients for this subscriptionId
for (Map.Entry<String,WsClientHolder> entry : metricToClientMap.entrySet()) {
entry.getValue().close(wsClientPool);
}
}
wsClients.remove(subscriptionId);
wsSubscriptions.remove(subscriptionId);
}
ctx.writeAndFlush(new CloseWebSocketFrame(1000, "Client requested close."));
}
} catch (ConnectException e1) {
log.error(e1.getMessage(), e1);
ChannelHandlerContext errorCtx = origContext == null ? ctx : origContext;
sendErrorResponse(errorCtx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e1);
} catch (Exception e2) {
log.error(e2.getMessage(), e2);
ChannelHandlerContext errorCtx = origContext == null ? ctx : origContext;
sendErrorResponse(errorCtx, HttpResponseStatus.INTERNAL_SERVER_ERROR, e2);
}
}
static public void sendErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status, Exception ex) {
try {
TimelyException te = new TimelyException(status.code(), ex.getMessage(), ex.getMessage());
String json = mapper.writeValueAsString(te);
ctx.writeAndFlush(new TextWebSocketFrame(json));
} catch (JsonProcessingException e) {
log.error("Error serializing exception");
}
}
}
+11
View File
@@ -0,0 +1,11 @@
===============================================================================
_______ _ _ ____ _
|__ __(_) | | | _ \ | |
| | _ _ __ ___ ___| |_ _ | |_) | __ _| | __ _ _ __ ___ ___ _ __
| | | | '_ ` _ \ / _ \ | | | | | _ < / _` | |/ _` | '_ \ / __/ _ \ '__|
| | | | | | | | | __/ | |_| | | |_) | (_| | | (_| | | | | (_| __/ |
|_| |_|_| |_| |_|\___|_|\__, | |____/ \__,_|_|\__,_|_| |_|\___\___|_|
__/ |
|___/
===============================================================================
TimelyBalancer ${application.formatted-version}
@@ -0,0 +1,6 @@
spring:
application:
name: timely-balancer
config:
name: timely-balancer
location: ${CONF_DIR}/
+2
View File
@@ -0,0 +1,2 @@
cd src/main/generated-java
flatc --java ../resources/fb/metric.fbs
+21
View File
@@ -0,0 +1,21 @@
namespace timely.api.flatbuffer;
file_identifier "TMFB";
table Tag {
key:string;
value:string;
}
table Metric {
name:string;
timestamp:long;
value:double;
tags:[Tag];
}
table Metrics {
metrics:[Metric];
}
root_type Metrics;
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="timely.balancer.Balancer" />
<Method name="shutdown" />
<Bug pattern="UC_USELESS_OBJECT" />
</Match>
<Match>
<Class name="timely.balancer.netty.http.HttpRelayHandler$MyByteArrayOutputStream" />
<Method name="getBuf" />
<Bug pattern="EI_EXPOSE_REP" />
</Match>
<Match>
<!-- these are probably avoidable, but needs further triage to fix -->
<Bug pattern="EI_EXPOSE_REP" />
</Match>
<Match>
<!-- these are probably avoidable, but needs further triage to fix -->
<Bug pattern="EI_EXPOSE_REP2" />
</Match>
</FindBugsFilter>
@@ -0,0 +1,56 @@
package timely.balancer.configuration;
import javax.net.ssl.SSLContext;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import io.netty.handler.ssl.SslContext;
import timely.balancer.HealthChecker;
import timely.balancer.MetricResolver;
import timely.balancer.test.TestBalancer;
import timely.balancer.test.TestHealthChecker;
import timely.balancer.test.TestMetricResolver;
import timely.common.component.AuthenticationService;
import timely.common.configuration.CacheProperties;
import timely.common.configuration.SecurityProperties;
import timely.common.configuration.SslClientProperties;
import timely.common.configuration.SslServerProperties;
import timely.common.configuration.ZookeeperProperties;
@Configuration
@ComponentScan(basePackages = {"timely.balancer", "timely.common", "timely.test"})
public class TestBalancerConfiguration {
@Bean
public TestBalancer balancer(ApplicationContext applicationContext, @Qualifier("nettySslContext") SslContext serverSslContext,
@Qualifier("outboundJDKSslContext") SSLContext clientSSLContext, AuthenticationService authenticationService, MetricResolver metricResolver,
BalancerProperties balancerProperties, ZookeeperProperties zookeeperProperties, SecurityProperties securityProperties,
SslServerProperties sslServerProperties, SslClientProperties sslClientProperties, BalancerServerProperties balancerServerProperties,
BalancerHttpProperties balancerHttpProperties, BalancerWebsocketProperties balancerWebsocketProperties) {
TestBalancer balancer = new TestBalancer(applicationContext, serverSslContext, clientSSLContext, authenticationService, metricResolver,
balancerProperties, zookeeperProperties, securityProperties, sslServerProperties, sslClientProperties, balancerServerProperties,
balancerHttpProperties, balancerWebsocketProperties);
balancer.start();
return balancer;
}
@Bean
public TestHealthChecker healthChecker(BalancerProperties balancerProperties, BalancerServerProperties balancerServerProperties) {
TestHealthChecker healthChecker = new TestHealthChecker(balancerProperties, balancerServerProperties);
healthChecker.start();
return healthChecker;
}
@Bean
public TestMetricResolver metricResolver(CuratorFramework curatorFramework, BalancerProperties balancerProperties, CacheProperties cacheProperties,
HealthChecker healthChecker) throws Exception {
TestMetricResolver metricResolver = new TestMetricResolver(curatorFramework, balancerProperties, cacheProperties, healthChecker);
metricResolver.start();
return metricResolver;
}
}
@@ -0,0 +1,80 @@
package timely.balancer.configuration;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.util.Properties;
import java.util.concurrent.Executors;
import org.apache.zookeeper.server.ServerConfig;
import org.apache.zookeeper.server.ZooKeeperServerMain;
import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import timely.common.configuration.ZookeeperProperties;
@Configuration
@ConditionalOnProperty(name = "timely.in-memory-accumulo.enabled", havingValue = "true")
public class TestZookeeperConfiguration {
private static final Logger log = LoggerFactory.getLogger(TestZookeeperConfiguration.class);
private static final int MAX_PORT = 100;
private int port;
private ZookeeperProperties zookeeperProperties;
@Bean(name = "ZooKeeperServerMain", destroyMethod = "close")
public ZooKeeperServerMain zooKeeperServerMain(ZookeeperProperties zookeeperProperties) throws Exception {
this.zookeeperProperties = zookeeperProperties;
this.port = getNextPort(2181);
this.zookeeperProperties.setServers(String.format("localhost:%d", port));
File tempDir;
try {
tempDir = Files.createTempDirectory("zookeeper_temp").toFile();
tempDir.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Unable to create temp directory for mini accumulo cluster");
}
Properties properties = new Properties();
properties.put("dataDir", tempDir.getAbsolutePath() + "/data");
properties.put("clientPort", port);
properties.put("skipACL", "true");
properties.put("leaderServes", "yes");
properties.put("4lw.commands.whitelist", "*");
properties.put("admin.enableServer", "false");
QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig();
quorumPeerConfig.parseProperties(properties);
ZooKeeperServerMain zookeeper = new ZooKeeperServerMain();
final ServerConfig config = new ServerConfig();
config.readFrom(quorumPeerConfig);
Executors.newSingleThreadExecutor().submit(() -> {
try {
zookeeper.runFromConfig(config);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
log.info("Started TestZookeeper on port {}", port);
return zookeeper;
}
private int getNextPort(int start) {
for (int port = start; port < start + MAX_PORT; ++port) {
try {
new ServerSocket(port).close();
return port;
} catch (IOException e) {
}
}
return -1;
}
}
@@ -0,0 +1,95 @@
package timely.balancer.integration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import timely.balancer.configuration.BalancerHttpProperties;
import timely.netty.http.HttpStrictTransportSecurityHandler;
import timely.test.IntegrationTest;
import timely.test.TimelyTestRule;
@Category(IntegrationTest.class)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class HTTPStrictTransportSecurityIT {
@Autowired
@Rule
public TimelyTestRule testRule;
@Autowired
private BalancerHttpProperties httpProperties;
@Autowired
protected SSLContext clientSslContext;
private String baseHttpUrl;
private String baseHttpsUrl;
private String HSTS_HEADER_VALUE;
@Before
public void setup() {
baseHttpUrl = "http://" + httpProperties.getHost() + ":" + httpProperties.getPort();
baseHttpsUrl = "https://" + httpProperties.getHost() + ":" + httpProperties.getPort();
HSTS_HEADER_VALUE = String.format("max-age=%d; includeSubDomains; preload", httpProperties.getStrictTransportMaxAge());
}
protected SSLSocketFactory getSSLSocketFactory() throws Exception {
return clientSslContext.getSocketFactory();
}
protected HttpsURLConnection getUrlConnection(URL url) throws Exception {
HttpsURLConnection.setDefaultSSLSocketFactory(getSSLSocketFactory());
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setHostnameVerifier((host, session) -> true);
return con;
}
@Test
public void testHttpRequest() throws Exception {
URL url = new URL(baseHttpUrl + "/api/metrics");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
assertEquals(403, responseCode);
// should not have HSTS header on HTTP response
assertNull(con.getHeaderField(HttpStrictTransportSecurityHandler.HSTS_HEADER_NAME));
con.disconnect();
}
@Test
public void testHttpsRequestHSTS() throws Exception {
URL url = new URL(baseHttpsUrl + "/version");
HttpsURLConnection con = getUrlConnection(url);
int responseCode = con.getResponseCode();
assertEquals(200, responseCode);
assertEquals(HSTS_HEADER_VALUE, con.getHeaderField(HttpStrictTransportSecurityHandler.HSTS_HEADER_NAME));
con.disconnect();
}
@Test
public void testHttpsRequestNotFound() throws Exception {
URL url = new URL(baseHttpsUrl);
HttpsURLConnection con = getUrlConnection(url);
int responseCode = con.getResponseCode();
assertEquals(404, responseCode);
assertEquals(HSTS_HEADER_VALUE, con.getHeaderField(HttpStrictTransportSecurityHandler.HSTS_HEADER_NAME));
con.disconnect();
}
}
@@ -0,0 +1,138 @@
package timely.balancer.integration;
import static timely.Constants.SERVICE_DISCOVERY_PATH;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.x.discovery.ServiceCache;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.curator.x.discovery.ServiceInstanceBuilder;
import org.apache.curator.x.discovery.details.ServiceCacheListener;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import timely.ServerDetails;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.test.TestHealthChecker;
import timely.balancer.test.TestMetricResolver;
import timely.test.IntegrationTest;
import timely.test.TimelyTestRule;
@Category(IntegrationTest.class)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MetricResolverIT {
private static final Logger log = LoggerFactory.getLogger(MetricResolverIT.class);
@Autowired
@Rule
public TimelyTestRule testRule;
@Autowired
private TestMetricResolver metricResolver;
@Autowired
private TestHealthChecker healthChecker;
@Autowired
private CuratorFramework curatorFramework;
private ServiceDiscovery<ServerDetails> discovery;
private ServiceCache<ServerDetails> serviceCache;
@Before
public void setup() throws Exception {
discovery = ServiceDiscoveryBuilder.builder(ServerDetails.class).client(curatorFramework).basePath(SERVICE_DISCOVERY_PATH).build();
discovery.start();
serviceCache = discovery.serviceCacheBuilder().name("timely-server").build();
}
@Test
public void ServiceRegistrationTest() {
LinkedBlockingQueue<List<ServiceInstance<ServerDetails>>> updateQueue = getServiceUpdateQueue(serviceCache);
registerService(curatorFramework, TimelyBalancedHost.of("host1", 5242, 5243, 5244, 5245, null));
registerService(curatorFramework, TimelyBalancedHost.of("host1", 5342, 5343, 5344, 5345, null));
registerService(curatorFramework, TimelyBalancedHost.of("host1", 5442, 5443, 5444, 5445, null));
registerService(curatorFramework, TimelyBalancedHost.of("host1", 5542, 5543, 5544, 5545, null));
try {
List<ServiceInstance<ServerDetails>> currentUpdate;
List<ServiceInstance<ServerDetails>> lastUpdate = null;
do {
currentUpdate = updateQueue.poll(200, TimeUnit.MILLISECONDS);
if (currentUpdate != null) {
lastUpdate = currentUpdate;
}
} while (currentUpdate != null);
Assert.assertNotNull(lastUpdate);
Assert.assertEquals(4, lastUpdate.size());
} catch (InterruptedException e) {
Assert.fail("" + e.getMessage());
}
Assert.assertEquals(4, metricResolver.getServerList().size());
Assert.assertEquals(4, healthChecker.getTimelyHosts().size());
}
public void registerService(CuratorFramework curatorFramework, TimelyBalancedHost server) {
try {
ServerDetails payload = new ServerDetails();
payload.setHost(server.getHost());
payload.setTcpPort(server.getTcpPort());
payload.setHttpPort(server.getHttpPort());
payload.setWsPort(server.getWsPort());
payload.setUdpPort(server.getUdpPort());
ServiceInstanceBuilder<ServerDetails> builder = ServiceInstance.builder();
String serviceName = server.getHost() + ":" + server.getTcpPort();
ServiceInstance<ServerDetails> serviceInstance = builder.id(serviceName).name("timely-server").address(server.getHost()).port(server.getTcpPort())
.payload(payload).build();
ServiceDiscovery<ServerDetails> discovery = ServiceDiscoveryBuilder.builder(ServerDetails.class).client(curatorFramework)
.basePath(SERVICE_DISCOVERY_PATH).build();
discovery.start();
discovery.registerService(serviceInstance);
} catch (Exception e) {
log.error(e.getMessage());
}
}
private LinkedBlockingQueue<List<ServiceInstance<ServerDetails>>> getServiceUpdateQueue(ServiceCache<ServerDetails> serviceCache) {
LinkedBlockingQueue<List<ServiceInstance<ServerDetails>>> updates = new LinkedBlockingQueue<>();
try {
ServiceCacheListener listener = new ServiceCacheListener() {
@Override
public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {
}
@Override
public void cacheChanged() {
updates.add(serviceCache.getInstances());
}
};
serviceCache.addListener(listener);
serviceCache.start();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return updates;
}
}
@@ -0,0 +1,36 @@
package timely.balancer.test;
import javax.net.ssl.SSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import io.netty.handler.ssl.SslContext;
import timely.balancer.Balancer;
import timely.balancer.MetricResolver;
import timely.balancer.configuration.BalancerHttpProperties;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.configuration.BalancerWebsocketProperties;
import timely.common.component.AuthenticationService;
import timely.common.configuration.SecurityProperties;
import timely.common.configuration.SslClientProperties;
import timely.common.configuration.SslServerProperties;
import timely.common.configuration.ZookeeperProperties;
public class TestBalancer extends Balancer {
private static final Logger log = LoggerFactory.getLogger(TestBalancer.class);
public TestBalancer(ApplicationContext applicationContext, SslContext serverSslContext, SSLContext clientSSLContext,
AuthenticationService authenticationService, MetricResolver metricResolver, BalancerProperties balancerProperties,
ZookeeperProperties zookeeperProperties, SecurityProperties securityProperties, SslServerProperties sslServerProperties,
SslClientProperties sslClientProperties, BalancerServerProperties balancerServerProperties, BalancerHttpProperties balancerHttpProperties,
BalancerWebsocketProperties balancerWebsocketProperties) {
super(applicationContext, serverSslContext, clientSSLContext, authenticationService, metricResolver, balancerProperties, zookeeperProperties,
securityProperties, sslServerProperties, sslClientProperties, balancerServerProperties, balancerHttpProperties,
balancerWebsocketProperties);
DEFAULT_EVENT_LOOP_THREADS = 1;
log.info("Starting TestBalancer");
}
}
@@ -0,0 +1,19 @@
package timely.balancer.test;
import java.util.List;
import timely.balancer.HealthChecker;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.configuration.BalancerServerProperties;
import timely.balancer.connection.TimelyBalancedHost;
public class TestHealthChecker extends HealthChecker {
public TestHealthChecker(BalancerProperties balancerProperties, BalancerServerProperties balancerServerProperties) {
super(balancerProperties, balancerServerProperties);
}
public List<TimelyBalancedHost> getTimelyHosts() {
return this.timelyHosts;
}
}
@@ -0,0 +1,44 @@
package timely.balancer.test;
import java.util.List;
import org.apache.curator.framework.CuratorFramework;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.balancer.HealthChecker;
import timely.balancer.MetricResolver;
import timely.balancer.configuration.BalancerProperties;
import timely.balancer.connection.TimelyBalancedHost;
import timely.common.configuration.CacheProperties;
public class TestMetricResolver extends MetricResolver {
private static final Logger log = LoggerFactory.getLogger(TestMetricResolver.class);
public TestMetricResolver(CuratorFramework curatorFramework, BalancerProperties balancerProperties, CacheProperties cacheProperties,
HealthChecker healthChecker) throws Exception {
super(curatorFramework, balancerProperties, cacheProperties, healthChecker);
}
protected void createAssignmentFile() {
try {
log.info("Creating assignment file: " + balancerProperties.getAssignmentFile());
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(configuration);
Path assignmentFile = new Path(balancerProperties.getAssignmentFile());
fs.setWriteChecksum(false);
fs.create(assignmentFile);
fs.deleteOnExit(assignmentFile);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
public List<TimelyBalancedHost> getServerList() {
return this.serverList;
}
}
@@ -0,0 +1,20 @@
package timely.balancer.test;
import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.stereotype.Component;
@Primary
@Component
public class TestTimelyAuthenticationManager extends ProviderManager {
@Autowired
public TestTimelyAuthenticationManager(@Qualifier("file") PreAuthenticatedAuthenticationProvider file) {
super(Collections.singletonList(file));
}
}
@@ -0,0 +1,178 @@
logging:
level:
ROOT: info
timely: debug
org.apache.zookeeper: error
timely:
test: true
instance: 1
accumulo:
instance-name: default
username: root
password: secret
mini-accumulo:
enabled: true
rootAuths: A,B,C,D,E,F,G,H,I
zookeeper:
servers: localhost:0
timeout: 30s
security:
session-max-age: 86400
cache-expiration-minutes: 60
cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
authorized-users:
- subject-dn: cn=example.com
roles: G,H,I
auths: D,E,F
user-type: USER
- subject-dn: cn=test a. user, ou=example developers, o=example corp, c=us
issuer-dn: cn=example corp ca, o=example corp, c=us
roles: G,H,I
auths: A,B,C
user-type: USER
server-ssl:
key-store-type: PKCS12
key-store-file: classpath:ssl/testServer.p12
key-store-password: ChangeIt
trust-store-type: JKS
trust-store-file: classpath:ssl/ca.jks
trust-store-password: ChangeIt
use-generated-keypair: false
ignore-ssl-handshake-errors: true
use-openssl: true
client-ssl:
key-store-type: PKCS12
key-store-file: classpath:ssl/testUser.p12
key-store-password: ChangeIt
trust-store-type: JKS
trust-store-file: classpath:ssl/ca.jks
trust-store-password: ChangeIt
cache:
enabled: false
metric-age-off-hours:
default: 26
non-cached-metrics:
balancer:
check-server-health-interval: 10000
server-failures-before-down: 6
server-successes-before-up: 1
assignment-file: file://${java.io.tmpdir}/timely/balancer/assignments.csv
server:
ip: 127.0.0.1
tcp-base-port: 9242
tcp-buffer-size: 100
tcp-client-pool:
min-idle-per-key: 5
max-idle-per-key: 5
max-total-per-key: 5
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
udp-base-port: 9245
udp-client-pool:
min-idle-per-key: 5
max-idle-per-key: 5
max-total-per-key: 5
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
http:
host: localhost
ip: 127.0.0.1
base-port: 9243
redirect-path: /secure-me
strict-transport-max-age: 604800
connection-request-timeout: 5000
connect-timeout: 5000
socket-timeout: 300000
request-timeout: 300000
cors:
allow-any-origin: false
allow-null-origin: false
allow-credentials: true
allowed-origins:
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
allowed-headers: content-type
http-client-pool:
min-idle-per-key: 10
max-idle-per-key: 40
max-total-per-key: 40
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
websocket:
ip: 127.0.0.1
base-port: 9244
timeout: 60
subscription-lag: 120
scanner-batch-size: 10000
flush-interval-seconds: 30
scanner-read-ahead: 1
subscription-batch-size: 5000
incoming-buffer-size: 50000000
ws-client-pool:
min-idle-per-key: 0
max-idle-per-key: 40
max-total-per-key: 40
max-total: -1
lifo: false
fairness: true
max-wait-millis: 60000
min-evictable-idle-time-millis: -1
evictor-shutdown-timeout-millis: 10000
soft-min-evictable-idle-time-millis: 300000
num-tests-per-eviction-run: 3
time-between-eviction-runs-millis: 30000
block-when-exhausted: true
remove-abandoned-on-borrow: false
remove-abandoned-on-maintenance: true
remove-abandoned-timeout-millis: 600000
remove-abandoned-logging: true
jmx-enabled: true
test-on-borrow: false
test-on-return: false
@@ -0,0 +1,3 @@
spring:
application:
name: timely-balancer
+24
View File
@@ -0,0 +1,24 @@
Configuration:
status: warn
monitorInterval: 60
Properties:
Property:
- name: PID
value: "????"
- name: LOG_PATTERN
value: "%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%wEx"
Appenders:
Console:
name: Console
target: SYSTEM_OUT
follow: true
PatternLayout:
pattern: "${LOG_PATTERN}"
Loggers:
Root:
level: warn
AppenderRef:
- ref: Console
+23
View File
@@ -0,0 +1,23 @@
Bag Attributes: <No Attributes>
subject=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
issuer=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIURUj8l+ms/i9jeabIBBi19KxEhAgwDQYJKoZIhvcNAQEL
BQAwPjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEYMBYGA1UE
AxMPRVhBTVBMRSBDT1JQIENBMCAXDTIzMDIwMTIzMTUwMFoYDzIxMjMwMTA4MjMx
NTAwWjA+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRXhhbXBsZSBDb3JwMRgwFgYD
VQQDEw9FWEFNUExFIENPUlAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDVBr2XF20SKmGXfvp2BUY44BF1fm3Qw83GkC66qIHgpgQxzJ8u44P34vV1
DYTj4XzrBpx23J0nCMEiAW8YW9MxZJPEokZafRNKFQAngVdgmAW9fHemHRQfaYwe
ySVz2UZ+z28DJ3C0QshvqXskrYstwLVs4Ss2uoX+XpeFbW9IVVg4aW+GX3LHPNTk
7Hji13Capp6BCiS61AGQAiMWNBW2aRgHVLOb7RDQug2Bka/dcJ44XxJDh26C6kV1
pNEZm0ToqWAiVfvyzionPec5im6NKX9kStZc+C/iikfx5vdmrvGIF5zxy6phdhJ7
AGd6cF6UBkkYm/SsIP3WTXpBek5LAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSPLNlCmmp9PH+crzCx9TNp5BoCajAN
BgkqhkiG9w0BAQsFAAOCAQEAkjLnZKqLMjbu5Hy8YOVoKaEKahP+t/tNYZW/NtrC
xSxKmGLz5kVCkjpLgCykkcpu4h2wZW4MGZJ4nc87+tN22mJOg1WnqEpBzRsEPajk
7duVjdU2FDCarERj53hxF41l/u8BxvMGbFnrAijv5SzspeLi+VCGF3wu53Bi6UHi
7WO1dIhZLMHepqgtwbDDudxwlKbG/JWjJ7OspCNqOpktpY8BFUAQ42ctYZcOHy2w
kdZDcK5IhRztkBH2aSWOhudYh/CF7BOu2nSUdtR5NwhXnsStmSbud7ahw3W5Jc9/
IVAFXf2brkrfIzwrY5kmjnEPJ8mjUyLepwSlOlMTBrehzQ==
-----END CERTIFICATE-----
Binary file not shown.
@@ -0,0 +1,51 @@
Bag Attributes
localKeyID: 22 89 1C EA 4E FF 9A F6 11 1B E7 9B 5E 4F 59 B7 B1 0F AA FA
friendlyName: example-server
subject=/C=US/O=Example Corp/OU=Servers/CN=testserver.example.com
issuer=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIUe6yBgkY1m/oXuPXA9j/c/7+w16UwDQYJKoZIhvcNAQEL
BQAwPjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEYMBYGA1UE
AxMPRVhBTVBMRSBDT1JQIENBMCAXDTIzMDIwMTIzMTUwMFoYDzIxMjMwMTA4MjMx
NTAwWjBXMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRXhhbXBsZSBDb3JwMRAwDgYD
VQQLEwdTZXJ2ZXJzMR8wHQYDVQQDExZ0ZXN0c2VydmVyLmV4YW1wbGUuY29tMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmb1pEWg9a6ZChQULazK75ppc
XScU5Ap29L70/5zcaOcbaBwqKhpKhdZCoZ1TD+Sdkk9kp7IlabCQjM2vVjknb6NY
msl3PbQFNva0EkH6s5cvpX4WhQDsWK77/4eztHT+0fpaG1YoKQn+Gy3VuW0CDyR6
GzGKifnI/aNLevgUBebbiiTPLEdGlo0jp3SXlZfoDZdbvnhBWI8Xc+aDekNlrXxW
X2TVnc02Fi914PECYJzkMpmxnBRPAcljU24WSqaUVgRoSbafkm4OXDw0xtvxnIrJ
s3iO0uZfRf7+OxAlqlvh4ZJ9/PufPMEKvmr6zea05KkFjBotKMip5Md43H3lQQID
AQABo4GcMIGZMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUDWwMrl9/uc1wplERWxas
bDROB4MwHwYDVR0jBBgwFoAUjyzZQppqfTx/nK8wsfUzaeQaAmowGgYDVR0RBBMw
EYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBfG7StgQf3WNk/
EnaYuurwvaOOpcOV5NDcZ6tmgaXUrISPVDpyIWfsDGetNEXc+4gpefLQtGF2wuiW
odiLc4H9JwQ/yEkVYwzrFViPdZKVvxNU2EjiwO/e/3sl+SxxkTDy6o838uTXX+X5
Q8DabmL5L8ukcnqUHVOqW38oA1vRNBKiJX58JzOr8ezX8crZmqU9h8cNwG/V+ZXE
5U7+hSRzLm7gRwtixj8ZsTDUiGJxOdbyUKImPClt1Se9TizB2Q/xOPTdmYB1qiiB
+XVKkGwQPgIbHckLsZWhBor2u5cgdrBGW09Q+H88maagwq5sBHNMs33vkkgbUDpB
6WSEBJMi
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
issuer=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIURUj8l+ms/i9jeabIBBi19KxEhAgwDQYJKoZIhvcNAQEL
BQAwPjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEYMBYGA1UE
AxMPRVhBTVBMRSBDT1JQIENBMCAXDTIzMDIwMTIzMTUwMFoYDzIxMjMwMTA4MjMx
NTAwWjA+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRXhhbXBsZSBDb3JwMRgwFgYD
VQQDEw9FWEFNUExFIENPUlAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDVBr2XF20SKmGXfvp2BUY44BF1fm3Qw83GkC66qIHgpgQxzJ8u44P34vV1
DYTj4XzrBpx23J0nCMEiAW8YW9MxZJPEokZafRNKFQAngVdgmAW9fHemHRQfaYwe
ySVz2UZ+z28DJ3C0QshvqXskrYstwLVs4Ss2uoX+XpeFbW9IVVg4aW+GX3LHPNTk
7Hji13Capp6BCiS61AGQAiMWNBW2aRgHVLOb7RDQug2Bka/dcJ44XxJDh26C6kV1
pNEZm0ToqWAiVfvyzionPec5im6NKX9kStZc+C/iikfx5vdmrvGIF5zxy6phdhJ7
AGd6cF6UBkkYm/SsIP3WTXpBek5LAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSPLNlCmmp9PH+crzCx9TNp5BoCajAN
BgkqhkiG9w0BAQsFAAOCAQEAkjLnZKqLMjbu5Hy8YOVoKaEKahP+t/tNYZW/NtrC
xSxKmGLz5kVCkjpLgCykkcpu4h2wZW4MGZJ4nc87+tN22mJOg1WnqEpBzRsEPajk
7duVjdU2FDCarERj53hxF41l/u8BxvMGbFnrAijv5SzspeLi+VCGF3wu53Bi6UHi
7WO1dIhZLMHepqgtwbDDudxwlKbG/JWjJ7OspCNqOpktpY8BFUAQ42ctYZcOHy2w
kdZDcK5IhRztkBH2aSWOhudYh/CF7BOu2nSUdtR5NwhXnsStmSbud7ahw3W5Jc9/
IVAFXf2brkrfIzwrY5kmjnEPJ8mjUyLepwSlOlMTBrehzQ==
-----END CERTIFICATE-----
@@ -0,0 +1,32 @@
Bag Attributes
localKeyID: 22 89 1C EA 4E FF 9A F6 11 1B E7 9B 5E 4F 59 B7 B1 0F AA FA
friendlyName: example-server
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZvWkRaD1rpkKF
BQtrMrvmmlxdJxTkCnb0vvT/nNxo5xtoHCoqGkqF1kKhnVMP5J2ST2SnsiVpsJCM
za9WOSdvo1iayXc9tAU29rQSQfqzly+lfhaFAOxYrvv/h7O0dP7R+lobVigpCf4b
LdW5bQIPJHobMYqJ+cj9o0t6+BQF5tuKJM8sR0aWjSOndJeVl+gNl1u+eEFYjxdz
5oN6Q2WtfFZfZNWdzTYWL3Xg8QJgnOQymbGcFE8ByWNTbhZKppRWBGhJtp+Sbg5c
PDTG2/GcismzeI7S5l9F/v47ECWqW+Hhkn38+588wQq+avrN5rTkqQWMGi0oyKnk
x3jcfeVBAgMBAAECggEAZEHC7NkfhL4NjxV1Ftkk9AMhzJWZ6ARzArQsrSysB7T+
iGwliwNOKe1J0n74FdsubqVbrIheIYhFG61edEodT44kT/ylyBVHeUp0r5tbVBZ5
ueB56w5pEoQ6Ta8/H6VUL7alYEJVOj4L+clwINXXi+xrvKQYBlZgHheJN43j8NxS
9lCrVcm58rzX3i29S7ErBf7RqDabgD6j2m7rehnSq5Ps2wxwV/LXMl4Lxlwa0jz/
LV8szNMZViPOWAqOCy7NZbwCCaCfKVUAwmNGBkE3p4JVPeLNLzCBY9G0rHVmFJLK
yPnCB7PtCZeFTsU+8OHHWoxMFsaOO9Pp8FtB/r8PYQKBgQDI0OzEo3t/QKvbwAdz
9Eze0LQvstzZIidTWbYMTxH0g7DIt+3p11d61GKtfeuYUTPlLodufdv/BaP8G9sI
NzxisB3uH/787M9fwi1OzO7zMMZZCVa7Q1vZk6ta1qY3PekcYMPqjBJhxP1X3S7x
8+mrW5vTq/l8ZRVFeLvEkh6bpQKBgQDD/MCRJoq6nzd2DoTyT9m5qO+Jlztmu9+T
vqVOg7q0NO7U7dzyMbWgMvMFoEBidnyllU1rEj2YOo4OrI2v/JmSPUVS8VXsBkkG
UqInV4F/g0Wp3hzrkZFMqHLa6AydugmRNkOpgurRKOiwAL0Pgvk8+bOIbKMM7JBE
tZ6Zvi0gbQKBgD4S5r0505jYQsdgqm0bXQ+a77e20DGGLyXCm1Ves6wjcOf6gt4/
pFjhgccjH0or/6CeK1rqKLDB65ioe6IkgNP4oKfU2BsSkF+HRD4RduNRdl5bVqX4
AlJVJSilJSf6SS5ucuDzf1MdczpPso0z2k0PujFJ/YqILHUXBuOST0fJAoGAZAdX
nX3HZpKmsumcLzRUEq9HerkmRzvyU60FH/qZyyoND75Le42lfpsicCenVNnWqoh2
WASaTVztbk3hjwXkt7/v5vJoASwAD2sTefLulE9Cuf00U4MbXr3GyTWQYtw1z9ib
ViQMhy5S/KmT1Rvq65UibanM2mbPB1KPHTb0P00CgYEAgieJ1YXYYHPjABWJ7/S9
3Z/8eJY0kuUe1DRFaKOlfej8SOCFFIa90IW2mAnujh55H4N2lww4s/N+FIRQoQpA
1Hrch9tYRiXyouQc67dFlLxmCOVJIf31ZUt7I4HcPhsfr4fuCJXUPBs1jaDmwQsk
90VWrXaTwSjmP+yHMiPfaA0=
-----END PRIVATE KEY-----
Binary file not shown.
@@ -0,0 +1,50 @@
Bag Attributes
localKeyID: 57 F6 9F DF B2 94 61 47 B0 B0 2E 3A F7 79 98 C8 15 5C 64 FA
friendlyName: example-client
subject=/C=US/O=Example Corp/OU=Example Developers/CN=Test A. User
issuer=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
-----BEGIN CERTIFICATE-----
MIIDmzCCAoOgAwIBAgIUFp4IuIaZ2XId7UNvzP6ZB9hChyUwDQYJKoZIhvcNAQEL
BQAwPjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEYMBYGA1UE
AxMPRVhBTVBMRSBDT1JQIENBMCAXDTIzMDIwMTIzMTUwMFoYDzIxMjMwMTA4MjMx
NTAwWjBYMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRXhhbXBsZSBDb3JwMRswGQYD
VQQLExJFeGFtcGxlIERldmVsb3BlcnMxFTATBgNVBAMTDFRlc3QgQS4gVXNlcjCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOMeN0ElMDxYKST2VD1wP6Z
jmy543Zv/QgRLTYizg3U9t3uZU0C3HqUJnK+c4+OJnRIyHuMXuJWEYVdairyAdYW
ZDF5sfiL9ikG9rZ2WxR2z7nsYLJcDGwKAt/Duzuoed6KSccEpiQc+BkoOTs9p87I
l4s7saX/anESMUY7YOomLSqS2zvQZh/zTjIdP1/9kG9PLzXPClJELuiC3pJCmr7Y
xdJIXecu6YKFpApy0y+rKcfYUVPML15gyRu9pIqH3U39y7mCkn+YP9Qc9GcZAoqR
iYdUEl1rFAgV/a3o0pmf1tFFRDOsoPSMUGNNQlaCjv1uZIbRm25nvvQlPfstFDEC
AwEAAaN1MHMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwG
A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPAe3n59DcNwRcnpIwQY2kPawbCVMB8GA1Ud
IwQYMBaAFI8s2UKaan08f5yvMLH1M2nkGgJqMA0GCSqGSIb3DQEBCwUAA4IBAQBo
+zadU9DwR1rnHTBZF6CJXmiIdrQmArojH4a3NOHbNjFjRXm7dkd/tAy5LtgDJkww
yg+ZrOyOKHbFgk6IXRZ8nw2b1Ck9I9MDW1vxfjid9YLW2nhDaMcIeTXz3aIBO5i6
gMCV+xMcFaU/O57Ohia+ADUqrqu14nG9Moi171UWPbMr5N3gYn3Fs4VrTGYs4i0Z
5x2itm88WB3bWj+cd06zKQopU9b0Y76mQv9h+w4EHxq4uWA7GmIDDkvtI61MQo8+
twdX159Ms6o4lW43SRqVBHeMgSJbiLDkdkypYJvd9PvAjWgmQK/sD1SYKxfgYhtx
hqIvAjlGeDwdf+55EGjf
-----END CERTIFICATE-----
Bag Attributes: <No Attributes>
subject=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
issuer=/C=US/O=Example Corp/CN=EXAMPLE CORP CA
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIURUj8l+ms/i9jeabIBBi19KxEhAgwDQYJKoZIhvcNAQEL
BQAwPjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEYMBYGA1UE
AxMPRVhBTVBMRSBDT1JQIENBMCAXDTIzMDIwMTIzMTUwMFoYDzIxMjMwMTA4MjMx
NTAwWjA+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRXhhbXBsZSBDb3JwMRgwFgYD
VQQDEw9FWEFNUExFIENPUlAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDVBr2XF20SKmGXfvp2BUY44BF1fm3Qw83GkC66qIHgpgQxzJ8u44P34vV1
DYTj4XzrBpx23J0nCMEiAW8YW9MxZJPEokZafRNKFQAngVdgmAW9fHemHRQfaYwe
ySVz2UZ+z28DJ3C0QshvqXskrYstwLVs4Ss2uoX+XpeFbW9IVVg4aW+GX3LHPNTk
7Hji13Capp6BCiS61AGQAiMWNBW2aRgHVLOb7RDQug2Bka/dcJ44XxJDh26C6kV1
pNEZm0ToqWAiVfvyzionPec5im6NKX9kStZc+C/iikfx5vdmrvGIF5zxy6phdhJ7
AGd6cF6UBkkYm/SsIP3WTXpBek5LAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSPLNlCmmp9PH+crzCx9TNp5BoCajAN
BgkqhkiG9w0BAQsFAAOCAQEAkjLnZKqLMjbu5Hy8YOVoKaEKahP+t/tNYZW/NtrC
xSxKmGLz5kVCkjpLgCykkcpu4h2wZW4MGZJ4nc87+tN22mJOg1WnqEpBzRsEPajk
7duVjdU2FDCarERj53hxF41l/u8BxvMGbFnrAijv5SzspeLi+VCGF3wu53Bi6UHi
7WO1dIhZLMHepqgtwbDDudxwlKbG/JWjJ7OspCNqOpktpY8BFUAQ42ctYZcOHy2w
kdZDcK5IhRztkBH2aSWOhudYh/CF7BOu2nSUdtR5NwhXnsStmSbud7ahw3W5Jc9/
IVAFXf2brkrfIzwrY5kmjnEPJ8mjUyLepwSlOlMTBrehzQ==
-----END CERTIFICATE-----
@@ -0,0 +1,32 @@
Bag Attributes
localKeyID: 57 F6 9F DF B2 94 61 47 B0 B0 2E 3A F7 79 98 C8 15 5C 64 FA
friendlyName: example-client
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDjHjdBJTA8WCk
k9lQ9cD+mY5sueN2b/0IES02Is4N1Pbd7mVNAtx6lCZyvnOPjiZ0SMh7jF7iVhGF
XWoq8gHWFmQxebH4i/YpBva2dlsUds+57GCyXAxsCgLfw7s7qHneiknHBKYkHPgZ
KDk7PafOyJeLO7Gl/2pxEjFGO2DqJi0qkts70GYf804yHT9f/ZBvTy81zwpSRC7o
gt6SQpq+2MXSSF3nLumChaQKctMvqynH2FFTzC9eYMkbvaSKh91N/cu5gpJ/mD/U
HPRnGQKKkYmHVBJdaxQIFf2t6NKZn9bRRUQzrKD0jFBjTUJWgo79bmSG0ZtuZ770
JT37LRQxAgMBAAECggEBALFWsT1vLCAWRxi55Hkhhhbe12UO55O/V+969E+GHOw6
WG9IBaJjl+Oqe3vP8CkeNw15/CbfVCVa/KS/5Mpxkuy+fbrIrHfFJkZqxE8Q4uSa
IyRvk/nstG8+XQDlFuWxkmQZcXMFak6R3wWhjtWEwqwrChQA3Mi366YDY6BXtTpi
MuLJVWXqYVF2RirWX3a7eosR4MYBCJ1S8NnpI1w4oZwuew6iYiCJNkURctxnGKOe
hoop/711PEyIb/qHETBIlReY9scvZgJQZMgGe5mTGgFmAA8Bzo4e1/F2QrVIi7wy
REOSj4U+tP/QrYMSPk0cMpH5ft5+2mfh/o+D1ugm/AkCgYEA2qt3P4vlm1TMmJru
vMYc8iZjfe7UQTnD+J1EgrASBI0JP565zSZKBRIXnheumWhWS+2KR1MP8N6KAu4Q
xE/E7TKfLuE1CPzn7Rh/WZAxDqGNeQthk0qTbeBZ4/mHPKvt8V7c+N4vLjZ00uul
bKitlLpb49T+8FpSgRmk+nrBsesCgYEA5O6LmxH1+ip5NoMHoZVBhLbgJkxoXEzn
xHT7On8CjQ9B0T85NW9O38/a2gyIb5dIavcrh4dMvOKX8hdrVlal3chrXFRemyvy
VzpW7HZpMwthJb/QSim+GQ4u2Jsm+eXQAswq/yfowls9mp7K81McBgdfcdWgWuqc
Ttf3dBRS71MCgYBCPpu7ppgCfKgWuykEC0iNm0+cwROQRv21fX9Z0BlFQ6y/l0hW
e/kEnDyHakk8tD0gd2/it6kmoWRSCukNb9UKvsE2pIc6l9NzIm7CJUqXUh3Xw7jl
tDwBl2vkAp4TpUpKSyDKTBUi/OBbXYQZ7OfZHLPly4sIKXTBI5G1SCcRzQKBgE3Y
Y7ZmRBs+CBumMJkcVLwZJ10+jBoeuIzQLoF0oNxwfLP3aK+n2s02M7gDnP09Fw3w
YGfkdQ6hioNyvTgNnLcuqHsr4UOojO5mUxpzJVCLJQyeDei6Cv8gGIPlNjqxj6rz
3AcJfhdgyTDjoCVfdF8EcMsXlkB0GJSi0W0yYAUrAoGAVK5v0cbEEV+gxdQgh848
FwSQXx7i9jVOB2BUTLX2Q+gLUJibtFzLnh03sGCXtTa8sK+hLW4FCMWZa9Ev5ZjG
ylFyA96U97CNpS+2/ZW5+hvRZ/GuY/0yB8w26BeS38lI8RnmxSPi5cnexdl32NIX
5FkU9WJ4HKmi1n0304CkF+4=
-----END PRIVATE KEY-----
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
/target/
+151
View File
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>2.2.5</version>
</parent>
<artifactId>timely-client</artifactId>
<properties>
<!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py -->
<!-- use .dev0 instead of -SNAPSHOT -->
<python.client.version>2.2.5</python.client.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-client</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
</dependency>
-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>filter-setup</id>
<goals>
<goal>copy-resources</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<outputDirectory>${project.basedir}/src/main/python</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/python/template</directory>
<includes>
<include>setup.py</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>python</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>python-dist</id>
<goals>
<goal>exec</goal>
</goals>
<phase>package</phase>
<configuration>
<executable>python</executable>
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
</configuration>
</execution>
<execution>
<id>python-clean</id>
<goals>
<goal>exec</goal>
</goals>
<phase>package</phase>
<configuration>
<executable>rm</executable>
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,81 @@
package timely.api;
import java.util.Map;
import java.util.Optional;
public class AddSubscription extends SubscriptionRequest {
private String operation = "add";
private String metric = null;
private Optional<Map<String,String>> tags = Optional.empty();
private Optional<Long> startTime = Optional.empty();
private Optional<Long> endTime = Optional.empty();
private Optional<Long> delayTime = Optional.empty();
public String getOperation() {
return operation;
}
public String getMetric() {
return metric;
}
public Optional<Map<String,String>> getTags() {
return tags;
}
public Optional<Long> getStartTime() {
return startTime;
}
public Optional<Long> getDelayTime() {
return delayTime;
}
public void setMetric(String metric) {
this.metric = metric;
}
public void setTags(Optional<Map<String,String>> tags) {
this.tags = tags;
}
public void setStartTime(Optional<Long> startTime) {
this.startTime = startTime;
}
public void setDelayTime(Optional<Long> delayTime) {
this.delayTime = delayTime;
}
public Optional<Long> getEndTime() {
return endTime;
}
public void setEndTime(Optional<Long> endTime) {
this.endTime = endTime;
}
@Override
public int hashCode() {
return (operation + subscriptionId + metric + tags + startTime + endTime + delayTime).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof AddSubscription) {
AddSubscription other = (AddSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId) && this.metric.equals(other.metric) && this.tags.equals(other.tags)
&& this.startTime.equals(other.startTime) && this.endTime.equals(other.endTime) && this.delayTime.equals(other.delayTime));
} else {
return false;
}
}
}
@@ -0,0 +1,32 @@
package timely.api;
public class CloseSubscription extends SubscriptionRequest {
private String operation = "close";
public String getOperation() {
return operation;
}
@Override
public int hashCode() {
return (operation + subscriptionId).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CloseSubscription) {
CloseSubscription other = (CloseSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId));
} else {
return false;
}
}
}
@@ -0,0 +1,32 @@
package timely.api;
public class CreateSubscription extends SubscriptionRequest {
private String operation = "create";
public String getOperation() {
return operation;
}
@Override
public int hashCode() {
return (operation + subscriptionId).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof CreateSubscription) {
CreateSubscription other = (CreateSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId));
} else {
return false;
}
}
}
@@ -0,0 +1,41 @@
package timely.api;
public class RemoveSubscription extends SubscriptionRequest {
private String operation = "remove";
private String metric = null;
public String getOperation() {
return operation;
}
public String getMetric() {
return metric;
}
public void setMetric(String metric) {
this.metric = metric;
}
@Override
public int hashCode() {
return (operation + subscriptionId + metric).hashCode();
}
@Override
public boolean equals(Object obj) {
if (null == obj) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof RemoveSubscription) {
RemoveSubscription other = (RemoveSubscription) obj;
return (this.subscriptionId.equals(other.subscriptionId) && this.metric.equals(other.metric));
} else {
return false;
}
}
}
@@ -0,0 +1,15 @@
package timely.api;
public abstract class SubscriptionRequest {
protected String subscriptionId = null;
public final String getSubscriptionId() {
return subscriptionId;
}
public final void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
}
@@ -0,0 +1,10 @@
package timely.api;
public class VersionRequest {
private String operation = "version";
public String getOperation() {
return operation;
}
}
@@ -6,14 +6,20 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import timely.api.model.Tag;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import timely.model.Metric;
import timely.model.Tag;
@JsonPropertyOrder({"metric", "timestamp", "value", "tags", "subscriptionId", "complete"})
public class MetricResponse {
private String metric;
private long timestamp;
private double value;
private List<Tag> tags;
private String subscriptionId;
private boolean complete = false;
public String getMetric() {
return metric;
@@ -31,6 +37,10 @@ public class MetricResponse {
return tags;
}
public boolean isComplete() {
return complete;
}
public void setMetric(String metric) {
this.metric = metric;
}
@@ -47,6 +57,29 @@ public class MetricResponse {
this.tags = tags;
}
public String getSubscriptionId() {
return subscriptionId;
}
public void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
public void setComplete(boolean closed) {
this.complete = closed;
}
public static MetricResponse fromMetric(Metric metric, String subscriptionId) {
MetricResponse response = new MetricResponse();
response.setMetric(metric.getName());
response.setTimestamp(metric.getValue().getTimestamp());
response.setTags(metric.getTags());
response.setValue(metric.getValue().getMeasure());
response.setSubscriptionId(subscriptionId);
response.setComplete(false);
return response;
}
@Override
public int hashCode() {
HashCodeBuilder hash = new HashCodeBuilder();
@@ -54,6 +87,8 @@ public class MetricResponse {
hash.append(this.timestamp);
hash.append(this.value);
hash.append(this.tags);
hash.append(this.subscriptionId);
hash.append(this.complete);
return hash.hashCode();
}
@@ -74,6 +109,8 @@ public class MetricResponse {
equals.append(this.timestamp, other.timestamp);
equals.append(this.value, other.value);
equals.append(this.tags, other.tags);
equals.append(this.subscriptionId, other.subscriptionId);
equals.append(this.complete, other.complete);
return equals.isEquals();
}
@@ -84,6 +121,8 @@ public class MetricResponse {
tsb.append("timestamp", this.timestamp);
tsb.append("value", this.value);
tsb.append("tags", this.tags);
tsb.append("subscriptionId", this.subscriptionId);
tsb.append("complete", this.complete);
return tsb.toString();
}
@@ -0,0 +1,29 @@
package timely.api.response;
import java.util.ArrayList;
import java.util.List;
public class MetricResponses {
private List<MetricResponse> responses = new ArrayList<>();
public synchronized List<MetricResponse> getResponses() {
return this.responses;
}
public synchronized void setResponses(List<MetricResponse> responses) {
this.responses = responses;
}
public synchronized void addResponse(MetricResponse r) {
this.responses.add(r);
}
public int size() {
return this.responses.size();
}
public synchronized void clear() {
this.responses.clear();
}
}
@@ -0,0 +1,69 @@
package timely.client.http;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.glassfish.tyrus.client.SslContextConfigurator;
public class HttpClient {
public static SSLContext getSSLContext(String trustStoreFile, String trustStoreType, String trustStorePass, String keyStoreFile, String keyStoreType,
String keyStorePass) {
SslContextConfigurator scc = new SslContextConfigurator();
scc.setTrustStoreFile(trustStoreFile);
scc.setTrustStoreType(trustStoreType);
if (null != trustStorePass) {
scc.setTrustStorePassword(trustStorePass);
}
scc.setKeyStoreFile(keyStoreFile);
scc.setKeyStoreType(keyStoreType);
if (null != keyStorePass) {
scc.setKeyStorePassword(keyStorePass);
}
return scc.createSSLContext();
}
public static CloseableHttpClient get(String trustStoreFile, String trustStoreType, String trustStorePass, String keyStoreFile, String keyStoreType,
String keyStorePass, CookieStore cookieStore, boolean hostVerificationEnabled) {
SSLContext ssl = getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType, keyStorePass);
return get(ssl, cookieStore, hostVerificationEnabled);
}
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
return get(ssl, cookieStore, hostVerificationEnabled, false);
}
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled, boolean clientAuth) {
HostnameVerifier hostnameVerifier;
if (hostVerificationEnabled) {
hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
} else {
hostnameVerifier = new NoopHostnameVerifier();
}
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
HttpClientBuilder builder;
if (cookieStore == null) {
builder = HttpClients.custom().setSSLContext(ssl).disableCookieManagement().setDefaultRequestConfig(defaultRequestConfig)
.setSSLHostnameVerifier(hostnameVerifier);
} else {
builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore).setDefaultRequestConfig(defaultRequestConfig)
.setSSLHostnameVerifier(hostnameVerifier);
}
if (clientAuth) {
SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(ssl, new String[] {"TLSv1.1", "TLSv1.2"}, null, hostnameVerifier);
builder.setSSLSocketFactory(sslCSF);
}
return builder.build();
}
}
@@ -0,0 +1,212 @@
package timely.client.tcp;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TcpClient implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(TcpClient.class);
private final String host;
private final int port;
private Socket sock = null;
private BufferedWriter out = null;
private long connectTime = 0L;
private long INIT_BACKOFF = 1000;
private long MAX_BACKOFF = 60000;
private long backoff = INIT_BACKOFF;
private int bufferSize;
private long latency;
private int writesSinceFlush = 0;
private long lastFlushTime = System.currentTimeMillis();
public TcpClient(String hostname, int port) {
this(hostname, port, -1);
}
public TcpClient(String hostname, int port, int bufferSize) {
this(hostname, port, bufferSize, -1);
}
public TcpClient(String hostname, int port, int bufferSize, long latency) {
this.host = hostname;
this.port = port;
this.bufferSize = bufferSize;
this.latency = latency;
}
/**
* Opens a TCP connection to the specified host and port
*
* @throws IOException
* if an error occurs
*/
public void open() throws IOException {
if (connect() != 0) {
throw new IOException();
}
}
/**
* Write a metric to Timely
*
* @param metric
* newline terminated string representation of Timely metric
*/
public synchronized void write(String metric) throws IOException {
if (connect() != 0) {
throw new IOException();
}
try {
out.write(metric);
writesSinceFlush++;
if (bufferSize > 0 && writesSinceFlush >= bufferSize) {
if (log.isTraceEnabled()) {
log.trace(String.format("Flushing buffer writesSinceFlush >= %d", writesSinceFlush));
}
flush();
}
if (latency > 0 && (System.currentTimeMillis() - lastFlushTime) >= latency) {
if (log.isTraceEnabled()) {
log.trace(String.format("Flushing buffer timeSinceFlush >= %d", latency));
}
flush();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
reset();
throw e;
}
}
public synchronized void flush() throws IOException {
if (null != out) {
try {
long start = System.currentTimeMillis();
out.flush();
lastFlushTime = System.currentTimeMillis();
if (log.isTraceEnabled()) {
log.trace(String.format("Flushed %d metrics to socket in %d ms", writesSinceFlush, (lastFlushTime - start)));
}
writesSinceFlush = 0;
} catch (IOException e) {
log.error(String.format("Failed to flush %d metrics to socket", writesSinceFlush), e);
reset();
throw e;
}
}
}
private synchronized void reset() {
out = null;
sock = null;
backoff = INIT_BACKOFF;
writesSinceFlush = 0;
lastFlushTime = System.currentTimeMillis();
}
/**
* Closes the tcp connection to Timely
*
* @throws IOException
* if an error occurs
*/
@Override
public synchronized void close() throws IOException {
log.info(String.format("Shutting down connection to Timely at %s:%d", host, port));
if (null != sock) {
try {
if (null != out) {
if (bufferSize > 0) {
flush();
}
out.close();
}
sock.close();
} catch (IOException e) {
log.error(String.format("Error closing connection to Timely at %s:%d", host, port), e);
} finally {
sock = null;
}
}
}
private synchronized int connect() {
if (null == sock || !sock.isConnected()) {
if (System.currentTimeMillis() > (connectTime + backoff)) {
OutputStreamWriter osw = null;
try {
connectTime = System.currentTimeMillis();
sock = new Socket(host, port);
osw = new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8);
out = new BufferedWriter(osw);
backoff = INIT_BACKOFF;
log.info(String.format("Connected to Timely at %s:%d", host, port));
} catch (Exception e) {
log.error(String.format("Error connecting to Timely at %s:%d", host, port), e);
if (sock != null) {
try {
sock.close();
} catch (IOException e1) {
log.error(e1.getMessage());
} finally {
sock = null;
}
}
if (osw != null) {
try {
osw.close();
} catch (IOException e1) {
log.error(e1.getMessage());
}
}
if (out != null) {
try {
out.close();
} catch (IOException e1) {
log.error(e1.getMessage());
} finally {
out = null;
}
}
backoff = Math.min(MAX_BACKOFF, backoff * 2);
if (log.isTraceEnabled()) {
log.trace(String.format("Will retry connection in %d ms", backoff));
}
return -1;
}
} else {
return -1;
}
}
return 0;
}
public long getConnectTime() {
return connectTime;
}
public int getWritesSinceFlush() {
return writesSinceFlush;
}
public long getTimeSinceFlush() {
return System.currentTimeMillis() - lastFlushTime;
}
public synchronized boolean isConnected() {
boolean isConnected = (null != sock && sock.isConnected());
if (isConnected) {
return true;
} else {
int status = connect();
return status == 0;
}
}
}
@@ -0,0 +1,54 @@
package timely.client.udp;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UdpClient implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(UdpClient.class);
private final String host;
private final int port;
private final InetSocketAddress address;
private final DatagramPacket packet;
private DatagramSocket sock;
public UdpClient(String host, int port) {
this.address = new InetSocketAddress(host, port);
this.packet = new DatagramPacket("".getBytes(UTF_8), 0, 0, address.getAddress(), port);
this.host = host;
this.port = port;
}
public void open() throws IOException {
if (null == sock) {
this.sock = new DatagramSocket();
}
}
public void write(String metric) throws IOException {
if (null == this.sock) {
throw new IllegalStateException("Must call open first");
}
this.packet.setData(metric.getBytes(UTF_8));
log.info("writing '" + metric + "' to " + this.host + ":" + this.port);
this.sock.send(packet);
}
public void flush() throws IOException {}
public void close() throws IOException {
if (null != sock) {
sock.close();
sock = null;
}
}
}
@@ -0,0 +1,37 @@
package timely.client.websocket;
import java.util.List;
import java.util.Map;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ClientHandler extends Endpoint {
private final static Logger log = LoggerFactory.getLogger(ClientHandler.class);
@Override
public void onOpen(Session session, EndpointConfig config) {
log.info("Websocket session {} opened.", session.getId());
session.addMessageHandler(String.class, message -> {
log.info("Message received on Websocket session {}: {}", session.getId(), message);
});
}
@Override
public void onClose(Session session, CloseReason reason) {
log.info("Websocket session {} closed.", session.getId());
}
@Override
public void onError(Session session, Throwable error) {
log.error("Error occurred on Websocket session" + session.getId(), error);
}
public void beforeRequest(Map<String,List<String>> headers) {}
}
@@ -0,0 +1,68 @@
package timely.client.websocket;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Extension;
import org.apache.http.cookie.Cookie;
public class TimelyEndpointConfig implements ClientEndpointConfig {
private String sessionCookie = null;
private final ClientHandler clientEndpoint;
private static final String FORMAT = "%s=%s;";
public TimelyEndpointConfig(ClientHandler clientEndpoint, Cookie sessionCookie) {
if (null != sessionCookie) {
this.sessionCookie = String.format(FORMAT, sessionCookie.getName(), sessionCookie.getValue());
}
this.clientEndpoint = clientEndpoint;
}
@Override
public List<Class<? extends Encoder>> getEncoders() {
return Collections.emptyList();
}
@Override
public List<Class<? extends Decoder>> getDecoders() {
return Collections.emptyList();
}
@Override
public Map<String,Object> getUserProperties() {
return Collections.emptyMap();
}
@Override
public List<String> getPreferredSubprotocols() {
return Collections.emptyList();
}
@Override
public List<Extension> getExtensions() {
return Collections.emptyList();
}
@Override
public Configurator getConfigurator() {
return new Configurator() {
@Override
public void beforeRequest(Map<String,List<String>> headers) {
super.beforeRequest(headers);
if (null != sessionCookie) {
headers.put("Cookie", Collections.singletonList(sessionCookie));
}
TimelyEndpointConfig.this.clientEndpoint.beforeRequest(headers);
}
};
}
}
@@ -0,0 +1,145 @@
package timely.client.websocket;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.websocket.DeploymentException;
import javax.websocket.Session;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.client.ClientProperties;
import org.glassfish.tyrus.client.SslEngineConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import timely.client.http.HttpClient;
public class WebSocketClient implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(WebSocketClient.class);
private final String timelyHostname;
private final int timelyHttpsPort;
private final int timelyWssPort;
private final boolean doLogin;
private final boolean clientAuth;
private final boolean hostVerificationEnabled;
private final int bufferSize;
private final SSLContext ssl;
private ClientManager webSocketClient = null;
protected Session session = null;
protected volatile boolean closed = true;
protected WebSocketClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth, boolean doLogin,
boolean hostVerificationEnabled, int bufferSize) {
this.ssl = ssl;
this.timelyHostname = timelyHostname;
this.timelyHttpsPort = timelyHttpsPort;
this.timelyWssPort = timelyWssPort;
this.doLogin = doLogin;
this.clientAuth = clientAuth;
this.hostVerificationEnabled = hostVerificationEnabled;
this.bufferSize = bufferSize;
Preconditions.checkNotNull(timelyHostname, "%s must be supplied", "Timely host name");
Preconditions.checkNotNull(timelyHttpsPort, "%s must be supplied", "Timely HTTPS port");
Preconditions.checkNotNull(timelyWssPort, "%s must be supplied", "Timely WSS port");
}
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth, boolean doLogin, String keyStoreFile,
String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
boolean hostVerificationEnabled, int bufferSize) {
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType, keyStorePass), timelyHostname,
timelyHttpsPort, timelyWssPort, clientAuth, doLogin, hostVerificationEnabled, bufferSize);
}
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
Cookie sessionCookie = null;
if (doLogin) {
BasicCookieStore cookieJar = new BasicCookieStore();
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled, clientAuth)) {
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
HttpRequestBase request = null;
// HTTP GET to /login to use certificate based login
request = new HttpGet(target);
log.trace("Performing client certificate login");
HttpResponse response = null;
try {
response = client.execute(request);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
}
for (Cookie c : cookieJar.getCookies()) {
if (c.getName().equals("TSESSIONID")) {
sessionCookie = c;
break;
}
}
if (null == sessionCookie) {
throw new IllegalStateException("Unable to find TSESSIONID cookie header in Timely login response");
}
} finally {
HttpClientUtils.closeQuietly(response);
}
}
}
SslEngineConfigurator sslEngine = new SslEngineConfigurator(ssl);
sslEngine.setClientMode(true);
sslEngine.setHostVerificationEnabled(hostVerificationEnabled);
webSocketClient = ClientManager.createClient();
webSocketClient.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngine);
webSocketClient.getProperties().put(ClientProperties.INCOMING_BUFFER_SIZE, bufferSize);
String wssPath = "wss://" + timelyHostname + ":" + timelyWssPort + "/websocket";
session = webSocketClient.connectToServer(clientEndpoint, new TimelyEndpointConfig(clientEndpoint, sessionCookie), new URI(wssPath));
final ByteBuffer pingData = ByteBuffer.allocate(0);
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
try {
session.getBasicRemote().sendPing(pingData);
} catch (Exception e) {
log.error("Error sending ping", e);
}
}, 30, 60, TimeUnit.SECONDS);
closed = false;
}
public synchronized void close() throws IOException {
if (closed) {
return;
}
try {
if (null != webSocketClient) {
webSocketClient.shutdown();
}
} finally {
session = null;
webSocketClient = null;
closed = true;
}
}
public boolean isClosed() {
return closed;
}
}
@@ -0,0 +1,92 @@
package timely.client.websocket.subscription;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Future;
import javax.net.ssl.SSLContext;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.AddSubscription;
import timely.api.CloseSubscription;
import timely.api.CreateSubscription;
import timely.api.RemoveSubscription;
import timely.api.VersionRequest;
import timely.client.websocket.ClientHandler;
import timely.client.websocket.WebSocketClient;
import timely.serialize.JsonSerializer;
public class WebSocketSubscriptionClient extends WebSocketClient {
private static final Logger log = LoggerFactory.getLogger(WebSocketSubscriptionClient.class);
private final String subscriptionId;
public WebSocketSubscriptionClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth, boolean doLogin,
boolean hostVerificationEnabled, int bufferSize) {
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString();
log.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
}
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth, boolean doLogin, String keyStoreFile,
String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
boolean hostVerificationEnabled, int bufferSize) {
super(timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, keyStoreFile, keyStoreType, keyStorePass, trustStoreFile, trustStoreType,
trustStorePass, hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString();
log.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
}
@Override
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
super.open(clientEndpoint);
CreateSubscription create = new CreateSubscription();
create.setSubscriptionId(subscriptionId);
session.getBasicRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(create));
}
@Override
public synchronized void close() throws IOException {
if (null != session) {
CloseSubscription close = new CloseSubscription();
close.setSubscriptionId(subscriptionId);
try {
session.getBasicRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(close));
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Client called close."));
} catch (Exception e) {
log.info("Unable to send close message to server: {}", e.getMessage());
}
}
super.close();
}
public Future<Void> addSubscription(String metric, Map<String,String> tags, long startTime, long endTime, long delayTime) throws IOException {
AddSubscription add = new AddSubscription();
add.setSubscriptionId(subscriptionId);
add.setMetric(metric);
add.setTags(Optional.ofNullable(tags));
add.setStartTime(Optional.ofNullable(startTime));
add.setEndTime(Optional.ofNullable(endTime));
add.setDelayTime(Optional.ofNullable(delayTime));
return session.getAsyncRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(add));
}
public Future<Void> removeSubscription(String metric) throws Exception {
RemoveSubscription remove = new RemoveSubscription();
remove.setSubscriptionId(subscriptionId);
remove.setMetric(metric);
return session.getAsyncRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(remove));
}
public Future<Void> requestVersion() throws Exception {
VersionRequest version = new VersionRequest();
return session.getAsyncRemote().sendText(JsonSerializer.getObjectMapper().writeValueAsString(version));
}
}
@@ -0,0 +1,22 @@
package timely.serialize;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
public class JsonSerializer {
private static ObjectMapper mapper = new ObjectMapper();
static {
mapper.registerModule(new Jdk8Module());
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
}
public static ObjectMapper getObjectMapper() {
return mapper;
}
}
+12
View File
@@ -0,0 +1,12 @@
from setuptools import setup
setup(
name='timely',
version='${python.client.version}',
packages=['timely'],
install_requires=[
'seaborn',
'plotly'
],
license='Apache License, Version 2.0'
)
@@ -0,0 +1,332 @@
import time
from datetime import timedelta
from datetime import datetime
from TimeDateRange import UTC
import pandas
import matplotlib.dates as mdates
import plotly.graph_objs as go
import plotly.offline as py
import seaborn
import matplotlib.ticker as tkr
import numpy as np
import os
utc = UTC()
def pivot(df, metric, groupByColumn=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if groupByColumn is not None:
dataFrame["date"] = dataFrame.index.to_pydatetime()
dataFrame = dataFrame.pivot_table(index="date", columns=groupByColumn, values=metric)
return dataFrame
def resample(df, sample, how='mean', interpolate=True, fill=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if sample is not None:
dataFrame = dataFrame.resample(sample, how=how)
if (interpolate == True):
dataFrame = dataFrame.interpolate()
if fill is not None:
dataFrame = dataFrame.fillna(value=fill)
return dataFrame
def unpivot(df, metric, groupByColumn=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame['date'] = dataFrame.index.values
dataFrame = pandas.melt(dataFrame, id_vars=['date'], value_name=metric, var_name=groupByColumn)
dataFrame = dataFrame.set_index('date')
return dataFrame
def rolling_average(df, metric, rolling_average=None):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
if rolling_average is not None:
dataFrame[metric] = pandas.rolling_mean(dataFrame[metric], rolling_average)
return dataFrame
def graph(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False, type='png'):
if type == 'png':
return graphSeaborn(analyticConfig, df, timelyMetric, seriesConfig=seriesConfig, graphConfig=graphConfig, notebook=notebook)
else:
return graphPlotly(analyticConfig, df, timelyMetric, seriesConfig=seriesConfig, graphConfig=graphConfig, notebook=notebook)
def graphSeaborn(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame = ensureMinSeriesLength(dataFrame, analyticConfig.groupByColumn)
dataFrame['date'] = dataFrame.index.values
dataFrame['dateFloat'] = dataFrame['date'].apply(lambda x: datetime2graphdays(x))
dataFrame['index'] = dataFrame['dateFloat']
groupByColumn = analyticConfig.groupByColumn
if groupByColumn is not None:
# sort on date and the groupByColumn so that the series legend is sorted
dataFrame = dataFrame.sort_values(['index', analyticConfig.groupByColumn])
else:
dataFrame = dataFrame.sort_values(['index'])
#dataFrame = dataFrame.set_index('index', drop=True)
dataFrame = dataFrame.dropna()
seaborn.set(style="darkgrid", palette="Set2")
fig, ax = seaborn.plt.subplots()
colors = np.random.random((len(dataFrame), 3))
i = 0
for col in df[analyticConfig.groupByColumn].unique():
tempDataFrame = dataFrame.loc[dataFrame[analyticConfig.groupByColumn] == col]
addlColConfig = seriesConfig.get(col, seriesConfig.get('default', dict()))
markerDict = addlColConfig.get("marker", dict())
color = markerDict.get("color", "")
if color == "red":
seaborn.tsplot(tempDataFrame, time="dateFloat", unit=None, interpolate=False, color="red", marker="d",
estimator=np.nanmean, condition=groupByColumn, value=timelyMetric.metric, ax=ax)
else:
seaborn.tsplot(tempDataFrame, time="dateFloat", unit=None, interpolate=True, color=colors[i],
estimator=np.nanmean, condition=groupByColumn, value=timelyMetric.metric, ax=ax)
i = i + 1
ax.xaxis_date()
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y/%m/%d %H:%M:%S'))
ax.set_xlim(xmin=dataFrame['dateFloat'].min(), xmax=dataFrame['dateFloat'].max())
formatter = tkr.ScalarFormatter(useMathText=False)
formatter.set_scientific(False)
ax.yaxis.set_major_formatter(formatter)
graphTitle = getTitle(timelyMetric, analyticConfig)
seaborn.plt.title(graphTitle, fontsize='large')
seaborn.plt.xlabel('date/time')
seaborn.plt.ylabel(timelyMetric.metric)
legendTitle = "Legend"
if groupByColumn is not None:
legendTitle = groupByColumn
seaborn.plt.legend(loc='upper center', prop={'size':8}, ncol=10, title=legendTitle, bbox_to_anchor=(0.5,-0.4))
fig.autofmt_xdate(rotation=45)
seaborn.plt.autoscale(tight=False)
if notebook == True:
seaborn.plt.show()
return None
else:
directory = analyticConfig.output_dir
try:
os.makedirs(directory, mode=0755)
except OSError, e:
# be happy if someone already created the path
pass
baseFilename = getFilename(timelyMetric.metric, analyticConfig)
filename = directory+'/'+baseFilename
fig.savefig(filename + '.png')
return baseFilename + '.png'
def graphPlotly(analyticConfig, df, timelyMetric, seriesConfig={}, graphConfig={}, notebook=False):
dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None:
dataFrame['colFromIndex'] = dataFrame.index
if analyticConfig.groupByColumn is not None:
# sort on date and the groupByColumn so that the series legend is sorted
dataFrame = dataFrame.sort_values(['colFromIndex', analyticConfig.groupByColumn])
else:
dataFrame = dataFrame.sort_values(['colFromIndex'])
title = getTitle(timelyMetric, analyticConfig)
dataFrame['date'] = dataFrame.index.values
layoutConfig = dict(
title=title,
autosize=False,
width=1000,
height=700,
showlegend=True,
xaxis=dict(
autorange=True,
tickangle=-45
),
yaxis=dict(
autorange=True,
title=timelyMetric.metric
)
)
layoutConfig.update(graphConfig)
layout = go.Layout(layoutConfig)
data = None
groupByValues = sorted(dataFrame[analyticConfig.groupByColumn].unique())
for col in groupByValues:
tempDataFrame = dataFrame.loc[dataFrame[analyticConfig.groupByColumn] == col]
addlColConfig = seriesConfig.get(col, seriesConfig.get('default', dict()))
config = dict(
name=col,
x=tempDataFrame.index,
y=tempDataFrame[timelyMetric.metric],
hoverinfo='x+y+text',
text=col
)
config.update(addlColConfig)
s = go.Scatter(config)
if data is None:
data = [s]
else:
data.append(s)
directory = analyticConfig.output_dir
try:
os.makedirs(directory, mode=0755)
except OSError, e:
# be happy if someone already created the path
pass
baseFilename = getFilename(timelyMetric.metric, analyticConfig)
filename = directory+'/'+baseFilename
fig = go.Figure(data=data, layout=layout)
if notebook:
py.iplot(fig, filename=filename, show_link=False)
return None
else:
py.plot(fig, output_type='file', filename=filename+'.html', image_filename=filename, image=None, auto_open=False, show_link=False)
return baseFilename+'.html'
def ensureMinSeriesLength(df, groupByColumn):
dataFrame = df
if groupByColumn is not None:
dataFrame = pandas.DataFrame(df, copy=True)
dataFrame = dataFrame.dropna()
extra = pandas.DataFrame()
for c, (cond, df_c) in enumerate(dataFrame.groupby(groupByColumn, sort=False)):
if len(df_c) <= 1:
extra = extra.append(df_c)
extra.index = extra.index + pandas.DateOffset(seconds=1)
dataFrame = dataFrame.append(extra)
return dataFrame
def datetime2graphdays(dt):
return mdates.epoch2num((time.mktime(dt.timetuple()) / 86400) * mdates.SEC_PER_DAY)
def graphdays2datetime(x):
epoch = mdates.num2epoch(x)
return datetime.fromtimestamp(int(epoch), utc)
def getFilename(metric, analyticConfig):
now = datetime.now(tz=utc)
format = "%Y%m%d%H%M%S"
nowStr = time.strftime(format, now.timetuple())
fileNameBase = metric
if analyticConfig.sample is not None:
fileNameBase += "-" + analyticConfig.sample
if analyticConfig.how is not None:
fileNameBase += "-" + analyticConfig.how
return fileNameBase + '-' + nowStr
def getDirectoryPathForDate(format='%Y/%m/%d'):
now = datetime.now(tz=utc)
return time.strftime(format, now.timetuple())
def getTitle(timelyMetric, config, separator='\n'):
if config.orCondition:
condition = "OR"
else:
condition = "AND"
metricTitle = timelyMetric.metric
if timelyMetric.tags is not None and len(timelyMetric.tags) > 0:
metricTitle += ' (' + timelyMetric.tags + ')'
sampleTitle = ""
alertTitle = ""
if config.sample_period is not None:
sampleTitle = config.sample_period + " " + config.how + " samples"
if config.rolling_average_period is not None:
sampleTitle += ', ' + config.rolling_average_period + ' rolling average'
if config.min_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y < " + str(config.min_threshold) + ")"
if config.average_min_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y-avg < " + str(config.average_min_threshold) + ")"
if config.max_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y > " + str(config.max_threshold) + ")"
if config.average_max_threshold is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
alertTitle += "(y-avg > " + str(config.average_max_threshold) + ")"
if config.min_threshold_percentage is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
if config.min_threshold_percentage >= 0:
descriptivePercentage = 100 + config.min_threshold_percentage
else:
descriptivePercentage = abs(config.min_threshold_percentage)
alertTitle += "(y < " + str(descriptivePercentage) + "% of " + str(
config.rolling_average_period) + " sample average)"
if config.max_threshold_percentage is not None:
if alertTitle != "":
alertTitle += " " + condition + " "
if config.max_threshold_percentage >= 0:
descriptivePercentage = 100 + config.max_threshold_percentage
else:
descriptivePercentage = abs(config.max_threshold_percentage)
alertTitle += "(y > " + str(descriptivePercentage) + "% of " + str(
config.rolling_average_period) + " sample average)"
if config.system_name is None:
title = metricTitle
else:
title = config.system_name + ' - ' + metricTitle
if sampleTitle != "":
title += separator + sampleTitle
if alertTitle != "":
if config.min_alert_period is not None:
alertTitle = '(' + alertTitle + ') for at least ' + config.min_alert_period
if config.last_alert is not None:
alertTitle = alertTitle + ' in last ' + config.last_alert
title += separator + alertTitle
return title
@@ -0,0 +1,127 @@
import time
from datetime import datetime
from datetime import timedelta
from datetime import tzinfo
import pandas
class TimeDateError(ValueError):
def __init__(self, message, *args):
self.message = message
super(TimeDateError, self).__init__(message, *args)
ZERO = timedelta(0)
# A UTC class.
class UTC(tzinfo):
"""UTC"""
def __str__(self):
return "UTC"
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
class TimeDateRange:
beginDt = None
endDt = None
format = "%Y%m%d %H%M%S"
def getBeginMs(self):
if self.beginDt == None:
return None
return TimeDateRange.unix_time_millis(self.beginDt)
def getBeginStr(self):
if self.beginDt == None:
return None
return time.strftime(self.format, self.beginDt.timetuple())
def getEndMs(self):
if self.endDt == None:
return None
return TimeDateRange.unix_time_millis(self.endDt)
def getEndStr(self):
if self.endDt == None:
return None
return time.strftime(self.format, self.endDt.timetuple())
def __init__(self, beginStr, endStr, period, format="%Y%m%d-%H%M%S"):
now = datetime.now(tz=utc)
nowMs = TimeDateRange.unix_time_millis(now)
if beginStr != None and endStr != None and period != None:
raise TimeDateError("You can not provide a begin, end, and period")
elif (beginStr == None and endStr == None and period == None) or (period == None and beginStr == None):
raise TimeDateError("You must provide a time range")
if period == None:
# calculate begin time
beginTuple = time.strptime(beginStr, self.format)
self.beginDt = datetime(*beginTuple[0:6], tzinfo=utc)
# calculate end time
# end is either specified or now
if endStr == None or endStr == 'now':
self.endDt = now
else:
self.endDt = datetime(*time.strptime(endStr, self.format)[0:6], tzinfo=utc)
if self.endDt > now:
self.endDt = now
else:
# period must be specified
td = pandas.to_timedelta(period)
rangeInSec = td.total_seconds()
if beginStr == None:
# calculate begin time from end tme and period
# calculate end time
# end is either specified or now
if endStr == None or endStr == 'now':
self.endDt = now
else:
self.endDt = datetime(*time.strptime(endStr, self.format)[0:6], tzinfo=utc)
if self.endDt > now:
self.endDt = now
# calculate begin time
self.beginDt = self.endDt - timedelta(milliseconds=rangeInSec * 1000)
else:
# calculate end time from begin tme and period
# calculate begin time
self.beginDt = datetime(*time.strptime(beginStr, self.format)[0:6], tzinfo=utc)
# calculate end time
self.endDt = self.beginDt + timedelta(milliseconds=rangeInSec * 1000)
if self.endDt > now:
self.endDt = now
if self.beginDt > now:
raise TimeDateError("begin date/time can not be in the future")
if self.beginDt > self.endDt:
raise TimeDateError("begin date/time can not be past end date/time")
epoch = datetime.utcfromtimestamp(0).replace(tzinfo=utc)
@classmethod
def unix_time_millis(cls, dt):
return int((dt - TimeDateRange.epoch).total_seconds() * 1000.0)
@@ -0,0 +1,72 @@
import smtplib
from os.path import basename
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import TimelyMetric
import syslog
import DataOperations
syslog.openlog("TimelyNotifications")
class TimelyAlert():
def __init__(self, timelyMetric, dataFrame, message, seriesConfig, analyticConfig, notebook):
self.timelyMetric = timelyMetric
self.dataFrame = dataFrame
self.seriesConfig = seriesConfig
self.analyticConfig = analyticConfig
self.notebook = notebook
self.message = message
def email(self, send_from, send_to, subject, text, files=None, server="127.0.0.1"):
assert isinstance(send_to, list)
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(MIMEText(text))
for f in files or []:
with open(f, "rb") as fil:
part = MIMEApplication(
fil.read(),
Name=basename(f)
)
part['Content-Disposition'] = 'attachment; filename="%s"' % basename(f)
msg.attach(part)
smtp = smtplib.SMTP(server)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
def log(self, message):
syslog.systlog(syslog.LOG_ALERT, message)
def graph(self, type="png"):
graphConfig = {}
graphConfig["title"] = DataOperations.getTitle(self.timelyMetric, self.analyticConfig)
return TimelyMetric.graph(self.analyticConfig, self.dataFrame, self.timelyMetric, seriesConfig=self.seriesConfig, graphConfig=graphConfig, notebook=self.notebook, type=type)
def getDataFrame(self):
return self.dataFrame
def getAnalyticConfig(self):
return self.analyticConfig
def getSeriesConfig(self):
return self.seriesConfig
def getTimelyMetric(self):
return self.timelyMetric
def getMessage(self):
return self.message
@@ -0,0 +1,224 @@
import TimelyMetric
from TimeDateRange import TimeDateRange
from TimeDateRange import UTC
from TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
import DataOperations
import pandas
import re
from datetime import datetime
from datetime import timedelta
from TimelyAlert import TimelyAlert
import numpy as np
utc = UTC()
def logTiming(timelyMetric, startTime, stopTime):
df = timelyMetric.getDataFrame()
print("start: " + str(startTime))
print("stop: " + str(stopTime))
print("num records: " + str(len(df)))
elapsedSec = (TimeDateRange.unix_time_millis(stopTime) - TimeDateRange.unix_time_millis(startTime)) / 1000
print("elapsed seconds: " + str(elapsedSec))
recPerSec = len(df) / elapsedSec
print("records per second: " + recPerSec)
def addCondition(orCondition, currResult, condition):
if orCondition:
currResult = currResult | condition
else:
currResult = currResult & condition
return currResult
def keepConsecutiveAlerts(dataFrame, exceptions, minimumSpan):
if exceptions.size > 0 and minimumSpan is not None:
currentFirst = None
currentLast = None
result_span_values = pandas.DataFrame()
result_span_values['bool'] = np.zeros(exceptions.shape, bool)
result_span_values = result_span_values.set_index(exceptions.index)
current_result_span_values = pandas.DataFrame()
current_result_span_values['bool'] = np.zeros(exceptions.shape, bool)
current_result_span_values = current_result_span_values.set_index(exceptions.index)
for index, row in dataFrame.iterrows():
if index in exceptions.index:
current_result_span_values.loc[index]['bool'] = True
currentLast = index
if currentFirst is None:
currentFirst = index
if int((currentLast - currentFirst).total_seconds() / 60) >= int(minimumSpan):
result_span_values['bool'] = result_span_values['bool'] | current_result_span_values['bool']
else:
current_result_span_values['bool'] = np.zeros(exceptions.shape, bool)
currentFirst = None
currentLast = None
return exceptions.loc[result_span_values['bool']]
else:
return exceptions
def convertCounterToRate(timelyMetric, analyticConfig):
# Adjust counter metric into a rate metric
df = timelyMetric.getDataFrame()
columnNames = df[analyticConfig.groupByColumn].unique()
df = DataOperations.pivot(df, timelyMetric.metric, analyticConfig.groupByColumn)
df = DataOperations.resample(df, analyticConfig.sample, how=analyticConfig.how)
for c in columnNames:
df[c] = df[c].diff(1) / (analyticConfig.sample_minutes * 60 * 1000)
df = DataOperations.unpivot(df, timelyMetric.metric, analyticConfig.groupByColumn)
df = df.dropna()
timelyMetric.setDataFrame(df)
def find_alerts(timelyMetric, analyticConfig, notebook=False):
if (analyticConfig.counter == True):
convertCounterToRate(timelyMetric, analyticConfig)
df = timelyMetric.getDataFrame()
graphDF = TimelyMetric.pivot(df, timelyMetric.metric, groupByColumn=analyticConfig.groupByColumn)
if analyticConfig.excludeColRegex is not None:
graphDF = graphDF.select(lambda x : not (re.search(analyticConfig.excludeColRegex, x)), axis=1)
if analyticConfig.includeColRegex is not None:
graphDF = graphDF.select(lambda x : re.search(analyticConfig.includeColRegex, x), axis=1)
if analyticConfig.sample is not None:
graphDF = TimelyMetric.resample(graphDF, analyticConfig.sample, how=analyticConfig.how, interpolate=analyticConfig.interpolate, fill=analyticConfig.fill)
graphDF_avg = pandas.DataFrame(graphDF, copy=True)
combined = pandas.DataFrame()
seriesConfig = {}
for i in graphDF_avg.columns:
col = str(i)
any_conditions_values = False
result_values = np.ones(graphDF[col].shape, bool)
if analyticConfig.orCondition:
result_values = np.zeros(graphDF[col].shape, bool)
any_conditions_average = False
result_average = np.ones(graphDF_avg[col].shape, bool)
if analyticConfig.orCondition:
result_average = np.zeros(graphDF_avg[col].shape, bool)
if analyticConfig.min_threshold is not None:
currCondition = graphDF[col].astype(float) < analyticConfig.min_threshold
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.max_threshold is not None:
currCondition = graphDF[col].astype(float) > analyticConfig.max_threshold
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.rolling_average_samples is not None:
graphDF_avg = TimelyMetric.rolling_average(graphDF_avg, col, rolling_average=analyticConfig.rolling_average_samples)
if analyticConfig.min_threshold_percentage is not None:
if analyticConfig.min_threshold_percentage >= 0:
multiple = 1.0 + (float(abs(analyticConfig.min_threshold_percentage)) / float(100))
else:
multiple = 1.0 - (float(abs(analyticConfig.min_threshold_percentage)) / float(100))
currCondition = graphDF[col].astype(float) < (graphDF_avg[col].astype(float) * multiple)
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.max_threshold_percentage is not None:
if analyticConfig.max_threshold_percentage >= 0:
multiple = 1.0 + (float(abs(analyticConfig.max_threshold_percentage)) / float(100))
else:
multiple = 1.0 - (float(abs(analyticConfig.max_threshold_percentage)) / float(100))
currCondition = graphDF[col].astype(float) > (graphDF_avg[col].astype(float) * multiple)
result_values = addCondition(analyticConfig.orCondition, result_values, currCondition)
any_conditions_values = True
if analyticConfig.average_min_threshold is not None:
currCondition = graphDF_avg[col].astype(float) < analyticConfig.average_min_threshold
result_average = addCondition(analyticConfig.orCondition, result_average, currCondition)
any_conditions_average = True
if analyticConfig.average_max_threshold is not None:
currCondition = graphDF_avg[col].astype(float) > analyticConfig.average_max_threshold
result_average = addCondition(analyticConfig.orCondition, result_average, currCondition)
any_conditions_average = True
# if orCondition is AND and no exceptional conditions have been found, then result_values will be all True
if any_conditions_values == False:
result_values = np.zeros(graphDF[col].shape, bool)
exceptional_values = graphDF.loc[result_values, col]
# if orCondition is AND and no exceptional conditions have been found, then result_average will be all True
if any_conditions_average == False:
result_average = np.zeros(graphDF_avg[col].shape, bool)
exceptional_average = graphDF_avg.loc[result_average, col]
# only keep alerts that are in consecutive periods of length analyticConfig.min_alert_minutes
exceptional_values = keepConsecutiveAlerts(graphDF, exceptional_values, analyticConfig.min_alert_minutes)
exceptional_average = keepConsecutiveAlerts(graphDF_avg, exceptional_average, analyticConfig.min_alert_minutes)
# only evaluate the last analyticConfig.last_alert_minutes if set
if analyticConfig.last_alert_minutes is not None:
end = datetime.fromtimestamp(timelyMetric.timeDateRange.getEndMs() / 1000.00, DataOperations.utc)
recentEnoughBegin = end - timedelta(minutes=analyticConfig.last_alert_minutes)
exceptional_values = exceptional_values.ix[recentEnoughBegin:end]
exceptional_average = exceptional_average.ix[recentEnoughBegin:end]
anyValueExceptions = exceptional_values.size > 0
anyAverageExceptions = exceptional_average.size > 0
if (analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyValueExceptions):
combined[col] = graphDF[col]
if analyticConfig.rolling_average_samples is not None:
if (analyticConfig.display.lower() == "all"):
combined[col + '_avg'] = graphDF_avg[col]
else:
if (anyAverageExceptions):
combined[col + '_avg'] = graphDF_avg[col]
if (anyValueExceptions and (analyticConfig.min_threshold_percentage is not None or analyticConfig.max_threshold_percentage is not None)):
combined[col + '_avg'] = graphDF_avg[col]
if ((analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyValueExceptions)):
combined[col + '_warn'] = exceptional_values.dropna()
seriesConfig[col + '_warn'] = {
"mode" : "markers",
"marker" : {
"symbol" : "hash-open",
"color" : "red"
}
}
if ((analyticConfig.display.lower() == "all") or (analyticConfig.display.lower() == "alerts" and anyAverageExceptions)):
combined[col + '_avg_warn'] = exceptional_average.dropna()
seriesConfig[col + '_avg_warn'] = {
"mode" : "markers",
"marker" : {
"symbol" : "hash-open",
"color" : "red"
}
}
timelyAlert = None
if not combined.empty:
alertAnalyticConfig = TimelyAnalyticConfiguration(analyticConfig)
if alertAnalyticConfig.groupByColumn is None:
alertAnalyticConfig.groupByColumn = timelyMetric.metric + "_obs"
combined = TimelyMetric.unpivot(combined, timelyMetric.metric, groupByColumn=alertAnalyticConfig.groupByColumn)
combined = combined.sort_index()
combined['date'] = combined.index.values
combined = combined.sort_values(['date', alertAnalyticConfig.groupByColumn])
combined = combined.drop(['date'], 1)
combined = combined.dropna()
combined = DataOperations.ensureMinSeriesLength(combined, alertAnalyticConfig.groupByColumn)
message = DataOperations.getTitle(timelyMetric, analyticConfig, separator=', ')
timelyAlert = TimelyAlert(timelyMetric, combined, message, seriesConfig, alertAnalyticConfig, notebook)
return timelyAlert
Loaded 100 of 721 files, more files were not shown because too many files have changed in this diff. Show more