/**
* Copyright (c) 2010--2015 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.frontend.action.channel.manage;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.util.FileUtils;
import com.redhat.rhn.common.util.RecurringEventPicker;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.channel.ChannelFactory;
import com.redhat.rhn.domain.channel.ContentSource;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.struts.RequestContext;
import com.redhat.rhn.frontend.struts.RhnAction;
import com.redhat.rhn.frontend.struts.RhnHelper;
import com.redhat.rhn.frontend.struts.StrutsDelegate;
import com.redhat.rhn.frontend.taglibs.list.helper.ListHelper;
import com.redhat.rhn.frontend.taglibs.list.helper.Listable;
import com.redhat.rhn.manager.satellite.SystemCommandExecutor;
import com.redhat.rhn.manager.channel.ChannelManager;
import com.redhat.rhn.manager.download.DownloadManager;
import com.redhat.rhn.manager.user.UserManager;
import com.redhat.rhn.taskomatic.TaskomaticApi;
import com.redhat.rhn.taskomatic.TaskomaticApiException;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* SyncRepositoriesAction
* @version $Rev$
*/
public class SyncRepositoriesAction extends RhnAction implements Listable {
private static final String REPOSYNC_LOCKFILE = "/var/run/spacewalk-repo-sync.pid";
/**
*
* {@inheritDoc}
*/
@Override
public ActionForward execute(ActionMapping mapping,
ActionForm formIn,
HttpServletRequest request,
HttpServletResponse response) {
RequestContext context = new RequestContext(request);
User user = context.getCurrentUser();
long cid = context.getRequiredParam("cid");
Channel chan = ChannelFactory.lookupByIdAndUser(cid, user);
request.setAttribute("channel_name", chan.getName());
request.setAttribute("cid", chan.getId());
boolean inProgress = isSyncInProgress(chan);
if (inProgress) {
addMessage(request, "message.syncinprogress");
request.setAttribute("in_progress", true);
}
request.setAttribute("status", parseSyncLog(chan, inProgress));
if (!chan.getSources().isEmpty()) {
String lastSync = LocalizationService.getInstance().getMessage(
"channel.edit.repo.neversynced");
if (chan.getLastSynced() != null) {
lastSync = LocalizationService.getInstance().formatCustomDate(
chan.getLastSynced());
}
request.setAttribute("last_sync", lastSync);
if (!ChannelManager.getLatestSyncLogFiles(chan).isEmpty()) {
request.setAttribute("log_url",
DownloadManager.getChannelSyncLogDownloadPath(chan,
context.getCurrentUser()));
}
}
Map<String, Object> params = new HashMap<String, Object>();
params.put(RequestContext.CID, chan.getId().toString());
ListHelper helper = new ListHelper(this, request, params);
helper.execute();
TaskomaticApi taskomatic = new TaskomaticApi();
String oldCronExpr = null;
try {
oldCronExpr = taskomatic.getRepoSyncSchedule(chan, user);
}
catch (TaskomaticApiException except) {
params.put("inactive", true);
request.setAttribute("inactive", true);
createErrorMessage(request,
"repos.jsp.message.taskomaticdown", null);
}
RecurringEventPicker picker = RecurringEventPicker.prepopulatePicker(
request, "date", oldCronExpr);
if (context.isSubmitted()) {
StrutsDelegate strutsDelegate = getStrutsDelegate();
// check user permissions first
if (!UserManager.verifyChannelAdmin(user, chan)) {
createErrorMessage(request,
"frontend.actions.channels.manager.add.permsfailure", null);
return mapping.findForward(RhnHelper.DEFAULT_FORWARD);
}
if (chan.getSources().isEmpty()) {
createErrorMessage(request,
"repos.jsp.channel.norepos", null);
return mapping.findForward(RhnHelper.DEFAULT_FORWARD);
}
try {
Map<String, String> mparams = new HashMap<String, String>();
String [] lparams = {"no-errata", "latest", "sync-kickstart", "fail"};
for (String p : lparams) {
if (request.getParameter(p) != null) {
mparams.put(p, "true");
}
}
if (context.wasDispatched("repos.jsp.button-sync")) {
// schedule one time repo sync
taskomatic.scheduleSingleRepoSync(chan, user, mparams);
createSuccessMessage(request, "message.syncscheduled",
chan.getName());
}
else if (context.wasDispatched("schedule.button")) {
if ((picker.isDisabled() ||
StringUtils.isEmpty(picker.getCronEntry())) &&
oldCronExpr != null) {
taskomatic.unscheduleRepoSync(chan, user);
createSuccessMessage(request, "message.syncschedule.disabled",
chan.getName());
}
else if (!StringUtils.isEmpty(picker.getCronEntry())) {
Date date = taskomatic.scheduleRepoSync(chan, user,
picker.getCronEntry(), mparams);
createSuccessMessage(request, "message.syncscheduled",
chan.getName());
}
}
}
catch (TaskomaticApiException e) {
if (e.getMessage().contains("InvalidParamException")) {
createErrorMessage(request,
"repos.jsp.message.invalidcron", picker.getCronEntry());
}
else {
createErrorMessage(request,
"repos.jsp.message.schedulefailed", null);
}
return mapping.findForward(RhnHelper.DEFAULT_FORWARD);
}
Map forwardParams = new HashMap();
forwardParams.put("cid", chan.getId());
return getStrutsDelegate().forwardParams(mapping.findForward("success"),
forwardParams);
}
return mapping.findForward(RhnHelper.DEFAULT_FORWARD);
}
private boolean isSyncInProgress(Channel chan) {
String pid;
try {
pid = FileUtils.readStringFromFile(REPOSYNC_LOCKFILE).trim();
}
catch (RuntimeException e) {
return false;
}
// Is this PID running?
String[] cmd = {"ps", "-o", "args", "-p", pid};
SystemCommandExecutor ce = new SystemCommandExecutor();
ce.execute(cmd);
return ce.getLastCommandOutput().contains(" " + chan.getLabel() + " ");
}
private String getLastSyncLog(Channel chan) {
List<String> files = ChannelManager.getLatestSyncLogFiles(chan);
String lastLog = "";
if (!files.isEmpty()) {
// Most recent file only
String allLogs = FileUtils.readStringFromFile(files.get(0));
int lastLogStart = allLogs.lastIndexOf("Sync started:");
if (lastLogStart > -1) {
lastLog = allLogs.substring(lastLogStart);
}
}
return lastLog;
}
private Map<String, Map<String, Object>> parseSyncLog(
Channel chan, boolean inProgress) {
String log = getLastSyncLog(chan);
Map<String, Map<String, Object>> repositories =
new HashMap<String, Map<String, Object>>();
String[] allRepoLog = log.split("Repo URL: ");
for (String repoLog : allRepoLog) {
Map<String, Object> syncingRepo = new HashMap<String, Object>();
String[] lines = repoLog.split("\\n");
String lastLine = lines[lines.length - 1];
// Downloading packages
if (lastLine.matches(".*\\d+/\\d+ : .+")) {
// Example:
// 2016/09/07 14:41:14 +02:00 22/22 : spacewalk-oscap-2.5.3-1.fc24.noarch
String[] lineParts = lastLine.split(" ");
// Remove timestamp part - 3 words
lineParts = Arrays.copyOfRange(lineParts, 3, lineParts.length);
String[] progress = lineParts[0].split("/");
int done = Integer.parseInt(progress[0]);
int total = Integer.parseInt(progress[1]);
int percentage = done * 100 / total;
syncingRepo.put("progress", String.valueOf(percentage));
syncingRepo.put("title", StringUtils.join(lineParts, " "));
// Mark as failed if reposync stopped running
syncingRepo.put("failed", !inProgress);
}
else {
for (String line : lines) {
// Packages are downloaded
if (line.contains("No new packages to sync.") ||
(line.contains("Linking packages to channel."))) {
syncingRepo.put("progress", "100");
// Mark as finished when all repos are synced
syncingRepo.put("finished", !inProgress);
}
else if (line.contains("ERROR: ")) {
syncingRepo.put("failed", true);
syncingRepo.put("title", line);
break;
}
}
}
// Using URL as a key
repositories.put(lines[0], syncingRepo);
}
return repositories;
}
/**
*
* {@inheritDoc}
*/
public List<ContentSource> getResult(RequestContext context) {
User user = context.getCurrentUser();
long cid = context.getRequiredParam("cid");
Channel chan = ChannelFactory.lookupByIdAndUser(cid, user);
return ChannelFactory.lookupContentSources(user.getOrg(), chan);
}
}