package com.thinkbiganalytics.servicemonitor.check;
/*-
* #%L
* thinkbig-service-monitor-ambari
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* 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.
* #L%
*/
import com.google.common.collect.Collections2;
import com.thinkbiganalytics.servicemonitor.model.DefaultServiceAlert;
import com.thinkbiganalytics.servicemonitor.model.DefaultServiceComponent;
import com.thinkbiganalytics.servicemonitor.model.DefaultServiceStatusResponse;
import com.thinkbiganalytics.servicemonitor.model.ServiceAlert;
import com.thinkbiganalytics.servicemonitor.model.ServiceComponent;
import com.thinkbiganalytics.servicemonitor.model.ServiceStatusResponse;
import com.thinkbiganalytics.servicemonitor.rest.client.ambari.AmbariClient;
import com.thinkbiganalytics.servicemonitor.rest.model.ambari.Alert;
import com.thinkbiganalytics.servicemonitor.rest.model.ambari.AlertItem;
import com.thinkbiganalytics.servicemonitor.rest.model.ambari.AlertSummary;
import com.thinkbiganalytics.servicemonitor.rest.model.ambari.ServiceComponentInfoItem;
import com.thinkbiganalytics.servicemonitor.rest.model.ambari.ServiceComponentInfoSummary;
import com.thinkbiganalytics.servicemonitor.support.ServiceMonitorCheckUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.client.RestClientException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import javax.inject.Inject;
/**
* Ambari Service bean autowired in via the ServiceStatusManager looking for this ServicesStatusCheck interface
*
* Users can define what services/components to track in the kylo properties file using the following format
*
* {SERVICE_NAME}/[{COMPONENT_NAME},{COMPONENT_NAME}],{SERVICE_NAME}... COMPONENT_NAMES are optional <p> Example
* application.properties ambari.services.status=HIVE/[HIVE_CLIENT],HDFS - this will track the HIVE Service and just the
* HIVE_CLIENT ambari.services.status=HDFS,HIVE,MAPREDUCE2,SQOOP - this will track all services and all components
*/
@Configuration
@PropertySource("classpath:/conf/ambari.properties")
public class AmbariServicesStatusCheck implements ServicesStatusCheck {
private static final Logger LOG = LoggerFactory.getLogger(AmbariServicesStatusCheck.class);
@Value("${ambari.services.status:#{null}}")
private String services;
@Inject
private AmbariClient ambariClient;
private void notifyServiceDown(ServiceStatusResponse serviceStatusResponse) {
//called every time a service is marked as down/unhealthy
LOG.debug("Ambari service is {}", serviceStatusResponse.getState());
}
private void notifyServiceUp(ServiceStatusResponse serviceStatusResponse) {
//called every time a service is marked as healthy
LOG.debug("Ambari service is {}", serviceStatusResponse.getState());
}
/**
* https://github.com/apache/ambari/blob/trunk/ambari-server/docs/api/v1/host-component-resources.md
*
* State Description <br>
* INIT The initial clean state after the component is first created. <br>
* INSTALLING In the process of installing the component. <br>
* INSTALL_FAILED The component install failed. <br>
* INSTALLED The component has been installed successfully but is not currently running. <br>
* STARTING In the process of starting the component. <br>
* STARTED The component has been installed and started. <br>
* STOPPING In the process of stopping the component. <br>
* UNINSTALLING In the process of uninstalling the component. <br>
* UNINSTALLED The component has been successfully uninstalled. <br>
* WIPING_OUT In the process of wiping out the installed component. <br>
* UPGRADING In the process of upgrading the component. <br>
* MAINTENANCE The component has been marked for maintenance. <br>
* UNKNOWN The component state can not be determined. <br>
*/
@Override
public List<ServiceStatusResponse> healthCheck() {
List<ServiceStatusResponse> serviceStatusResponseList = new ArrayList<>();
//Get the Map of Services and optional Components we are tracking
Map<String, List<String>> definedServiceComponentMap = ServiceMonitorCheckUtil.getMapOfServiceAndComponents(services);
if (definedServiceComponentMap != null && !definedServiceComponentMap.isEmpty()) {
try {
AmbariClient client = ambariClient;
//get the Clusers from ambari
List<String> clusterNames = client.getAmbariClusterNames();
//get the Service Status from Ambari
ServiceComponentInfoSummary response = client.getServiceComponentInfo(clusterNames, services);
//get alert info for these services as well
AlertSummary alertSummary = client.getAlerts(clusterNames, services);
//Convert the Ambari Alerts to the Pipeline Controller Alert
List<ServiceAlert> serviceAlerts = transformAmbariAlert(alertSummary);
//Convert the Ambari ServiceComponentInfo objects to Pipeline Controller ServiceComponents
serviceStatusResponseList = transformAmbariServiceComponents(response, serviceAlerts, definedServiceComponentMap);
} catch (RestClientException e) {
Throwable cause;
if (e.getCause() != null) {
cause = e.getCause();
} else {
cause = e;
}
ServiceComponent ambariServiceComponent = new DefaultServiceComponent.Builder("Unknown", "Ambari", "Ambari REST_CLIENT", ServiceComponent.STATE.DOWN).exception(cause).build();
List<ServiceComponent> ambariComponents = new ArrayList<>();
ambariComponents.add(ambariServiceComponent);
ServiceStatusResponse
serviceStatusResponse =
new DefaultServiceStatusResponse(ambariServiceComponent.getServiceName(), ambariComponents);
serviceStatusResponseList.add(serviceStatusResponse);
//add the other services as being Warnings
addAmbariServiceErrors(cause.getMessage(), serviceStatusResponseList, definedServiceComponentMap);
}
}
return serviceStatusResponseList;
}
/**
* Convert Ambari ServiceComponentInfo into ServiceComponent objects
*/
private List<ServiceStatusResponse> transformAmbariServiceComponents(ServiceComponentInfoSummary ambariServiceComponents,
List<ServiceAlert> serviceAlerts,
Map<String, List<String>> definedServiceComponentMap) {
List<ServiceStatusResponse> list = new ArrayList<>();
if (ambariServiceComponents != null) {
Map<String, List<ServiceComponent>> serviceComponentMap = new HashMap<>();
for (ServiceComponentInfoItem item : ambariServiceComponents.getItems()) {
ServiceComponent.STATE state = getServiceComponentState(item);
String message = item.getServiceComponentInfo().getState();
String name = item.getServiceComponentInfo().getComponentName();
String serviceName = item.getServiceComponentInfo().getServiceName();
String clusterName = item.getServiceComponentInfo().getClusterName();
ServiceComponent
component =
new DefaultServiceComponent.Builder(clusterName, serviceName, name, state)
.alerts(alertsForComponent(serviceAlerts, item.getServiceComponentInfo().getComponentName())).message(message)
.build();
if (!serviceComponentMap.containsKey(component.getServiceName())) {
serviceComponentMap.put(component.getServiceName(), new ArrayList<>());
}
if (definedServiceComponentMap.get(component.getServiceName()).contains(ServiceMonitorCheckUtil.ALL_COMPONENTS)
|| definedServiceComponentMap.get(component.getServiceName()).contains(component.getName())) {
serviceComponentMap.get(component.getServiceName()).add(component);
}
}
//build the response
for (Map.Entry<String, List<ServiceComponent>> entry : serviceComponentMap.entrySet()) {
List<ServiceAlert> alertsForService = alertsForService(serviceAlerts, entry.getKey());
ServiceStatusResponse
serviceStatusResponse =
new DefaultServiceStatusResponse(entry.getKey(), entry.getValue(), alertsForService);
if (ServiceStatusResponse.STATE.DOWN.equals(serviceStatusResponse.getState())) {
notifyServiceDown(serviceStatusResponse);
} else if (ServiceStatusResponse.STATE.UP.equals(serviceStatusResponse.getState())) {
notifyServiceUp(serviceStatusResponse);
}
list.add(serviceStatusResponse);
}
}
return list;
}
/**
* add ambari Service errors to the supplied list
*/
private void addAmbariServiceErrors(String exceptionMessage, List<ServiceStatusResponse> list,
Map<String, List<String>> definedServiceComponentMap) {
if (definedServiceComponentMap != null && !definedServiceComponentMap.isEmpty()) {
String message = "Status Unknown. Unable to check service. Ambari connection error: " + exceptionMessage;
for (Map.Entry<String, List<String>> entry : definedServiceComponentMap.entrySet()) {
String serviceName = entry.getKey();
List<String> componentNames = entry.getValue();
List<ServiceComponent> components = new ArrayList<>();
if (componentNames != null && !componentNames.isEmpty()) {
for (String componentName : componentNames) {
if (ServiceMonitorCheckUtil.ALL_COMPONENTS.equals(componentName)) {
componentName = serviceName;
}
ServiceComponent
serviceComponent =
new DefaultServiceComponent.Builder("Unknown", serviceName, componentName, ServiceComponent.STATE.UNKNOWN)
.message(message).build();
components.add(serviceComponent);
}
} else {
//add the component based uppon the Service Name
ServiceComponent
serviceComponent =
new DefaultServiceComponent.Builder("Unknown", serviceName, serviceName, ServiceComponent.STATE.UNKNOWN)
.message(message).build();
components.add(serviceComponent);
}
ServiceStatusResponse serviceStatusResponse = new DefaultServiceStatusResponse(serviceName, components);
list.add(serviceStatusResponse);
}
}
}
/**
* for a given Ambari component and state return the respective Component state
*
* @return the state of the component
*/
private ServiceComponent.STATE getServiceComponentState(ServiceComponentInfoItem serviceComponentInfoItem) {
ServiceComponent.STATE state = ServiceComponent.STATE.DOWN;
serviceComponentInfoItem.updateServiceComponentInfoState();
String ambariState = serviceComponentInfoItem.getServiceComponentInfo().getState();
//check for category
boolean isClient = serviceComponentInfoItem.getServiceComponentInfo().getCategory().equalsIgnoreCase("CLIENT");
if (isClient && "INSTALLED".equals(ambariState)) {
state = ServiceComponent.STATE.UP;
} else {
if ("STARTING".equalsIgnoreCase(ambariState)) {
state = ServiceComponent.STATE.STARTING;
} else if ("STARTED".equalsIgnoreCase(ambariState)) {
state = ServiceComponent.STATE.UP;
} else if ("UNKNOWN".equalsIgnoreCase(ambariState)) {
state = ServiceComponent.STATE.UNKNOWN;
}
}
return state;
}
/**
* return a matching List of ServiceAlerts based upon the incoming component name
*/
private List<ServiceAlert> alertsForComponent(List<ServiceAlert> alerts, final String component) {
Predicate<ServiceAlert> predicate = alert -> alert.getComponentName() != null && alert.getComponentName().equals(component);
Collection<ServiceAlert> matchingAlerts = Collections2.filter(alerts, predicate::test);
if (matchingAlerts != null && !matchingAlerts.isEmpty()) {
return new ArrayList<>(matchingAlerts);
}
return null;
}
/**
* get the list of alerts for a give service name
*/
private List<ServiceAlert> alertsForService(List<ServiceAlert> alerts, final String service) {
Predicate<ServiceAlert> predicate = alert -> alert.getServiceName() != null && alert.getServiceName().equals(service);
Collection<ServiceAlert> matchingAlerts = Collections2.filter(alerts, predicate::test);
if (matchingAlerts != null && !matchingAlerts.isEmpty()) {
return new ArrayList<>(matchingAlerts);
}
return null;
}
/**
* Transform the ambari alerts to Kylo service alerts
*/
private List<ServiceAlert> transformAmbariAlert(AlertSummary alertSummary) {
List<ServiceAlert> serviceAlerts = new ArrayList<>();
if (alertSummary != null) {
List<AlertItem> alertItems = alertSummary.getItems();
if (alertItems != null) {
for (AlertItem alertItem : alertItems) {
Alert alert = alertItem.getAlert();
ServiceAlert serviceAlert = new DefaultServiceAlert();
serviceAlert.setServiceName(alertItem.getAlert().getServiceName());
serviceAlert.setComponentName(alert.getComponentName());
serviceAlert.setFirstTimestamp(new Date(alert.getOriginalTimestamp()));
serviceAlert.setLatestTimestamp(new Date(alert.getLatestTimestamp()));
serviceAlert.setLabel(alert.getLabel());
serviceAlert.setMessage(alert.getText());
if (StringUtils.isNotBlank(alert.getState())) {
try {
serviceAlert.setState(ServiceAlert.STATE.valueOf(alert.getState()));
} catch (IllegalArgumentException e) {
serviceAlert.setState(ServiceAlert.STATE.UNKNOWN);
}
} else {
serviceAlert.setState(ServiceAlert.STATE.UNKNOWN);
}
serviceAlerts.add(serviceAlert);
}
}
}
return serviceAlerts;
}
protected void setServices(String services) {
this.services = services;
}
}