You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by mr...@apache.org on 2017/11/27 23:29:29 UTC

[17/30] ambari git commit: Merge trunk with feature branch and fix some UT compilation issues (mradhakrishnan)

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
index e187b00..a715adc 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts
@@ -17,9 +17,17 @@
  */
 
 import {Injectable} from '@angular/core';
+import {FormGroup, FormControl} from '@angular/forms';
 import {Response} from '@angular/http';
+import {Subject} from 'rxjs/Subject';
+import {Observable} from 'rxjs/Observable';
+import 'rxjs/add/observable/timer';
+import 'rxjs/add/observable/combineLatest';
+import 'rxjs/add/operator/first';
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/takeUntil';
+import * as moment from 'moment-timezone';
 import {HttpClientService} from '@app/services/http-client.service';
-import {FilteringService} from '@app/services/filtering.service';
 import {AuditLogsService} from '@app/services/storage/audit-logs.service';
 import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service';
 import {ServiceLogsService} from '@app/services/storage/service-logs.service';
@@ -27,30 +35,437 @@ import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-field
 import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service';
 import {ServiceLogsTruncatedService} from '@app/services/storage/service-logs-truncated.service';
 import {AppStateService} from '@app/services/storage/app-state.service';
+import {AppSettingsService} from '@app/services/storage/app-settings.service';
 import {TabsService} from '@app/services/storage/tabs.service';
+import {ClustersService} from '@app/services/storage/clusters.service';
+import {ComponentsService} from '@app/services/storage/components.service';
+import {HostsService} from '@app/services/storage/hosts.service';
 import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry';
+import {FilterCondition, TimeUnitListItem, SortingListItem} from '@app/classes/filtering';
+import {ListItem} from '@app/classes/list-item';
 import {Tab} from '@app/classes/models/tab';
+import {LogField} from '@app/classes/models/log-field';
+import {AuditLog} from '@app/classes/models/audit-log';
 import {AuditLogField} from '@app/classes/models/audit-log-field';
+import {ServiceLog} from '@app/classes/models/service-log';
 import {ServiceLogField} from '@app/classes/models/service-log-field';
 import {BarGraph} from '@app/classes/models/bar-graph';
+import {NodeItem} from '@app/classes/models/node-item';
 
 @Injectable()
 export class LogsContainerService {
 
-  constructor(private httpClient: HttpClientService, private auditLogsStorage: AuditLogsService, private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsStorage: ServiceLogsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService, private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private appState: AppStateService, private tabsStorage: TabsService, private filtering: FilteringService) {
+  constructor(
+    private httpClient: HttpClientService, private auditLogsStorage: AuditLogsService,
+    private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsStorage: ServiceLogsService,
+    private serviceLogsFieldsStorage: ServiceLogsFieldsService,
+    private serviceLogsHistogramStorage: ServiceLogsHistogramDataService,
+    private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private appState: AppStateService,
+    private appSettings: AppSettingsService, private tabsStorage: TabsService, private clustersStorage: ClustersService,
+    private componentsStorage: ComponentsService, private hostsStorage: HostsService
+  ) {
+    const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): {[key: string]: FormControl} => {
+      let formControl = new FormControl(),
+        item = {
+          [key]: formControl
+        };
+      formControl.setValue(this.filters[key].defaultSelection);
+      return Object.assign(currentObject, item);
+    }, {});
+    this.filtersForm = new FormGroup(formItems);
+    this.loadClusters();
+    this.loadComponents();
+    this.loadHosts();
     appState.getParameter('activeLog').subscribe((value: ActiveServiceLogEntry | null) => this.activeLog = value);
-    appState.getParameter('isServiceLogsFileView').subscribe((value: boolean): void => {
-      const activeLog = this.activeLog,
-        filtersForm = this.filtering.activeFiltersForm;
-      if (value && activeLog) {
-        filtersForm.controls.hosts.setValue(activeLog.host_name);
-        filtersForm.controls.components.setValue(activeLog.component_name);
-      }
-      this.isServiceLogsFileView = value;
-    });
+    appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value);
     appState.getParameter('activeLogsType').subscribe((value: string) => this.activeLogsType = value);
+    appSettings.getParameter('timeZone').subscribe((value: string) => this.timeZone = value || this.defaultTimeZone);
+    tabsStorage.mapCollection((tab: Tab): Tab => {
+      let currentAppState = tab.appState || {};
+      const appState = Object.assign({}, currentAppState, {
+        activeFilters: this.getFiltersData(tab.type)
+      });
+      return Object.assign({}, tab, {
+        appState
+      });
+    });
+    appState.getParameter('activeFilters').subscribe((filters: object): void => {
+      this.filtersFormChange.next();
+      if (filters) {
+        const controls = this.filtersForm.controls;
+        Object.keys(controls).forEach((key: string): void => {
+          controls[key].setValue(filters.hasOwnProperty(key) ? filters[key] : null);
+        });
+      }
+      this.loadLogs();
+      this.filtersForm.valueChanges.takeUntil(this.filtersFormChange).subscribe((value: object): void => {
+        this.tabsStorage.mapCollection((tab: Tab): Tab => {
+          const currentAppState = tab.appState || {},
+            appState = Object.assign({}, currentAppState, tab.isActive ? {
+              activeFilters: value
+            } : null);
+          return Object.assign({}, tab, {
+            appState
+          });
+        });
+        this.loadLogs();
+      });
+    });
   }
 
