/**
* 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.resource;
import org.candlepin.auth.Access;
import org.candlepin.auth.Principal;
import org.candlepin.auth.SubResource;
import org.candlepin.auth.Verify;
import org.candlepin.common.exceptions.BadRequestException;
import org.candlepin.common.exceptions.NotFoundException;
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.Owner;
import org.candlepin.model.OwnerCurator;
import org.candlepin.model.VirtConsumerMap;
import org.candlepin.pinsetter.tasks.HypervisorUpdateJob;
import org.candlepin.resource.dto.HypervisorCheckInResult;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import org.apache.commons.lang.StringUtils;
import org.quartz.JobDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
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 javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
/**
* HypervisorResource
*/
@Path("/hypervisors")
@Api(value = "hypervisors", authorizations = { @Authorization("basic") })
public class HypervisorResource {
private static Logger log = LoggerFactory.getLogger(HypervisorResource.class);
private ConsumerCurator consumerCurator;
private ConsumerResource consumerResource;
private I18n i18n;
private OwnerCurator ownerCurator;
@Inject
public HypervisorResource(ConsumerResource consumerResource,
ConsumerCurator consumerCurator, I18n i18n, OwnerCurator ownerCurator) {
this.consumerResource = consumerResource;
this.consumerCurator = consumerCurator;
this.i18n = i18n;
this.ownerCurator = ownerCurator;
}
/**
* @deprecated Use the asynchronous method
* @return HypervisorCheckInResult
*/
@ApiOperation(notes = "Updates the list of Hypervisor Guests Allows agents such as " +
"virt-who to update its host list and associate the guests for each host. This is " +
"typically used when a host is unable to register to candlepin via subscription" +
" manager. In situations where consumers already exist it is probably best not " +
"to allow creation of new hypervisor consumers. Most consumers do not have a" +
" hypervisorId attribute, so that should be added manually when necessary by the " +
"management environment. @deprecated Use the asynchronous method",
value = "hypervisorUpdate")
@ApiResponses({ @ApiResponse(code = 202, message = "") })
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Deprecated
@Transactional
@SuppressWarnings("checkstyle:indentation")
public HypervisorCheckInResult hypervisorUpdate(
Map<String, List<GuestId>> hostGuestMap, @Context Principal principal,
@QueryParam("owner") @Verify(value = Owner.class,
require = Access.READ_ONLY,
subResource = SubResource.HYPERVISOR) String ownerKey,
@ApiParam("specify whether or not to create missing hypervisors." +
"Default is true. If false is specified, hypervisorIds that are not found" +
"will result in failed entries in the resulting HypervisorCheckInResult")
@QueryParam("create_missing") @DefaultValue("true") boolean createMissing) {
log.debug("Hypervisor check-in by principal: {}", principal);
if (hostGuestMap == null) {
log.debug("Host/Guest mapping provided during hypervisor checkin was null.");
throw new BadRequestException(
i18n.tr("Host to guest mapping was not provided for hypervisor check-in."));
}
Owner owner = this.getOwner(ownerKey);
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()));
}
if (hostGuestMap.remove("") != null) {
log.warn("Ignoring empty hypervisor id");
}
// Maps virt hypervisor ID to registered consumer for that hypervisor, should one exist:
VirtConsumerMap hypervisorConsumersMap =
consumerCurator.getHostConsumersMap(owner, hostGuestMap.keySet());
int emptyGuestIdCount = 0;
Set<String> allGuestIds = new HashSet<String>();
Collection<List<GuestId>> idsLists = hostGuestMap.values();
for (List<GuestId> guestIds : idsLists) {
// ignore null guest lists
// See bzs 1332637, 1332635
if (guestIds == null) {
continue;
}
for (Iterator<GuestId> guestIdsItr = guestIds.iterator(); guestIdsItr.hasNext();) {
String id = guestIdsItr.next().getGuestId();
if (StringUtils.isEmpty(id)) {
emptyGuestIdCount++;
guestIdsItr.remove();
}
else {
allGuestIds.add(id);
}
}
}
if (emptyGuestIdCount > 0) {
log.warn("Ignoring {} empty/null guest id(s).", emptyGuestIdCount);
}
// Maps virt guest ID to registered consumer for guest, if one exists:
VirtConsumerMap guestConsumersMap = consumerCurator.getGuestConsumersMap(
owner, allGuestIds);
HypervisorCheckInResult result = new HypervisorCheckInResult();
for (Entry<String, List<GuestId>> hostEntry : hostGuestMap.entrySet()) {
String hypervisorId = hostEntry.getKey();
// Treat null guest list as an empty list.
// We can get an empty list here from katello due to an update
// to ruby on rails.
// (https://github.com/rails/rails/issues/13766#issuecomment-32730270)
// See bzs 1332637, 1332635
if (hostEntry.getValue() == null) {
hostEntry.setValue(new ArrayList<GuestId>());
}
try {
log.debug("Syncing virt host: {} ({} guest IDs)", hypervisorId, hostEntry.getValue().size());
boolean hostConsumerCreated = false;
// Attempt to find a consumer for the given hypervisorId
Consumer consumer = null;
if (hypervisorConsumersMap.get(hypervisorId) == null) {
if (!createMissing) {
log.info("Unable to find hypervisor with id {} in org {}", hypervisorId, ownerKey);
result.failed(hypervisorId, i18n.tr(
"Unable to find hypervisor in org ''{0}''", ownerKey));
continue;
}
log.debug("Registering new host consumer for hypervisor ID: {}", hypervisorId);
consumer = createConsumerForHypervisorId(hypervisorId, owner, principal);
hostConsumerCreated = true;
}
else {
consumer = hypervisorConsumersMap.get(hypervisorId);
}
boolean guestIdsUpdated = addGuestIds(consumer, hostEntry.getValue(), guestConsumersMap);
Date now = new Date();
consumerCurator.updateLastCheckin(consumer, now);
consumer.setLastCheckin(now);
// Populate the result with the processed consumer.
if (hostConsumerCreated) {
result.created(consumer);
}
else if (guestIdsUpdated) {
result.updated(consumer);
}
else {
result.unchanged(consumer);
}
}
catch (Exception e) {
log.error("Hypervisor checkin failed", e);
result.failed(hypervisorId, e.getMessage());
}
}
log.info("Summary of hypervisor checkin by principal \"{}\": {}", principal, result);
return result;
}
@ApiOperation(notes = "Creates or Updates the list of Hypervisor hosts Allows agents such" +
" as virt-who to update hosts' information . This is typically used when a host is" +
" unable to register to candlepin via subscription manager. In situations where " +
"consumers already exist it is probably best not to allow creation of new hypervisor" +
" consumers. Most consumers do not have a hypervisorId attribute, so that should be" +
" added manually when necessary by the management environment. Default is true. " +
"If false is specified, hypervisorIds that are not found will result in a failed " +
"state of the job.", value = "hypervisorUpdateAsync")
@ApiResponses({ @ApiResponse(code = 202, message = "") })
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Path("/{owner}")
@SuppressWarnings("checkstyle:indentation")
public JobDetail hypervisorUpdateAsync(
String hypervisorJson, @Context Principal principal,
@PathParam("owner") @Verify(value = Owner.class,
require = Access.READ_ONLY,
subResource = SubResource.HYPERVISOR) String ownerKey,
@ApiParam("specify whether or not to create missing hypervisors." +
"Default is true. If false is specified, hypervisorIds that are not found" +
"will result in failed entries in the resulting HypervisorCheckInResult")
@QueryParam("create_missing") @DefaultValue("true") boolean createMissing,
@QueryParam("reporter_id") String reporterId) {
if (hypervisorJson == null || hypervisorJson.isEmpty()) {
log.debug("Host/Guest mapping provided during hypervisor update was null.");
throw new BadRequestException(
i18n.tr("Host to guest mapping was not provided for hypervisor update."));
}
log.info("Hypervisor update by principal: " + principal);
Owner owner = this.getOwner(ownerKey);
return HypervisorUpdateJob.forOwner(owner, hypervisorJson, createMissing, principal, reporterId);
}
/*
* Get the owner or bust
*/
private Owner getOwner(String ownerKey) {
Owner owner = ownerCurator.lookupByKey(ownerKey);
if (owner == null) {
throw new NotFoundException(i18n.tr(
"owner with key: {0} was not found.", ownerKey));
}
return owner;
}
/*
* Add a list of guestIds to the given consumer,
* return whether or not there was any change
*/
private boolean addGuestIds(Consumer consumer, List<GuestId> guestIds,
VirtConsumerMap guestConsumerMap) {
Consumer withIds = new Consumer();
withIds.setGuestIds(guestIds);
boolean guestIdsUpdated =
consumerResource.performConsumerUpdates(withIds, consumer, guestConsumerMap);
if (guestIdsUpdated) {
consumerCurator.update(consumer);
}
return guestIdsUpdated;
}
/*
* 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);
// Create Consumer
return consumerResource.create(consumer, principal, null, owner.getKey(), null, false);
}
}