http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
 
b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
index 5488209..b8cdf1f 100644
--- 
a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
+++ 
b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
@@ -15,17 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {async, TestBed, ComponentFixture} from '@angular/core/testing';
-import {KafkaService} from '../../service/kafka.service';
-import {Observable} from  'rxjs/Observable';
-import {SampleDataComponent} from './sample-data.component';
-import {SharedModule} from '../shared.module';
-import '../../rxjs-operators';
+import { async, TestBed, ComponentFixture } from '@angular/core/testing';
+import { KafkaService } from '../../service/kafka.service';
+import { Observable, throwError } from 'rxjs';
+import { SampleDataComponent } from './sample-data.component';
+import { SharedModule } from '../shared.module';
 
 class MockKafkaService {
   _sample: string[];
-  _sampleCounter: number = 0;
-
+  _sampleCounter = 0;
 
   public setSample(sampleMessages: string[]): void {
     this._sample = sampleMessages;
@@ -33,7 +31,6 @@ class MockKafkaService {
   }
 
   public sample(name: string): Observable<string> {
-
     if (this._sampleCounter < this._sample.length) {
       return Observable.create(observer => {
         observer.next(this._sample[this._sampleCounter++]);
@@ -41,7 +38,7 @@ class MockKafkaService {
       });
     }
 
-    return Observable.throw('Error');
+    return throwError('Error');
   }
 }
 
@@ -58,24 +55,21 @@ describe('SampleDataComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [SharedModule],
-      declarations: [ SampleDataComponent],
+      declarations: [SampleDataComponent],
       providers: [
         SampleDataComponent,
-        {provide: KafkaService, useClass: MockKafkaService}
+        { provide: KafkaService, useClass: MockKafkaService }
       ]
     });
-
     fixture = TestBed.createComponent(SampleDataComponent);
     sampleDataComponent = fixture.componentInstance;
-    kafkaService = fixture.debugElement.injector.get(KafkaService);
-
+    kafkaService = TestBed.get(KafkaService);
   }));
 
   it('can instantiate SampleDataComponent', async(() => {
     expect(sampleDataComponent instanceof SampleDataComponent).toBe(true);
   }));
 