+  private readonly paginationOptions: string[] = ['10', '25', '50', '100'];
+
+  filters: {[key: string]: FilterCondition} = {
+    clusters: {
+      label: 'filter.clusters',
+      options: [],
+      defaultSelection: []
+    },
+    timeRange: {
+      options: [
+        [
+          {
+            label: 'filter.timeRange.7d',
+            value: {
+              type: 'LAST',
+              unit: 'd',
+              interval: 7
+            }
+          },
+          {
+            label: 'filter.timeRange.30d',
+            value: {
+              type: 'LAST',
+              unit: 'd',
+              interval: 30
+            }
+          },
+          {
+            label: 'filter.timeRange.60d',
+            value: {
+              type: 'LAST',
+              unit: 'd',
+              interval: 60
+            }
+          },
+          {
+            label: 'filter.timeRange.90d',
+            value: {
+              type: 'LAST',
+              unit: 'd',
+              interval: 90
+            }
+          },
+          {
+            label: 'filter.timeRange.6m',
+            value: {
+              type: 'LAST',
+              unit: 'M',
+              interval: 6
+            }
+          },
+          {
+            label: 'filter.timeRange.1y',
+            value: {
+              type: 'LAST',
+              unit: 'y',
+              interval: 1
+            }
+          },
+          {
+            label: 'filter.timeRange.2y',
+            value: {
+              type: 'LAST',
+              unit: 'y',
+              interval: 2
+            }
+          },
+          {
+            label: 'filter.timeRange.5y',
+            value: {
+              type: 'LAST',
+              unit: 'y',
+              interval: 5
+            }
+          }
+        ],
+        [
+          {
+            label: 'filter.timeRange.yesterday',
+            value: {
+              type: 'PAST',
+              unit: 'd'
+            }
+          },
+          // TODO implement time range calculation
+          /*
+           {
+           label: 'filter.timeRange.beforeYesterday',
+           value: {
+           type: 'PAST',
+           unit: 'd'
+           }
+           },
+           {
+           label: 'filter.timeRange.thisDayLastWeek',
+           value: {
+           type: 'PAST',
+           unit: 'd'
+           }
+           },
+           */
+          {
+            label: 'filter.timeRange.previousWeek',
+            value: {
+              type: 'PAST',
+              unit: 'w'
+            }
+          },
+          {
+            label: 'filter.timeRange.previousMonth',
+            value: {
+              type: 'PAST',
+              unit: 'M'
+            }
+          },
+          {
+            label: 'filter.timeRange.previousYear',
+            value: {
+              type: 'PAST',
+              unit: 'y'
+            }
+          }
+        ],
+        [
+          {
+            label: 'filter.timeRange.today',
+            value: {
+              type: 'CURRENT',
+              unit: 'd'
+            }
+          },
+          {
+            label: 'filter.timeRange.thisWeek',
+            value: {
+              type: 'CURRENT',
+              unit: 'w'
+            }
+          },
+          {
+            label: 'filter.timeRange.thisMonth',
+            value: {
+              type: 'CURRENT',
+              unit: 'M'
+            }
+          },
+          {
+            label: 'filter.timeRange.thisYear',
+            value: {
+              type: 'CURRENT',
+              unit: 'y'
+            }
+          }
+        ],
+        [
+          {
+            label: 'filter.timeRange.5min',
+            value: {
+              type: 'LAST',
+              unit: 'm',
+              interval: 5
+            }
+          },
+          {
+            label: 'filter.timeRange.15min',
+            value: {
+              type: 'LAST',
+              unit: 'm',
+              interval: 15
+            }
+          },
+          {
+            label: 'filter.timeRange.30min',
+            value: {
+              type: 'LAST',
+              unit: 'm',
+              interval: 30
+            }
+          },
+          {
+            label: 'filter.timeRange.1hr',
+            value: {
+              type: 'LAST',
+              unit: 'h',
+              interval: 1
+            }
+          },
+          {
+            label: 'filter.timeRange.3hr',
+            value: {
+              type: 'LAST',
+              unit: 'h',
+              interval: 3
+            }
+          },
+          {
+            label: 'filter.timeRange.6hr',
+            value: {
+              type: 'LAST',
+              unit: 'h',
+              interval: 6
+            }
+          },
+          {
+            label: 'filter.timeRange.12hr',
+            value: {
+              type: 'LAST',
+              unit: 'h',
+              interval: 12
+            }
+          },
+          {
+            label: 'filter.timeRange.24hr',
+            value: {
+              type: 'LAST',
+              unit: 'h',
+              interval: 24
+            }
+          },
+        ]
+      ],
+      defaultSelection: {
+        value: {
+          type: 'LAST',
+          unit: 'h',
+          interval: 1
+        },
+        label: 'filter.timeRange.1hr'
+      }
+    },
+    components: {
+      label: 'filter.components',
+      iconClass: 'fa fa-cubes',
+      options: [],
+      defaultSelection: []
+    },
+    levels: {
+      label: 'filter.levels',
+      iconClass: 'fa fa-sort-amount-asc',
+      options: [
+        {
+          label: 'levels.fatal',
+          value: 'FATAL'
+        },
+        {
+          label: 'levels.error',
+          value: 'ERROR'
+        },
+        {
+          label: 'levels.warn',
+          value: 'WARN'
+        },
+        {
+          label: 'levels.info',
+          value: 'INFO'
+        },
+        {
+          label: 'levels.debug',
+          value: 'DEBUG'
+        },
+        {
+          label: 'levels.trace',
+          value: 'TRACE'
+        },
+        {
+          label: 'levels.unknown',
+          value: 'UNKNOWN'
+        }
+      ],
+      defaultSelection: []
+    },
+    hosts: {
+      label: 'filter.hosts',
+      iconClass: 'fa fa-server',
+      options: [],
+      defaultSelection: []
+    },
+    auditLogsSorting: {
+      label: 'sorting.title',
+      options: [
+        {
+          label: 'sorting.time.asc',
+          value: {
+            key: 'evtTime',
+            type: 'asc'
+          }
+        },
+        {
+          label: 'sorting.time.desc',
+          value: {
+            key: 'evtTime',
+            type: 'desc'
+          }
+        }
+      ],
+      defaultSelection: [
+        {
+          label: 'sorting.time.desc',
+          value: {
+            key: 'evtTime',
+            type: 'desc'
+          }
+        }
+      ]
+    },
+    serviceLogsSorting: {
+      label: 'sorting.title',
+      options: [
+        {
+          label: 'sorting.time.asc',
+          value: {
+            key: 'logtime',
+            type: 'asc'
+          }
+        },
+        {
+          label: 'sorting.time.desc',
+          value: {
+            key: 'logtime',
+            type: 'desc'
+          }
+        }
+      ],
+      defaultSelection: [
+        {
+          label: 'sorting.time.desc',
+          value: {
+            key: 'logtime',
+            type: 'desc'
+          }
+        }
+      ]
+    },
+    pageSize: {
+      label: 'pagination.title',
+      options: this.paginationOptions.map((option: string): ListItem => {
+        return {
+          label: option,
+          value: option
+        }
+      }),
+      defaultSelection: [
+        {
+          label: '10',
+          value: '10'
+        }
+      ]
+    },
+    page: {
+      defaultSelection: 0
+    },
+    query: {}
+  };
+
   readonly colors = {
     WARN: '#FF8916',
     ERROR: '#E81D1D',
@@ -61,13 +476,14 @@ export class LogsContainerService {
     UNKNOWN: '#BDBDBD'
   };
 
-  private readonly listFilters = {
+  private readonly filtersMapping = {
     clusters: ['clusters'],
     timeRange: ['to', 'from'],
     components: ['mustBe'],
     levels: ['level'],
     hosts: ['hostList'],
-    sorting: ['sortType', 'sortBy'],
+    auditLogsSorting: ['sortType', 'sortBy'],
+    serviceLogsSorting: ['sortType', 'sortBy'],
     pageSize: ['pageSize'],
     page: ['page'],
     query: ['includeQuery', 'excludeQuery']
@@ -85,22 +501,116 @@ export class LogsContainerService {
   readonly logsTypeMap = {
     auditLogs: {
       logsModel: this.auditLogsStorage,
-      fieldsModel: this.auditLogsFieldsStorage
+      fieldsModel: this.auditLogsFieldsStorage,
+      // TODO add all the required fields
+      listFilters: ['clusters', 'timeRange', 'auditLogsSorting', 'pageSize', 'page', 'query'],
+      histogramFilters: ['clusters', 'timeRange', 'query']
     },
     serviceLogs: {
       logsModel: this.serviceLogsStorage,
-      fieldsModel: this.serviceLogsFieldsStorage
+      fieldsModel: this.serviceLogsFieldsStorage,
+      listFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query'],
+      histogramFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query']
     }
   };
 
+  private readonly defaultTimeZone = moment.tz.guess();
+
+  timeZone: string = this.defaultTimeZone;
+
   totalCount: number = 0;
 
+  /**
+   * A configurable property to indicate the maximum capture time in milliseconds.
+   * @type {number}
+   * @default 600000 (10 minutes)
+   */
+  private readonly maximumCaptureTimeLimit: number = 600000;
+
   isServiceLogsFileView: boolean = false;
 
+  filtersForm: FormGroup;
+
   activeLog: ActiveServiceLogEntry | null = null;
 
   activeLogsType: string;
 
+  private filtersFormChange: Subject<any> = new Subject();
+
+  private columnsMapper<FieldT extends LogField>(fields: FieldT[]): ListItem[] {
+    return fields.filter((field: FieldT): boolean => field.isAvailable).map((field: FieldT): ListItem => {
+      return {
+        value: field.name,
+        label: field.displayName || field.name,
+        isChecked: field.isDisplayed
+      };
+    });
+  }
+
+  private logsMapper<LogT extends AuditLog & ServiceLog>(result: [LogT[], ListItem[]]): LogT[] {
+    const [logs, fields] = result;
+    if (fields.length) {
+      const names = fields.map((field: ListItem): string => field.value);
+      return logs.map((log: LogT): LogT => {
+        return names.reduce((currentObject: object, key: string) => Object.assign(currentObject, {
+          [key]: log[key]
+        }), {}) as LogT;
+      });
+    } else {
+      return [];
+    }
+  }
+
+  auditLogsColumns: Observable<ListItem[]> = this.auditLogsFieldsStorage.getAll().map(this.columnsMapper);
+
+  serviceLogsColumns: Observable<ListItem[]> = this.serviceLogsFieldsStorage.getAll().map(this.columnsMapper);
+
+  serviceLogs: Observable<ServiceLog[]> = Observable.combineLatest(this.serviceLogsStorage.getAll(), this.serviceLogsColumns).map(this.logsMapper);
+
+  auditLogs: Observable<AuditLog[]> = Observable.combineLatest(this.auditLogsStorage.getAll(), this.auditLogsColumns).map(this.logsMapper);
+
+  /**
+   * Get instance for dropdown list from string
+   * @param name {string}
+   * @returns {ListItem}
+   */
+  private getListItemFromString(name: string): ListItem {
+    return {
+      label: name,
+      value: name
+    };
+  }
+
+  /**
+   * Get instance for dropdown list from NodeItem object
+   * @param node {NodeItem}
+   * @returns {ListItem}
+   */
+  private getListItemFromNode(node: NodeItem): ListItem {
+    return {
+      label: `${node.name} (${node.value})`,
+      value: node.name
+    };
+  }
+
+  queryParameterNameChange: Subject<any> = new Subject();
+
+  queryParameterAdd: Subject<any> = new Subject();
+
+  private stopTimer: Subject<any> = new Subject();
+
+  private stopAutoRefreshCountdown: Subject<any> = new Subject();
+
+  captureSeconds: number = 0;
+
+  private readonly autoRefreshInterval: number = 30000;
+
+  autoRefreshRemainingSeconds: number = 0;
+
+  private startCaptureTime: number;
+
+  private stopCaptureTime: number;
+
   loadLogs = (logsType: string = this.activeLogsType): void => {
     this.httpClient.get(logsType, this.getParams('listFilters')).subscribe((response: Response): void => {
       const jsonResponse = response.json(),
@@ -128,7 +638,7 @@ export class LogsContainerService {
         }
       });
     }
-  }
+  };
 
   loadLogContext(id: string, hostName: string, componentName: string, scrollType: 'before' | 'after' | '' = ''): void {
     const params = {
@@ -161,22 +671,18 @@ export class LogsContainerService {
     });
   }
 
-  private getParams(filtersMapName: string): any {
+  private getParams(filtersMapName: string, logsType: string = this.activeLogsType): {[key: string]: string} {
     let params = {};
-    Object.keys(this[filtersMapName]).forEach((key: string): void => {
-      const inputValue = this.filtering.activeFiltersForm.getRawValue()[key],
-        paramNames = this[filtersMapName][key];
-      paramNames.forEach(paramName => {
+    this.logsTypeMap[logsType][filtersMapName].forEach((key: string): void => {
+      const inputValue = this.filtersForm.getRawValue()[key],
+        paramNames = this.filtersMapping[key];
+      paramNames.forEach((paramName: string): void => {
         let value;
-        const valueGetter = this.filtering.valueGetters[paramName];
-        if (valueGetter) {
-          if (paramName === 'from') {
-            value = valueGetter(inputValue, params['to']);
-          } else {
-            value = valueGetter(inputValue);
-          }
+        const valueGetter = this.valueGetters[paramName] || this.defaultValueGetter;
+        if (paramName === 'from') {
+          value = valueGetter(inputValue, params['to']);
         } else {
-          value = inputValue;
+          value = valueGetter(inputValue);
         }
         if (value != null && value !== '') {
           params[paramName] = value;
@@ -222,12 +728,207 @@ export class LogsContainerService {
     return Object.keys(keysObject).map((key: string): {fieldClass} => new fieldClass(key));
   }
 
+  getStartTimeMoment = (selection: TimeUnitListItem, end: moment.Moment): moment.Moment | undefined => {
+    let time;
+    const value = selection && selection.value;
+    if (value) {
+      const endTime = end.clone();
+      switch (value.type) {
+        case 'LAST':
+          time = endTime.subtract(value.interval, value.unit);
+          break;
+        case 'CURRENT':
+          time = moment().tz(this.timeZone).startOf(value.unit);
+          break;
+        case 'PAST':
+          time = endTime.startOf(value.unit);
+          break;
+        case 'CUSTOM':
+          time = value.start;
+          break;
+        default:
+          break;
+      }
+    }
+    return time;
+  };
+
+  private getStartTime = (selection: TimeUnitListItem, current: string): string => {
+    const startMoment = this.getStartTimeMoment(selection, moment(moment(current).valueOf()));
+    return startMoment ? startMoment.toISOString() : '';
+  };
+
+  getEndTimeMoment = (selection: TimeUnitListItem): moment.Moment | undefined => {
+    let time;
+    const value = selection && selection.value;
+    if (value) {
+      switch (value.type) {
+        case 'LAST':
+          time = moment();
+          break;
+        case 'CURRENT':
+          time = moment().tz(this.timeZone).endOf(value.unit);
+          break;
+        case 'PAST':
+          time = moment().tz(this.timeZone).startOf(value.unit).millisecond(-1);
+          break;
+        case 'CUSTOM':
+          time = value.end;
+          break;
+        default:
+          break;
+      }
+    }
+    return time;
+  };
+
+  private getEndTime = (selection: TimeUnitListItem): string => {
+    const endMoment = this.getEndTimeMoment(selection);
+    return endMoment ? endMoment.toISOString() : '';
+  };
+
+  private getQuery(isExclude: boolean): (value: any[]) => string {
+    return (value: any[]): string => {
+      let parameters;
+      if (value && value.length) {
+        parameters = value.filter(item => item.isExclude === isExclude).map(parameter => {
+          return {
+            [parameter.name]: parameter.value.replace(/\s/g, '+')
+          };
+        });
+      }
+      return parameters && parameters.length ? JSON.stringify(parameters) : '';
+    }
+  }
+
+  private getSortType(selection: SortingListItem[] = []): 'asc' | 'desc' {
+    return selection[0] && selection[0].value ? selection[0].value.type : 'desc';
+  }
+
+  private getSortKey(selection: SortingListItem[] = []): string {
+    return selection[0] && selection[0].value ? selection[0].value.key : '';
+  }
+
+  private getPage(value: number | undefined): string | undefined {
+    return typeof value === 'undefined' ? value : value.toString();
+  }
+
+  private defaultValueGetter(selection: ListItem | ListItem[] | null): string {
+    if (Array.isArray(selection)) {
+      return selection.map((item: ListItem): any => item.value).join(',');
+    } else if (selection) {
+      return selection.value;
+    } else {
+      return '';
+    }
+  }
+
+  private readonly valueGetters = {
+    to: this.getEndTime,
+    from: this.getStartTime,
+    sortType: this.getSortType,
+    sortBy: this.getSortKey,
+    page: this.getPage,
+    includeQuery: this.getQuery(false),
+    excludeQuery: this.getQuery(true)
+  };
+
   switchTab(activeTab: Tab): void {
+    this.tabsStorage.mapCollection((tab: Tab): Tab => {
+      return Object.assign({}, tab, {
+        isActive: tab.id === activeTab.id
+      });
+    });
     this.appState.setParameters(activeTab.appState);
-    this.tabsStorage.mapCollection((tab: Tab): Tab => Object.assign({}, tab, {
-      isActive: tab.id === activeTab.id
-    }));
-    this.loadLogs();
+  }
+
+  startCaptureTimer(): void {
+    this.startCaptureTime = new Date().valueOf();
+    const maxCaptureTimeInSeconds = this.maximumCaptureTimeLimit / 1000;
+    Observable.timer(0, 1000).takeUntil(this.stopTimer).subscribe((seconds: number): void => {
+      this.captureSeconds = seconds;
+      if (this.captureSeconds >= maxCaptureTimeInSeconds) {
+        this.stopCaptureTimer();
+      }
+    });
+  }
+
+  stopCaptureTimer(): void {
+    const autoRefreshIntervalSeconds = this.autoRefreshInterval / 1000;
+    this.stopCaptureTime = new Date().valueOf();
+    this.captureSeconds = 0;
+    this.stopTimer.next();
+    this.setCustomTimeRange(this.startCaptureTime, this.stopCaptureTime);
+    Observable.timer(0, 1000).takeUntil(this.stopAutoRefreshCountdown).subscribe((seconds: number): void => {
+      this.autoRefreshRemainingSeconds = autoRefreshIntervalSeconds - seconds;
+      if (!this.autoRefreshRemainingSeconds) {
+        this.stopAutoRefreshCountdown.next();
+        this.setCustomTimeRange(this.startCaptureTime, this.stopCaptureTime);
+      }
+    });
+  }
+
+  loadClusters(): Observable<Response> {
+    const request = this.httpClient.get('clusters');
+    request.subscribe((response: Response): void => {
+      const clusterNames = response.json();
+      if (clusterNames) {
+        this.filters.clusters.options.push(...clusterNames.map(this.getListItemFromString));
+        this.clustersStorage.addInstances(clusterNames);
+      }
+    });
+    return request;
+  }
+
+  loadComponents(): Observable<Response> {
+    const request = this.httpClient.get('components');
+    request.subscribe((response: Response): void => {
+      const jsonResponse = response.json(),
+        components = jsonResponse && jsonResponse.vNodeList.map((item): NodeItem => Object.assign(item, {
+            value: item.logLevelCount.reduce((currentValue: number, currentItem): number => {
+              return currentValue + Number(currentItem.value);
+            }, 0)
+          }));
+      if (components) {
+        this.filters.components.options.push(...components.map(this.getListItemFromNode));
+        this.componentsStorage.addInstances(components);
+      }
+    });
+    return request;
+  }
+
+  loadHosts(): Observable<Response> {
+    const request = this.httpClient.get('hosts');
+    request.subscribe((response: Response): void => {
+      const jsonResponse = response.json(),
+        hosts = jsonResponse && jsonResponse.vNodeList;
+      if (hosts) {
+        this.filters.hosts.options.push(...hosts.map(this.getListItemFromNode));
+        this.hostsStorage.addInstances(hosts);
+      }
+    });
+    return request;
+  }
+
+  setCustomTimeRange(startTime: number, endTime: number): void {
+    this.filtersForm.controls.timeRange.setValue({
+      label: 'filter.timeRange.custom',
+      value: {
+        type: 'CUSTOM',
+        start: moment(startTime),
+        end: moment(endTime)
+      }
+    });
+  }
+
+  getFiltersData(listType: string): object {
+    const itemsList = this.logsTypeMap[listType].listFilters,
+      keys = Object.keys(this.filters).filter((key: string): boolean => itemsList.indexOf(key) > -1);
+    return keys.reduce((currentObject: object, key: string): object => {
+      return Object.assign(currentObject, {
+        [key]: this.filters[key].defaultSelection
+      });
+    }, {});
   }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts
index 8b157df..985b52f 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/mock-api-data.service.ts
@@ -65,6 +65,28 @@ export class mockApiDataService implements InMemoryDbService {
           isValuesList: true
         }
       }
+    },
+    'api/v1/audit/logs': {
+      pathToCollection: 'logList',
+      totalCountKey: 'totalCount',
+      filters: {
+        clusters: {
+          key: 'cluster',
+          isValuesList: true
+        },
+        iMessage: {
+          key: 'log_message',
+          filterFunction: (value, filterValue) => value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1
+        },
+        from: {
+          key: 'evtTime',
+          filterFunction: (value, filterValue) => value >= moment(filterValue).valueOf()
+        },
+        to: {
+          key: 'evtTime',
+          filterFunction: (value, filterValue) => value < moment(filterValue).valueOf()
+        }
+      }
     }
   };
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.spec.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.spec.ts
index a4a0cf8..23d3726 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.spec.ts
@@ -31,56 +31,273 @@ describe('UtilsService', () => {
     expect(service).toBeTruthy();
   }));
 
-  describe('#updateMultiSelectValue()', () => {
+  describe('#isEqual()', () => {
     const cases = [
       {
-        currentValue: '',
-        value: 'v0',
-        isChecked: true,
-        result: 'v0',
-        title: 'check; no checked items before'
+        valueA: 1,
+        valueB: 1,
+        result: true,
+        title: 'same numbers'
       },
       {
-        currentValue: 'v1,v2',
-        value: 'v3',
-        isChecked: true,
-        result: 'v1,v2,v3',
-        title: 'check'
+        valueA: 1,
+        valueB: 2,
+        result: false,
+        title: 'different numbers'
       },
       {
-        currentValue: 'v4,v5',
-        value: 'v4',
-        isChecked: false,
-        result: 'v5',
-        title: 'uncheck'
+        valueA: 'a',
+        valueB: 'a',
+        result: true,
+        title: 'same strings'
       },
       {
-        currentValue: 'v6,v7',
-        value: 'v6',
-        isChecked: true,
-        result: 'v6,v7',
-        title: 'avoid repeating check action'
+        valueA: 'a',
+        valueB: 'b',
+        result: false,
+        title: 'different strings'
       },
       {
-        currentValue: 'v8,v9',
-        value: 'v10',
-        isChecked: false,
-        result: 'v8,v9',
-        title: 'avoid repeating uncheck action'
+        valueA: '1',
+        valueB: 1,
+        result: false,
+        title: 'different types'
       },
       {
-        currentValue: 'v11',
-        value: 'v11',
-        isChecked: false,
-        result: '',
-        title: 'uncheck last item'
+        valueA: true,
+        valueB: true,
+        result: true,
+        title: 'same booleans'
+      },
+      {
+        valueA: false,
+        valueB: true,
+        result: false,
+        title: 'different booleans'
+      },
+      {
+        valueA: {},
+        valueB: {},
+        result: true,
+        title: 'empty objects'
+      },
+      {
+        valueA: {
+          p0: 'v0'
+        },
+        valueB: {
+          p0: 'v0'
+        },
+        result: true,
+        title: 'same objects'
+      },
+      {
+        valueA: {
+          p0: 'v0'
+        },
+        valueB: {
+          p0: 'v1'
+        },
+        result: false,
+        title: 'different objects'
+      },
+      {
+        valueA: {
+          p0: {
+            p1: 'v1'
+          }
+        },
+        valueB: {
+          p0: {
+            p1: 'v1'
+          }
+        },
+        result: true,
+        title: 'same objects in depth'
+      },
+      {
+        valueA: {
+          p0: {
+            p1: 'v1'
+          }
+        },
+        valueB: {
+          p0: {
+            p1: 'v2'
+          }
+        },
+        result: false,
+        title: 'different objects in depth'
+      },
+      {
+        valueA: [],
+        valueB: [],
+        result: true,
+        title: 'empty arrays'
+      },
+      {
+        valueA: [1, 'a'],
+        valueB: [1, 'a'],
+        result: true,
+        title: 'same arrays'
+      },
+      {
+        valueA: [1, 'a'],
+        valueB: [1, 'b'],
+        result: false,
+        title: 'different arrays'
+      },
+      {
+        valueA: [1, 1],
+        valueB: [1, 1, 1],
+        result: false,
+        title: 'arrays of different length'
+      },
+      {
+        valueA: [{}],
+        valueB: [{}],
+        result: true,
+        title: 'arrays of empty objects'
+      },
+      {
+        valueA: [
+          {
+            p0: 'v0'
+          }
+        ],
+        valueB: [
+          {
+            p0: 'v0'
+          }
+        ],
+        result: true,
+        title: 'arrays of same objects'
+      },
+      {
+        valueA: [
+          {
+            p0: 'v0'
+          }
+        ],
+        valueB: [
+          {
+            p0: 'v1'
+          }
+        ],
+        result: false,
+        title: 'arrays of different objects'
+      },
+      {
+        valueA: function() {},
+        valueB: function() {},
+        result: true,
+        title: 'same functions'
+      },
+      {
+        valueA: function(a) {
+          return a;
+        },
+        valueB: function(b) {
+          return !b;
+        },
+        result: false,
+        title: 'different functions'
+      },
+      {
+        valueA: new Date(1),
+        valueB: new Date(1),
+        result: true,
+        title: 'same dates'
+      },
+      {
+        valueA: new Date(1),
+        valueB: new Date(2),
+        result: false,
+        title: 'different dates'
+      },
+      {
+        valueA: new RegExp('a'),
+        valueB: new RegExp('a'),
+        result: true,
+        title: 'same regexps'
+      },
+      {
+        valueA: new RegExp('a', 'i'),
+        valueB: new RegExp('a', 'g'),
+        result: false,
+        title: 'same regexps with different flags'
+      },
+      {
+        valueA: new RegExp('a'),
+        valueB: new RegExp('b'),
+        result: false,
+        title: 'different regexps'
+      },
+      {
+        valueA: new Number(1),
+        valueB: new Number(1),
+        result: true,
+        title: 'same number objects'
+      },
+      {
+        valueA: new Number(1),
+        valueB: new Number(2),
+        result: false,
+        title: 'different number objects'
+      },
+      {
+        valueA: new String('a'),
+        valueB: new String('a'),
+        result: true,
+        title: 'same string objects'
+      },
+      {
+        valueA: new String('a'),
+        valueB: new String('b'),
+        result: false,
+        title: 'different string objects'
+      },
+      {
+        valueA: new Boolean(true),
+        valueB: new Boolean(true),
+        result: true,
+        title: 'same boolean objects'
+      },
+      {
+        valueA: new Boolean(true),
+        valueB: new Boolean(false),
+        result: false,
+        title: 'different boolean objects'
+      },
+      {
+        valueA: null,
+        valueB: null,
+        result: true,
+        title: 'null values'
+      },
+      {
+        valueA: undefined,
+        valueB: undefined,
+        result: true,
+        title: 'undefined values'
+      },
+      {
+        valueA: undefined,
+        valueB: null,
+        result: false,
+        title: 'undefined vs null'
       }
     ];
 
     cases.forEach(test => {
-      it(test.title, inject([UtilsService], (service: UtilsService) => {
-        expect(service.updateMultiSelectValue(test.currentValue, test.value, test.isChecked)).toEqual(test.result);
-      }));
+      describe(test.title, () => {
+        it('equality', inject([UtilsService], (service: UtilsService) => {
+          expect(service.isEqual(test.valueA, test.valueB)).toEqual(test.result);
+        }));
+        it('symmetry', inject([UtilsService], (service: UtilsService) => {
+          expect(service.isEqual(test.valueA, test.valueB)).toEqual(service.isEqual(test.valueB, test.valueA));
+        }));
+      });
     });
   });
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts
index 3448dd4..175b585 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts
@@ -22,27 +22,56 @@ import * as moment from 'moment-timezone';
 @Injectable()
 export class UtilsService {
 
-  valueHasChanged(currentValue: any, newValue: any): boolean {
-    if (newValue == null) {
+  /**
+   * Comparison of two instances of any data type be value instead of reference
+   * @param valueA
+   * @param valueB
+   * @returns {boolean}
+   */
+  isEqual = (valueA: any, valueB: any): boolean => {
+    if (valueA === valueB) {
+      return true;
+    }
+    if (valueA instanceof Date && valueB instanceof Date) {
+      return valueA.valueOf() === valueB.valueOf();
+    }
+    if ((typeof valueA === 'function' && typeof valueB === 'function') ||
+      (valueA instanceof RegExp && valueB instanceof RegExp) ||
+      (valueA instanceof String && valueB instanceof String) ||
+      (valueA instanceof Number && valueB instanceof Number) ||
+      (valueA instanceof Boolean && valueB instanceof Boolean)) {
+      return valueA.toString() === valueB.toString();
+    }
+    if (!(valueA instanceof Object) || !(valueB instanceof Object)) {
       return false;
     }
-    if (typeof newValue === 'object') {
-      return JSON.stringify(currentValue) !== JSON.stringify(newValue);
-    } else {
-      return currentValue !== newValue;
+    if (valueA.constructor !== valueB.constructor) {
+      return false;
     }
-  }
-
-  updateMultiSelectValue(currentValue: string, value: string, isChecked: boolean): string {
-    let valuesArray = currentValue ? currentValue.split(',') : [],
-      valuePosition = valuesArray.indexOf(value);
-    if (isChecked && valuePosition === -1) {
-      valuesArray.push(value);
-    } else if (!isChecked && valuePosition > -1) {
-      valuesArray.splice(valuePosition, 1);
-    }
-    return valuesArray.join(',');
-  }
+    if (valueA.isPrototypeOf(valueB) || valueB.isPrototypeOf(valueA)) {
+      return false;
+    }
+    for (const key in valueA) {
+      if (!valueA.hasOwnProperty(key)) {
+        continue;
+      }
+      if (!valueB.hasOwnProperty(key)) {
+        return false;
+      }
+      if (valueA[key] === valueB[key]) {
+        continue;
+      }
+      if (typeof valueA[key] !== 'object' || !this.isEqual(valueA[key], valueB[key])) {
+        return false;
+      }
+    }
+    for (const key in valueB) {
+      if (valueB.hasOwnProperty(key) && !valueA.hasOwnProperty(key)) {
+        return false;
+      }
+    }
+    return true;
+  };
 
   isEnterPressed(event: KeyboardEvent): boolean {
     return event.keyCode === 13;

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
index 16b4b32..98b9e29 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
@@ -10,6 +10,7 @@
   "modal.apply": "Apply",
   "modal.close": "Close",
 
+  "authorization.logout": "Logout",
   "authorization.name": "Username",
   "authorization.password": "Password",
   "authorization.signIn": "Sign In",
@@ -61,7 +62,9 @@
   "filter.timeRange.6hr": "Last 6 hours",
   "filter.timeRange.12hr": "Last 12 hours",
   "filter.timeRange.24hr": "Last 24 hours",
-  "filter.timeRange.custom": "Custom time range",
+  "filter.timeRange.custom": "Custom",
+  "filter.timeRange.from": "from",
+  "filter.timeRange.to": "to",
 
   "levels.fatal": "Fatal",
   "levels.error": "Error",

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/webpack.config.js
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/webpack.config.js b/ambari-logsearch/ambari-logsearch-web/webpack.config.js
index 7a60df2..75d6aee 100644
--- a/ambari-logsearch/ambari-logsearch-web/webpack.config.js
+++ b/ambari-logsearch/ambari-logsearch-web/webpack.config.js
@@ -81,18 +81,17 @@ module.exports = {
   "resolve": {
     "extensions": [
       ".ts",
-      ".js"
+      ".js",
+      ".less"
     ],
     "modules": [
-      "./node_modules",
-      "./node_modules"
+      "node_modules"
     ],
     "symlinks": true
   },
   "resolveLoader": {
     "modules": [
-      "./node_modules",
-      "./node_modules"
+      "node_modules"
     ]
   },
   "entry": {
@@ -229,7 +228,9 @@ module.exports = {
             "loader": "less-loader",
             "options": {
               "sourceMap": false,
-              "paths": []
+              "paths": [
+                "./node_modules"
+              ]
             }
           }
         ]
@@ -359,7 +360,7 @@ module.exports = {
             "loader": "less-loader",
             "options": {
               "sourceMap": false,
-              "paths": []
+              "paths": ["./node_modules"]
             }
           }
         ]

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-metrics/ambari-metrics-assembly/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-metrics/ambari-metrics-assembly/pom.xml b/ambari-metrics/ambari-metrics-assembly/pom.xml
index 9925947..43ff285 100644
--- a/ambari-metrics/ambari-metrics-assembly/pom.xml
+++ b/ambari-metrics/ambari-metrics-assembly/pom.xml
@@ -226,7 +226,6 @@
                   <description>Maven Recipe: RPM Package.</description>
                   <autoRequires>false</autoRequires>
                   <requires>
-                    <require>snappy</require>
                     <require>${python.ver}</require>
                   </requires>
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-metrics/ambari-metrics-hadoop-sink/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-metrics/ambari-metrics-hadoop-sink/pom.xml b/ambari-metrics/ambari-metrics-hadoop-sink/pom.xml
index 23f9ba9..a9d342f 100644
--- a/ambari-metrics/ambari-metrics-hadoop-sink/pom.xml
+++ b/ambari-metrics/ambari-metrics-hadoop-sink/pom.xml
@@ -31,6 +31,7 @@ limitations under the License.
   <packaging>jar</packaging>
   <properties>
     <sinkJarName>${project.artifactId}-with-common-${project.version}.jar</sinkJarName>
+    <hadoop.version>3.0.0-beta1</hadoop.version>
   </properties>
 
 
@@ -141,7 +142,7 @@ limitations under the License.
     <dependency>
       <groupId>org.apache.hadoop</groupId>
       <artifactId>hadoop-common</artifactId>
-      <version>2.4.0</version>
+      <version>${hadoop.version}</version>
       <scope>compile</scope>
     </dependency>
     <dependency>

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-metrics/ambari-metrics-hadoop-sink/src/main/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSink.java
----------------------------------------------------------------------
diff --git a/ambari-metrics/ambari-metrics-hadoop-sink/src/main/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSink.java b/ambari-metrics/ambari-metrics-hadoop-sink/src/main/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSink.java
index a290ced..bbc9617 100644
--- a/ambari-metrics/ambari-metrics-hadoop-sink/src/main/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSink.java
+++ b/ambari-metrics/ambari-metrics-hadoop-sink/src/main/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSink.java
@@ -17,7 +17,8 @@
  */
 package org.apache.hadoop.metrics2.sink.timeline;
 
-import org.apache.commons.configuration.SubsetConfiguration;
+import org.apache.commons.configuration2.SubsetConfiguration;
+import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.classification.InterfaceStability;
@@ -34,7 +35,6 @@ import java.io.IOException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -143,7 +143,7 @@ public class HadoopTimelineMetricsSink extends AbstractTimelineMetricsSink imple
     metricsCache = new TimelineMetricsCache(maxRowCacheSize,
       metricsSendInterval, conf.getBoolean(SKIP_COUNTER_TRANSFROMATION, true));
 
-    conf.setListDelimiter(',');
+    conf.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
     Iterator<String> it = (Iterator<String>) conf.getKeys();
     while (it.hasNext()) {
       String propertyName = it.next();

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-metrics/ambari-metrics-hadoop-sink/src/test/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSinkTest.java
----------------------------------------------------------------------
diff --git a/ambari-metrics/ambari-metrics-hadoop-sink/src/test/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSinkTest.java b/ambari-metrics/ambari-metrics-hadoop-sink/src/test/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSinkTest.java
index 30c5c23..6bb6454 100644
--- a/ambari-metrics/ambari-metrics-hadoop-sink/src/test/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSinkTest.java
+++ b/ambari-metrics/ambari-metrics-hadoop-sink/src/test/java/org/apache/hadoop/metrics2/sink/timeline/HadoopTimelineMetricsSinkTest.java
@@ -19,7 +19,8 @@
 package org.apache.hadoop.metrics2.sink.timeline;
 
 import com.google.gson.Gson;
-import org.apache.commons.configuration.SubsetConfiguration;
+import org.apache.commons.configuration2.SubsetConfiguration;
+import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.metrics2.AbstractMetric;
 import org.apache.hadoop.metrics2.MetricType;
@@ -74,7 +75,7 @@ import static org.powermock.api.easymock.PowerMock.replayAll;
 import static org.powermock.api.easymock.PowerMock.verifyAll;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({AbstractTimelineMetricsSink.class, HttpURLConnection.class})
+@PrepareForTest({AbstractTimelineMetricsSink.class, HttpURLConnection.class, SubsetConfiguration.class})
 public class HadoopTimelineMetricsSinkTest {
   Gson gson = new Gson();
 
@@ -84,7 +85,7 @@ public class HadoopTimelineMetricsSinkTest {
   }
 
   @Test
-  @PrepareForTest({URL.class, OutputStream.class, AbstractTimelineMetricsSink.class, HttpURLConnection.class, TimelineMetric.class, HadoopTimelineMetricsSink.class})
+  @PrepareForTest({URL.class, OutputStream.class, AbstractTimelineMetricsSink.class, HttpURLConnection.class, TimelineMetric.class, HadoopTimelineMetricsSink.class, SubsetConfiguration.class})
   public void testPutMetrics() throws Exception {
     HadoopTimelineMetricsSink sink = new HadoopTimelineMetricsSink();
 
@@ -102,7 +103,7 @@ public class HadoopTimelineMetricsSinkTest {
     OutputStream os = PowerMock.createNiceMock(OutputStream.class);
     expect(connection.getOutputStream()).andReturn(os).anyTimes();
 
-    SubsetConfiguration conf = createNiceMock(SubsetConfiguration.class);
+    SubsetConfiguration conf = PowerMock.createNiceMock(SubsetConfiguration.class);
     expect(conf.getString("slave.host.name")).andReturn("localhost").anyTimes();
     expect(conf.getParent()).andReturn(null).anyTimes();
     expect(conf.getPrefix()).andReturn("service").anyTimes();
@@ -116,7 +117,7 @@ public class HadoopTimelineMetricsSinkTest {
     expect(conf.getBoolean(eq(SET_INSTANCE_ID_PROPERTY), eq(false))).andReturn(true).anyTimes();
     expect(conf.getString(eq(INSTANCE_ID_PROPERTY), anyString())).andReturn("instanceId").anyTimes();
 
-    conf.setListDelimiter(eq(','));
+    conf.setListDelimiterHandler(new DefaultListDelimiterHandler(eq(',')));
     expectLastCall().anyTimes();
 
     expect(conf.getKeys()).andReturn(new Iterator() {
@@ -157,7 +158,7 @@ public class HadoopTimelineMetricsSinkTest {
     timelineMetric.setInstanceId(eq("instanceId"));
     EasyMock.expectLastCall();
 
-    replay(conf, record, metric);
+    replay(record, metric);
     replayAll();
 
     sink.init(conf);
@@ -179,7 +180,7 @@ public class HadoopTimelineMetricsSinkTest {
         .addMockedMethod("findLiveCollectorHostsFromKnownCollector")
         .addMockedMethod("emitMetrics").createNiceMock();
 
-    SubsetConfiguration conf = createNiceMock(SubsetConfiguration.class);
+    SubsetConfiguration conf = PowerMock.createNiceMock(SubsetConfiguration.class);
     expect(conf.getString("slave.host.name")).andReturn("localhost").anyTimes();
     expect(conf.getParent()).andReturn(null).anyTimes();
     expect(conf.getPrefix()).andReturn("service").anyTimes();
@@ -198,7 +199,7 @@ public class HadoopTimelineMetricsSinkTest {
     expect(sink.findLiveCollectorHostsFromKnownCollector("localhost2", "6188"))
             .andReturn(Collections.singletonList("localhost2")).anyTimes();
 
-    conf.setListDelimiter(eq(','));
+    conf.setListDelimiterHandler(new DefaultListDelimiterHandler(eq(',')));
     expectLastCall().anyTimes();
 
     expect(conf.getKeys()).andReturn(new Iterator() {
@@ -309,7 +310,7 @@ public class HadoopTimelineMetricsSinkTest {
         .addMockedMethod("findLiveCollectorHostsFromKnownCollector")
         .addMockedMethod("emitMetrics").createNiceMock();
 
-    SubsetConfiguration conf = createNiceMock(SubsetConfiguration.class);
+    SubsetConfiguration conf = PowerMock.createNiceMock(SubsetConfiguration.class);
     expect(conf.getString("slave.host.name")).andReturn("localhost").anyTimes();
     expect(conf.getParent()).andReturn(null).anyTimes();
     expect(conf.getPrefix()).andReturn("service").anyTimes();
@@ -326,7 +327,7 @@ public class HadoopTimelineMetricsSinkTest {
     expect(conf.getInt(eq(MAX_METRIC_ROW_CACHE_SIZE), anyInt())).andReturn(10).anyTimes();
     expect(conf.getInt(eq(METRICS_SEND_INTERVAL), anyInt())).andReturn(10).anyTimes();
 
-    conf.setListDelimiter(eq(','));
+    conf.setListDelimiterHandler(new DefaultListDelimiterHandler(eq(',')));
     expectLastCall().anyTimes();
 
     Set<String> rpcPortSuffixes = new HashSet<String>() {{

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/docs/security/kerberos/kerberos_service.md
----------------------------------------------------------------------
diff --git a/ambari-server/docs/security/kerberos/kerberos_service.md b/ambari-server/docs/security/kerberos/kerberos_service.md
index 65e312b..c9cbd49 100644
--- a/ambari-server/docs/security/kerberos/kerberos_service.md
+++ b/ambari-server/docs/security/kerberos/kerberos_service.md
@@ -231,32 +231,12 @@ _Example:_ `-requires_preauth max_renew_life=7d`
 
 This property is optional and only used if the `kdc_type` is `mit-kdc`
 
-##### group
+##### ipa_user_group
 
 The group in IPA user principals should be member of
 
 This property is mandatory and only used if the `kdc_type` is `ipa`
 
-##### set_password_expiry
-
-Indicates whether Ambari should set the password expiry for the principals it creates. By default
-IPA does not allow this. It requires write permission of the admin principal to the krbPasswordExpiry
-attribute. If set IPA principal password expiry is not true it is assumed that a suitable password
-policy is in place for the IPA Group principals are added to.
-
-_Possible values:_ `true`, `false`
-
-_Default value:_ `false`
-
-This property is mandatory and only used if the `kdc_type` is `ipa`
-
-##### password_chat_timeout
-
-Indicates the timeout in seconds that Ambari should wait for a response during a password chat. This is
-because it can take some time due to lookups before a response is there.
-
-This property is mandatory and only used if the `kdc_type` is `ipa`
-
 <a name="krb5-conf"></a>
 #### krb5-conf
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index 5e07b3c..7172ed6 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -199,6 +199,18 @@
               </target>
             </configuration>
           </execution>
+          <execution>
+            <id>generate-test-oozie2-server-actions-dir</id>
+            <phase>process-test-classes</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <target>
+                <mkdir dir="target/test-classes/extensions/EXT/0.1/services/OOZIE2/server_actions/tmp"/>
+              </target>
+            </configuration>
+          </execution>
         </executions>
       </plugin>
       <plugin>

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ExecutionCommandWrapper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ExecutionCommandWrapper.java b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ExecutionCommandWrapper.java
index ed2819a..649f44d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ExecutionCommandWrapper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ExecutionCommandWrapper.java
@@ -20,7 +20,6 @@ package org.apache.ambari.server.actionmanager;
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.HOOKS_FOLDER;
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.SERVICE_PACKAGE_FOLDER;
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.VERSION;
-import static org.apache.ambari.server.stack.StackManager.DEFAULT_HOOKS_FOLDER;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -31,15 +30,20 @@ import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.RoleCommand;
 import org.apache.ambari.server.ServiceNotFoundException;
 import org.apache.ambari.server.agent.AgentCommand.AgentCommandType;
+import org.apache.ambari.server.agent.CommandRepository;
 import org.apache.ambari.server.agent.ExecutionCommand;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.entities.OperatingSystemEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.DesiredConfig;
+import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.Service;
 import org.apache.ambari.server.state.ServiceComponent;
 import org.apache.ambari.server.state.ServiceInfo;
@@ -48,6 +52,7 @@ import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.UpgradeContext;
 import org.apache.ambari.server.state.UpgradeContext.UpgradeSummary;
 import org.apache.ambari.server.state.UpgradeContextFactory;
+import org.apache.ambari.server.state.stack.upgrade.RepositoryVersionHelper;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
@@ -79,12 +84,18 @@ public class ExecutionCommandWrapper {
   @Inject
   private UpgradeContextFactory upgradeContextFactory;
 
+  @Inject
+  private RepositoryVersionHelper repoVersionHelper;
+
   /**
    * Used for injecting hooks and common-services into the command.
    */
   @Inject
   private AmbariMetaInfo ambariMetaInfo;
 
+  @Inject
+  private Configuration configuration;
+
   @AssistedInject
   public ExecutionCommandWrapper(@Assisted String jsonExecutionCommand) {
     this.jsonExecutionCommand = jsonExecutionCommand;
@@ -215,9 +226,36 @@ public class ExecutionCommandWrapper {
       if (null != upgrade) {
         UpgradeContext upgradeContext = upgradeContextFactory.create(cluster, upgrade);
         UpgradeSummary upgradeSummary = upgradeContext.getUpgradeSummary();
+
         executionCommand.setUpgradeSummary(upgradeSummary);
       }
 
+      // setting repositoryFile
+      final Host host = cluster.getHost(executionCommand.getHostname());  // can be null on internal commands
+      final String serviceName = executionCommand.getServiceName(); // can be null on executing special RU tasks
+
+      if (null == executionCommand.getRepositoryFile() && null != host && null != serviceName) {
+        final CommandRepository commandRepository;
+        final Service service = cluster.getService(serviceName);
+        final String componentName = executionCommand.getComponentName();
+
+        try {
+
+          if (null != componentName) {
+            ServiceComponent serviceComponent = service.getServiceComponent(componentName);
+            commandRepository = repoVersionHelper.getCommandRepository(null, serviceComponent, host);
+          } else {
+            RepositoryVersionEntity repoVersion = service.getDesiredRepositoryVersion();
+            OperatingSystemEntity osEntity = repoVersionHelper.getOSEntityForHost(host, repoVersion);
+            commandRepository = repoVersionHelper.getCommandRepository(repoVersion, osEntity);
+          }
+          executionCommand.setRepositoryFile(commandRepository);
+
+        } catch (SystemException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
     } catch (ClusterNotFoundException cnfe) {
       // it's possible that there are commands without clusters; in such cases,
       // just return the de-serialized command and don't try to read configs
@@ -271,7 +309,7 @@ public class ExecutionCommandWrapper {
           stackId.getStackVersion());
 
         if (!commandParams.containsKey(HOOKS_FOLDER)) {
-          commandParams.put(HOOKS_FOLDER, DEFAULT_HOOKS_FOLDER);
+          commandParams.put(HOOKS_FOLDER,configuration.getProperty(Configuration.HOOKS_FOLDER));
         }
 
         if (!commandParams.containsKey(SERVICE_PACKAGE_FOLDER)) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/agent/CommandRepository.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/agent/CommandRepository.java b/ambari-server/src/main/java/org/apache/ambari/server/agent/CommandRepository.java
index a70326e..449d2d5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/agent/CommandRepository.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/agent/CommandRepository.java
@@ -51,7 +51,7 @@ public class CommandRepository {
   private String m_repoFileName;
 
   @SerializedName("feature")
-  private CommandRepositoryFeature feature = new CommandRepositoryFeature();
+  private final CommandRepositoryFeature feature = new CommandRepositoryFeature();
 
   /**
    * Provides {@link CommandRepository} feature

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/agent/ExecutionCommand.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/agent/ExecutionCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/agent/ExecutionCommand.java
index 9198164..5ee4bf6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/agent/ExecutionCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/agent/ExecutionCommand.java
@@ -160,6 +160,9 @@ public class ExecutionCommand extends AgentCommand {
   @SerializedName("upgradeSummary")
   private UpgradeSummary upgradeSummary;
 
+  @SerializedName("roleParameters")
+  private Map<String, Object> roleParameters;
+
   public void setConfigurationCredentials(Map<String, Map<String, String>> configurationCredentials) {
     this.configurationCredentials = configurationCredentials;
   }
@@ -235,6 +238,10 @@ public class ExecutionCommand extends AgentCommand {
     return roleParams;
   }
 
+  /**
+   * Sets the roleParams for the command.  Consider instead using {@link #setRoleParameters}
+   * @param roleParams
+   */
   public void setRoleParams(Map<String, String> roleParams) {
     this.roleParams = roleParams;
   }
@@ -332,11 +339,11 @@ public class ExecutionCommand extends AgentCommand {
   }
 
   public String getServiceType() {
-	return serviceType;
+    return serviceType;
   }
 
   public void setServiceType(String serviceType) {
-	this.serviceType = serviceType;
+    this.serviceType = serviceType;
   }
 
   /**
@@ -413,6 +420,23 @@ public class ExecutionCommand extends AgentCommand {
   }
 
   /**
+   * Gets the object-based role parameters for the command.
+   */
+  public Map<String, Object> getRoleParameters() {
+    return roleParameters;
+  }
+
+  /**
+   * Sets the role parameters for the command.  This is preferred over {@link #setRoleParams(Map)},
+   * as this form will pass values as structured data, as opposed to unstructured, escaped json.
+   *
+   * @param params
+   */
+  public void setRoleParameters(Map<String, Object> params) {
+    roleParameters = params;
+  }
+
+  /**
    * Contains key name strings. These strings are used inside maps
    * incapsulated inside command.
    */
@@ -432,6 +456,7 @@ public class ExecutionCommand extends AgentCommand {
     String PACKAGE_LIST = "package_list";
     String JDK_LOCATION = "jdk_location";
     String JAVA_HOME = "java_home";
+    String GPL_LICENSE_ACCEPTED = "gpl_license_accepted";
     String AMBARI_JAVA_HOME = "ambari_java_home";
     String AMBARI_JDK_NAME = "ambari_jdk_name";
     String AMBARI_JCE_NAME = "ambari_jce_name";
@@ -512,6 +537,12 @@ public class ExecutionCommand extends AgentCommand {
         feature = ExperimentalFeature.PATCH_UPGRADES,
         comment = "Change this to reflect the component version")
     String VERSION = "version";
+
+
+    /**
+     * When installing packages, includes what services will be included in the upgrade
+     */
+    String CLUSTER_VERSION_SUMMARY = "cluster_version_summary";
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java
index 4be62cb..4ca34c4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatMonitor.java
@@ -25,7 +25,6 @@ import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.SCRIPT_TY
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.SERVICE_PACKAGE_FOLDER;
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.STACK_NAME;
 import static org.apache.ambari.server.agent.ExecutionCommand.KeyNames.STACK_VERSION;
-import static org.apache.ambari.server.stack.StackManager.DEFAULT_HOOKS_FOLDER;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -343,7 +342,7 @@ public class HeartbeatMonitor implements Runnable {
     commandParams.put(COMMAND_TIMEOUT, commandTimeout);
     commandParams.put(SERVICE_PACKAGE_FOLDER,
        serviceInfo.getServicePackageFolder());
-    commandParams.put(HOOKS_FOLDER, DEFAULT_HOOKS_FOLDER);
+    commandParams.put(HOOKS_FOLDER, configuration.getProperty(Configuration.HOOKS_FOLDER));
     // Fill host level params
     Map<String, String> hostLevelParams = statusCmd.getHostLevelParams();
     hostLevelParams.put(JDK_LOCATION, ambariManagementController.getJdkResourceUrl());

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
index 3dae84b..9eefda2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
@@ -52,7 +52,9 @@ import org.apache.ambari.server.events.publishers.AlertEventPublisher;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
 import org.apache.ambari.server.events.publishers.VersionEventPublisher;
 import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.KerberosKeytabDAO;
 import org.apache.ambari.server.orm.dao.KerberosPrincipalHostDAO;
+import org.apache.ambari.server.orm.entities.KerberosPrincipalHostEntity;
 import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
@@ -88,7 +90,6 @@ import com.google.inject.Injector;
 
 /**
  * HeartbeatProcessor class is used for bulk processing data retrieved from agents in background
- *
  */
 public class HeartbeatProcessor extends AbstractService{
   private static final Logger LOG = LoggerFactory.getLogger(HeartbeatProcessor.class);
@@ -135,6 +136,9 @@ public class HeartbeatProcessor extends AbstractService{
   KerberosPrincipalHostDAO kerberosPrincipalHostDAO;
 
   @Inject
+  KerberosKeytabDAO kerberosKeytabDao;
+
+  @Inject
   Gson gson;
 
   @Inject
@@ -153,7 +157,7 @@ public class HeartbeatProcessor extends AbstractService{
   @Override
   protected void doStart() {
     LOG.info("**** Starting heartbeats processing threads ****");
-    for (int i=0; i< poolSize; i++) {
+    for (int i = 0; i < poolSize; i++) {
       executor.scheduleAtFixedRate(new HeartbeatProcessingTask(), delay, period, TimeUnit.MILLISECONDS);
     }
   }
@@ -201,6 +205,7 @@ public class HeartbeatProcessor extends AbstractService{
 
   /**
    * Incapsulates logic for processing data from agent heartbeat
+   *
    * @param heartbeat Agent heartbeat object
    * @throws AmbariException
    */
@@ -217,14 +222,12 @@ public class HeartbeatProcessor extends AbstractService{
   }
 
 
-
   /**
    * Extracts all of the {@link Alert}s from the heartbeat and fires
    * {@link AlertEvent}s for each one. If there is a problem looking up the
    * cluster, then alerts will not be processed.
    *
-   * @param heartbeat
-   *          the heartbeat to process.
+   * @param heartbeat the heartbeat to process.
    */
   protected void processAlerts(HeartBeat heartbeat) {
     if (heartbeat == null) {
@@ -247,6 +250,7 @@ public class HeartbeatProcessor extends AbstractService{
 
   /**
    * Update host status basing on components statuses
+   *
    * @param heartbeat heartbeat to process
    * @throws AmbariException
    */
@@ -349,8 +353,9 @@ public class HeartbeatProcessor extends AbstractService{
 
   /**
    * Process reports of tasks executed on agents
+   *
    * @param heartbeat heartbeat to process
-   * @param now cached current time
+   * @param now       cached current time
    * @throws AmbariException
    */
   protected void processCommandReports(
@@ -424,8 +429,7 @@ public class HeartbeatProcessor extends AbstractService{
 
         String customCommand = report.getCustomCommand();
 
-        boolean adding = SET_KEYTAB.equalsIgnoreCase(customCommand);
-        if (adding || REMOVE_KEYTAB.equalsIgnoreCase(customCommand)) {
+        if (SET_KEYTAB.equalsIgnoreCase(customCommand) || REMOVE_KEYTAB.equalsIgnoreCase(customCommand)) {
           WriteKeytabsStructuredOut writeKeytabsStructuredOut;
           try {
             writeKeytabsStructuredOut = gson.fromJson(report.getStructuredOut(), WriteKeytabsStructuredOut.class);
@@ -435,25 +439,35 @@ public class HeartbeatProcessor extends AbstractService{
           }
 
           if (writeKeytabsStructuredOut != null) {
-            Map<String, String> keytabs = writeKeytabsStructuredOut.getKeytabs();
-            if (keytabs != null) {
-              for (Map.Entry<String, String> entry : keytabs.entrySet()) {
-                String principal = entry.getKey();
-                if (!kerberosPrincipalHostDAO.exists(principal, host.getHostId())) {
-                  if (adding) {
-                    kerberosPrincipalHostDAO.create(principal, host.getHostId());
-                  } else if ("_REMOVED_".equalsIgnoreCase(entry.getValue())) {
-                    kerberosPrincipalHostDAO.remove(principal, host.getHostId());
-                  }
+            if (SET_KEYTAB.equalsIgnoreCase(customCommand)) {
+              Map<String, String> keytabs = writeKeytabsStructuredOut.getKeytabs();
+              if (keytabs != null) {
+                for (Map.Entry<String, String> entry : keytabs.entrySet()) {
+                  String principal = entry.getKey();
+                  String keytabPath = entry.getValue();
+                  KerberosPrincipalHostEntity kphe = kerberosPrincipalHostDAO.find(principal, host.getHostId(), keytabPath);
+                  kphe.setDistributed(true);
+                  kerberosPrincipalHostDAO.merge(kphe);
+                }
+              }
+            } else if (REMOVE_KEYTAB.equalsIgnoreCase(customCommand)) {
+              Map<String, String> deletedKeytabs = writeKeytabsStructuredOut.getRemovedKeytabs();
+              if (deletedKeytabs != null) {
+                for (Map.Entry<String, String> entry : deletedKeytabs.entrySet()) {
+                  String keytabPath = entry.getValue();
+                  kerberosPrincipalHostDAO.removeByKeytabPath(keytabPath);
+                  kerberosKeytabDao.remove(keytabPath);
                 }
               }
             }
           }
         } else if (CHECK_KEYTABS.equalsIgnoreCase(customCommand)) {
           ListKeytabsStructuredOut structuredOut = gson.fromJson(report.getStructuredOut(), ListKeytabsStructuredOut.class);
-          for (MissingKeytab each : structuredOut.missingKeytabs){
-            LOG.info("Missing keytab: {} on host: {} principal: {}", each.keytabFilePath, hostname, each.principal);
-            kerberosPrincipalHostDAO.remove(each.principal, host.getHostId());
+          for (MissingKeytab each : structuredOut.missingKeytabs) {
+            LOG.info("Missing principal: {} for keytab: {} on host: {}", each.principal, each.keytabFilePath, hostname);
+            KerberosPrincipalHostEntity kphe = kerberosPrincipalHostDAO.find(each.principal, host.getHostId(), each.keytabFilePath);
+            kphe.setDistributed(false);
+            kerberosPrincipalHostDAO.merge(kphe);
           }
         }
       }
@@ -518,7 +532,7 @@ public class HeartbeatProcessor extends AbstractService{
             // Necessary for resetting clients stale configs after starting service
             if ((RoleCommand.INSTALL.toString().equals(report.getRoleCommand()) ||
                 (RoleCommand.CUSTOM_COMMAND.toString().equals(report.getRoleCommand()) &&
-                    "INSTALL".equals(report.getCustomCommand()))) && svcComp.isClientComponent()){
+                    "INSTALL".equals(report.getCustomCommand()))) && svcComp.isClientComponent()) {
               scHost.updateActualConfigs(report.getConfigurationTags());
               scHost.setRestartRequired(false);
             }
@@ -589,6 +603,7 @@ public class HeartbeatProcessor extends AbstractService{
 
   /**
    * Process reports of status commands
+   *
    * @param heartbeat heartbeat to process
    * @throws AmbariException
    */
@@ -702,7 +717,10 @@ public class HeartbeatProcessor extends AbstractService{
    */
   private static class WriteKeytabsStructuredOut {
     @SerializedName("keytabs")
-    private Map<String,String> keytabs;
+    private Map<String, String> keytabs;
+
+    @SerializedName("removedKeytabs")
+    private Map<String, String> removedKeytabs;
 
     public Map<String, String> getKeytabs() {
       return keytabs;
@@ -711,6 +729,14 @@ public class HeartbeatProcessor extends AbstractService{
     public void setKeytabs(Map<String, String> keytabs) {
       this.keytabs = keytabs;
     }
+
+    public Map<String, String> getRemovedKeytabs() {
+      return removedKeytabs;
+    }
+
+    public void setRemovedKeytabs(Map<String, String> removedKeytabs) {
+      this.removedKeytabs = removedKeytabs;
+    }
   }
 
   private static class ListKeytabsStructuredOut {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
index d0bd5d3..1fa55eb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
@@ -36,20 +36,12 @@ import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Base handler for operations that persist state to the back-end.
  */
 public abstract class BaseManagementHandler implements RequestHandler {
 
-  /**
-   * Logger instance.
-   */
-  protected final static Logger LOG =
-      LoggerFactory.getLogger(BaseManagementHandler.class);
-
   public static final String RESOURCES_NODE_NAME = "resources";
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java
index 549da76..b614c5e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java
@@ -31,6 +31,8 @@ import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 /**
@@ -38,6 +40,8 @@ import org.apache.ambari.server.security.authorization.AuthorizationException;
  */
 public class CreateHandler extends BaseManagementHandler {
 
+  private final static Logger LOG = LoggerFactory.getLogger(CreateHandler.class);
+
   @Override
   protected Result persist(ResourceInstance resource, RequestBody body) {
     Result result;

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index 2d2e75e..85ba768 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -289,6 +289,11 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new RootServiceComponentResourceDefinition();
         break;
 
+      case RootServiceComponentConfiguration:
+        resourceDefinition = new SimpleResourceDefinition(Resource.Type.RootServiceComponentConfiguration,
+            "configuration", "configurations");
+        break;
+
       case RootServiceHostComponent:
         resourceDefinition = new RootServiceHostComponentResourceDefinition();
         break;
@@ -523,10 +528,6 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
       case RemoteCluster:
         resourceDefinition = new RemoteClusterResourceDefinition();
         break;
-      case AmbariConfiguration:
-        resourceDefinition = new SimpleResourceDefinition(Resource.Type.AmbariConfiguration, "ambariconfiguration", "ambariconfigurations");
-
-        break;
 
       default:
         throw new IllegalArgumentException("Unsupported resource type: " + type);

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RootServiceComponentResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RootServiceComponentResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RootServiceComponentResourceDefinition.java
index e8cb570..1c036e4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RootServiceComponentResourceDefinition.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RootServiceComponentResourceDefinition.java
@@ -19,6 +19,7 @@
 package org.apache.ambari.server.api.resources;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.ambari.server.controller.spi.Resource;
@@ -44,10 +45,12 @@ public class RootServiceComponentResourceDefinition extends
   public String getSingularName() {
     return "component";
   }
-  
+
   @Override
   public Set<SubResourceDefinition> getSubResourceDefinitions() {
-    return Collections.singleton(new SubResourceDefinition(
-        Resource.Type.RootServiceHostComponent, Collections.singleton(Resource.Type.Host), true));
+    Set<SubResourceDefinition> definitions = new HashSet<>();
+    definitions.add(new SubResourceDefinition(Resource.Type.RootServiceHostComponent, Collections.singleton(Resource.Type.Host), true));
+    definitions.add(new SubResourceDefinition(Resource.Type.RootServiceComponentConfiguration));
+    return definitions;
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationRequestSwagger.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationRequestSwagger.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationRequestSwagger.java
deleted file mode 100644
index 5e8094e..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationRequestSwagger.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed 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.ambari.server.api.services;
-
-import java.util.Map;
-
-import org.apache.ambari.server.controller.ApiModel;
-
-import io.swagger.annotations.ApiModelProperty;
-
-/**
- * Request data model for {@link org.apache.ambari.server.api.services.AmbariConfigurationService}
- */
-public interface AmbariConfigurationRequestSwagger extends ApiModel {
-
-  @ApiModelProperty(name = "AmbariConfiguration")
-  AmbariConfigurationRequestInfo getAmbariConfiguration();
-
-  interface AmbariConfigurationRequestInfo {
-    @ApiModelProperty
-    Long getId();
-
-    @ApiModelProperty
-    Map<String, Object> getData();
-
-    @ApiModelProperty
-    String getType();
-
-    @ApiModelProperty
-    Long getVersion();
-
-    @ApiModelProperty(name = "version_tag")
-    String getVersionTag();
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationResponseSwagger.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationResponseSwagger.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationResponseSwagger.java
deleted file mode 100644
index c55ac1d..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariConfigurationResponseSwagger.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed 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.ambari.server.api.services;
-
-import java.util.Map;
-
-import org.apache.ambari.server.controller.ApiModel;
-
-import io.swagger.annotations.ApiModelProperty;
-
-/**
- * Response data model for {@link org.apache.ambari.server.api.services.AmbariConfigurationService}
- */
-public interface AmbariConfigurationResponseSwagger extends ApiModel {
-
-  @ApiModelProperty(name = "AmbariConfiguration")
-  AmbariConfigurationResponseInfo getAmbariConfigurationResponse();
-
-  interface AmbariConfigurationResponseInfo {
-    @ApiModelProperty
-    Long getId();
-
-    @ApiModelProperty
-    Map<String, Object> getData();
-
-    @ApiModelProperty
-    String getType();
-  }
-}