/******************************************************************************* * Copyright (c) 2016-2017 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.openshift.internal.ui.handler; import java.util.stream.Collectors; import org.apache.commons.lang.math.NumberUtils; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.core.databinding.validation.ValidationStatus; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.ISelection; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Spinner; import org.eclipse.ui.handlers.HandlerUtil; import org.jboss.tools.common.ui.databinding.ValueBindingBuilder; import org.jboss.tools.openshift.internal.common.ui.OpenShiftCommonImages; import org.jboss.tools.openshift.internal.common.ui.databinding.FormPresenterSupport; import org.jboss.tools.openshift.internal.common.ui.utils.DisposeUtils; import org.jboss.tools.openshift.internal.common.ui.utils.UIUtils; import org.jboss.tools.openshift.internal.core.util.ResourceUtils; import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; import org.jboss.tools.openshift.internal.ui.models.IResourceWrapper; import org.jboss.tools.openshift.internal.ui.models.IServiceWrapper; import org.jboss.tools.openshift.internal.ui.utils.ResourceWrapperUtils; import com.openshift.restclient.ResourceKind; import com.openshift.restclient.api.capabilities.IScalable; import com.openshift.restclient.capability.CapabilityVisitor; import com.openshift.restclient.model.IDeploymentConfig; import com.openshift.restclient.model.IPod; import com.openshift.restclient.model.IReplicationController; import com.openshift.restclient.model.IResource; import com.openshift.restclient.model.IService; /** * Handle for scaling deployments * * @author jeff.cantrill * @author Andre Dietisheim * */ public class ScaleDeploymentHandler extends AbstractHandler { public static final String REPLICA_DIFF = "org.jboss.tools.openshift.ui.command.deployment.scale.replicadiff"; @Override public Object execute(ExecutionEvent event) throws ExecutionException { IDeploymentConfig dc = getDeploymentConfig(getSelectedElement(event, IResourceWrapper.class)); if (dc == null) { IResource resource = ResourceWrapperUtils.getResource(UIUtils.getFirstElement(HandlerUtil.getCurrentSelection(event))); return OpenShiftUIActivator.statusFactory().errorStatus( NLS.bind("Could not scale {0}: Could not find deployment config", resource == null? "" : resource.getName())); } scaleUsing(event, dc, dc.getName()); return null; } protected <T> T getSelectedElement(ExecutionEvent event, Class<T> klass) { ISelection selection = UIUtils.getCurrentSelection(event); return UIUtils.getFirstElement(selection, klass); } private IDeploymentConfig getDeploymentConfig(IResourceWrapper<?,?> wrapper) { if (wrapper == null) { return null; } IDeploymentConfig dc = null; IResource wrapped = wrapper.getWrapped(); if (wrapper instanceof IServiceWrapper) { // service selected dc = getDeploymentConfig((IServiceWrapper) wrapper); } else if (wrapped instanceof IPod) { // pod selected dc = getDeploymentConfig((IPod) wrapped, wrapper); } else if (wrapped instanceof IDeploymentConfig) { // deployment config selected // has to be tested before IReplicationController, IDeploymentConfig extends IReplicationController dc = (IDeploymentConfig) wrapped; } else if (wrapped instanceof IReplicationController) { // replication controller selected (deployment tab in properties) // has to be tested after IDeploymentConfig, IDeploymentConfig extends IReplicationController dc = getDeploymentConfig((IReplicationController) wrapped, wrapper); } return dc; } private IDeploymentConfig getDeploymentConfig(IReplicationController rc, IResourceWrapper<?, ?> wrapper) { IDeploymentConfig dc = null; IServiceWrapper service = ResourceWrapperUtils.getServiceWrapperFor(wrapper, serviceWrapper -> ResourceUtils.areRelated(rc, (IService) serviceWrapper.getWrapped())); if (service != null) { dc = ResourceUtils.getDeploymentConfigFor(rc, ResourceWrapperUtils.getResources(service.getResourcesOfKind(ResourceKind.DEPLOYMENT_CONFIG))); } return dc; } private IDeploymentConfig getDeploymentConfig(IPod pod, IResourceWrapper<?, ?> wrapper) { IDeploymentConfig dc = null; if (!ResourceUtils.isBuildPod(pod)) { IServiceWrapper service = ResourceWrapperUtils.getServiceWrapperFor( wrapper, serviceWrapper -> ResourceUtils.areRelated(pod, (IService) serviceWrapper.getWrapped())); dc = getDeploymentConfig(service); } return dc; } private IDeploymentConfig getDeploymentConfig(IServiceWrapper service) { return ResourceUtils.getLatestResourceVersion( service.getResourcesOfKind(ResourceKind.DEPLOYMENT_CONFIG).stream() .map(wrapper -> (IDeploymentConfig) wrapper.getWrapped()) .collect(Collectors.<IDeploymentConfig>toList())); } protected void scaleUsing(ExecutionEvent event, IReplicationController rc, String name) { final int requestedReplicas = getRequestedReplicas(rc, name, event); final int currentReplicas = rc.getCurrentReplicaCount(); if (requestedReplicas != -1 && currentReplicas != requestedReplicas) { if (requestedReplicas == 0 && !showStopDeploymentWarning(name, HandlerUtil.getActiveShell(event))) { return; } scaleDeployment(event, name, rc, requestedReplicas); } } protected boolean showStopDeploymentWarning(String name, Shell shell) { MessageDialog dialog = new MessageDialog(shell, "Stop all deployments?", OpenShiftCommonImages.OPENSHIFT_LOGO_WHITE_ICON_IMG, NLS.bind("Are you sure you want to scale {0} to 0 replicas?\nThis will stop all pods for the deployment.", name), MessageDialog.WARNING, 1, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL }); return dialog.open() == Dialog.OK; } protected void scaleDeployment(ExecutionEvent event, String name, IReplicationController rc, int replicas) { if (replicas >= 0) { new Job(NLS.bind("Scaling {0} deployment to {1}...", name, replicas)) { @Override protected IStatus run(IProgressMonitor monitor) { try { return rc.accept(new CapabilityVisitor<IScalable, IStatus>() { @Override public IStatus visit(IScalable capability) { capability.scaleTo(replicas); return Status.OK_STATUS; } }, new Status(Status.ERROR, OpenShiftUIActivator.PLUGIN_ID, "Scaling is not supported for this resource")); } catch (Exception e) { String message = NLS.bind("Unable to scale {0}", name); OpenShiftUIActivator.getDefault().getLogger().logError(message, e); return new Status(Status.ERROR, OpenShiftUIActivator.PLUGIN_ID, message, e); } } }.schedule(); } } private int getRequestedReplicas(IReplicationController rc, String name, ExecutionEvent event) { String diff = event.getParameter(REPLICA_DIFF); int currentReplicas = rc.getCurrentReplicaCount(); if (NumberUtils.isNumber(diff)) { return currentReplicas + Integer.parseInt(diff); } else { return showScaleReplicasDialog(name, currentReplicas, HandlerUtil.getActiveShell(event)); } } protected int showScaleReplicasDialog(String name, int currentReplicas, Shell shell) { ScaleReplicasDialog dialog = new ScaleReplicasDialog(currentReplicas, name, shell); if (dialog.open() == Dialog.OK) { return dialog.getRequestedReplicas(); } return -1; } public class ScaleReplicasDialog extends TitleAreaDialog { private int currentReplicas; private IObservableValue<Integer> requestedReplicas; private String name; public ScaleReplicasDialog(int currentReplicas, String name, Shell parentShell) { super(parentShell); this.currentReplicas = currentReplicas; this.requestedReplicas = new WritableValue<Integer>(currentReplicas, Integer.class); this.name = name; } @Override protected Control createContents(Composite parent) { Control control = super.createContents(parent); setupDialog(name, parent.getShell()); return control; } @Override protected Control createDialogArea(Composite parent) { DataBindingContext dbc = new DataBindingContext(); Label titleSeparator = new Label(parent, SWT.HORIZONTAL | SWT.SEPARATOR); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.TOP).grab(true, false).applyTo(titleSeparator); final Composite dialogArea = new Composite(parent, SWT.NONE); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(dialogArea); GridLayoutFactory.fillDefaults() .numColumns(2).margins(10, 20).spacing(20, SWT.DEFAULT).applyTo(dialogArea); // scale label Label scaleLabel = new Label(dialogArea, SWT.NONE); scaleLabel.setText("Scale to number of replicas:"); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.CENTER).applyTo(scaleLabel); // scale spinner Spinner scaleSpinner = new Spinner(dialogArea, SWT.BORDER); scaleSpinner.setMinimum(0); scaleSpinner.setSelection(currentReplicas); scaleSpinner.setPageIncrement(1); GridDataFactory.fillDefaults() .align(SWT.FILL, SWT.FILL).applyTo(scaleSpinner); ValueBindingBuilder .bind(WidgetProperties.selection().observe(scaleSpinner)) .validatingAfterConvert(new IValidator() { @Override public IStatus validate(Object value) { if (!(value instanceof Integer)) { return ValidationStatus.error(NLS.bind("You need to provide a positive number of replicas for deployment {0}", name)); } int requestedReplicas = (int) value; if (requestedReplicas == currentReplicas) { return ValidationStatus.cancel(""); } if (requestedReplicas == 0) { return ValidationStatus.warning(NLS.bind("Scaling to 0 replicas will stop all pods.", name)); } return ValidationStatus.ok(); }}) .to(requestedReplicas) .in(dbc); new FormPresenterSupport(new FormPresenterSupport.IFormPresenter() { @Override public void setMessage(String message, int type) { ScaleReplicasDialog.this.setMessage(message, type); } @Override public void setComplete(boolean complete) { Button button = ScaleReplicasDialog.this.getButton(IDialogConstants.OK_ID); if (!DisposeUtils.isDisposed(button)) { button.setEnabled(complete); } } @Override public Control getControl() { return dialogArea; } }, dbc); return dialogArea; } private void setupDialog(String name, Shell shell) { shell.setText("Scale Deployments"); setTitle(NLS.bind("Enter the desired number of replicas for deployment {0}", name)); setTitleImage(OpenShiftCommonImages.OPENSHIFT_LOGO_WHITE_MEDIUM_IMG); setHelpAvailable(false); } @Override protected void createButtonsForButtonBar(Composite parent) { Button okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); okButton.setEnabled(false); // initially disable ok since scaling set to current replicas createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); } /** * Returns the requested number of replicas. Returns -1 if the user cancelled the dialog. * @return */ public int getRequestedReplicas() { if (Dialog.OK == getReturnCode()) { return requestedReplicas.getValue(); } else { return -1; } } } }