/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.core.pc.upgrade; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jmock.api.Invocation; import org.jmock.lib.action.CustomAction; import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeRequest; import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeResponse; import org.rhq.core.clientapi.server.discovery.InventoryReport; import org.rhq.core.domain.discovery.MergeInventoryReportResults; import org.rhq.core.domain.discovery.PlatformSyncInfo; import org.rhq.core.domain.discovery.ResourceSyncInfo; import org.rhq.core.domain.resource.InventoryStatus; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceError; import org.rhq.core.domain.resource.ResourceErrorType; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pc.inventory.InventoryManager; /** * This class represents a server side database store of the inventory for the purposes * of the ResourceUpgradeTest unit test. * * @author Lukas Krejci */ public class FakeServerInventory { public interface InventoryStatusJudge { InventoryStatus judge(Resource resource); } private Resource platform; private Map<String, Resource> resourceStore = new HashMap<String, Resource>(); private int counter; private boolean failing; private boolean failUpgrade; private static final Comparator<Resource> ID_COMPARATOR = new Comparator<Resource>() { public int compare(Resource o1, Resource o2) { return o1.getId() - o2.getId(); } }; private static final Comparator<Resource> RESOURCE_TYPE_COMPARATOR = new Comparator<Resource>() { public int compare(Resource o1, Resource o2) { return o1.getResourceType().equals(o2.getResourceType()) ? 0 : o1.getId() - o2.getId(); } }; private static final Comparator<Resource> RESOURCE_TYPE_AND_STATUS_COMPARATOR = new Comparator<Resource>() { public int compare(Resource o1, Resource o2) { return o1.getResourceType().equals(o2.getResourceType()) && o1.getInventoryStatus() == o2.getInventoryStatus() ? 0 : o1.getId() - o2.getId(); } }; public FakeServerInventory() { this(false); } public FakeServerInventory(boolean failing) { this.failing = failing; } public synchronized void prepopulateInventory(Resource platform, Collection<Resource> topLevelServers) { this.platform = fakePersist(platform, getSimpleJudge(InventoryStatus.COMMITTED), new HashSet<String>()); for (Resource res : topLevelServers) { res.setParentResource(this.platform); fakePersist(res, getSimpleJudge(InventoryStatus.COMMITTED), new HashSet<String>()); } } public synchronized CustomAction mergeInventoryReport(final InventoryStatus requiredInventoryStatus) { return mergeInventoryReport(getSimpleJudge(requiredInventoryStatus)); } public synchronized CustomAction mergeInventoryReport(final InventoryStatusJudge judge) { return new CustomAction("updateServerSideInventory") { public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); InventoryReport inventoryReport = (InventoryReport) invocation.getParameter(0); for (Resource res : inventoryReport.getAddedRoots()) { Resource persisted = fakePersist(res, judge, new HashSet<String>()); if (res.getParentResource() == Resource.ROOT) { platform = persisted; } } return new MergeInventoryReportResults(getPlatformSyncInfo(), null); } } }; } public synchronized CustomAction getResourceSyncInfo() { return new CustomAction("getResourceSyncInfo") { public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); Integer resourceId = (Integer) invocation.getParameter(0); for (Resource r : resourceStore.values()) { if (resourceId.equals(r.getId())) { return convert(r); } } return null; } } }; } public synchronized CustomAction clearPlatform() { return new CustomAction("updateServerSideInventory - report platform deleted on the server") { public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); platform = null; return new MergeInventoryReportResults(getPlatformSyncInfo(), null); } } }; } public synchronized CustomAction setResourceError() { return new CustomAction("setResourceError") { public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); ResourceError error = (ResourceError) invocation.getParameter(0); Resource serverSideResource = resourceStore.get(error.getResource().getUuid()); if (serverSideResource != null) { List<ResourceError> currentErrors = serverSideResource.getResourceErrors(); currentErrors.add(error); } return null; } } }; } public synchronized CustomAction upgradeResources() { return new CustomAction("upgradeServerSideInventory") { @SuppressWarnings({ "serial", "unchecked" }) public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); if (failUpgrade) { throw new RuntimeException("Failing the upgrade purposefully."); } Set<ResourceUpgradeRequest> requests = (Set<ResourceUpgradeRequest>) invocation.getParameter(0); Set<ResourceUpgradeResponse> responses = new HashSet<ResourceUpgradeResponse>(); for (final ResourceUpgradeRequest request : requests) { Resource resource = findResource(platform, new Resource() { public int getId() { return request.getResourceId(); } }, ID_COMPARATOR); if (resource != null) { ResourceUpgradeResponse resp = new ResourceUpgradeResponse(); resp.setResourceId(resource.getId()); if (request.getNewDescription() != null) { resource.setDescription(request.getNewDescription()); resp.setUpgradedResourceDescription(resource.getDescription()); } if (request.getNewVersion() != null) { resource.setVersion(request.getNewVersion()); resp.setUpgradedResourceVersion(resource.getVersion()); } if (request.getNewName() != null) { resource.setName(request.getNewName()); resp.setUpgradedResourceName(resource.getName()); } if (request.getNewResourceKey() != null) { resource.setResourceKey(request.getNewResourceKey()); resp.setUpgradedResourceKey(resource.getResourceKey()); } if (request.getNewPluginConfiguration() != null) { resource.setPluginConfiguration(request.getNewPluginConfiguration()); resp.setUpgradedResourcePluginConfiguration(resource.getPluginConfiguration()); } if (request.getUpgradeErrorMessage() != null) { ResourceError error = new ResourceError(resource, ResourceErrorType.UPGRADE, request.getUpgradeErrorMessage(), request.getUpgradeErrorStackTrace(), request.getTimestamp()); resource.addResourceError(error); } responses.add(resp); } } return responses; } } }; } public synchronized CustomAction getResources() { return new CustomAction("getResources") { @SuppressWarnings("unchecked") public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); Set<Integer> resourceIds = (Set<Integer>) invocation.getParameter(0); boolean includeDescendants = (Boolean) invocation.getParameter(1); return getResources(resourceIds, includeDescendants); } } }; } public synchronized CustomAction getResourcesAsList() { return new CustomAction("getResourcesAsList") { @Override public Object invoke(Invocation invocation) throws Throwable { synchronized (FakeServerInventory.this) { throwIfFailing(); Integer[] resourceIds = (Integer[]) invocation.getParameter(0); Set<Resource> resources = getResources(new LinkedHashSet<Integer>(Arrays.asList(resourceIds)), false); return new ArrayList<Resource>(resources); } } }; } public synchronized boolean isFailing() { return failing; } public synchronized void setFailing(boolean failing) { this.failing = failing; } public synchronized boolean isFailUpgrade() { return failUpgrade; } public synchronized void setFailUpgrade(boolean failUpgrade) { this.failUpgrade = failUpgrade; } @SuppressWarnings("serial") public synchronized Set<Resource> findResourcesByType(final ResourceType type) { Set<Resource> result = new HashSet<Resource>(); if (platform != null) { findResources(platform, new Resource() { public ResourceType getResourceType() { return type; } }, result, RESOURCE_TYPE_COMPARATOR); } return result; } @SuppressWarnings("serial") public synchronized Set<Resource> findResourcesByTypeAndStatus(final ResourceType type, final InventoryStatus status) { Set<Resource> result = new HashSet<Resource>(); if (platform != null) { findResources(platform, new Resource() { public ResourceType getResourceType() { return type; } public InventoryStatus getInventoryStatus() { return status; } }, result, RESOURCE_TYPE_AND_STATUS_COMPARATOR); } return result; } @SuppressWarnings("serial") private Set<Resource> getResources(Set<Integer> resourceIds, boolean includeDescendants) { //it is important to keep the hierarchical order of the resource in the returned set //so that plugin container can merge the resources from top to bottom. Set<Resource> result = new LinkedHashSet<Resource>(); for (final Integer id : resourceIds) { Resource r = findResource(platform, new Resource() { public int getId() { return id; } }, ID_COMPARATOR); if (r != null) { result.add(r); if (includeDescendants) { for (Resource child : r.getChildResources()) { result.addAll(getResources(Collections.singleton(child.getId()), true)); } } } } return result; } private Resource fakePersist(Resource agentSideResource, InventoryStatusJudge statusJudge, Set<String> inProgressUUIds) { Resource persisted = resourceStore.get(agentSideResource.getUuid()); if (!inProgressUUIds.add(agentSideResource.getUuid())) { return persisted; } if (persisted == null) { persisted = new Resource(); if (agentSideResource.getId() != 0 && counter < agentSideResource.getId()) { counter = agentSideResource.getId() - 1; } persisted.setId(++counter); persisted.setUuid(agentSideResource.getUuid()); resourceStore.put(persisted.getUuid(), persisted); } persisted.setAgent(agentSideResource.getAgent()); persisted.setCurrentAvailability(agentSideResource.getCurrentAvailability()); persisted.setDescription(agentSideResource.getDescription()); persisted.setName(agentSideResource.getName()); persisted.setPluginConfiguration(agentSideResource.getPluginConfiguration().clone()); persisted.setResourceConfiguration(InventoryManager.getResourceConfiguration(agentSideResource).clone()); persisted.setVersion(agentSideResource.getVersion()); persisted.setResourceKey(agentSideResource.getResourceKey()); persisted.setResourceType(agentSideResource.getResourceType()); InventoryStatus status = statusJudge.judge(persisted); persisted.setInventoryStatus(status); Resource parent = agentSideResource.getParentResource(); if (parent != null && parent != Resource.ROOT) { parent = fakePersist(agentSideResource.getParentResource(), statusJudge, inProgressUUIds); persisted.setParentResource(parent); parent.addChildResource(persisted); } else { persisted.setParentResource(parent); } //persist the children Set<Resource> childResources = new LinkedHashSet<Resource>(); for (Resource child : agentSideResource.getChildResources()) { childResources.add(fakePersist(child, statusJudge, inProgressUUIds)); } //now update the list with whatever the persisted resource contained in the past //i.e. we prefer the current results from the agent but keep the children we used to //have in the past. This is the same behavior as the actual RHQ server has. childResources.addAll(persisted.getChildResources()); persisted.setChildResources(childResources); inProgressUUIds.remove(agentSideResource.getUuid()); return persisted; } private void throwIfFailing() { if (failing) { throw new RuntimeException("Fake server inventory is in the failing mode."); } } private PlatformSyncInfo getPlatformSyncInfo() { return platform == null ? null : PlatformSyncInfo.buildPlatformSyncInfo(platform); } private static Collection<ResourceSyncInfo> convert(Resource root) { Set<ResourceSyncInfo> result = new HashSet<ResourceSyncInfo>(); convertInternal(root, result); return result; } private static void convertInternal(Resource root, Collection<ResourceSyncInfo> result) { ResourceSyncInfo rootSyncInfo = ResourceSyncInfo.buildResourceSyncInfo(root); if (result.contains(rootSyncInfo)) { return; } try { result.add(rootSyncInfo); for (Resource child : root.getChildResources()) { convertInternal(child, result); } } catch (Exception e) { throw new IllegalStateException("Failed to convert resource " + root + " to a ResourceSyncInfo. This should not happen.", e); } } private static Resource findResource(Resource root, Resource template, Comparator<Resource> comparator) { if (root == null) return null; if (comparator.compare(root, template) == 0) { return root; } else { for (Resource child : root.getChildResources()) { Resource found = findResource(child, template, comparator); if (found != null) return found; } } return null; } private static void findResources(Resource root, Resource template, Set<Resource> result, Comparator<Resource> comparator) { if (root == null) return; if (comparator.compare(root, template) == 0) { result.add(root); } else { for (Resource child : root.getChildResources()) { findResources(child, template, result, comparator); } } } private InventoryStatusJudge getSimpleJudge(final InventoryStatus requiredStatus) { return new InventoryStatusJudge() { @Override public InventoryStatus judge(Resource resource) { return requiredStatus; } }; } }