/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.pinsetter.tasks;
import static org.quartz.JobBuilder.newJob;
import org.candlepin.auth.Principal;
import org.candlepin.common.exceptions.BadRequestException;
import org.candlepin.common.filter.LoggingFilter;
import org.candlepin.model.Consumer;
import org.candlepin.model.ConsumerCurator;
import org.candlepin.model.ConsumerType;
import org.candlepin.model.ConsumerType.ConsumerTypeEnum;
import org.candlepin.model.GuestId;
import org.candlepin.model.HypervisorId;
import org.candlepin.model.JobCurator;
import org.candlepin.model.Owner;
import org.candlepin.model.OwnerCurator;
import org.candlepin.model.VirtConsumerMap;
import org.candlepin.pinsetter.core.model.JobStatus;
import org.candlepin.resource.ConsumerResource;
import org.candlepin.resource.dto.HypervisorUpdateResult;
import org.candlepin.util.Util;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.MDC;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
/**
* Asynchronous job for refreshing the entitlement pools for specific
* {@link Owner}. A job will wait for a running job of the same Owner to
* finish before beginning execution
*/
public class HypervisorUpdateJob extends KingpinJob {
private static Logger log = LoggerFactory.getLogger(HypervisorUpdateJob.class);
private OwnerCurator ownerCurator;
private ConsumerCurator consumerCurator;
private ConsumerResource consumerResource;
private I18n i18n;
public static final String CREATE = "create";
public static final String REPORTER_ID = "reporter_id";
public static final String DATA = "data";
public static final String PRINCIPAL = "principal";
protected static String prefix = "hypervisor_update_";
@Inject
public HypervisorUpdateJob(OwnerCurator ownerCurator, ConsumerCurator consumerCurator,
ConsumerResource consumerResource, I18n i18n) {
this.ownerCurator = ownerCurator;
this.consumerCurator = consumerCurator;
this.consumerResource = consumerResource;
this.i18n = i18n;
}
public static JobStatus scheduleJob(JobCurator jobCurator,
Scheduler scheduler, JobDetail detail,
Trigger trigger) throws SchedulerException {
JobStatus result = jobCurator.getByClassAndTarget(
detail.getJobDataMap().getString(JobStatus.TARGET_ID),
HypervisorUpdateJob.class);
if (result == null) {
return KingpinJob.scheduleJob(jobCurator, scheduler, detail, trigger);
}
log.debug("Scheduling job without a trigger: " + detail.getKey().getName());
JobStatus status = KingpinJob.scheduleJob(jobCurator, scheduler, detail, null);
return status;
}
public static boolean isSchedulable(JobCurator jobCurator, JobStatus status) {
long running = jobCurator.findNumRunningByClassAndTarget(
status.getTargetId(), HypervisorUpdateJob.class);
return running == 0; // We can start the job if there are 0 like it running
}
private void parseHypervisorList(HypervisorList hypervisorList, Set<String> hosts,
Set<String> guests, Map<String, Consumer> incomingHosts) {
int emptyGuestIdCount = 0;
int emptyHypervisorIdCount = 0;
List<Consumer> l = hypervisorList.getHypervisors();
for (Iterator<Consumer> hypervisors = l.iterator(); hypervisors.hasNext();) {
Consumer hypervisor = hypervisors.next();
HypervisorId idWrapper = hypervisor.getHypervisorId();
if (idWrapper == null) {
continue;
}
String id = idWrapper.getHypervisorId();
if (id == null) {
continue;
}
if ("".equals(id)) {
hypervisors.remove();
emptyHypervisorIdCount++;
continue;
}
incomingHosts.put(id, hypervisor);
hosts.add(id);
List<GuestId> guestsIdList = hypervisor.getGuestIds();
if (guestsIdList == null || guestsIdList.isEmpty()) {
continue;
}
for (Iterator<GuestId> guestIds = guestsIdList.iterator(); guestIds.hasNext();) {
GuestId guestId = guestIds.next();
if (StringUtils.isEmpty(guestId.getGuestId())) {
guestIds.remove();
emptyGuestIdCount++;
}
else {
guests.add(guestId.getGuestId());
}
}
}
if (emptyHypervisorIdCount > 0) {
log.debug("Ignoring {} hypervisors with empty hypervisor IDs", emptyHypervisorIdCount);
}
if (emptyGuestIdCount > 0) {
log.debug("Ignoring {} empty/null guestId(s)", emptyGuestIdCount);
}
}
/**
* {@inheritDoc}
*
* Executes {@link ConsumerResource#create(org.candlepin.model.Consumer, org.candlepin.auth.Principal,
* java.utl.String, java.utl.String, java.utl.String)}
* Executes (@link ConusmerResource#performConsumerUpdates(java.utl.String, org.candlepin.model.Consumer)}
* as a pinsetter job.
*
* @param context the job's execution context
*/
@Transactional
@SuppressWarnings("checkstyle:indentation")
public void toExecute(JobExecutionContext context) throws JobExecutionException {
try {
JobDataMap map = context.getMergedJobDataMap();
String ownerKey = map.getString(JobStatus.TARGET_ID);
Boolean create = map.getBoolean(CREATE);
Principal principal = (Principal) map.get(PRINCIPAL);
String jobReporterId = map.getString(REPORTER_ID);
HypervisorUpdateResult result = new HypervisorUpdateResult();
Owner owner = ownerCurator.lookupByKey(ownerKey);
if (owner == null) {
context.setResult("Nothing to do. Owner does not exist");
log.warn("Hypervisor update attempted against non-existent org id ''{0}''", ownerKey);
return;
}
if (owner.autobindDisabled()) {
log.debug("Could not update host/guest mapping. Auto-Attach is disabled for owner {}",
owner.getKey());
throw new BadRequestException(
i18n.tr("Could not update host/guest mapping. Auto-attach is disabled for owner {0}.",
owner.getKey()));
}
byte[] data = (byte[]) map.get(DATA);
String json = decompress(data);
HypervisorList hypervisors = (HypervisorList) Util.fromJson(json, HypervisorList.class);
log.debug("Hypervisor consumers for create/update: {}", hypervisors.getHypervisors().size());
log.debug("Updating hypervisor consumers for org {0}", ownerKey);
Set<String> hosts = new HashSet<String>();
Set<String> guests = new HashSet<String>();
Map<String, Consumer> incomingHosts = new HashMap<String, Consumer>();
parseHypervisorList(hypervisors, hosts, guests, incomingHosts);
// Maps virt hypervisor ID to registered consumer for that hypervisor, should one exist:
VirtConsumerMap hypervisorConsumersMap = consumerCurator.getHostConsumersMap(owner, hosts);
// Maps virt guest ID to registered consumer for guest, if one exists:
VirtConsumerMap guestConsumersMap = consumerCurator.getGuestConsumersMap(owner, guests);
for (String hypervisorId : hosts) {
Consumer knownHost = hypervisorConsumersMap.get(hypervisorId);
Consumer incoming = incomingHosts.get(hypervisorId);
Consumer reportedOnConsumer = null;
if (knownHost == null) {
if (!create) {
result.failed(hypervisorId, "Unable to find hypervisor with id " +
hypervisorId + " in org " + ownerKey);
}
else {
log.debug("Registering new host consumer for hypervisor ID: {}", hypervisorId);
Consumer newHost = createConsumerForHypervisorId(hypervisorId, owner, principal);
consumerResource.performConsumerUpdates(incoming, newHost, guestConsumersMap, false);
consumerResource.create(newHost, principal, null, owner.getKey(), null, false);
hypervisorConsumersMap.add(hypervisorId, newHost);
result.created(updateCheckinTime(newHost));
reportedOnConsumer = newHost;
}
}
else {
reportedOnConsumer = knownHost;
if (jobReporterId != null && knownHost.getHypervisorId() != null &&
hypervisorId.equalsIgnoreCase(knownHost.getHypervisorId().getHypervisorId()) &&
knownHost.getHypervisorId().getReporterId() != null &&
!jobReporterId.equalsIgnoreCase(knownHost.getHypervisorId().getReporterId())) {
log.debug("Reporter changed for Hypervisor {} of Owner {} from {} to {}",
hypervisorId, ownerKey, knownHost.getHypervisorId().getReporterId(),
jobReporterId);
}
if (consumerResource.performConsumerUpdates(incoming, knownHost, guestConsumersMap,
false)) {
consumerCurator.update(knownHost);
result.updated(updateCheckinTime(knownHost));
}
else {
result.unchanged(updateCheckinTime(knownHost));
}
}
// update reporter id if it changed
if (jobReporterId != null && reportedOnConsumer != null &&
reportedOnConsumer.getHypervisorId() != null &&
(reportedOnConsumer.getHypervisorId().getReporterId() == null ||
!jobReporterId.contentEquals(reportedOnConsumer.getHypervisorId().getReporterId()))) {
reportedOnConsumer.getHypervisorId().setReporterId(jobReporterId);
}
else if (jobReporterId == null) {
log.debug("hypervisor checkin reported asynchronously without reporter id " +
"for hypervisor:{} of owner:{}", hypervisorId, ownerKey);
}
}
log.info("Summary for report from {} by principal {}\n {}", jobReporterId, principal, result);
context.setResult(result);
}
catch (Exception e) {
log.error("HypervisorUpdateJob encountered a problem.", e);
context.setResult(e.getMessage());
throw new JobExecutionException(e.getMessage(), e, false);
}
}
/**
* Creates a {@link JobDetail} that runs this job for the given {@link Owner}.
*
* @param owner the owner to refresh
* @return a {@link JobDetail} that describes the job run
*/
public static JobDetail forOwner(Owner owner, String data, Boolean create, Principal principal,
String reporterId) {
JobDataMap map = new JobDataMap();
map.put(JobStatus.TARGET_TYPE, JobStatus.TargetType.OWNER);
map.put(JobStatus.TARGET_ID, owner.getKey());
map.put(JobStatus.OWNER_ID, owner.getKey());
map.put(CREATE, create);
map.put(DATA, compress(data));
map.put(PRINCIPAL, principal);
if (reporterId != null) {
map.put(REPORTER_ID, reporterId);
}
map.put(JobStatus.CORRELATION_ID, MDC.get(LoggingFilter.CSID));
// Not sure if this is the best way to go:
// Give each job a UUID to ensure that it is unique
JobDetail detail = newJob(HypervisorUpdateJob.class)
.withIdentity(prefix + Util.generateUUID())
.requestRecovery(true) // recover the job upon restarts
.usingJobData(map)
.storeDurably(true) // required if we have to postpone the job
.build();
return detail;
}
public static byte[] compress(String text) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
OutputStream out = new DeflaterOutputStream(baos);
out.write(text.getBytes("UTF-8"));
out.close();
}
catch (IOException e) {
throw new AssertionError(e);
}
return baos.toByteArray();
}
public static String decompress(byte[] bytes) {
InputStream in = new InflaterInputStream(new ByteArrayInputStream(bytes));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
return new String(baos.toByteArray(), "UTF-8");
}
catch (IOException e) {
throw new AssertionError(e);
}
}
/*
* Create a new hypervisor type consumer to represent the incoming hypervisorId
*/
private Consumer createConsumerForHypervisorId(String incHypervisorId,
Owner owner, Principal principal) {
Consumer consumer = new Consumer();
consumer.setName(incHypervisorId);
consumer.setType(new ConsumerType(ConsumerTypeEnum.HYPERVISOR));
consumer.setFact("uname.machine", "x86_64");
consumer.setGuestIds(new ArrayList<GuestId>());
consumer.setOwner(owner);
// Create HypervisorId
HypervisorId hypervisorId = new HypervisorId(consumer, incHypervisorId);
consumer.setHypervisorId(hypervisorId);
return consumer;
}
/**
* Class for holding the list of consumers in the stored json text
*
* @author wpoteat
*
*/
public static class HypervisorList{
private List<Consumer> hypervisors;
public HypervisorList() {
}
public List<Consumer> getHypervisors() {
return this.hypervisors;
}
public void setConsumers(List<Consumer> hypervisors) {
this.hypervisors = hypervisors;
}
}
public Consumer updateCheckinTime(Consumer consumer) {
Date now = new Date();
consumerCurator.updateLastCheckin(consumer, now);
consumer.setLastCheckin(now);
return consumer;
}
}