This repo contains modules used for handling various configurations of an OpenTSDB observability system including alerts, dashboards, namespaces, users, etc.
tie it all together.
Please follow the IntelliJ Idea setup procedure.
Step by step guide and setup for Eclipse are available here.
- Update local db schema:
./gradlew local-db update
-
Upgrade Liquibase
Ref: Liquibase doc
- Make sure all of your Liquibase managed databases are up to date by running
gradle updateon them before upgrading to the new version of the Liquibase plugin. - Create a new, throw away database to test your Liquibase change sets. Run
gradle updateon the new database using the latest version of the Liquibase plugin. This is important because of the deprecated items in the Groovy DSL, and because there are some subtle differences in the ways the different Liquibase versions generate SQL. For example, adding a default value to a boolean column in MySql usingdefaultValue: "0"worked fine in Liquibase 2, but in Liquibase 3, it generates SQL that doesn't work for MySql -defaultValueNumeric: 0needs to be used instead. - Once you are sure all of your change sets work with the latest Liquibase plugin,
clear all checksums that were calculated by the old version of Liquibase 2 by
running
gradle clearChecksumsagainst all databases. - Finally, run
gradle changeLogSyncon all databases to calculate new checksums.
- Upgrade Local Liquibase
./gradlew local-db clearChecksums
./gradlew local-db changeLogSync
- Run unit tests:
./gradlew test
- Run integration tests:
./gradlew integrationTest -Penv=dev
Follow these steps:
- Create certificates Run the following command from the project root. It creates a java keystore file (javakeystore.jks) with the self signed certificates in it.
scripts/create_dev_certificates.sh
- Add the following entry to the host file (/etc/hosts)
127.0.0.1 dev-config.horizon.com
-
Swagger Configuration Download Swagger UI. Extract the zip file and copy the
distdirectory to your user home directory. You can find the fileindex.htmlunderdist. Modify the url link fromhttps://petstore.swagger.io/v2/swagger.jsontohttps://dev-config.horizon.com:4443/api/swagger.jsonin index.html. Rename the directorydisttoswagger -
Start server
./gradlew run --args 'dev'
- Start server in debug mode
./gradlew run --args 'dev' --debug-jvm
- Connect to remote db machine through ssh tunnel
ssh -L 3306:127.0.0.1:3306 {db_host_name}
You should be able access the Swagger page
- Docker run
docker run --name configapi -p 8080:8080 IMAGE_ID
or
docker run -dit --rm -e JAVA_OPTS=-Dlog4j.configurationFile=/opt/opentsdb/horizon/configapi/log4j2/log4j2.xml -e LOG_DIR=/opt/opentsdb/horizon/configapi/shared-data/logs --mount type=bind,source="$(pwd)",target=/opt/opentsdb/horizon/configapi/shared-data --name configapi -p 8080:8080 {IMAGE_ID} -c=file:///opt/opentsdb/horizon/configapi/shared-data/config.yaml
- SSH into docker container:
docker exec -it configapi /bin/sh
This is still a bit of a work in progress but ConfigDB can run as servlets in the OpenTSDB server and work with the OpenTSDB Horizon UI to manage dashboards for a default user. Here are the steps to get it running.
- TODO - Somehow get a collection of the Horizon Config libraries and dependencies
in a directory. In the OpenTSDB config (typically
opentsdb.yaml) plugins config section ala:pluginLocations: - /usr/share/opentsdb/opentsdb-horizon-config/build/libs
- Also in the
opentsdb.yamlconfig, add the MySQL pool, Horizon service and Horizon config resources (Some day we should be able to load them automatically) e.g.:tsd.plugin.config: configs: - plugin: net.opentsdb.threadpools.UserAwareThreadPoolExecutor isDefault: true type: net.opentsdb.threadpools.TSDBThreadPoolExecutor - plugin: net.opentsdb.horizon.SharedJDBCPool isDefault: true type: net.opentsdb.horizon.SharedJDBCPool - plugin: net.opentsdb.horizon.service.HorizonConfigServices isDefault: true type: net.opentsdb.horizon.service.HorizonConfigServices - plugin: net.opentsdb.horizon.resource.DashboardResource id: DashboardResource type: net.opentsdb.servlet.resources.ServletResource - plugin: net.opentsdb.horizon.resource.NamespaceResource id: NamespaceResource type: net.opentsdb.servlet.resources.ServletResource - plugin: net.opentsdb.horizon.resource.UserResource id: UserResource type: net.opentsdb.servlet.resources.ServletResource - plugin: net.opentsdb.horizon.resource.SnapshotResource id: SnapshotResource type: net.opentsdb.servlet.resources.ServletResource - plugin: net.opentsdb.horizon.resource.AlertResource id: AlertResource type: net.opentsdb.servlet.resources.ServletResource - plugin: net.opentsdb.horizon.resource.ContactResource id: ContactResource type: net.opentsdb.servlet.resources.ServletResource ...
Now, pick a data store:
For a production system, use MySQL.
- Setup a MySQL server (help us support others!). Tons of resources on how to do this.
- Setup a database called
opentsdb, e.g. viaCREATE DATABASE 'opentsdb'; - Create and grant a user full rights to the
opentsdbdatabase. - Temporarily edit the
schema/build.gradlefile, modifying thelocal-dbtasks liquibase stanza ala:Replace theliquibase { activities { main { changeLogFile changeLog url 'jdbc:mysql://<DB_HOSTNAME>/opentsdb?serverTimezone=UTC' username '<DB_USER>' password '<DB_PASS>' } } }DB_HOSTNAME,DB_USERNAMEandDB_PASSWORDwith the proper host and credentials. E.g.liquibase { activities { main { changeLogFile changeLog url 'jdbc:mysql://localhost/opentsdb?serverTimezone=UTC' username 'tsdb' password 'temppass' } } }⚠️ Do NOT commit the contents after you edit. - Run
./gradlew local-db updateand it should connect and create tables in the DB.
- Setup a database called
- Next in the same
opentsdb.yamlyou'll need to set the passwords for the MySQL pool. Add the following lines:Again, replace thejdbcpool.write.user: <DB_USER> jdbcpool.write.secret.key: PT:dbpass jdbcpool.write.url: jdbc:mysql://<DB_HOSTNAME>/opentsdb?serverTimezone=UTC # NOTE: This is the insecure plain-text password. Use a proper secrets plugin. dbpass: <DB_PASS>
<DB_USER>,<DB_HOSTNAME>and<DB_PASS>. Also make sure that the plain text secret plugin is loaded and namedPTby providing a command line config like:--config.providers=secrets://net.opentsdb.configuration.provider.PlainTextSecretProvider:PT,file:///etc/opentsdb/opentsdb.yaml - Start the TSDB process and check the logs to make sure the resources loaded
properly. If so you should be able to add a default user by making a POST
call to the TSD
http://localhost:4242/api/v1/user/listwith a payload of:If you get a 200 back with a payload containing an[{ "userid":"user.noauth", "name": "Default User", "creationMode": 0, "enabled":true }]updatetimeyou're good to go.
For the all-in-one Docker container or to simply test out the project you can use
an H2 database, either ephemerally in memory or saved to disk. When starting from
memory or with an empty database director, a default user noauth and namespace
_default will be created for use with Horizon UI.
-
Edit the
opentsdb.yamlfile to set the JDBC driver to H2 and choose a file to write to. Add the following lines:jdbcpool.write.user: sa jdbcpool.write.secret.key: PT:dbpass # jdbcpool.write.url: jdbc:h2:<PATH_TO_DB>;MODE=MYSQL;NON_KEYWORDS=USER jdbcpool.write.url: jdbc:h2:./opentsdb;MODE=MYSQL;NON_KEYWORDS=USER # NOTE: This is the insecure plain-text password. Use a proper secrets plugin. dbpass: sa
NOTE: This uses the default
sauser and pass for H2. If you intend to run this for a while and want to secure it, follow the H2 documentation to change the user and pass.
At this point the config API should be ready to go. Use
--config.providers=secrets://net.opentsdb.configuration.provider.PlainTextSecretProvider:PT,file:///etc/opentsdb/opentsdb.yaml
as the config to start the TSD.
To use the Horizon UI with OpenTSDB and the config API you'll need a few more settings:
- First add rewrites to the
opentsdb.yamlconfig:tsd.http.rewrites: ^/$: /index.html ^/d/|a/.*: /index.html ^/main$: /index.html
- Set the
opentsdb.yamlstatic directory to point to the location of the packaged UI files. If building from source it would be in theserver/publicdirectory from the git repo. For the pre-build OpenTSDB docker container the files are in/usr/share/opentsdb/static.tsd.http.staticroot: /usr/share/opentsdb/static
- Add CORs settings to the
opentsdb.yamlfile:tsd.http.request.cors.pattern: .* tsd.http.request.cors.headers: Authorization,Content-Type,Link,X-Total-Count,Range,X-Horizon-DSHBID,X-Horizon-WID
- And finally you'll want to edit the Horizon UI config with a file named
configin the same directory as the Horizon UI static files:{ "name" : "OpenTSDB Horizon", "production": true, "readonly": false, "queryParams": null, "debugLevel": "ERROR", "uiBranding": { "logo": { "imageUrl": "/assets/horizon-logo-icon-only.png", "homeUrl": "/main" } }, "tsdbCacheMode": null, "tsdbSource": null, "tsdb_hosts": [ "http://localhost:4242" ], "configdb": "http://localhost:4242/api/v1", "metaApi": "http://localhost:4242/api", "auraUI": "http://localhost:4242", "alert": { "recipient": { "http": { "enable": false }, "email": { "enable": true } } }, "helpLinks": [ { "label": "User guide", "href": "#" }, { "label": "File a ticket", "href": "#" }, { "label": "Talk to us", "icon": "d-slack", "href": "#" } ], "modules": { "dashboard": { "widget": { "overrideTime": true } } }, "namespace": { "enabled": false, "default": "_default" }, "auth": { "loginURL": "/login", "heartbeatURL": "/heartbeat", "heartbeatImgURL": "/heartbeatimg", "heartbeatInterval": 600 } } - Run (or restart) the TSD and if you hit
http://localhost:4242you should see the Horizon UI load!
To use a custom authentication filter, you will have to provide an implementation
of javax.servlet.Filter and configure the fully qualified class name as shown below.
You can provide the filter init parameters as key and value pair. After successful
authentication, the filter should set java.security.Principal in the HttpServletRequest.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.security.Principal;
public class CustomAuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// initialize the filter
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Cookie cookie = readCookie(httpServletRequest);
if (null == cookie) {
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "auth cookie not found");
} else {
Principal principal = authenticate(cookie.getValue());
if (principal == null) {
httpServletResponse.sendError(
HttpServletResponse.SC_UNAUTHORIZED, "authentication failure");
} else {
HttpServletRequestWrapper requestWrapper =
new HttpServletRequestWrapper(httpServletRequest) {
@Override
public Principal getUserPrincipal() {
return principal;
}
};
chain.doFilter(requestWrapper, response);
}
}
}
}serverConfig:
port: 8080
...
authConfig:
authFilterClassName: {FQCN of CustomAuthFilter}
initParams:
{key1}: {value1}
{key2}: {value2}It's not a good idea to mention the secrets for example the database user password
in the free text format in the configuration file. It's recommended to store the
secrets in an external key store and use the key name in the configuration file.
You need to provide an implementation of net.opentsdb.horizon.secrets.KeyReader
for the application to read the secrets from the key store.
You would also have to provide and implementation of
net.opentsdb.horizon.secrets.KeyReaderFactory and configure it as shown below.
public class CustomKeyReader extends KeyReader {
@Override
protected byte[] readSecret(final String key) {
// read the secret from the key store and return.
}
}
public class CustomKeyReaderFactory implements KeyReaderFactory<CustomKeyReader> {
@Override
public CustomKeyReader createKeyReader(Map<String, Object> initParams) {
// instantiate the CustomKeyReader and return
}
}Configure the key read factory in the applicationConfig section as shown here.
applicationConfig:
keyReaderFactoryClassName: {FQDN of CustomKeyReaderFactory}
initParams:
initParams:
{key1}: {value1}
{key2}: {value2}Please see the Contributing file for information on how to get involved. We welcome issues, questions, and pull requests.
- Smruti Ranjan Sahoo
- Chris Larsen
This project is licensed under the terms of the Apache 2.0 open source license. Please refer to LICENSE for the full terms.