/**
* Copyright (c) 2009--2014 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 com.redhat.rhn.manager.profile;
import com.redhat.rhn.common.db.datasource.DataResult;
import com.redhat.rhn.common.db.datasource.ModeFactory;
import com.redhat.rhn.common.db.datasource.SelectMode;
import com.redhat.rhn.common.db.datasource.WriteMode;
import com.redhat.rhn.common.hibernate.LookupException;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.util.RpmVersionComparator;
import com.redhat.rhn.domain.action.rhnpackage.PackageAction;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.channel.ChannelFactory;
import com.redhat.rhn.domain.channel.NoBaseChannelFoundException;
import com.redhat.rhn.domain.org.Org;
import com.redhat.rhn.domain.rhnpackage.MissingPackagesException;
import com.redhat.rhn.domain.rhnpackage.profile.DuplicateProfileNameException;
import com.redhat.rhn.domain.rhnpackage.profile.Profile;
import com.redhat.rhn.domain.rhnpackage.profile.ProfileFactory;
import com.redhat.rhn.domain.rhnpackage.profile.ProfileType;
import com.redhat.rhn.domain.server.Server;
import com.redhat.rhn.domain.server.ServerFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.dto.PackageListItem;
import com.redhat.rhn.frontend.dto.PackageMetadata;
import com.redhat.rhn.frontend.dto.ProfileDto;
import com.redhat.rhn.frontend.dto.ProfileOverviewDto;
import com.redhat.rhn.frontend.dto.ProfilePackageOverviewDto;
import com.redhat.rhn.frontend.listview.PageControl;
import com.redhat.rhn.manager.BaseManager;
import com.redhat.rhn.manager.MissingEntitlementException;
import com.redhat.rhn.manager.action.ActionManager;
import com.redhat.rhn.manager.channel.ChannelManager;
import com.redhat.rhn.manager.entitlement.EntitlementManager;
import com.redhat.rhn.manager.rhnpackage.PackageManager;
import com.redhat.rhn.manager.system.SystemManager;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ProfileManager
* @version $Rev$
*/
public class ProfileManager extends BaseManager {
private static Logger log = Logger.getLogger(ProfileManager.class);
public static final String OPTION_REMOVE = "remove";
public static final String OPTION_SUBSCRIBE = "subscribe";
/**
* Removes the given profile.
* @param profile Profile to delete
* @return number of profiles affected (should be 1 or 0)
*/
public static int deleteProfile(Profile profile) {
return ProfileFactory.remove(profile);
}
/**
* Creates and persists a Server Package Profile for the given Server
* with the name and description.
* @param type ProfileType we want to create
* @param user Logged in User
* @param channel Channel that this Profile is created from
* @param name Name of profile
* @param description Profile description
* @return Profile for the given Server.
*/
public static Profile createProfile(ProfileType type, User user, Channel channel,
String name, String description) {
if (isNameInUse(name, user.getOrg().getId())) {
throw new DuplicateProfileNameException(name);
}
if (channel == null) {
throw new NoBaseChannelFoundException("Channel is null when trying to create " +
"a profile.");
}
Profile p = ProfileFactory.createProfile(type);
p.setName(name);
p.setDescription(description);
p.setOrg(user.getOrg());
p.setBaseChannel(channel);
ProfileFactory.save(p);
return p;
}
/**
* Creates and persists a Server Package Profile for the given Server
* with the name and description.
* @param user Logged in User
* @param server Server which profile should be associated with.
* @param name Name of profile
* @param description Profile description
* @return Profile for the given Server.
*/
public static Profile createProfile(User user, Server server,
String name, String description) {
Channel baseChannel = ChannelFactory.getBaseChannel(server.getId());
return createProfile(ProfileFactory.TYPE_NORMAL, user,
baseChannel, name, description);
}
private static boolean isNameInUse(String name, Long orgid) {
return (ProfileFactory.findByNameAndOrgId(name, orgid) != null);
}
/**
* Copies the packages from a given Server to the given Profile.
* @param server Server whose packages are to be copied.
* @param profile Profile where packages are copied to.
*/
public static void copyFrom(Server server, Profile profile) {
WriteMode m = ModeFactory.getWriteMode("profile_queries",
"delete_package_profile");
Map<String, Object> params = new HashMap<String, Object>();
params.put("sid", server.getId());
params.put("prid", profile.getId());
m.executeUpdate(params);
m = ModeFactory.getWriteMode("profile_queries",
"insert_package_profile");
params = new HashMap();
params.put("sid", server.getId());
params.put("prid", profile.getId());
m.executeUpdate(params);
}
/**
* Returns a list of Profiles which are compatible with the given server.
* @param server Server whose profiles we want.
* @param org Org owner
* @return a list of Profiles which are compatible with the given server.
*/
public static List compatibleWithServer(Server server, Org org) {
return ProfileFactory.compatibleWithServer(server, org);
}
private static DataResult canonicalProfilePackages(Long prid, Long orgid,
PageControl pc) {
SelectMode m = ModeFactory.getMode("Package_queries",
"profile_canonical_package_list");
Map<String, Object> params = new HashMap<String, Object>();
params.put("prid", prid);
params.put("org_id", orgid);
Map<String, Object> elabParams = new HashMap<String, Object>();
return makeDataResult(params, elabParams, pc, m);
}
private static DataResult canonicalSystemsPackages(Long sid, Long orgid,
PageControl pc) {
SelectMode m = ModeFactory.getMode("Package_queries",
"system_canonical_package_list");
Map<String, Object> params = new HashMap<String, Object>();
params.put("sid", sid);
params.put("org_id", orgid);
Map<String, Object> elabParams = new HashMap<String, Object>();
return makeDataResult(params, elabParams, pc, m);
}
/**
* compares the given lists of Packages.
*
* NOTE: For lists that contain entries with the same package with
* multiple versions we show the entries as "OTHER_ONLY" when the version
* isn't on one of the other lists.
*
* @param profiles Packages to compare
* @param systems Packages to compare
* @param param comparison parameter
* @return List of differences
*/
public static List comparePackageLists(DataResult profiles,
DataResult systems, String param) {
List result = new LinkedList();
Map profilesNameIdMap = buildPackagesMap(profiles);
Map systemsNameIdMap = buildPackagesMap(systems);
if (log.isDebugEnabled()) {
log.debug("profilesIdComboMap: " + profilesNameIdMap);
log.debug("systemsIdComboMap: " + systemsNameIdMap);
}
// Here is the real work. Iterate over the list of packages in the
// system and see what matches we get against the profile list.
// skipPkg is used to store the names of packages once they are
// identified as either having a matching package in both lists (sys & prof)
// or they have been identified as being valid difference. This purpose
// of having this set is to avoid processing the same package multiple times.
Set skipPkg = new HashSet();
for (Iterator itr = systemsNameIdMap.keySet().iterator(); itr.hasNext();) {
Object key = itr.next();
List syslist = (List) systemsNameIdMap.get(key);
List plist = (List) profilesNameIdMap.get(key);
if (plist == null) {
// No packages in profile with same name. We know its only in the System
for (int i = 0; i < syslist.size(); i++) {
PackageListItem syspkgitem = (PackageListItem) syslist.get(i);
PackageMetadata pm = createPackageMetadata(syspkgitem,
null, PackageMetadata.KEY_THIS_ONLY, param);
log.debug("plist is null - adding KEY_THIS_ONLY: " +
pm.getSystem().getVersion());
skipPkg.add(syspkgitem.getNevra());
result.add(pm);
}
}
else {
// We have packages on the system that are also in the Profile. If either
// the system or the profile list has more than one version of a package
// installed we need to run a different algorithm.
log.debug("syslist.size: " + syslist.size() +
" plist.size: " + plist.size());
if (syslist.size() > 1 || plist.size() > 1) {
Map compareMap = new HashMap();
for (int i = 0; i < syslist.size(); i++) {
PackageListItem syspkgitem = (PackageListItem) syslist.get(i);
for (int j = 0; j < plist.size(); j++) {
PackageListItem profpkgitem = (PackageListItem) plist.get(j);
if (skipPkg.contains(profpkgitem.getNevra())) {
// this package was evaluated on a previous pass through
// the plist and identified as a match on the syslist;
// therefore, it may be skipped
continue;
}
log.debug("Checking on : " + profpkgitem.getEvr());
if (compareArch(syspkgitem.getArch(),
profpkgitem.getArch()) != 0) {
// if the arch of the packages doesn't match, we don't
// need to compare the EVR; therefore, if at end of the
// list, add both packages to the result
if ((j + 1) == plist.size()) {
PackageMetadata pm = createPackageMetadata(
syspkgitem, null,
PackageMetadata.KEY_THIS_ONLY, param);
skipPkg.add(syspkgitem.getNevra());
result.add(pm);
pm = createPackageMetadata(null, profpkgitem,
PackageMetadata.KEY_OTHER_ONLY, param);
skipPkg.add(profpkgitem.getNevra());
result.add(pm);
}
}
else {
PackageMetadata pm = compareAndCreatePackageMetaData(
syspkgitem, profpkgitem, param);
String evrKey = pm.getSystemEvr() + "|" +
pm.getOtherEvr();
// If the package exists on one but not the other we
// need to add it to the compare map
if (pm.getComparisonAsInt() !=
PackageMetadata.KEY_NO_DIFF) {
if ((j + 1) == plist.size()) {
// this is the last entry in plist; therefore,
// this must be a difference between pkgs
log.debug("Adding to cm: " + evrKey +
" comp: " + pm.getComparison());
pm.setComparison(
PackageMetadata.KEY_OTHER_ONLY);
compareMap.put(evrKey, pm);
skipPkg.add(syspkgitem.getNevra());
skipPkg.add(profpkgitem.getNevra());
}
}
else {
log.debug("Removing from cm: " + evrKey);
compareMap.remove(evrKey);
skipPkg.add(profpkgitem.getNevra());
// pkg found in both plist & syslist, skip to next
// syslist entry
break;
}
}
}
if (!skipPkg.contains(syspkgitem.getNevra())) {
// reached end of plist w/o finding match in syslist
// or recording a difference; therefore, add one now
log.debug("Checking on : " + syspkgitem.getEvr());
PackageMetadata pm = createPackageMetadata(syspkgitem,
null, PackageMetadata.KEY_THIS_ONLY, param);
log.debug("*** adding a PM(1): " + pm.hashCode());
skipPkg.add(syspkgitem.getNevra());
result.add(pm);
}
}
// Copy into the result map
Iterator i = compareMap.values().iterator();
while (i.hasNext()) {
PackageMetadata pm = (PackageMetadata) i.next();
log.debug("*** adding a PM(2): " + pm.hashCode());
result.add(pm);
}
}
// Else the system and profile list just have one rev so we
// can do a standard compare
else {
PackageListItem syspkgitem = (PackageListItem) syslist.get(0);
PackageListItem profpkgitem = (PackageListItem) plist.get(0);
if (compareArch(syspkgitem.getArch(), profpkgitem.getArch()) != 0) {
// pkg arches do not match; therefore, no need to check evr
PackageMetadata pm = createPackageMetadata(syspkgitem, null,
PackageMetadata.KEY_THIS_ONLY, param);
skipPkg.add(syspkgitem.getNevra());
result.add(pm);
pm = createPackageMetadata(null, profpkgitem,
PackageMetadata.KEY_OTHER_ONLY, param);
skipPkg.add(profpkgitem.getNevra());
result.add(pm);
}
else {
PackageMetadata pm = compareAndCreatePackageMetaData(syspkgitem,
profpkgitem, param);
if (pm != null && pm.getComparisonAsInt() !=
PackageMetadata.KEY_NO_DIFF) {
log.debug("*** adding a PM(3): " + pm.hashCode());
result.add(pm);
}
}
skipPkg.add(profpkgitem.getNevra());
skipPkg.add(syspkgitem.getNevra());
}
}
}
// Reverse of above so we can check for pkgs that are *only* in the profile
for (Iterator itr = profilesNameIdMap.keySet().iterator(); itr.hasNext();) {
Object key = itr.next();
List syslist = (List) systemsNameIdMap.get(key);
List plist = (List) profilesNameIdMap.get(key);
if (syslist == null) {
// No packages in system with same name. We know its only in the Profile
for (int i = 0; i < plist.size(); i++) {
PackageMetadata pm = createPackageMetadata(null,
(PackageListItem) plist.get(i), PackageMetadata.KEY_OTHER_ONLY,
param);
result.add(pm);
}
}
else {
for (int i = 0; i < plist.size(); i++) {
PackageListItem profpkgitem = (PackageListItem) plist.get(i);
if (!skipPkg.contains(profpkgitem.getNevra())) {
PackageMetadata pm = createPackageMetadata(
null, profpkgitem, PackageMetadata.KEY_OTHER_ONLY, param);
log.debug("*** adding a PM(4): " + pm.hashCode());
result.add(pm);
}
}
}
}
return result;
}
/**
* Build a map of packages based on the list of PackageListItems provided.
* @param packageListItems - set of PackageListItems to be processed
* @return Map where the key is the package name Id and the value is a list
* of evr values for each of the packages associated with that name.
* E.g. for kernel, the nameid might be 23 and there may be multiple
* versions of that package in the list 2.1, 2.2, 2.3...etc
*/
private static Map buildPackagesMap(DataResult packageListItems) {
Map packages = new HashMap();
for (Iterator itr = packageListItems.iterator(); itr.hasNext();) {
PackageListItem item = (PackageListItem) itr.next();
// We actually put *each* package in a sub-list in the map.
// This is so we can have a List containing each package
// by name. Used for when we have multiple revs of the same
// package, kernel-2.1, kernel-2.2, kernel-2.3 etc..
String mapId = item.getMapHash();
List list = (List) packages.get(mapId);
if (list == null) {
list = new LinkedList();
}
list.add(item);
packages.put(mapId, list);
}
return packages;
}
/*
* Compare the arches provided.
* @param a1 - arch value
* @param a2 - arch value
* @return returns 0 if arch are equal; otherwise, return 1
*/
private static int compareArch(String a1, String a2) {
if (((a1 == null) && (a2 != null)) ||
((a1 != null) && (a2 == null))) {
return 1;
}
if (((a1 == null) && (a2 == null)) || (a1.equals(a2))) {
return 0;
}
return 1;
}
private static PackageMetadata compareAndCreatePackageMetaData(
PackageListItem syspkgitem, PackageListItem profpkgitem, String param) {
log.debug(" Sys: " + syspkgitem.getName() + " get: " +
syspkgitem.getVersion());
log.debug(" Pro: " + profpkgitem.getName() + " get: " +
profpkgitem.getVersion());
PackageMetadata retval = null;
int rc = vercmp(syspkgitem, profpkgitem);
log.debug(" rc: " + rc);
// do nothing if they are equal
if (rc < 0) {
retval = createPackageMetadata(
syspkgitem,
profpkgitem,
PackageMetadata.KEY_OTHER_NEWER,
param);
}
else if (rc > 0) {
retval = createPackageMetadata(
syspkgitem,
profpkgitem,
PackageMetadata.KEY_THIS_NEWER,
param);
}
else if (rc == 0) {
retval = createPackageMetadata(
syspkgitem,
profpkgitem,
PackageMetadata.KEY_NO_DIFF,
param);
}
return retval;
}
/**
* Returns a DataResult with a diff of the server's packages and those
* in the profile.
* @param sid Server whose packages are to be compared.
* @param sid1 Server whose packages should be used in the comparison.
* @param orgid Org owner
* @param pc PageControl
* @return a DataResult with a diff of the server's packages and those
* in the profile.
*/
public static DataResult compareServerToServer(Long sid,
Long sid1, Long orgid, PageControl pc) {
Server source = ServerFactory.lookupById(sid1);
if (!SystemManager.hasEntitlement(sid, EntitlementManager.MANAGEMENT) ||
!SystemManager.hasEntitlement(sid1, EntitlementManager.MANAGEMENT)) {
throw new MissingEntitlementException(
EntitlementManager.MANAGEMENT.getHumanReadableLabel());
}
// passing in null PageControls since we want ALL of the records
// so we can reconcile them here.
DataResult othersystems = canonicalSystemsPackages(sid1, orgid, null);
DataResult systems = canonicalSystemsPackages(sid, orgid, null);
List result = comparePackageLists(othersystems, systems, source.getName());
// this has to return a DataResult full of PackageMetadata
Collections.sort(result);
return prepareList(result, pc);
}
/**
* Returns a DataResult with a diff of the server's packages and those
* in the profile.
* @param sid Server whose packages are to be compared.
* @param prid Profile whose packages should be used in the comparison.
* @param orgid Org owner
* @param pc PageControl
* @return a DataResult with a diff of the server's packages and those
* in the profile.
*/
public static DataResult compareServerToProfile(Long sid,
Long prid, Long orgid, PageControl pc) {
// passing in null PageControls since we want ALL of the records
// so we can reconcile them here.
DataResult profiles = canonicalProfilePackages(prid, orgid, null);
DataResult systems = canonicalSystemsPackages(sid, orgid, null);
List result = comparePackageLists(profiles, systems, null);
// this has to return a DataResult full of PackageMetadata
Collections.sort(result);
return prepareList(result, pc);
}
/**
* Prepares the list of packages to be synced for comfirmation.
* @param sid Server involved in sync.
* @param prid Profile we're syncing with.
* @param orgid Org id
* @param pc PageControl
* @param pkgIdCombos Set of packages selected.
* @return DataResult of PackageMetadata's suitable for listview display.
*/
public static DataResult prepareSyncToProfile(Long sid, Long prid,
Long orgid, PageControl pc, Set pkgIdCombos) {
Map profilesMap = new HashMap();
List packagesToSync = new ArrayList();
// seems like a waste, but that's how it works
DataResult profiles = compareServerToProfile(sid, prid, orgid, null);
// in order to search the list by combo id (name_id|evr_id|arch_id),
// it's easiest to create a map instead of looping through n times where n
// is the size of the RhnSet.
for (Iterator itr = profiles.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
profilesMap.put(pm.getIdCombo(), pm);
}
// find all of the items in profiles which are in RhnSet
for (Iterator itr = pkgIdCombos.iterator(); itr.hasNext();) {
String pkgIdCombo = (String) itr.next();
PackageMetadata pm = (PackageMetadata) profilesMap.get(pkgIdCombo);
pm.updateActionStatus();
packagesToSync.add(pm);
}
Collections.sort(packagesToSync);
return prepareList(packagesToSync, pc);
}
/**
* Prepares the list of packages to be synced for comfirmation.
* @param sid Server involved in sync.
* @param sid1 Profile we're syncing with.
* @param orgid Org id
* @param pc PageControl
* @param pkgIdCombos Set of packages selected.
* @return DataResult of PackageMetadata's suitable for listview display.
*/
public static DataResult prepareSyncToServer(Long sid, Long sid1,
Long orgid, PageControl pc, Set pkgIdCombos) {
Map profilesMap = new HashMap();
List packagesToSync = new ArrayList();
// seems like a waste, but that's how it works
DataResult profiles = compareServerToServer(sid, sid1, orgid, null);
if (log.isDebugEnabled()) {
log.debug(" profiles .. " + profiles);
}
// in order to search the list by combo id (name_id|evr_id|arch_id),
// it's easiest to create a map instead of looping through n times where n
// is the size of the RhnSet.
for (Iterator itr = profiles.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
if (log.isDebugEnabled()) {
log.debug(" pm, putting: " + pm.getIdCombo());
}
profilesMap.put(pm.getIdCombo(), pm);
}
// find all of the items in profiles which are in RhnSet
for (Iterator itr = pkgIdCombos.iterator(); itr.hasNext();) {
String pkgIdCombo = (String) itr.next();
if (log.isDebugEnabled()) {
log.debug(" rse, fetching: " + pkgIdCombo);
}
PackageMetadata pm = (PackageMetadata) profilesMap.get(pkgIdCombo);
if (pm != null) {
pm.updateActionStatus();
packagesToSync.add(pm);
}
}
Collections.sort(packagesToSync);
return prepareList(packagesToSync, pc);
}
/**
* Syncs the given server id to the given profile id.
* @param user Current user
* @param sid Server id to be affected.
* @param sid1 Profile id to be used to sync.
* @param pkgIdCombos Set of packages which will be synced.
* @param missingoption Defines what do to if packages go missing. null means
* ask the user.
* @param earliest The earliest Date to perform this action
* @return The PackageAction which was scheduled containing the sync information.
*/
public static PackageAction syncToSystem(User user, Long sid, Long sid1,
Set pkgIdCombos, String missingoption, Date earliest) {
if (log.isDebugEnabled()) {
log.debug("in syncToSystem: " + missingoption);
}
if (!SystemManager.hasEntitlement(sid, EntitlementManager.MANAGEMENT) ||
!SystemManager.hasEntitlement(sid1, EntitlementManager.MANAGEMENT)) {
throw new MissingEntitlementException(
EntitlementManager.MANAGEMENT.getHumanReadableLabel());
}
DataResult dr = prepareSyncToServer(sid, sid1, user.getOrg().getId(),
null, pkgIdCombos);
if (log.isDebugEnabled()) {
log.debug("prepareTosyncServer results: " + dr);
}
// dr should be the list of packages and actions that need to be taken.
// Now get the channels of the victim (victim being the server).
Server server = ServerFactory.lookupById(sid);
Set channels = server.getChannels();
List missingPackages = findMissingPackages(dr, channels);
PackageAction action = null;
log.debug("is missingpackages empty: " + missingPackages.isEmpty());
if (missingPackages.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("Schedule sync, no missing packages, so we're good");
}
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
if (log.isDebugEnabled()) {
log.debug("created an action: " + action);
}
}
else if (OPTION_REMOVE.equals(missingoption)) {
// User chose to have missing packages removed. So we will remove
// any package that exists in the missing packages list and is NOT
// on the server. This means that if the PackageMetadata has a
// comparison value of KEY_OTHER_ONLY we remove it, otherwise,
// the server has a version of the package and we don't want to
// touch it.
if (log.isDebugEnabled()) {
log.debug("Missingoption set to remove. DataResult size [" +
dr.size() + "]");
}
for (Iterator itr = missingPackages.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
int compare = pm.getComparisonAsInt();
if (compare == PackageMetadata.KEY_OTHER_ONLY ||
compare == PackageMetadata.KEY_OTHER_NEWER) {
dr.remove(pm);
}
}
if (log.isDebugEnabled()) {
log.debug("DataResult size after removals [" + dr.size() + "]");
}
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
if (log.isDebugEnabled()) {
log.debug("Action: " + action);
}
}
else if (OPTION_SUBSCRIBE.equals(missingoption)) {
// subscribe to channels and continue
if (log.isDebugEnabled()) {
log.debug("Missingoption set to subscribe");
}
// get list of accessible channels for the current user
// for each accessible channel found, see if any of the
// missing packages are in that channel. If so,
// add the channel to the "needed channels list" and
// remove package from missingpackages list.
Channel baseChannel = ChannelFactory.getBaseChannel(server.getId());
List validChannels = ChannelManager.userAccessibleChildChannels(
user.getOrg().getId(), baseChannel.getId());
List neededChannels = new ArrayList();
for (Iterator itr = validChannels.iterator(); itr.hasNext();) {
Channel validChannel = (Channel) itr.next();
for (Iterator innerItr = missingPackages.iterator(); innerItr.hasNext();) {
PackageMetadata pm = (PackageMetadata) innerItr.next();
if (PackageManager.isPackageInChannel(
validChannel.getId(), pm.getNameId(),
pm.getEvrId())) {
if (log.isDebugEnabled()) {
log.debug("Package [" + pm.getName() +
"] is in Channel [" + validChannel.getId() +
"]");
}
neededChannels.add(validChannel);
// remove from missingpkgs
innerItr.remove();
}
}
}
// finally for each channel needed, subscribe the server
// to that channel. if there's an error throw an exception
// TODO: what type of exception
// once subscribed, throw away any of the remaining missing
// packages.
for (Iterator itr = neededChannels.iterator(); itr.hasNext();) {
Channel needed = (Channel) itr.next();
if (log.isDebugEnabled()) {
log.debug("Subscribing to [" + needed.getName() + "]");
}
SystemManager.subscribeServerToChannel(user, server, needed);
}
// if we still have some missing packages, just remove them.
if (!missingPackages.isEmpty()) {
for (Iterator itr = missingPackages.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
int compare = pm.getComparisonAsInt();
if (compare == PackageMetadata.KEY_OTHER_ONLY ||
compare == PackageMetadata.KEY_OTHER_NEWER) {
if (log.isDebugEnabled()) {
log.debug("Removing pm [" + pm.getName() + "]");
}
dr.remove(pm);
}
}
}
if (log.isDebugEnabled()) {
log.debug("DataResult size after removals [" + dr.size() + "]");
}
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
}
else {
if (log.isDebugEnabled()) {
log.debug("We have [" + missingPackages.size() + "] missing packages");
}
throw new MissingPackagesException("There are [" +
missingPackages.size() + "] missing packages");
}
return action;
}
private static void updatePackageListWithChannels(Channel baseChannel,
User user, List pkgs) {
List validChannels = ChannelManager.userAccessibleChildChannels(
user.getOrg().getId(), baseChannel.getId());
if (log.isDebugEnabled()) {
log.debug("updatePackageListWithChannels: validchannels [" +
validChannels.size() + "]");
}
for (Iterator itr = validChannels.iterator(); itr.hasNext();) {
Channel validChannel = (Channel) itr.next();
for (Iterator innerItr = pkgs.iterator(); innerItr.hasNext();) {
PackageMetadata pm = (PackageMetadata) innerItr.next();
if (PackageManager.isPackageInChannel(
validChannel.getId(), pm.getNameId(),
pm.getEvrId())) {
if (log.isDebugEnabled()) {
log.debug("Package [" + pm.getName() +
"] is in Channel [" + validChannel.getId() +
"]");
}
pm.addChannel(validChannel);
}
}
}
}
/**
* Syncs the given server id to the given profile id.
* @param user Current user
* @param sid Server id to be affected.
* @param prid Profile id to be used to sync.
* @param pkgIdCombos Set of packages which will be synced.
* @param missingoption Defines what do to if packages go missing. null means
* ask the user.
* @param earliest The earliest time to perform this action
* @return The PackageAction which was scheduled containing the sync information.
*/
public static PackageAction syncToProfile(User user, Long sid, Long prid,
Set pkgIdCombos, String missingoption, Date earliest) {
DataResult dr = prepareSyncToProfile(sid, prid, user.getOrg().getId(),
null, pkgIdCombos);
// dr should be the list of packages and actions that need to be taken.
// Now get the channels of the victim (victim being the server).
Server server = ServerFactory.lookupById(sid);
Set channels = server.getChannels();
List missingPackages = findMissingPackages(dr, channels);
PackageAction action = null;
// this code makes me want spaghetti!
if (missingPackages.isEmpty()) {
// schedule sync
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
}
else if (OPTION_REMOVE.equals(missingoption)) {
// User chose to have missing packages removed. So we will remove
// any package that exists in the missing packages list and is NOT
// on the server. This means that if the PackageMetadata has a
// comparison value of KEY_OTHER_ONLY we remove it, otherwise,
// the server has a version of the package and we don't want to
// touch it.
if (log.isDebugEnabled()) {
log.debug("Missingoption set to remove. DataResult size [" +
dr.size() + "]");
}
for (Iterator itr = missingPackages.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
int compare = pm.getComparisonAsInt();
if (compare == PackageMetadata.KEY_OTHER_ONLY ||
compare == PackageMetadata.KEY_OTHER_NEWER) {
dr.remove(pm);
}
}
if (log.isDebugEnabled()) {
log.debug("DataResult size after removals [" + dr.size() + "]");
}
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
}
else if (OPTION_SUBSCRIBE.equals(missingoption)) {
// subscribe to channels and continue
if (log.isDebugEnabled()) {
log.debug("Missingoption set to subscribe");
}
// get list of accessible channels for the current user
// for each accessible channel found, see if any of the
// missing packages are in that channel. If so,
// add the channel to the "needed channels list" and
// remove package from missingpackages list.
Channel baseChannel = ChannelFactory.getBaseChannel(server.getId());
List validChannels = ChannelManager.userAccessibleChildChannels(
user.getOrg().getId(), baseChannel.getId());
List neededChannels = new ArrayList();
for (Iterator itr = validChannels.iterator(); itr.hasNext();) {
Channel validChannel = (Channel) itr.next();
for (Iterator innerItr = missingPackages.iterator(); innerItr.hasNext();) {
PackageMetadata pm = (PackageMetadata) innerItr.next();
if (PackageManager.isPackageInChannel(
validChannel.getId(), pm.getNameId(),
pm.getEvrId())) {
if (log.isDebugEnabled()) {
log.debug("Package [" + pm.getName() +
"] is in Channel [" + validChannel.getId() +
"]");
}
neededChannels.add(validChannel);
// remove from missingpkgs
innerItr.remove();
}
}
}
// finally for each channel needed, subscribe the server
// to that channel. if there's an error throw an exception
// TODO: what type of exception
// once subscribed, throw away any of the remaining missing
// packages.
for (Iterator itr = neededChannels.iterator(); itr.hasNext();) {
Channel needed = (Channel) itr.next();
SystemManager.subscribeServerToChannel(user, server, needed);
}
// if we still have some missing packages, just remove them.
if (!missingPackages.isEmpty()) {
for (Iterator itr = missingPackages.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
int compare = pm.getComparisonAsInt();
if (compare == PackageMetadata.KEY_OTHER_ONLY ||
compare == PackageMetadata.KEY_OTHER_NEWER) {
if (log.isDebugEnabled()) {
log.debug("Removing pm [" + pm.getName() + "]");
}
dr.remove(pm);
}
}
}
if (log.isDebugEnabled()) {
log.debug("DataResult size after removals [" + dr.size() + "]");
}
action = ActionManager.schedulePackageRunTransaction(user, server, dr,
earliest);
}
else {
if (log.isDebugEnabled()) {
log.debug("We have [" + missingPackages.size() + "] missing packages");
}
throw new MissingPackagesException("There are [" +
missingPackages.size() + "] missing packages");
}
return action;
}
/**
* Returns a list of missing packages.
* @param user Current user
* @param sid Server id
* @param prid Profile id
* @param pkgIdCombos Set of packages
* @param pc page control
* @return a list of missing packages.
*/
public static DataResult getMissingProfilePackages(User user, Long sid,
Long prid, Set pkgIdCombos, PageControl pc) {
DataResult dr = prepareSyncToProfile(sid, prid, user.getOrg().getId(),
null, pkgIdCombos);
Server server = ServerFactory.lookupById(sid);
Set channels = server.getChannels();
List missingpkgs = findMissingPackages(dr, channels);
DataResult missing = new DataResult(missingpkgs);
missing = prepareList(missing, pc);
Channel baseChannel = ChannelFactory.getBaseChannel(sid);
updatePackageListWithChannels(baseChannel, user, missing);
return missing;
}
/**
* Returns a list of missing packages.
* @param user Current user
* @param sid Server id
* @param sid1 Profile id
* @param pkgIdCombos Set of packages
* @param pc page control
* @return a list of missing packages.
*/
public static DataResult getMissingSystemPackages(User user, Long sid,
Long sid1, Set pkgIdCombos, PageControl pc) {
DataResult dr = prepareSyncToServer(sid, sid1, user.getOrg().getId(),
null, pkgIdCombos);
Server server = ServerFactory.lookupById(sid);
Set channels = server.getChannels();
List missingpkgs = findMissingPackages(dr, channels);
DataResult missing = new DataResult(missingpkgs);
// should have the subset we plan to work with.
// NOW, we need to find the channels for each
// of the missing packages in this list for display purposes.
// Can this get any worse? hmm let me think, I bet it could.
missing = prepareList(missing, pc);
Channel baseChannel = ChannelFactory.getBaseChannel(sid);
updatePackageListWithChannels(baseChannel, user, missing);
return missing;
}
/**
* Get the List of child Channels of the passed in base Channel that contain the
* packages found in the Profile. This is useful if you want to compute the child
* channels required to be subscribed to in order to get a system to sync with a
* profile.
*
* This method iterates over *each* package in the profile and checks for the proper
* child channel. Can be expensive.
*
* @param user making the call
* @param baseChannel to look for child channels for
* @param profileIn to iterate over the set of packages for.
* @return List of Channel objects.
*/
public static List getChildChannelsNeededForProfile(User user, Channel baseChannel,
Profile profileIn) {
List retval = new LinkedList();
List profilePackages = canonicalProfilePackages(profileIn.getId(),
user.getOrg().getId(), null);
log.debug("getChildChannelsNeededForProfile profile has: " +
profilePackages.size() + " packages in it.");
Set evrNameIds = new HashSet();
// Create the Set of evr_id's
Iterator pi = profilePackages.iterator();
while (pi.hasNext()) {
PackageListItem pli = (PackageListItem) pi.next();
evrNameIds.add(pli.getNevr());
log.debug("Added nevr: " + pli.getNevr());
}
Iterator i = ChannelManager.userAccessibleChildChannels(
user.getOrg().getId(), baseChannel.getId()).iterator();
while (i.hasNext()) {
Channel child = (Channel) i.next();
log.debug("working with child channel: " + child.getLabel());
List packages = getPackagesInChannelByIdCombo(child.getId());
for (int x = 0; x < packages.size(); x++) {
PackageListItem row = (PackageListItem) packages.get(x);
log.debug("Checking: " + row.getNevr());
if (evrNameIds.contains(row.getNevr())) {
retval.add(child);
log.debug("found package, breaking out of loop");
break;
}
}
}
return retval;
}
private static DataResult getPackagesInChannelByIdCombo(Long cid) {
SelectMode m = ModeFactory.getMode("Package_queries",
"packages_in_channel_by_id_combo");
Map<String, Object> params = new HashMap<String, Object>();
Map<String, Object> elabParams = new HashMap<String, Object>();
params.put("cid", cid);
DataResult dr = makeDataResult(params, elabParams, null, m);
return dr;
}
private static List findMissingPackages(DataResult pkgs, Set channels) {
List missingPkgs = new ArrayList();
DataResult pkgsInChannels = new DataResult(new ArrayList());
for (Iterator itr = channels.iterator(); itr.hasNext();) {
Channel c = (Channel) itr.next();
DataResult dr = getPackagesInChannelByIdCombo(c.getId());
pkgsInChannels.addAll(dr);
}
// now determine which packages are in pkgs but not in pkgsInChannels
// using pkgsInChannels, build a map of name ids (i.e. key) to list
// of packages (i.e. value) associated w/the id
Map pkgsInChannelsByNameId = buildPackagesMap(pkgsInChannels);
// for each of the pkgs to be synced
for (Iterator itr = pkgs.iterator(); itr.hasNext();) {
PackageMetadata pm = (PackageMetadata) itr.next();
// retrieve the packages with the same name that exist w/in channels
List<PackageListItem> pkgsInChannel = (List<PackageListItem>)
pkgsInChannelsByNameId.get(pm.getMapHash());
if (pm.getComparisonAsInt() == PackageMetadata.KEY_THIS_ONLY) {
// makes no sense to check whether missing
continue;
}
// attempt to locate a package from pkgsInChannel that has the same nvre
// as the pkg to be synced
boolean foundMatch = false;
if (pkgsInChannel != null) {
for (int i = 0; i < pkgsInChannel.size(); i++) {
PackageListItem pkgInChannel = pkgsInChannel.get(i);
if (pkgInChannel.getVersion().equals(pm.getVersion()) &&
pkgInChannel.getRelease().equals(pm.getRelease()) &&
(epochcmp(pkgInChannel.getEpoch(), pm.getEpoch()) == 0)) {
foundMatch = true;
break; //stop searching for match
}
}
}
if (!foundMatch) {
missingPkgs.add(pm);
}
}
return missingPkgs;
}
private static DataResult prepareList(List result, PageControl pc) {
DataResult dr = new DataResult(result);
dr.setTotalSize(result.size());
if (pc != null) {
dr.setFilter(pc.hasFilter());
if (pc.hasFilter()) {
pc.filterData(dr);
}
// If we are filtering the content, _don't_ show the alphabar.
// This matches what the perl code does. If we want to show a
// smaller alphabar, just remove the if statement.
if (pc.getFilterData() == null || pc.getFilterData().equals("")) {
if (pc.hasIndex()) {
dr.setIndex(pc.createIndex(dr));
}
}
// now use the PageControl to limit the list to the
// selected region.
dr = dr.subList(pc.getStart() - 1, pc.getEnd());
}
return dr;
}
/**
* Creates a packagemetadata
* @param pli PackageListItem info
* @param systemEvr evr of curent system
* @param profileEvr evr of profile or other system
* @param comparison comparison
* @param param compare string param
* @return Packagemetadata with the information from the PackageListItem
*/
private static PackageMetadata createPackageMetadata(PackageListItem sys,
PackageListItem other, int comparison, String param) {
PackageMetadata pm = new PackageMetadata(sys, other);
pm.setComparison(comparison);
pm.setCompareParam(param);
return pm;
}
/**
* compares metadatas from 2 package list items
* @param p1 the first PackageListItem
* @param p2 the second PackageListItem
* @return 1, -1, or 0
*/
private static int vercmp(PackageListItem p1, PackageListItem p2) {
int epochCmpValue = epochcmp(p1.getEpoch(), p2.getEpoch());
if (epochCmpValue != 0) {
// Epochs are different; therefore, no need to check version/release
return epochCmpValue;
}
log.debug("Epoch is the same. Checking version: " + p1.getVersion() +
" vs: " + p2.getVersion());
RpmVersionComparator rpmvercmp = new RpmVersionComparator();
int c = rpmvercmp.compare(p1.getVersion(), p2.getVersion());
if (c != 0) {
return c;
}
log.debug("Version is the same. Checking release: " + p1.getRelease() +
" vs: " + p2.getRelease());
return rpmvercmp.compare(p1.getRelease(), p2.getRelease());
}
/**
* compare 2 epoch values
* @param e1 the first epoch value
* @param e2 the second epoch value
* @return 1 indicating e1 > e2, -1 indicating e1 < e2, or 0 indicating e1 == e2
*/
private static int epochcmp(String e1, String e2) {
int epoch1 = -1, epoch2 = -1;
if (e1 != null) {
epoch1 = Integer.parseInt(e1);
}
if (e2 != null) {
epoch2 = Integer.parseInt(e2);
}
// Epoch of 0 and null should be treated as if they are equal.
// This is necessary due to an issue that exists where packages in a channel
// have an epoch of null; however, when a client installs the same package
// (e.g. using yum install) the epoch for the package associated with the
// system is stored as 0.
boolean e1IsNull = false, e2IsNull = false;
if ((epoch1 == -1) || (epoch1 == 0)) {
e1IsNull = true;
}
if ((epoch2 == -1) || (epoch2 == 0)) {
e2IsNull = true;
}
if (e1IsNull && !e2IsNull) {
return -1;
}
else if (!e1IsNull && e2IsNull) {
return 1;
}
if (!e1IsNull && !e2IsNull) {
if (epoch1 < epoch2) {
return -1;
}
else if (epoch1 > epoch2) {
return 1;
}
}
return 0;
}
/**
* Returns the list of stored profiles.
* @param orgId The id of the org the profiles are associated with.
* @return DataResult of ProfileOverviewDto
*/
public static DataResult<ProfileOverviewDto> listProfileOverviews(Long orgId) {
SelectMode m = ModeFactory.getMode("profile_queries", "profile_overview");
Map<String, Object> params = new HashMap<String, Object>();
params.put("org_id", orgId);
Map<String, Object> elabParams = new HashMap<String, Object>();
return makeDataResult(params, elabParams, null, m);
}
/**
* Returns the list of packages associated with a stored profile.
* @param profileId The id of the profile the packages are associated with.
* @return DataResult of ProfilePackageOverviewDto
*/
public static DataResult<ProfilePackageOverviewDto> listProfilePackages(
Long profileId) {
SelectMode m = ModeFactory.getMode("profile_queries", "profile_package_overview");
Map<String, Object> params = new HashMap<String, Object>();
params.put("prid", profileId);
Map<String, Object> elabParams = new HashMap<String, Object>();
return makeDataResult(params, elabParams, null, m);
}
/**
* Returns the Profile whose id is prid.
* @param prid Profile id sought.
* @param org The org in which this profile should be found.
* @return Profile whose id is prid.
*/
public static Profile lookupByIdAndOrg(Long prid, Org org) {
Profile retval = ProfileFactory.lookupByIdAndOrg(prid, org);
if (retval == null) {
LocalizationService ls = LocalizationService.getInstance();
LookupException e = new LookupException("The profile " + prid + "could not" +
"be found for org " + org.getId());
e.setLocalizedTitle(ls.getMessage("lookup.jsp.title.profile"));
e.setLocalizedReason1(ls.getMessage("lookup.jsp.reason1.profile"));
e.setLocalizedReason2(ls.getMessage("lookup.jsp.reason2.profile"));
throw e;
}
return retval;
}
/**
* Get the list of Profile's that are compatible with the given Base
* Channel ID passed in.
* @param channelIn that you want the list of Profiles against.
* @param orgIn who owns the Profiles
* @param pc PageControl to filter the list.
* @return DataResult containing ProfileDto objects
*/
public static DataResult<ProfileDto> compatibleWithChannel(
Channel channelIn,
Org orgIn, PageControl pc) {
SelectMode m = ModeFactory.getMode("profile_queries",
"compatible_with_channel");
Map<String, Object> params = new HashMap<String, Object>();
params.put("org_id", orgIn.getId());
params.put("cid", channelIn.getId());
return makeDataResult(params, new HashMap(), pc, m);
}
}