/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.yarn.server.timeline; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; import org.apache.hadoop.service.AbstractService; import org.apache.hadoop.yarn.api.records.timeline.TimelineDomain; import org.apache.hadoop.yarn.api.records.timeline.TimelineDomains; import org.apache.hadoop.yarn.api.records.timeline.TimelineEntities; import org.apache.hadoop.yarn.api.records.timeline.TimelineEntity; import org.apache.hadoop.yarn.api.records.timeline.TimelineEvent; import org.apache.hadoop.yarn.api.records.timeline.TimelineEvents; import org.apache.hadoop.yarn.api.records.timeline.TimelineEvents.EventsOfOneEntity; import org.apache.hadoop.yarn.api.records.timeline.TimelinePutResponse; import org.apache.hadoop.yarn.api.records.timeline.TimelinePutResponse.TimelinePutError; import org.apache.hadoop.yarn.server.timeline.TimelineDataManager.CheckAcl; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import static org.apache.hadoop.yarn.server.timeline.TimelineDataManager.DEFAULT_DOMAIN_ID; /** * Map based implementation of {@link TimelineStore}. A hash map * implementation should be connected to this implementation through a * {@link TimelineStoreMapAdapter}. * * The methods are synchronized to avoid concurrent modifications. * */ @Private @Unstable abstract class KeyValueBasedTimelineStore extends AbstractService implements TimelineStore { protected TimelineStoreMapAdapter<EntityIdentifier, TimelineEntity> entities; protected TimelineStoreMapAdapter<EntityIdentifier, Long> entityInsertTimes; protected TimelineStoreMapAdapter<String, TimelineDomain> domainById; protected TimelineStoreMapAdapter<String, Set<TimelineDomain>> domainsByOwner; private boolean serviceStopped = false; private static final Log LOG = LogFactory.getLog(KeyValueBasedTimelineStore.class); public KeyValueBasedTimelineStore() { super(KeyValueBasedTimelineStore.class.getName()); } public KeyValueBasedTimelineStore(String name) { super(name); } public synchronized boolean getServiceStopped() { return serviceStopped; } @Override protected synchronized void serviceStop() throws Exception { serviceStopped = true; super.serviceStop(); } @Override public synchronized TimelineEntities getEntities(String entityType, Long limit, Long windowStart, Long windowEnd, String fromId, Long fromTs, NameValuePair primaryFilter, Collection<NameValuePair> secondaryFilters, EnumSet<Field> fields, CheckAcl checkAcl) throws IOException { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return null; } if (limit == null) { limit = DEFAULT_LIMIT; } if (windowStart == null) { windowStart = Long.MIN_VALUE; } if (windowEnd == null) { windowEnd = Long.MAX_VALUE; } if (fields == null) { fields = EnumSet.allOf(Field.class); } Iterator<TimelineEntity> entityIterator = null; if (fromId != null) { TimelineEntity firstEntity = entities.get(new EntityIdentifier(fromId, entityType)); if (firstEntity == null) { return new TimelineEntities(); } else { entityIterator = entities.valueSetIterator(firstEntity); } } if (entityIterator == null) { entityIterator = entities.valueSetIterator(); } List<TimelineEntity> entitiesSelected = new ArrayList<TimelineEntity>(); while (entityIterator.hasNext()) { TimelineEntity entity = entityIterator.next(); if (entitiesSelected.size() >= limit) { break; } if (!entity.getEntityType().equals(entityType)) { continue; } if (entity.getStartTime() <= windowStart) { continue; } if (entity.getStartTime() > windowEnd) { continue; } if (fromTs != null && entityInsertTimes.get(new EntityIdentifier( entity.getEntityId(), entity.getEntityType())) > fromTs) { continue; } if (primaryFilter != null && !KeyValueBasedTimelineStoreUtils.matchPrimaryFilter( entity.getPrimaryFilters(), primaryFilter)) { continue; } if (secondaryFilters != null) { // AND logic boolean flag = true; for (NameValuePair secondaryFilter : secondaryFilters) { if (secondaryFilter != null && !KeyValueBasedTimelineStoreUtils .matchPrimaryFilter(entity.getPrimaryFilters(), secondaryFilter) && !KeyValueBasedTimelineStoreUtils.matchFilter( entity.getOtherInfo(), secondaryFilter)) { flag = false; break; } } if (!flag) { continue; } } if (entity.getDomainId() == null) { entity.setDomainId(DEFAULT_DOMAIN_ID); } if (checkAcl == null || checkAcl.check(entity)) { entitiesSelected.add(entity); } } List<TimelineEntity> entitiesToReturn = new ArrayList<TimelineEntity>(); for (TimelineEntity entitySelected : entitiesSelected) { entitiesToReturn.add(KeyValueBasedTimelineStoreUtils.maskFields( entitySelected, fields)); } Collections.sort(entitiesToReturn); TimelineEntities entitiesWrapper = new TimelineEntities(); entitiesWrapper.setEntities(entitiesToReturn); return entitiesWrapper; } @Override public synchronized TimelineEntity getEntity(String entityId, String entityType, EnumSet<Field> fieldsToRetrieve) { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return null; } if (fieldsToRetrieve == null) { fieldsToRetrieve = EnumSet.allOf(Field.class); } TimelineEntity entity = entities.get(new EntityIdentifier(entityId, entityType)); if (entity == null) { return null; } else { return KeyValueBasedTimelineStoreUtils.maskFields( entity, fieldsToRetrieve); } } @Override public synchronized TimelineEvents getEntityTimelines(String entityType, SortedSet<String> entityIds, Long limit, Long windowStart, Long windowEnd, Set<String> eventTypes) { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return null; } TimelineEvents allEvents = new TimelineEvents(); if (entityIds == null) { return allEvents; } if (limit == null) { limit = DEFAULT_LIMIT; } if (windowStart == null) { windowStart = Long.MIN_VALUE; } if (windowEnd == null) { windowEnd = Long.MAX_VALUE; } for (String entityId : entityIds) { EntityIdentifier entityID = new EntityIdentifier(entityId, entityType); TimelineEntity entity = entities.get(entityID); if (entity == null) { continue; } EventsOfOneEntity events = new EventsOfOneEntity(); events.setEntityId(entityId); events.setEntityType(entityType); for (TimelineEvent event : entity.getEvents()) { if (events.getEvents().size() >= limit) { break; } if (event.getTimestamp() <= windowStart) { continue; } if (event.getTimestamp() > windowEnd) { continue; } if (eventTypes != null && !eventTypes.contains(event.getEventType())) { continue; } events.addEvent(event); } allEvents.addEvent(events); } return allEvents; } @Override public TimelineDomain getDomain(String domainId) throws IOException { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return null; } TimelineDomain domain = domainById.get(domainId); if (domain == null) { return null; } else { return KeyValueBasedTimelineStoreUtils.createTimelineDomain( domain.getId(), domain.getDescription(), domain.getOwner(), domain.getReaders(), domain.getWriters(), domain.getCreatedTime(), domain.getModifiedTime()); } } @Override public TimelineDomains getDomains(String owner) throws IOException { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return null; } List<TimelineDomain> domains = new ArrayList<TimelineDomain>(); Set<TimelineDomain> domainsOfOneOwner = domainsByOwner.get(owner); if (domainsOfOneOwner == null) { return new TimelineDomains(); } for (TimelineDomain domain : domainsByOwner.get(owner)) { TimelineDomain domainToReturn = KeyValueBasedTimelineStoreUtils .createTimelineDomain( domain.getId(), domain.getDescription(), domain.getOwner(), domain.getReaders(), domain.getWriters(), domain.getCreatedTime(), domain.getModifiedTime()); domains.add(domainToReturn); } Collections.sort(domains, new Comparator<TimelineDomain>() { @Override public int compare( TimelineDomain domain1, TimelineDomain domain2) { int result = domain2.getCreatedTime().compareTo( domain1.getCreatedTime()); if (result == 0) { return domain2.getModifiedTime().compareTo( domain1.getModifiedTime()); } else { return result; } } }); TimelineDomains domainsToReturn = new TimelineDomains(); domainsToReturn.addDomains(domains); return domainsToReturn; } @Override public synchronized TimelinePutResponse put(TimelineEntities data) { TimelinePutResponse response = new TimelinePutResponse(); if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); TimelinePutError error = new TimelinePutError(); error.setErrorCode(TimelinePutError.IO_EXCEPTION); response.addError(error); return response; } for (TimelineEntity entity : data.getEntities()) { EntityIdentifier entityId = new EntityIdentifier(entity.getEntityId(), entity.getEntityType()); // store entity info in memory TimelineEntity existingEntity = entities.get(entityId); boolean needsPut = false; if (existingEntity == null) { existingEntity = new TimelineEntity(); existingEntity.setEntityId(entity.getEntityId()); existingEntity.setEntityType(entity.getEntityType()); existingEntity.setStartTime(entity.getStartTime()); if (entity.getDomainId() == null || entity.getDomainId().length() == 0) { TimelinePutError error = new TimelinePutError(); error.setEntityId(entityId.getId()); error.setEntityType(entityId.getType()); error.setErrorCode(TimelinePutError.NO_DOMAIN); response.addError(error); continue; } existingEntity.setDomainId(entity.getDomainId()); // insert a new entity to the storage, update insert time map entityInsertTimes.put(entityId, System.currentTimeMillis()); needsPut = true; } if (entity.getEvents() != null) { if (existingEntity.getEvents() == null) { existingEntity.setEvents(entity.getEvents()); } else { existingEntity.addEvents(entity.getEvents()); } Collections.sort(existingEntity.getEvents()); needsPut = true; } // check startTime if (existingEntity.getStartTime() == null) { if (existingEntity.getEvents() == null || existingEntity.getEvents().isEmpty()) { TimelinePutError error = new TimelinePutError(); error.setEntityId(entityId.getId()); error.setEntityType(entityId.getType()); error.setErrorCode(TimelinePutError.NO_START_TIME); response.addError(error); entities.remove(entityId); entityInsertTimes.remove(entityId); continue; } else { Long min = Long.MAX_VALUE; for (TimelineEvent e : entity.getEvents()) { if (min > e.getTimestamp()) { min = e.getTimestamp(); } } existingEntity.setStartTime(min); needsPut = true; } } if (entity.getPrimaryFilters() != null) { if (existingEntity.getPrimaryFilters() == null) { existingEntity.setPrimaryFilters(new HashMap<String, Set<Object>>()); } for (Entry<String, Set<Object>> pf : entity.getPrimaryFilters().entrySet()) { for (Object pfo : pf.getValue()) { existingEntity.addPrimaryFilter(pf.getKey(), KeyValueBasedTimelineStoreUtils.compactNumber(pfo)); needsPut = true; } } } if (entity.getOtherInfo() != null) { if (existingEntity.getOtherInfo() == null) { existingEntity.setOtherInfo(new HashMap<String, Object>()); } for (Entry<String, Object> info : entity.getOtherInfo().entrySet()) { existingEntity.addOtherInfo(info.getKey(), KeyValueBasedTimelineStoreUtils.compactNumber(info.getValue())); needsPut = true; } } if (needsPut) { entities.put(entityId, existingEntity); } // relate it to other entities if (entity.getRelatedEntities() == null) { continue; } for (Entry<String, Set<String>> partRelatedEntities : entity .getRelatedEntities().entrySet()) { if (partRelatedEntities == null) { continue; } for (String idStr : partRelatedEntities.getValue()) { EntityIdentifier relatedEntityId = new EntityIdentifier(idStr, partRelatedEntities.getKey()); TimelineEntity relatedEntity = entities.get(relatedEntityId); if (relatedEntity != null) { if (relatedEntity.getDomainId().equals( existingEntity.getDomainId())) { relatedEntity.addRelatedEntity( existingEntity.getEntityType(), existingEntity.getEntityId()); entities.put(relatedEntityId, relatedEntity); } else { // in this case the entity will be put, but the relation will be // ignored TimelinePutError error = new TimelinePutError(); error.setEntityType(existingEntity.getEntityType()); error.setEntityId(existingEntity.getEntityId()); error.setErrorCode(TimelinePutError.FORBIDDEN_RELATION); response.addError(error); } } else { relatedEntity = new TimelineEntity(); relatedEntity.setEntityId(relatedEntityId.getId()); relatedEntity.setEntityType(relatedEntityId.getType()); relatedEntity.setStartTime(existingEntity.getStartTime()); relatedEntity.addRelatedEntity(existingEntity.getEntityType(), existingEntity.getEntityId()); relatedEntity.setDomainId(existingEntity.getDomainId()); entities.put(relatedEntityId, relatedEntity); entityInsertTimes.put(relatedEntityId, System.currentTimeMillis()); } } } } return response; } public void put(TimelineDomain domain) throws IOException { if (getServiceStopped()) { LOG.info("Service stopped, return null for the storage"); return; } TimelineDomain domainToReplace = domainById.get(domain.getId()); Long currentTimestamp = System.currentTimeMillis(); TimelineDomain domainToStore = KeyValueBasedTimelineStoreUtils.createTimelineDomain( domain.getId(), domain.getDescription(), domain.getOwner(), domain.getReaders(), domain.getWriters(), (domainToReplace == null ? currentTimestamp : domainToReplace.getCreatedTime()), currentTimestamp); domainById.put(domainToStore.getId(), domainToStore); Set<TimelineDomain> domainsByOneOwner = domainsByOwner.get(domainToStore.getOwner()); if (domainsByOneOwner == null) { domainsByOneOwner = new HashSet<TimelineDomain>(); domainsByOwner.put(domainToStore.getOwner(), domainsByOneOwner); } if (domainToReplace != null) { domainsByOneOwner.remove(domainToReplace); } domainsByOneOwner.add(domainToStore); } private static class KeyValueBasedTimelineStoreUtils { static TimelineDomain createTimelineDomain( String id, String description, String owner, String readers, String writers, Long createdTime, Long modifiedTime) { TimelineDomain domainToStore = new TimelineDomain(); domainToStore.setId(id); domainToStore.setDescription(description); domainToStore.setOwner(owner); domainToStore.setReaders(readers); domainToStore.setWriters(writers); domainToStore.setCreatedTime(createdTime); domainToStore.setModifiedTime(modifiedTime); return domainToStore; } static TimelineEntity maskFields( TimelineEntity entity, EnumSet<Field> fields) { // Conceal the fields that are not going to be exposed TimelineEntity entityToReturn = new TimelineEntity(); entityToReturn.setEntityId(entity.getEntityId()); entityToReturn.setEntityType(entity.getEntityType()); entityToReturn.setStartTime(entity.getStartTime()); entityToReturn.setDomainId(entity.getDomainId()); // Deep copy if (fields.contains(Field.EVENTS)) { entityToReturn.addEvents(entity.getEvents()); } else if (fields.contains(Field.LAST_EVENT_ONLY)) { entityToReturn.addEvent(entity.getEvents().get(0)); } else { entityToReturn.setEvents(null); } if (fields.contains(Field.RELATED_ENTITIES)) { entityToReturn.addRelatedEntities(entity.getRelatedEntities()); } else { entityToReturn.setRelatedEntities(null); } if (fields.contains(Field.PRIMARY_FILTERS)) { entityToReturn.addPrimaryFilters(entity.getPrimaryFilters()); } else { entityToReturn.setPrimaryFilters(null); } if (fields.contains(Field.OTHER_INFO)) { entityToReturn.addOtherInfo(entity.getOtherInfo()); } else { entityToReturn.setOtherInfo(null); } return entityToReturn; } static boolean matchFilter(Map<String, Object> tags, NameValuePair filter) { Object value = tags.get(filter.getName()); if (value == null) { // doesn't have the filter return false; } else if (!value.equals(filter.getValue())) { // doesn't match the filter return false; } return true; } static boolean matchPrimaryFilter(Map<String, Set<Object>> tags, NameValuePair filter) { Set<Object> value = tags.get(filter.getName()); if (value == null) { // doesn't have the filter return false; } else { return value.contains(filter.getValue()); } } static Object compactNumber(Object o) { if (o instanceof Long) { Long l = (Long) o; if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { return l.intValue(); } } return o; } } }