-
   it('should emmit messages', async(() => {
     let expectedMessage;
     let successCount = 0;
@@ -140,11 +134,9 @@ describe('SampleDataComponent', () => {
     sampleDataComponent.getPreviousSample();
     expect(successCount).toEqual(7);
     expect(failureCount).toEqual(1);
-
   }));
 
   it('should emmit messages on blur', async(() => {
-
     let expectedMessage;
     let successCount = 0;
 
@@ -155,9 +147,10 @@ describe('SampleDataComponent', () => {
       expect(message).toEqual(expectedMessage);
     });
 
-
     expectedMessage = 'This is a simple message';
-    fixture.debugElement.nativeElement.querySelector('textarea').value = 
expectedMessage;
+    fixture.debugElement.nativeElement.querySelector(
+      'textarea'
+    ).value = expectedMessage;
     sampleDataComponent.onBlur();
 
     expect(successCount).toEqual(1);
@@ -165,16 +158,16 @@ describe('SampleDataComponent', () => {
     expect(sampleDataComponent.sampleData.length).toEqual(1);
     expect(sampleDataComponent.sampleData[0]).toEqual(expectedMessage);
 
-
     expectedMessage = '';
-    fixture.debugElement.nativeElement.querySelector('textarea').value = 
expectedMessage;
+    fixture.debugElement.nativeElement.querySelector(
+      'textarea'
+    ).value = expectedMessage;
     sampleDataComponent.onBlur();
 
     expect(successCount).toEqual(2);
     expect(sampleDataComponent.sampleDataIndex).toEqual(0);
     expect(sampleDataComponent.sampleData.length).toEqual(1);
 
-
     expectedMessage = sampleMessages[0];
     sampleDataComponent.getNextSample();
 
@@ -182,7 +175,5 @@ describe('SampleDataComponent', () => {
     expect(sampleDataComponent.sampleDataIndex).toEqual(1);
     expect(sampleDataComponent.sampleData.length).toEqual(2);
     expect(sampleDataComponent.sampleData[1]).toEqual(sampleMessages[0]);
-
   }));
-
 });

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/util/httpUtil.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/util/httpUtil.ts 
b/metron-interface/metron-config/src/app/util/httpUtil.ts
index dfcb61f..d8a21a5 100644
--- a/metron-interface/metron-config/src/app/util/httpUtil.ts
+++ b/metron-interface/metron-config/src/app/util/httpUtil.ts
@@ -15,34 +15,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Response} from '@angular/http';
-import {Observable}     from 'rxjs/Observable';
-import {RestError} from '../model/rest-error';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { throwError, Observable } from 'rxjs';
 
-export class HttpUtil {
+import { RestError } from '../model/rest-error';
 
-  public static extractString(res: Response): string {
-    let text: string = res.text();
+export class HttpUtil {
+  public static extractString(res: HttpResponse<any>): string {
+    let text: string = res.toString();
     return text || '';
   }
 
-  public static extractData(res: Response): any {
-    let body = res.json();
+  public static extractData(res: HttpResponse<any>): any {
+    let body = res;
     return body || {};
   }
 
-  public static handleError(res: Response): Observable<RestError> {
+  public static handleError(res: HttpErrorResponse): Observable<RestError> {
     // In a real world app, we might use a remote logging infrastructure
     // We'd also dig deeper into the error to get a better message
     let restError: RestError;
     if (res.status === 401) {
       window.location.assign('/login?sessionExpired=true');
     } else if (res.status !== 404) {
-      restError = res.json();
+      restError = res;
     } else {
       restError = new RestError();
-      restError.responseCode = 404;
+      restError.status = 404;
     }
-    return Observable.throw(restError);
+    return throwError(restError);
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/util/httpUtils.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/util/httpUtils.spec.ts 
b/metron-interface/metron-config/src/app/util/httpUtils.spec.ts
index ff1e39d..c2b0e6b 100644
--- a/metron-interface/metron-config/src/app/util/httpUtils.spec.ts
+++ b/metron-interface/metron-config/src/app/util/httpUtils.spec.ts
@@ -15,33 +15,52 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {HttpUtil} from './httpUtil';
-import {Response, ResponseOptions, ResponseType} from '@angular/http';
-import {Observable} from 'rxjs/Observable';
-import {RestError} from '../model/rest-error';
+import { HttpUtil } from './httpUtil';
+import { RestError } from '../model/rest-error';
+import { HttpErrorResponse } from '@angular/common/http';
+import { noop } from 'rxjs';
 
 describe('HttpUtil', () => {
-
   it('should create an instance', () => {
     expect(HttpUtil.handleError).toBeTruthy();
     expect(HttpUtil.extractString).toBeTruthy();
     expect(HttpUtil.extractData).toBeTruthy();
   });
 
-  it('should handleError', () => {
-    let error500: RestError = {message: 'This is error', responseCode: 500, 
fullMessage: 'This is error'};
-    let responseOptions = new ResponseOptions();
-    responseOptions.body = error500;
-    let response = new Response(responseOptions);
-    response.type = ResponseType.Basic;
-    expect(HttpUtil.handleError(response)).toEqual(Observable.throw(error500));
-
-    let error404 = new RestError();
-    error404.responseCode = 404;
-    response = new Response(new ResponseOptions());
-    response.type = ResponseType.Basic;
-    response.status = 404;
-    expect(HttpUtil.handleError(response)).toEqual(Observable.throw(error404));
+  it('should handleError 500', () => {
+    let error500: RestError = {
+      message: 'This is error',
+      status: 500,
+      error: 'This is error'
+    };
+    let response = new HttpErrorResponse(error500);
+    let httpUtilSub = HttpUtil.handleError(response).subscribe(
+      noop,
+      e => {
+        expect(e.status).toBe(500);
+        expect(e.message).toBe(
+          'Http failure response for (unknown url): 500 undefined'
+        );
+        expect(e.error).toBe('This is error');
+      },
+      noop
+    );
+    httpUtilSub.unsubscribe();
   });
 
+  it('should handleError 404', () => {
+    const error404 = new RestError();
+    error404.status = 404;
+    const response = new HttpErrorResponse(error404);
+    const httpUtilSub = HttpUtil.handleError(response).subscribe(
+      noop,
+      e => {
+        expect(e.status).toBe(404);
+        expect(e.message).toBe(undefined);
+        expect(e.error).toBe(undefined);
+      },
+      noop
+    );
+    httpUtilSub.unsubscribe();
+  });
 });

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.spec.ts
 
b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.spec.ts
index 7be3ccd..432ec2b 100644
--- 
a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.spec.ts
+++ 
b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.spec.ts
@@ -15,39 +15,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {async, inject, TestBed} from '@angular/core/testing';
+import {TestBed} from '@angular/core/testing';
 import {Router} from '@angular/router';
 import {VerticalNavbarComponent} from './verticalnavbar.component';
 
 class MockRouter {
-  url: string = '';
+  url = '';
 }
 
 describe('VerticalNavbarComponent', () => {
-
-  beforeEach(async(() => {
+  let router: Router;
+  let verticalNavbarComponent: VerticalNavbarComponent;
+  beforeEach(() => {
     TestBed.configureTestingModule({
       providers: [
         VerticalNavbarComponent,
         {provide: Router, useClass: MockRouter}
       ]
-    }).compileComponents();
-
-  }));
+    });
+    verticalNavbarComponent = TestBed.get(VerticalNavbarComponent);
+  });
 
-  it('can instantiate VerticalNavbarComponent',
-    inject([VerticalNavbarComponent], (verticalNavbarComponent: 
VerticalNavbarComponent) => {
+  it('can instantiate VerticalNavbarComponent', () => {
       expect(verticalNavbarComponent instanceof 
VerticalNavbarComponent).toBe(true);
-  }));
-
-  it('check isActive for a URL VerticalNavbarComponent',
-    inject([VerticalNavbarComponent, Router], (component: 
VerticalNavbarComponent, router: Router) => {
-
-      router.url = '/abc';
-      expect(component.isActive(['/def'])).toEqual(false);
-      expect(component.isActive(['/abc'])).toEqual(true);
-      expect(component.isActive(['/def', '/abc'])).toEqual(true);
-
-  }));
+  });
 
 });

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.ts
 
b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.ts
index 4067f6c..3425612 100644
--- 
a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.ts
+++ 
b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.component.ts
@@ -29,7 +29,4 @@ export class VerticalNavbarComponent {
 
   constructor(private router: Router) {}
 
-  isActive(validRoutes: string[]) {
-    return validRoutes.indexOf(this.router.url) !== -1;
-  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.html
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.html 
b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.html
index a4052b3..994711a 100644
--- a/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.html
+++ b/metron-interface/metron-config/src/app/verticalnavbar/verticalnavbar.html
@@ -19,10 +19,10 @@
 
   <ul class="nav">
     <li class="nav-item">
-      <a [routerLink]="['/sensors']"  class="nav-link" [ngClass]="{'active': 
isActive(['/', '/sensors'])}" href="#">Sensors</a>
+      <a [routerLink]="['/sensors']"  class="nav-link" 
routerLinkActive="active" href="#">Sensors</a>
     </li>
     <li class="nav-item">
-      <a [routerLink]="['/general-settings']"  class="nav-link" 
[ngClass]="{'active': isActive(['/general-settings'])}" href="#">General 
Settings</a>
+      <a [routerLink]="['/general-settings']"  class="nav-link" 
routerLinkActive="active" href="#">General Settings</a>
     </li>
   </ul>
 

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/styles.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/styles.scss 
b/metron-interface/metron-config/src/styles.scss
index db57e91..26bb57c 100644
--- a/metron-interface/metron-config/src/styles.scss
+++ b/metron-interface/metron-config/src/styles.scss
@@ -17,7 +17,7 @@
  */
 /* You can add global styles to this file, and also import other style files */
 @import "app/_variables.scss";
-@import "app/_main.scss";
+@import "app/_fonts.scss";
 
 $height: 60px;
 

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/test.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/test.ts 
b/metron-interface/metron-config/src/test.ts
index 347d245..606c810 100644
--- a/metron-interface/metron-config/src/test.ts
+++ b/metron-interface/metron-config/src/test.ts
@@ -15,50 +15,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import './polyfills.ts';
 
-import 'zone.js/dist/long-stack-trace-zone';
-import 'zone.js/dist/proxy.js';
-import 'zone.js/dist/sync-test';
-import 'zone.js/dist/jasmine-patch';
-import 'zone.js/dist/async-test';
-import 'zone.js/dist/fake-async-test';
-
-import 'jquery/dist/jquery';
-import 'tether/dist/js/tether';
-
-import * as $ from 'jquery';
-window['$'] = window['jQuery'] = $;
-
-import  * as Tether from 'tether';
-window['Tether'] = Tether;
-
-import 'ace-builds/src-noconflict/ace.js';
-import 'bootstrap/dist/js/bootstrap';
-import 'bootstrap';
-
-// Unfortunately there's no typing for the `__karma__` variable. Just declare 
it as any.
-declare var __karma__: any;
-declare var require: any;
-
-// Prevent Karma from running prematurely.
-__karma__.loaded = function () {};
-
-
-Promise.all([
-  System.import('@angular/core/testing'),
-  System.import('@angular/platform-browser-dynamic/testing')
-])
-  // First, initialize the Angular testing environment.
-  .then(([testing, testingBrowser]) => {
-    testing.getTestBed().initTestEnvironment(
-      testingBrowser.BrowserDynamicTestingModule,
-      testingBrowser.platformBrowserDynamicTesting()
-    );
-  })
-  // Then we find all the tests.
-  .then(() => require.context('./', true, /\.spec\.ts/))
-  // And load the modules.
-  .then(context => context.keys().map(context))
-  // Finally, start Karma to run the tests.
-  .then(__karma__.start, __karma__.error);
+// This file is required by karma.conf.js and loads recursively all the .spec 
and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/tsconfig.app.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/tsconfig.app.json 
b/metron-interface/metron-config/src/tsconfig.app.json
new file mode 100644
index 0000000..107b74d
--- /dev/null
+++ b/metron-interface/metron-config/src/tsconfig.app.json
@@ -0,0 +1,23 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/app",
+    "module": "es2015",
+    "baseUrl": "",
+    "types": [
+      "jquery",
+      "bootstrap",
+      "ace",
+      "node"
+    ],
+    "typeRoots": [ "../node_modules/@types" ]
+  },
+  "exclude": [
+    "test.ts",
+    "**/*.spec.ts"
+  ],
+  "angularCompilerOptions": {
+    "preserveWhitespaces": true
+  },
+}
+

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/tsconfig.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/tsconfig.json 
b/metron-interface/metron-config/src/tsconfig.json
deleted file mode 100644
index 65756e3..0000000
--- a/metron-interface/metron-config/src/tsconfig.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "compilerOptions": {
-    "declaration": false,
-    "emitDecoratorMetadata": true,
-    "experimentalDecorators": true,
-    "lib": ["es6", "dom"],
-    "mapRoot": "./",
-    "module": "es6",
-    "moduleResolution": "node",
-    "outDir": "../dist/out-tsc",
-    "sourceMap": true,
-    "target": "es5",
-    "typeRoots": [
-      "../node_modules/@types"
-    ],
-    "types": [
-      "jasmine",
-      "jquery",
-      "bootstrap",
-      "ace"
-    ]
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/tsconfig.spec.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/tsconfig.spec.json 
b/metron-interface/metron-config/src/tsconfig.spec.json
new file mode 100644
index 0000000..eb43ad2
--- /dev/null
+++ b/metron-interface/metron-config/src/tsconfig.spec.json
@@ -0,0 +1,24 @@
+{
+    "extends": "../tsconfig.json",
+    "compilerOptions": {
+      "outDir": "../out-tsc/spec",
+      "module": "commonjs",
+      "target": "es5",
+      "baseUrl": "",
+      "types": [
+        "jquery",
+        "bootstrap",
+        "jasmine",
+        "node"
+      ],
+      "typeRoots": [ "../node_modules/@types" ]
+    },
+    "files": [
+      "test.ts",
+      "polyfills.ts"
+    ],
+    "include": [
+      "**/*.spec.ts",
+      "**/*.d.ts"
+    ]
+  }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/src/typings.d.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/typings.d.ts 
b/metron-interface/metron-config/src/typings.d.ts
index c0aaf0c..75b5619e 100644
--- a/metron-interface/metron-config/src/typings.d.ts
+++ b/metron-interface/metron-config/src/typings.d.ts
@@ -20,4 +20,4 @@
 // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
 
 declare var System: any;
-declare var require: any;
+declare var require: NodeRequire;

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/tsconfig.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/tsconfig.json 
b/metron-interface/metron-config/tsconfig.json
new file mode 100644
index 0000000..1ca28d2
--- /dev/null
+++ b/metron-interface/metron-config/tsconfig.json
@@ -0,0 +1,20 @@
+{
+    "compileOnSave": false,
+    "compilerOptions": {
+      "outDir": "./dist/out-tsc",
+      "baseUrl": "src",
+      "sourceMap": true,
+      "declaration": false,
+      "moduleResolution": "node",
+      "emitDecoratorMetadata": true,
+      "experimentalDecorators": true,
+      "target": "es5",
+      "typeRoots": [
+        "node_modules/@types"
+      ],
+      "lib": [
+        "es2016",
+        "dom"
+      ]
+    }
+  }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/README.md
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/README.md 
b/metron-interface/metron-rest/README.md
index 7f00cde..f85707f 100644
--- a/metron-interface/metron-rest/README.md
+++ b/metron-interface/metron-rest/README.md
@@ -194,6 +194,44 @@ METRON_PRINCIPAL_NAME="met...@example.com"
 METRON_SERVICE_KEYTAB="/etc/security/keytabs/metron.keytab"
 ```
 
+### LDAP
+
+Metron REST can be configured to use LDAP for authentication and roles. 
Configuration can be performed via Ambari in the "Security" tab.
+
+Configuration will default to matching Knox's Demo LDAP for convenience. This 
should only be used for development purposes. Manual instructions for setting 
up demo LDAP and finalizing configuration (e.g. setting up the user LDIF file) 
can be found in the [Development 
README](../../metron-deployment/development/README.md#knox-demo-ldap).
+
+#### LDAPS
+There is configuration to provide a path to a truststore with SSL certificates 
and provide a password. Users should import certificates as needed to 
appropriate truststores.  An example of doing this is:
+```
+keytool -import -alias <alias> -file <certificate> -keystore <keystore_file> 
-storepass <password>
+```
+
+
+#### Roles
+Roles used by Metron are ROLE_ADMIN and ROLE_USER. Metron will use a property 
in a group containing the appropriate role to construct this.
+
+For example, our ldif file could create this group:
+```
+dn: cn=admin,ou=groups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: admin
+description:admin group
+member: uid=admin,ou=people,dc=hadoop,dc=apache,dc=org
+```
+
+If we are using "cn" as our role attribute, Metron will give the "admin" user 
the role "ROLE_ADMIN".
+
+Similarly, we could give a user "sam" ROLE_USER with the following group:
+```
+dn: cn=user,ou=groups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: user
+description: user group
+member: uid=sam,ou=people,dc=hadoop,dc=apache,dc=org
+```
+
 ## Spring Profiles
 
 The REST application comes with a few [Spring 
Profiles](http://docs.spring.io/autorepo/docs/spring-boot/current/reference/html/boot-features-profiles.html)
 to aid in testing and development.

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/pom.xml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/pom.xml 
b/metron-interface/metron-rest/pom.xml
index 7fc373b..52b0175 100644
--- a/metron-interface/metron-rest/pom.xml
+++ b/metron-interface/metron-rest/pom.xml
@@ -31,6 +31,8 @@
         <powermock.version>1.6.4</powermock.version>
         <spring.boot.version>2.0.1.RELEASE</spring.boot.version>
         <spring.kerberos.version>1.0.1.RELEASE</spring.kerberos.version>
+        <spring.ldap.core.version>2.3.2.RELEASE</spring.ldap.core.version>
+        
<spring.security.ldap.version>5.1.1.RELEASE</spring.security.ldap.version>
         <swagger.version>2.5.0</swagger.version>
         <mysql.client.version>5.1.40</mysql.client.version>
         <spring-kafka.version>2.0.4.RELEASE</spring-kafka.version>
@@ -118,6 +120,16 @@
             </exclusions>
         </dependency>
         <dependency>
+          <groupId>org.springframework.ldap</groupId>
+          <artifactId>spring-ldap-core</artifactId>
+          <version>${spring.ldap.core.version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.springframework.security</groupId>
+          <artifactId>spring-security-ldap</artifactId>
+          <version>${spring.security.ldap.version}</version>
+        </dependency>
+        <dependency>
             <groupId>com.googlecode.json-simple</groupId>
             <artifactId>json-simple</artifactId>
             <version>${global_json_simple_version}</version>

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/config/rest_application.yml
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/config/rest_application.yml 
b/metron-interface/metron-rest/src/main/config/rest_application.yml
index 84efc01..7ab7b73 100644
--- a/metron-interface/metron-rest/src/main/config/rest_application.yml
+++ b/metron-interface/metron-rest/src/main/config/rest_application.yml
@@ -13,14 +13,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-spring:
-  datasource:
-      driverClassName: ${METRON_JDBC_DRIVER}
-      url: ${METRON_JDBC_URL}
-      username: ${METRON_JDBC_USERNAME}
-      password: ${METRON_JDBC_PASSWORD}
-      platform: ${METRON_JDBC_PLATFORM}
-      continue-on-error: true
 
 zookeeper:
   url: ${ZOOKEEPER}
@@ -62,3 +54,27 @@ pcap:
   page.size: ${PCAP_PAGE_SIZE}
   yarn.queue: ${PCAP_YARN_QUEUE}
   finalizer.threadpool.size: ${PCAP_FINALIZER_THREADPOOL_SIZE}
+
+spring:
+  datasource:
+    driverClassName: ${METRON_JDBC_DRIVER}
+    url: ${METRON_JDBC_URL}
+    username: ${METRON_JDBC_USERNAME}
+    password: ${METRON_JDBC_PASSWORD}
+    platform: ${METRON_JDBC_PLATFORM}
+    continue-on-error: true
+
+ldap:
+  provider:
+    url: ${METRON_LDAP_URL}
+    userdn: ${METRON_LDAP_USERDN}
+    password: ${METRON_LDAP_PASSWORD}
+  user:
+    dn.patterns: ${METRON_LDAP_USER_PATTERN}
+    passwordAttribute: ${METRON_LDAP_USER_PASSWORD}
+    searchBase: ${METRON_LDAP_USER_SEARCHBASE}
+    searchFilter: ${METRON_LDAP_USER_SEARCHFILTER}
+  group:
+    searchBase: ${METRON_LDAP_GROUP_SEARCHBASE}
+    searchFilter: ${METRON_LDAP_GROUP_SEARCHFILTER}
+    roleAttribute: ${METRON_LDAP_GROUP_ROLE}

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
index 94e8e35..80ac2bf 100644
--- 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
+++ 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
@@ -23,6 +23,7 @@ public class MetronRestConstants {
 
   public static final String DEV_PROFILE = "dev";
   public static final String TEST_PROFILE = "test";
+  public static final String LDAP_PROFILE = "ldap";
   public static final String DOCKER_PROFILE = "docker";
   public static final String CSRF_ENABLE_PROFILE = "csrf-enable";
 

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
index f84cdfa..7ca3a46 100644
--- 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
+++ 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
@@ -20,8 +20,14 @@ package org.apache.metron.rest.config;
 import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN;
 import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_USER;
 
+import java.util.Arrays;
+import java.util.List;
+import javax.sql.DataSource;
 import org.apache.metron.rest.MetronRestConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.env.Environment;
@@ -30,7 +36,7 @@ import 
org.springframework.security.config.annotation.method.configuration.Enabl
 import 
org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import 
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import 
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
-import org.springframework.security.core.userdetails.User;
+import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import 
org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
@@ -39,19 +45,40 @@ import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 
-import javax.sql.DataSource;
-import java.util.Arrays;
-import java.util.List;
-
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(securedEnabled = true)
 @Controller
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    private static final Logger LOG = 
LoggerFactory.getLogger(WebSecurityConfig.class);
 
     @Autowired
     private Environment environment;
 
+    @Autowired
+    private DataSource dataSource;
+
+    @Value("${ldap.provider.url}")
+    private String providerUrl;
+    @Value("${ldap.provider.userdn}")
+    private String providerUserDn;
+    @Value("${ldap.provider.password}")
+    private String providerPassword;
+    @Value("${ldap.user.dn.patterns}")
+    private String userDnPatterns;
+    @Value("${ldap.user.passwordAttribute}")
+    private String passwordAttribute;
+    @Value("${ldap.user.searchBase}")
+    private String userSearchBase;
+    @Value("${ldap.user.searchFilter}")
+    private String userSearchFilter;
+    @Value("${ldap.group.searchBase}")
+    private String groupSearchBase;
+    @Value("${ldap.group.roleAttribute}")
+    private String groupRoleAttribute;
+    @Value("${ldap.group.searchFilter}")
+    private String groupSearchFilter;
+
     @RequestMapping(value = {"/login", "/logout", "/sensors", "/sensors*/**"}, 
method = RequestMethod.GET)
     public String handleNGRequests() {
         return "forward:/index.html";
@@ -84,18 +111,34 @@ public class WebSecurityConfig extends 
WebSecurityConfigurerAdapter {
     }
 
     @Autowired
-    private DataSource dataSource;
-
-    @Autowired
     public void configureJdbc(AuthenticationManagerBuilder auth) throws 
Exception {
+        // Note that we can switch profiles on the fly in Ambari.
         List<String> activeProfiles = 
Arrays.asList(environment.getActiveProfiles());
-        if (activeProfiles.contains(MetronRestConstants.DEV_PROFILE) ||
-                activeProfiles.contains(MetronRestConstants.TEST_PROFILE)) {
-          auth.jdbcAuthentication().dataSource(dataSource)
-                  
.withUser("user").password("password").roles(SECURITY_ROLE_USER).and()
-                  
.withUser("user1").password("password").roles(SECURITY_ROLE_USER).and()
-                  
.withUser("user2").password("password").roles(SECURITY_ROLE_USER).and()
-                  
.withUser("admin").password("password").roles(SECURITY_ROLE_USER, 
SECURITY_ROLE_ADMIN);
+        if (activeProfiles.contains(MetronRestConstants.LDAP_PROFILE)) {
+            LOG.debug("Setting up LDAP authentication against {}.", 
providerUrl);
+            auth.ldapAuthentication()
+                .userDnPatterns(userDnPatterns)
+                .userSearchBase(userSearchBase)
+                .userSearchFilter(userSearchFilter)
+                .groupRoleAttribute(groupRoleAttribute)
+                .groupSearchFilter(groupSearchFilter)
+                .groupSearchBase(groupSearchBase)
+                .contextSource()
+                .url(providerUrl)
+                .managerDn(providerUserDn)
+                .managerPassword(providerPassword)
+                .and()
+                .passwordCompare()
+                .passwordEncoder(new LdapShaPasswordEncoder())
+                .passwordAttribute(passwordAttribute);
+        } else if (activeProfiles.contains(MetronRestConstants.DEV_PROFILE) ||
+            activeProfiles.contains(MetronRestConstants.TEST_PROFILE)) {
+            auth.jdbcAuthentication()
+                .dataSource(dataSource)
+                
.withUser("user").password("password").roles(SECURITY_ROLE_USER).and()
+                
.withUser("user1").password("password").roles(SECURITY_ROLE_USER).and()
+                
.withUser("user2").password("password").roles(SECURITY_ROLE_USER).and()
+                
.withUser("admin").password("password").roles(SECURITY_ROLE_USER, 
SECURITY_ROLE_ADMIN);
         } else {
             auth.jdbcAuthentication().dataSource(dataSource);
         }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UserController.java
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UserController.java
 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UserController.java
index b5f7765..24c781c 100644
--- 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UserController.java
+++ 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/UserController.java
@@ -19,6 +19,11 @@ package org.apache.metron.rest.controller;
 
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
@@ -35,4 +40,12 @@ public class UserController {
     public String user(Principal user) {
         return user.getName();
     }
+
+  @Secured("IS_AUTHENTICATED_FULLY")
+  @RequestMapping(path = "/whoami/roles", method = RequestMethod.GET)
+  public List<String> user() {
+    UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().
+        getAuthentication().getPrincipal();
+    return userDetails.getAuthorities().stream().map(ga -> 
ga.getAuthority()).collect(Collectors.toList());
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/SensorParserConfigServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/SensorParserConfigServiceImpl.java
 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/SensorParserConfigServiceImpl.java
index 85b84b8..d0e4b3d 100644
--- 
a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/SensorParserConfigServiceImpl.java
+++ 
b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/service/impl/SensorParserConfigServiceImpl.java
@@ -20,11 +20,10 @@ package org.apache.metron.rest.service.impl;
 import static org.apache.metron.rest.MetronRestConstants.GROK_CLASS_NAME;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Optional;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.hadoop.fs.Path;
 import org.apache.metron.common.configuration.ConfigurationType;
@@ -33,17 +32,15 @@ import 
org.apache.metron.common.configuration.ParserConfigurations;
 import org.apache.metron.common.configuration.SensorParserConfig;
 import org.apache.metron.common.zookeeper.ConfigurationsCache;
 import org.apache.metron.parsers.interfaces.MessageParser;
+import org.apache.metron.parsers.interfaces.MessageParserResult;
 import org.apache.metron.rest.MetronRestConstants;
 import org.apache.metron.rest.RestException;
 import org.apache.metron.rest.model.ParseMessageRequest;
 import org.apache.metron.rest.service.GrokService;
 import org.apache.metron.rest.service.SensorParserConfigService;
 import org.apache.metron.rest.util.ParserIndex;
-import org.apache.metron.common.zookeeper.ZKConfigurationsCache;
 import org.apache.zookeeper.KeeperException;
 import org.json.simple.JSONObject;
-import org.reflections.Reflections;
-import org.reflections.util.ConfigurationBuilder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -154,11 +151,24 @@ public class SensorParserConfigServiceImpl implements 
SensorParserConfigService
       }
       parser.configure(sensorParserConfig.getParserConfig());
       parser.init();
-      JSONObject results = 
parser.parse(parseMessageRequest.getSampleData().getBytes()).get(0);
+
+      Optional<MessageParserResult<JSONObject>> result = 
parser.parseOptionalResult(parseMessageRequest.getSampleData().getBytes());
+      if (!result.isPresent()) {
+        throw new RestException("Unknown error parsing sample data");
+      }
+
+      if (result.get().getMasterThrowable().isPresent()) {
+        throw new RestException("Error parsing sample 
data",result.get().getMasterThrowable().get());
+      }
+
+      if (result.get().getMessages().isEmpty()) {
+        throw new RestException("No results from parsing sample data");
+      }
+
       if (isGrokConfig(sensorParserConfig) && temporaryGrokPath != null) {
         grokService.deleteTemporary();
       }
-      return results;
+      return result.get().getMessages().get(0);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/resources/application-vagrant.yml
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-rest/src/main/resources/application-vagrant.yml 
b/metron-interface/metron-rest/src/main/resources/application-vagrant.yml
index 3eea24a..05ea571 100644
--- a/metron-interface/metron-rest/src/main/resources/application-vagrant.yml
+++ b/metron-interface/metron-rest/src/main/resources/application-vagrant.yml
@@ -52,9 +52,22 @@ storm:
     randomaccess.script.path: 
/usr/metron/${metron.version}/bin/start_elasticsearch_topology.sh
     batch.script.path: /usr/metron/${metron.version}/bin/start_hdfs_topology.sh
 
-
 kerberos:
   enabled: false
   principal: met...@example.com
   keytab: /etc/security/keytabs/metron.headless.keytab
 
+ldap:
+  provider:
+    url: ldap://node1:33389
+    userdn: uid=admin,ou=people,dc=hadoop,dc=apache,dc=org
+    password: "admin-password"
+  user:
+    dn.patterns: uid={0},ou=people,dc=hadoop,dc=apache,dc=org
+    passwordAttribute: userPassword
+    searchBase: ou=people,dc=hadoop,dc=apache,dc=org
+    searchFilter: ""
+  group:
+    searchBase: ou=groups,dc=hadoop,dc=apache,dc=org
+    searchFilter: "member={0}"
+    roleAttribute: "cn"

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-rest/src/main/scripts/metron-rest.sh
----------------------------------------------------------------------
diff --git a/metron-interface/metron-rest/src/main/scripts/metron-rest.sh 
b/metron-interface/metron-rest/src/main/scripts/metron-rest.sh
index 7c89ae5..b454b8e 100644
--- a/metron-interface/metron-rest/src/main/scripts/metron-rest.sh
+++ b/metron-interface/metron-rest/src/main/scripts/metron-rest.sh
@@ -17,8 +17,8 @@
 # limitations under the License.
 #
 
-if [ -z "${METRON_JDBC_PASSWORD}" ]; then
-    echo "METRON_JDBC_PASSWORD unset. Exiting."
+if [ -z "${METRON_JDBC_PASSWORD}" ] && [ -z "${METRON_LDAP_PASSWORD}" ]; then
+    echo "Authentication password unset. Exiting."
     exit 1
 fi
 ## Join a list by a character
@@ -35,6 +35,7 @@ METRON_REST_PORT=8082
 METRON_SYSCONFIG="${METRON_SYSCONFIG:-/etc/default/metron}"
 METRON_LOG_DIR="${METRON_LOG_DIR:-/var/log/metron}"
 METRON_PID_FILE="${METRON_PID_FILE:-/var/run/metron/metron-rest.pid}"
+
 PARSER_CONTRIB=${PARSER_CONTRIB:-$METRON_HOME/parser_contrib}
 INDEXING_CONTRIB=${INDEXING_CONTRIB:-$METRON_HOME/indexing_contrib}
 PARSER_LIB=$(find $METRON_HOME/lib/ -name metron-parsers*.jar)
@@ -112,6 +113,24 @@ METRON_REST_CLASSPATH+=":${indexing_files[0]}"
 
 echo "METRON_REST_CLASSPATH=${METRON_REST_CLASSPATH}"
 
+echo "METRON_JDBC_DRIVER=${METRON_JDBC_DRIVER}"
+echo "METRON_JDBC_URL=${METRON_JDBC_URL}"
+echo "METRON_JDBC_USERNAME=${METRON_JDBC_USERNAME}"
+echo "METRON_JDBC_PLATFORM=${METRON_JDBC_PLATFORM}"
+
+echo "METRON_LDAP_URL=${METRON_LDAP_URL}"
+echo "METRON_LDAP_USERDN=${METRON_LDAP_USERDN}"
+
+echo "METRON_LDAP_USER_PATTERN=${METRON_LDAP_USER_PATTERN}"
+echo "METRON_LDAP_USER_PASSWORD=${METRON_LDAP_USER_PASSWORD}"
+echo "METRON_LDAP_USER_SEARCHBASE=${METRON_LDAP_USER_SEARCHBASE}"
+echo "METRON_LDAP_USER_SEARCHFILTER=${METRON_LDAP_USER_SEARCHFILTER}"
+
+echo "METRON_LDAP_GROUP_SEARCHBASE=${METRON_LDAP_GROUP_SEARCHBASE}"
+echo "METRON_LDAP_GROUP_SEARCHFILTER=${METRON_LDAP_GROUP_SEARCHFILTER}"
+echo "METRON_LDAP_GROUP_ROLE=${METRON_LDAP_GROUP_ROLE}"
+echo "METRON_LDAP_SSL_TRUSTSTORE=${METRON_LDAP_SSL_TRUSTSTORE}"
+
 #Use Solr daos if ra indexing writer set to Solr
 if [[ ${METRON_RA_INDEXING_WRITER} == "Solr" ]]; then
     METRON_INDEX_DAO=" 
--index.dao.impl=org.apache.metron.solr.dao.SolrDao,org.apache.metron.indexing.dao.HBaseDao"
@@ -125,6 +144,11 @@ if [[ ${METRON_RA_INDEXING_WRITER} == "Solr" ]]; then
     METRON_SPRING_OPTIONS+=${METRON_WRITER_NAME}
 fi
 
+if [ -n "${METRON_LDAP_SSL_TRUSTSTORE}" ]; then
+  METRON_JVMFLAGS+=" -Djavax.net.ssl.trustStore=${METRON_LDAP_SSL_TRUSTSTORE}"
+  METRON_JVMFLAGS+=" 
-Djavax.net.ssl.trustStorePassword=${METRON_LDAP_SSL_TRUSTSTORE_PASSWORD}"
+fi
+
 echo "Starting application"
 ${JAVA_HOME}/bin/java -Dhdp.version=${HDP_VERSION} ${METRON_JVMFLAGS} \
 -cp ${METRON_REST_CLASSPATH} \

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/Performance-tuning-guide.md
----------------------------------------------------------------------
diff --git a/metron-platform/Performance-tuning-guide.md 
b/metron-platform/Performance-tuning-guide.md
index bfc36dd..2e976e9 100644
--- a/metron-platform/Performance-tuning-guide.md
+++ b/metron-platform/Performance-tuning-guide.md
@@ -188,9 +188,11 @@ See more detail on starting parsers 
[here](https://github.com/apache/metron/blob
 
 **Enrichment**
 
+__Note__ These recommendations are based on the deprecated split-join 
enrichment topology. See [Enrichment 
Performance](metron-enrichment/Performance.md) for tuning recommendations for 
the new default unified enrichment topology.
+
 This is a mapping of the various performance tuning properties for enrichments 
and how they are materialized.
 
-Flux file found here - $METRON_HOME/flux/enrichment/remote.yaml
+Flux file found here - $METRON_HOME/flux/enrichment/remote-splitjoin.yaml
 
 _Note 1:_ Changes to Flux file properties that are managed by Ambari will 
render Ambari unable to further manage the property.
 
@@ -458,6 +460,8 @@ usage: start_parser_topology.sh
 
 ### Enrichment Tuning
 
+__Note__ These tuning suggestions are based on the deprecated split-join 
topology.
+
 We landed on the same number of partitions for enrichemnt and indexing as we 
did for bro - 48.
 
 For configuring Storm, there is a flux file and properties file that we 
modified. Here are the settings we changed for bro in Flux.

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/elasticsearch-shaded/pom.xml
----------------------------------------------------------------------
diff --git a/metron-platform/elasticsearch-shaded/pom.xml 
b/metron-platform/elasticsearch-shaded/pom.xml
index d9002e4..7766e3d 100644
--- a/metron-platform/elasticsearch-shaded/pom.xml
+++ b/metron-platform/elasticsearch-shaded/pom.xml
@@ -34,57 +34,10 @@
             <artifactId>netty-common</artifactId>
             <version>4.1.13.Final</version>
         </dependency>
-        <!--dependency>
-            <groupId>org.elasticsearch.client</groupId>
-            <artifactId>transport</artifactId>
-            <version>${global_elasticsearch_version}</version>
-            <exclusions>
-              <exclusion>
-                <groupId>com.fasterxml.jackson.dataformat</groupId>
-                <artifactId>jackson-dataformat-smile</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>com.fasterxml.jackson.dataformat</groupId>
-                <artifactId>jackson-dataformat-yaml</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>com.fasterxml.jackson.dataformat</groupId>
-                <artifactId>jackson-dataformat-cbor</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>com.fasterxml.jackson.core</groupId>
-                <artifactId>jackson-core</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>org.slf4j</groupId>
-                <artifactId>slf4j-api</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>org.slf4j</groupId>
-                <artifactId>slf4j-log4j12</artifactId>
-              </exclusion>
-              <exclusion>
-                <groupId>log4j</groupId>
-                <artifactId>log4j</artifactId>
-              </exclusion>
-            </exclusions>
-          </dependency-->
         <dependency>
             <groupId>org.elasticsearch.client</groupId>
             <artifactId>elasticsearch-rest-high-level-client</artifactId>
             <version>${global_elasticsearch_version}</version>
-            <exclusions>
-                <exclusion>
-                    <!--
-                    TODO: This shouldn't be required, but the Shade services 
resources transformer is botching the services
-                    file in META-INF.  You should not merge before figuring 
out if there's a way to avoid the
-                    botched merge.  One way to do this is to create a new 
resources transformer that handles the merge
-                    properly.  I have NO idea if excluding this matters.
-                    -->
-                    <groupId>org.elasticsearch.plugin</groupId>
-                    <artifactId>aggs-matrix-stats-client</artifactId>
-                </exclusion>
-            </exclusions>
         </dependency>
           <dependency>
             <groupId>org.apache.logging.log4j</groupId>
@@ -117,10 +70,6 @@
                     <groupId>com.fasterxml.jackson.core</groupId>
                     <artifactId>jackson-core</artifactId>
                 </exclusion>
-                <exclusion> <!-- this is causing a weird build error if not 
excluded - Error creating shaded jar: null: IllegalArgumentException -->
-                    <groupId>org.apache.logging.log4j</groupId>
-                    <artifactId>log4j-api</artifactId>
-                </exclusion>
             </exclusions>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/README.md
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/README.md 
b/metron-platform/metron-common/README.md
index dac1974..d68259a 100644
--- a/metron-platform/metron-common/README.md
+++ b/metron-platform/metron-common/README.md
@@ -79,29 +79,30 @@ This configuration is stored in zookeeper, but looks 
something like
 Various parts of our stack uses the global config are documented throughout 
the Metron documentation,
 but a convenient index is provided here:
 
-| Property Name                                                                
                                       | Subsystem     | Type       | Ambari 
Property            |
-|---------------------------------------------------------------------------------------------------------------------|---------------|------------|----------------------------|
-| [`es.clustername`](../metron-elasticsearch#esclustername)                    
                                       | Indexing      | String     | 
`es_cluster_name`          |
-| [`es.ip`](../metron-elasticsearch#esip)                                      
                                       | Indexing      | String     | 
`es_hosts`                 |
-| [`es.port`](../metron-elasticsearch#esport)                                  
                                       | Indexing      | String     | `es_port` 
                 |
-| [`es.date.format`](../metron-elasticsearch#esdateformat)                     
                                       | Indexing      | String     | 
`es_date_format`           |
-| [`fieldValidations`](#validation-framework)                                  
                                       | Parsing       | Object     | N/A       
                 |
-| [`parser.error.topic`](../metron-parsers#parsererrortopic)                   
                                       | Parsing       | String     | N/A       
                 |
-| 
[`stellar.function.paths`](../../metron-stellar/stellar-common#stellarfunctionpaths)
                                | Stellar       | CSV String | N/A              
          |
-| 
[`stellar.function.resolver.includes`](../../metron-stellar/stellar-common#stellarfunctionresolverincludesexcludes)
 | Stellar       | CSV String | N/A                        |
-| 
[`stellar.function.resolver.excludes`](../../metron-stellar/stellar-common#stellarfunctionresolverincludesexcludes)
 | Stellar       | CSV String | N/A                        |
-| 
[`profiler.period.duration`](../../metron-analytics/metron-profiler#profilerperiodduration)
                         | Profiler      | Integer    | 
`profiler_period_duration` |
-| 
[`profiler.period.duration.units`](../../metron-analytics/metron-profiler#profilerperioddurationunits)
              | Profiler      | String     | `profiler_period_units`    |
-| 
[`profiler.writer.batchSize`](../../metron-analytics/metron-profiler/#profilerwriterbatchsize)
                      | Profiler      | Integer    |  N/A                       
|
-| 
[`profiler.writer.batchTimeout`](../../metron-analytics/metron-profiler/#profilerwriterbatchtimeout)
                | Profiler      | Integer    |  N/A                       |
-| [`update.hbase.table`](../metron-indexing#updatehbasetable)                  
                                       | REST/Indexing | String     | 
`update_hbase_table`       |
-| [`update.hbase.cf`](../metron-indexing#updatehbasecf)                        
                                       | REST/Indexing | String     | 
`update_hbase_cf`          |
-| [`geo.hdfs.file`](../metron-enrichment#geohdfsfile)                          
                                       | Enrichment    | String     | 
`geo_hdfs_file`            |
-| 
[`enrichment.writer.batchSize`](../metron-enrichment#enrichmentwriterbatchsize) 
                                    | Enrichment    | Integer    |  N/A         
              |
-| 
[`enrichment.writer.batchTimeout`](../metron-enrichment#enrichmentwriterbatchtimeout)
                               | Enrichment    | Integer    |  N/A              
         |
-| [`geo.hdfs.file`](../metron-enrichment#geohdfsfile)                          
                                       | Enrichment    | String     | 
`geo_hdfs_file`            |
-| [`source.type.field`](../../metron-interface/metron-alerts#sourcetypefield)  
                                       | UI            | String     |  
`source_type_field`       |
-| 
[`threat.triage.score.field`](../../metron-interface/metron-alerts#threattriagescorefield)
                          | UI            | String     |  
`threat_triage_score_field`       |
+| Property Name                                                                
                                       | Subsystem     | Type       | Ambari 
Property              |
+|---------------------------------------------------------------------------------------------------------------------|---------------|------------|------------------------------|
+| [`es.clustername`](../metron-elasticsearch#esclustername)                    
                                       | Indexing      | String     | 
`es_cluster_name`            |
+| [`es.ip`](../metron-elasticsearch#esip)                                      
                                       | Indexing      | String     | 
`es_hosts`                   |
+| [`es.port`](../metron-elasticsearch#esport)                                  
                                       | Indexing      | String     | `es_port` 
                   |
+| [`es.date.format`](../metron-elasticsearch#esdateformat)                     
                                       | Indexing      | String     | 
`es_date_format`             |
+| [`es.client.settings`](../metron-elasticsearch#esclientsettings)             
                                       | Indexing      | Object     |  N/A      
                   |
+| [`fieldValidations`](#validation-framework)                                  
                                       | Parsing       | Object     |  N/A      
                   |
+| [`parser.error.topic`](../metron-parsers#parsererrortopic)                   
                                       | Parsing       | String     |  N/A      
                   |
+| 
[`stellar.function.paths`](../../metron-stellar/stellar-common#stellarfunctionpaths)
                                | Stellar       | CSV String |  N/A             
            |
+| 
[`stellar.function.resolver.includes`](../../metron-stellar/stellar-common#stellarfunctionresolverincludesexcludes)
 | Stellar       | CSV String |  N/A                         |
+| 
[`stellar.function.resolver.excludes`](../../metron-stellar/stellar-common#stellarfunctionresolverincludesexcludes)
 | Stellar       | CSV String |  N/A                         |
+| 
[`profiler.period.duration`](../../metron-analytics/metron-profiler#profilerperiodduration)
                         | Profiler      | Integer    | 
`profiler_period_duration`   |
+| 
[`profiler.period.duration.units`](../../metron-analytics/metron-profiler#profilerperioddurationunits)
              | Profiler      | String     | `profiler_period_units`      |
+| 
[`profiler.writer.batchSize`](../../metron-analytics/metron-profiler/#profilerwriterbatchsize)
                      | Profiler      | Integer    |  N/A                       
  |
+| 
[`profiler.writer.batchTimeout`](../../metron-analytics/metron-profiler/#profilerwriterbatchtimeout)
                | Profiler      | Integer    |  N/A                         |
+| [`update.hbase.table`](../metron-indexing#updatehbasetable)                  
                                       | REST/Indexing | String     | 
`update_hbase_table`         |
+| [`update.hbase.cf`](../metron-indexing#updatehbasecf)                        
                                       | REST/Indexing | String     | 
`update_hbase_cf`            |
+| [`geo.hdfs.file`](../metron-enrichment#geohdfsfile)                          
                                       | Enrichment    | String     | 
`geo_hdfs_file`              |
+| 
[`enrichment.writer.batchSize`](../metron-enrichment#enrichmentwriterbatchsize) 
                                    | Enrichment    | Integer    |  N/A         
                |
+| 
[`enrichment.writer.batchTimeout`](../metron-enrichment#enrichmentwriterbatchtimeout)
                               | Enrichment    | Integer    |  N/A              
           |
+| [`geo.hdfs.file`](../metron-enrichment#geohdfsfile)                          
                                       | Enrichment    | String     | 
`geo_hdfs_file`              |
+| [`source.type.field`](../../metron-interface/metron-alerts#sourcetypefield)  
                                       | UI            | String     | 
`source_type_field`          |
+| 
[`threat.triage.score.field`](../../metron-interface/metron-alerts#threattriagescorefield)
                          | UI            | String     | 
`threat_triage_score_field`  |
 
 ## Note Configs in Ambari
 If a field is managed via ambari, you should change the field via
@@ -439,3 +440,4 @@ Options:
   -p DIRECTORY, --hdp_home=DIRECTORY
                         HDP home directory
 ```
+`

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/src/main/config/zookeeper/global.json
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-common/src/main/config/zookeeper/global.json 
b/metron-platform/metron-common/src/main/config/zookeeper/global.json
index 9e5402e..b638ca3 100644
--- a/metron-platform/metron-common/src/main/config/zookeeper/global.json
+++ b/metron-platform/metron-common/src/main/config/zookeeper/global.json
@@ -6,6 +6,5 @@
   "update.hbase.table": "metron_update",
   "update.hbase.cf": "t",
   "es.client.settings": {
-    "client.transport.ping_timeout": "500s"
   }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredEnrichmentBolt.java
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredEnrichmentBolt.java
 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredEnrichmentBolt.java
index c28ca7b..2e03a36 100644
--- 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredEnrichmentBolt.java
+++ 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredEnrichmentBolt.java
@@ -17,8 +17,10 @@
  */
 package org.apache.metron.common.bolt;
 
+import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import org.apache.metron.common.configuration.EnrichmentConfigurations;
+import org.apache.metron.stellar.dsl.StellarFunctions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,4 +33,16 @@ public abstract class ConfiguredEnrichmentBolt extends 
ConfiguredBolt<Enrichment
     super(zookeeperUrl, "ENRICHMENT");
   }
 
+  @Override
+  public void cleanup() {
+    // This method may not be called in production.
+    // See 
https://storm.apache.org/releases/1.0.6/javadocs/org/apache/storm/task/IBolt.html#cleanup--
 for more detail.
+    super.cleanup();
+    try {
+      StellarFunctions.close();
+    } catch (IOException e) {
+      LOG.error(e.getMessage(), e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredParserBolt.java
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredParserBolt.java
 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredParserBolt.java
index 14ce50b..17b614b 100644
--- 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredParserBolt.java
+++ 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/bolt/ConfiguredParserBolt.java
@@ -17,9 +17,11 @@
  */
 package org.apache.metron.common.bolt;
 
+import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import org.apache.metron.common.configuration.ParserConfigurations;
 import org.apache.metron.common.configuration.SensorParserConfig;
+import org.apache.metron.stellar.dsl.StellarFunctions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,4 +38,15 @@ public abstract class ConfiguredParserBolt extends 
ConfiguredBolt<ParserConfigur
     return getConfigurations().getSensorParserConfig(sensorType);
   }
 
+  @Override
+  public void cleanup() {
+    // This method may not be called in production.
+    // See 
https://storm.apache.org/releases/1.0.6/javadocs/org/apache/storm/task/IBolt.html#cleanup--
 for more detail.
+    super.cleanup();
+    try {
+      StellarFunctions.close();
+    } catch (IOException e) {
+      LOG.error(e.getMessage(), e);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/ConfigOption.java
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/ConfigOption.java
 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/ConfigOption.java
index 6308f0a..14d5b69 100644
--- 
a/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/ConfigOption.java
+++ 
b/metron-platform/metron-common/src/main/java/org/apache/metron/common/configuration/ConfigOption.java
@@ -30,6 +30,13 @@ public interface ConfigOption {
     return (s, o) -> o;
   }
 
+  /**
+   * Returns true if the map contains the key for the defined config option
+   */
+  default boolean containsOption(Map<String, Object> map) {
+    return map.containsKey(getKey());
+  }
+
   default void put(Map<String, Object> map, Object value) {
     map.put(getKey(), value);
   }

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-common/src/main/scripts/stellar
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/scripts/stellar 
b/metron-platform/metron-common/src/main/scripts/stellar
index c831c62..690a9f0 100644
--- a/metron-platform/metron-common/src/main/scripts/stellar
+++ b/metron-platform/metron-common/src/main/scripts/stellar
@@ -28,10 +28,16 @@ elif [ -e /usr/lib/bigtop-utils/bigtop-detect-javahome ]; 
then
   . /usr/lib/bigtop-utils/bigtop-detect-javahome
 fi
 
+export METRON_SYSCONFIG="/etc/default/metron"
+if [ -f "$METRON_SYSCONFIG" ]; then
+       source $METRON_SYSCONFIG
+fi
+
+# treat unset vars as an error; METRON_HOME
+set -u
+
 export HBASE_CONFIGS=$(hbase classpath)
-export METRON_VERSION=${project.version}
-export METRON_HOME=/usr/metron/$METRON_VERSION
 export STELLAR_LIB=$(find $METRON_HOME/lib/ -name metron-parsers*.jar)
 export MANAGEMENT_LIB=$(find $METRON_HOME/lib/ -name metron-management*.jar)
 export PROFILER_LIB=$(find $METRON_HOME/lib/ -name metron-profiler-repl*.jar)
-java $JVMFLAGS -cp 
"${CONTRIB:-$METRON_HOME/contrib/*}:$STELLAR_LIB:$MANAGEMENT_LIB:$PROFILER_LIB:$HBASE_CONFIGS"
 org.apache.metron.stellar.common.shell.cli.StellarShell "$@"
+java $METRON_JVMFLAGS -cp 
"${CONTRIB:-$METRON_HOME/contrib/*}:$STELLAR_LIB:$MANAGEMENT_LIB:$PROFILER_LIB:$HBASE_CONFIGS"
 org.apache.metron.stellar.common.shell.cli.StellarShell "$@"

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-data-management/pom.xml
----------------------------------------------------------------------
diff --git a/metron-platform/metron-data-management/pom.xml 
b/metron-platform/metron-data-management/pom.xml
index c4dbb74..a7a5a40 100644
--- a/metron-platform/metron-data-management/pom.xml
+++ b/metron-platform/metron-data-management/pom.xml
@@ -26,7 +26,6 @@
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <httpcore.version>4.3.2</httpcore.version>
         <lucene.test.version>5.5.0</lucene.test.version>
     </properties>
 
@@ -221,12 +220,12 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpcore</artifactId>
-            <version>${httpcore.version}</version>
+            <version>${global_httpclient_version}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>${httpcore.version}</version>
+            <version>${global_httpclient_version}</version>
         </dependency>
         <dependency>
             <groupId>org.hamcrest</groupId>

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-elasticsearch/README.md
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/README.md 
b/metron-platform/metron-elasticsearch/README.md
index d889e27..463a0b8 100644
--- a/metron-platform/metron-elasticsearch/README.md
+++ b/metron-platform/metron-elasticsearch/README.md
@@ -59,6 +59,49 @@ For instance, an `es.date.format` of `yyyy.MM.dd.HH` would 
have the consequence
 roll hourly, whereas an `es.date.format` of `yyyy.MM.dd` would have the 
consequence that the indices would
 roll daily.
 
+### `es.client.settings`
+
+This field in global config allows you to specify Elasticsearch REST client 
options. These are used in conjunction with the previously mentioned 
Elasticsearch properties
+when setting up client connections to an Elasticsearch cluster. The available 
properties should be supplied as an object map. Current available options are 
as follows:
+
+| Property Name                       | Type      | Required? | Default Value  
| Description                                                                   
                                                                                
      |
+|-------------------------------------|-----------|-----------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| connection.timeout.millis           | Integer   | No        | 1000           
| Sets connection timeout.                                                      
                                                                                
      |
+| socket.timeout.millis               | Integer   | No        | 30000          
| Sets socket timeout.                                                          
                                                                                
      |
+| max.retry.timeout.millis            | Integer   | No        | 30000          
| Sets the maximum timeout (in milliseconds) to honour in case of multiple 
retries of the same request.                                                    
           |
+| num.client.connection.threads       | Integer   | No        | 1              
| Number of worker threads used by the connection manager. Defaults to 
Runtime.getRuntime().availableProcessors().                                     
               |
+| xpack.username                      | String    | No        | null           
| X-Pack username.                                                              
                                                                                
      |
+| xpack.password.file                 | String    | No        | null           
| 1-line HDFS file where the X-Pack password is set.                            
                                                                                
      |
+| ssl.enabled                         | Boolean   | No        | false          
| Turn on SSL connections.                                                      
                                                                                
      |
+| keystore.type                       | String    | No        | "jks"          
| Allows you to specify a keytstore type. See 
https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
 for more details.           |
+| keystore.path                       | String    | No        | null           
| Path to the Trust Store that holds your Elasticsearch certificate authorities 
and certificate.                                                                
      |
+| keystore.password.file              | String    | No        | null           
| 1-line HDFS file where the keystore password is set.                          
                                                                                
      |
+
+__Note:__ The migration from Elasticsearch's TransportClient to the Java REST 
client has resulted in some existing properties to change. Below is a mapping 
of the old properties to the new ones:
+
+| Old Property Name                      | New Property Name                   
|
+|----------------------------------------|-------------------------------------|
+| client.transport.ping_timeout          | n/a                                 
|
+| n/a                                    | connection.timeout.millis           
|
+| n/a                                    | socket.timeout.millis               
|
+| n/a                                    | max.retry.timeout.millis            
|
+| n/a                                    | num.client.connection.threads       
|
+| es.client.class                        | n/a                                 
|
+| es.xpack.username                      | xpack.username                      
|
+| es.xpack.password.file                 | xpack.password.file                 
|
+| xpack.security.transport.ssl.enabled   | ssl.enabled                         
|
+| xpack.ssl.key                          | n/a                                 
|
+| xpack.ssl.certificate                  | n/a                                 
|
+| xpack.ssl.certificate_authorities      | n/a                                 
|
+| n/a                                    | keystore.type                       
|
+| keystore.path                          | keystore.path                       
|
+| n/a                                    | keystore.password.file              
|
+
+__Notes:__
+* The transport client implementation provides for a 'xpack.security.user' 
property, however we never used this property directly. Rather, in order to 
secure the password we used custom properties for user/pass. These properties 
have been carried over as `xpack.username` and `xpack.password.file`.
+* See 
[https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/_common_configuration.html](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/_common_configuration.html)
 for more specifics on the new client properties.
+* Other notes on JSSE - 
[https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)
+
 ## Upgrading to 5.6.2
 
 Users should be prepared to re-index when migrating from Elasticsearch 2.3.3 
to 5.6.2. There are a number of template changes, most notably around
@@ -269,9 +312,27 @@ Notes on other settings for types in ES
 * 
[https://www.elastic.co/guide/en/elasticsearch/reference/5.6/breaking_50_mapping_changes.html](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/breaking_50_mapping_changes.html)
 * 
[https://www.elastic.co/blog/strings-are-dead-long-live-strings](https://www.elastic.co/blog/strings-are-dead-long-live-strings)
 
+### Metron Properties
+
+Metron depends on some internal fields being defined in sensor templates.  A 
field is defined in Elasticsearch by adding an entry to the `properties` 
section of the template:
+```
+"properties": {
+  "metron_field": {
+    "type": "keyword"
+  }
+}
+```
+
+The following is a list of properties that need to be defined along with their 
type:
+* source:type - keyword
+* alert_status - keyword
+* metron_alert - nested
+
 ## Using Metron with Elasticsearch 5.6.2
 
-There is a requirement that all sensors templates have a nested `metron_alert` 
field defined.  This field is a dummy field.  See [Ignoring Unmapped 
Fields](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#_ignoring_unmapped_fields)
 for more information
+Although infrequent, sometimes an internal field is added in Metron and 
existing templates must be updated.  The following steps outlines how to do 
this, using `metron_alert` as an example.
+
+With the addition of the meta alert feature, there is a requirement that all 
sensors templates have a nested `metron_alert` field defined.  This field is a 
dummy field.  See [Ignoring Unmapped 
Fields](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html#_ignoring_unmapped_fields)
 for more information
 
 Without this field, an error will be thrown during ALL searches (including 
from UIs, resulting in no alerts being found for any sensor). This error will 
be found in the REST service's logs.
 

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-elasticsearch/pom.xml
----------------------------------------------------------------------
diff --git a/metron-platform/metron-elasticsearch/pom.xml 
b/metron-platform/metron-elasticsearch/pom.xml
index 593e80b..e3cf840 100644
--- a/metron-platform/metron-elasticsearch/pom.xml
+++ b/metron-platform/metron-elasticsearch/pom.xml
@@ -217,34 +217,6 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-api</artifactId>
-            <version>2.8.2</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
-            <version>2.8.2</version>
-            <scope>test</scope>
-        </dependency>
-        <!--dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-api</artifactId>
-            <version>${global_log4j_core_version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
-            <version>${global_log4j_core_version}</version>
-        </dependency-->
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava-testlib</artifactId>
-            <version>${global_guava_version}</version>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <build>
@@ -306,7 +278,6 @@
                                 </excludes>
                             </artifactSet>
                             <transformers>
-                                <transformer 
implementation="org.atteo.classindex.ClassIndexTransformer"/>
                                 <transformer
                                   
implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
                                      <resources>
@@ -320,13 +291,15 @@
                                 <!--transformer 
implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
                                     <addHeader>false</addHeader>
                                     <projectName>${project.name}</projectName>
-                                </transformer>
+                                </transformer>-->
                                 <transformer
-                                        
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/-->
+                                        
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                                 <transformer
                                         
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                     <mainClass></mainClass>
                                 </transformer>
+                                <!-- ClassIndexTransformer needs to go LAST. 
For some reason it will clobber other transformers from operating when it is 
put first -->
+                                <transformer 
implementation="org.atteo.classindex.ClassIndexTransformer"/>
                             </transformers>
                         </configuration>
                     </execution>

http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/client/ElasticsearchClient.java
----------------------------------------------------------------------
diff --git 
a/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/client/ElasticsearchClient.java
 
b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/client/ElasticsearchClient.java
new file mode 100644
index 0000000..d62a7c0
--- /dev/null
+++ 
b/metron-platform/metron-elasticsearch/src/main/java/org/apache/metron/elasticsearch/client/ElasticsearchClient.java
@@ -0,0 +1,245 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.metron.elasticsearch.client;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.metron.common.utils.JSONUtils;
+import org.apache.metron.elasticsearch.utils.FieldMapping;
+import org.apache.metron.elasticsearch.utils.FieldProperties;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestHighLevelClient;
+
+/**
+ * Wrapper around the Elasticsearch REST clients. Exposes capabilities of the 
low and high-level clients.
+ * 
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/java-rest-overview.html.
 Most, if not
+ * all of use in Metron would be focused through the high-level client. It 
handles marshaling/unmarshaling.
+ */
+public class ElasticsearchClient implements AutoCloseable{
+  private RestClient lowLevelClient;
+  private RestHighLevelClient highLevelClient;
+
+  /**
+   * Instantiate with ElasticsearchClientFactory.
+   *
+   * @param lowLevelClient
+   * @param highLevelClient
+   */
+  public ElasticsearchClient(RestClient lowLevelClient, RestHighLevelClient 
highLevelClient) {
+    this.lowLevelClient = lowLevelClient;
+    this.highLevelClient = highLevelClient;
+  }
+
+  /**
+   * Exposes an Elasticsearch low-level client. Prefer the high level client.
+   */
+  public RestClient getLowLevelClient() {
+    return lowLevelClient;
+  }
+
+  /**
+   * <p>
+   * Exposes an Elasticsearch high-level client. Prefer to use this client 
over the low-level client where possible. This client wraps the low-level
+   * client and exposes some additional sugar on top of the low level methods 
including marshaling/unmarshaling.
+   * </p>
+   * <p>
+   *   Note, as of 5.6.2 it does NOT support index or cluster management 
operations.
+   *   
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/_changing_the_application_8217_s_code.html
+   *   <br>
+   * &nbsp;&nbsp;&nbsp;&nbsp;<i>Does not provide indices or cluster management 
APIs. Management operations can be executed by external scripts or using the 
low-level client.</i>
+   * </p>
+   * <p>
+   * Current supported ES API's seen here - 
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/java-rest-high-supported-apis.html
+   * </p>
+   *
+   * <ul>
+   *   <li>Single document APIs
+   *     <ul>
+   *       <li>Index API</li>
+   *       <li>Get API</li>
+   *       <li>Delete API</li>
+   *       <li>Update API</li>
+   *     </ul>
+   *   </li>
+   *   <li>Multi document APIs
+   *     <ul>
+   *       <li>Bulk API</li>
+   *     </ul>
+   *   </li>
+   *   <li>Search APIs
+   *     <ul>
+   *       <li>Search API</li>
+   *       <li>Search Scroll API</li>
+   *       <li>Clear Scroll API</li>
+   *     </ul>
+   *   </li>
+   *   <li>Miscellaneous APIs
+   *     <ul>
+   *       <li>Info API</li>
+   *     </ul>
+   *   </li>
+   * </ul>
+   */
+  public RestHighLevelClient getHighLevelClient() {
+    return highLevelClient;
+  }
+
+  /**
+   * Included as part of AutoCloseable because Elasticsearch recommends 
closing the client when not
+   * being used.
+   * 
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/5.6/_changing_the_client_8217_s_initialization_code.html
+   * @throws IOException
+   */
+  @Override
+  public void close() throws IOException {
+    if (lowLevelClient != null) {
+      lowLevelClient.close();
+    }
+  }
+
+  /**
+   * 
https://www.elastic.co/guide/en/elasticsearch/reference/5.6/indices-put-mapping.html
+   * @param index
+   * @param mappingType 
https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping.html#mapping-type
+   * @param mapping
+   * @throws IOException
+   */
+  public void putMapping(String index, String mappingType, String mapping) 
throws IOException {
+    HttpEntity entity = new StringEntity(mapping);
+    Response response = lowLevelClient.performRequest("PUT"
+            , "/" + index + "/_mapping/" + mappingType
+            , Collections.emptyMap()
+            , entity
+    );
+
+    if(response.getStatusLine().getStatusCode() != 200) {
+      String responseStr = IOUtils.toString(response.getEntity().getContent());
+      throw new IllegalStateException("Got a " + 
response.getStatusLine().getStatusCode() + " due to " + responseStr);
+    }
+  }
+
+  /**
+   * Gets ALL Elasticsearch indices, or null if status code returned is not OK 
200.
+   */
+  public String[] getIndices() throws IOException {
+    Response response = lowLevelClient.performRequest("GET", "/_cat/indices");
+    if(response.getStatusLine().getStatusCode() == 200) {
+      String responseStr = IOUtils.toString(response.getEntity().getContent());
+      List<String> indices = new ArrayList<>();
+      for(String line : Splitter.on("\n").split(responseStr)) {
+        Iterable<String> splits = Splitter.on(" 
").split(line.replaceAll("\\s+", " ").trim());
+        if(Iterables.size(splits) > 3) {
+          String index = Iterables.get(splits, 2, "");
+          if(!StringUtils.isEmpty(index)) {
+            indices.add(index.trim());
+          }
+        }
+      }
+      String[] ret = new String[indices.size()];
+      ret=indices.toArray(ret);
+      return ret;
+    }
+    return null;
+  }
+
+  /**
+   * Gets FieldMapping detail for a list of indices.
+   *
+   * @param indices get field mapppings for the provided indices
+   * @return mapping of index name to FieldMapping
+   */
+  public Map<String, FieldMapping> getMappingByIndex(String[] indices) throws 
IOException {
+    Map<String, FieldMapping> ret = new HashMap<>();
+    String indicesCsv = Joiner.on(",").join(indices);
+    Response response = lowLevelClient.performRequest("GET", "/" + indicesCsv 
+ "/_mapping");
+    if(response.getStatusLine().getStatusCode() == 200) {
+      String responseStr = IOUtils.toString(response.getEntity().getContent());
+      Map<String, Object> indexToMapping = 
JSONUtils.INSTANCE.load(responseStr, JSONUtils.MAP_SUPPLIER);
+      for(Map.Entry<String, Object> index2Mapping : indexToMapping.entrySet()) 
{
+        String index = index2Mapping.getKey();
+        Map<String, Object> mappings = getInnerMap((Map<String, 
Object>)index2Mapping.getValue(), "mappings");
+        if(mappings.size() > 0) {
+          Map.Entry<String, Object> docMap = 
Iterables.getFirst(mappings.entrySet(), null);
+          if(docMap != null) {
+            Map<String, Object> fieldPropertiesMap = getInnerMap((Map<String, 
Object>)docMap.getValue(), "properties");
+            if(fieldPropertiesMap != null) {
+              FieldMapping mapping = new FieldMapping();
+              for (Map.Entry<String, Object> field2PropsKV : 
fieldPropertiesMap.entrySet()) {
+                if(field2PropsKV.getValue() != null) {
+                  FieldProperties props = new FieldProperties((Map<String, 
Object>) field2PropsKV.getValue());
+                  mapping.put(field2PropsKV.getKey(), props);
+                }
+              }
+              ret.put(index, mapping);
+            }
+          }
+        }
+      }
+    }
+    return ret;
+  }
+
+  /**
+   * Traverses the outer map to retrieve a leaf map by iteratively calling 
get(key) using the provided keys in order. e.g.
+   * for an outer map provided as follows:
+   * <pre>
+   * {
+   *   "foo" : {
+   *     "bar" : {
+   *       "baz" : {
+   *         "hello" : "world"
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   * calling getInnerMap(outerMap, new String[] { "foo", "bar", "baz" }) would 
return the following:
+   * <pre>
+   * {hello=world}
+   * </pre>
+   * @param outerMap Complex map of nested keys/values
+   * @param keys ordered list of keys to iterate over to grab a leaf mapping.
+   * @return leaf node, or innermost matching node from outerMap if no leaf 
exists
+   */
+  private Map<String, Object> getInnerMap(Map<String, Object> outerMap, 
String... keys) {
+    Map<String, Object> ret = outerMap;
+    if(keys.length == 0) {
+      return outerMap;
+    }
+    for(String key : keys) {
+      ret = (Map<String, Object>)ret.get(key);
+      if(ret == null) {
+        return ret;
+      }
+    }
+    return ret;
+  }
+
+}

Reply via email to