/******************************************************************************* * Copyright (c) 2015 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 com.openshift.internal.restclient.model.volume; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import com.openshift.internal.restclient.model.KubernetesResource; import com.openshift.internal.restclient.model.volume.property.AbstractPersistentVolumeProperties; import com.openshift.internal.restclient.model.volume.property.HostPathVolumeProperties; import com.openshift.internal.restclient.model.volume.property.NfsVolumeProperties; import com.openshift.restclient.IClient; import com.openshift.restclient.model.volume.IPersistentVolume; import com.openshift.restclient.model.volume.VolumeType; import com.openshift.restclient.model.volume.property.IHostPathVolumeProperties; import com.openshift.restclient.model.volume.property.INfsVolumeProperties; import com.openshift.restclient.model.volume.property.IPersistentVolumeProperties; import com.openshift.restclient.utils.MemoryUnit; public class PersistentVolume extends KubernetesResource implements IPersistentVolume { private static final String PV_ACCESS_MODES = "spec.accessModes"; private static final String PV_CAPACITY = "spec.capacity.storage"; private static final String PV_RECLAIM_POLICY = "spec.persistentVolumeReclaimPolicy"; private static final String PV_NFS = "spec.nfs"; private static final String PV_HOST_PATH = "spec.hostPath"; private static final String PV_SPEC = "spec"; private static final String SERVER = "server"; private static final String PATH = "path"; private static final String READ_ONLY = "readOnly"; public PersistentVolume(ModelNode node, IClient client, Map<String, String[]> propertyKeys) { super(node, client, propertyKeys); } /** * @param unit the designated unit. One of "Ki", "Mi", "Gi", "Ti", "Pi", "Ei". * @return 0, if conversion not possible. Otherwise the capacity in given units. * @see {@link PersistentVolume#convert(String, MemoryUnit)} * @throws IllegalArgumentException if not supported {@code unit} given. */ @Override public long getCapacity(String unit) { String capacity = asString(PV_CAPACITY); return convert(capacity, MemoryUnit.valueOf(unit)); } /** * @param unit the designated unit. One of {@link MemoryUnit}'s Ki, Mi, Gi, Ti, Pi, Ei. * @return 0, if conversion not possible. Otherwise the capacity in given units. * @see {@link PersistentVolume#convert(String, MemoryUnit)} * @throws IllegalArgumentException if not supported {@code unit} given. */ @Override public long getCapacity(MemoryUnit unit) { String capacity = asString(PV_CAPACITY); return convert(capacity, unit); } /** * @return the capacity in bytes. */ @Override public long getCapacity() { String capacity = asString(PV_CAPACITY); return Math.multiplyExact(convert(capacity, MemoryUnit.Ki), 1024L); } /** * Sets the capacity. There is no conversion between units. * @param capacity the capacity * @param unit the unit */ @Override public void setCapacity(long capacity, MemoryUnit unit) { set(PV_CAPACITY, capacity + unit.name()); } /** * @return the unit in which the capacity is represent. * @see {@link MemoryUnit} */ @Override public MemoryUnit getCapacityUnit() { return parseCapacityUnit(asString(PV_CAPACITY)); } /** * @return access modes * @see {@link com.openshift.restclient.model.volume.PVCAccessModes} * @see */ @Override public Set<String> getAccessModes() { return asSet(PV_ACCESS_MODES, ModelType.STRING); } /** * Sets the access modes. * If there are any modes present, they are overridden by the {@code modes} parameter. * @param modes the access modes */ @Override public void setAccessModes(String... modes) { get(PV_ACCESS_MODES).setEmptyList(); set(PV_ACCESS_MODES, modes); } /** * @return the value of 'spec.persistentVolumeReclaimPolicy' */ @Override public String getReclaimPolicy() { return asString(PV_RECLAIM_POLICY); } /** * Sets the 'spec.persistentVolumeReclaimPolicy' value * @param policy the policy string */ @Override public void setReclaimPolicy(String policy) { set(PV_RECLAIM_POLICY, policy); } /** * @return specific persistent volume type */ @Override public IPersistentVolumeProperties getPersistentVolumeProperties() { IPersistentVolumeProperties properties; switch(getPVType()) { case VolumeType.HOST_PATH: properties = createHostPathVolumeProperties(); break; case VolumeType.NFS: properties = createNFSVolumeProperties(); break; default: properties = null; break; } return properties; } /** * Sets the volume type properties. * @param properties the properties */ @Override public void setPersistentVolumeProperties(IPersistentVolumeProperties properties) { if(properties instanceof AbstractPersistentVolumeProperties) { ((AbstractPersistentVolumeProperties) properties).setProperties(getNode()); } } /** * @return a string value of the type. One of {@link VolumeType}. */ public String getPVType() { final ModelNode spec = get(PV_SPEC); return VolumeType.getTypes().stream() .filter(spec::hasDefined) .findFirst().get(); } /** * @return values of 'spec.nfs' wrapped in {@link INfsVolumeProperties} */ private INfsVolumeProperties createNFSVolumeProperties() { ModelNode node = get(PV_NFS); String server = asString(node, SERVER); String path = asString(node, PATH); boolean readOnly = asBoolean(node, READ_ONLY); return new NfsVolumeProperties(server, path, readOnly); } /** * @return the value of 'spec.hostPath.path' wrapped in {@link IHostPathVolumeProperties} */ private IHostPathVolumeProperties createHostPathVolumeProperties() { ModelNode node = get(PV_HOST_PATH); String path = asString(node, PATH); return new HostPathVolumeProperties(path); } /** * Converts capacity string (i.e. '10Gi') to it's numeric representation. * The following assumptions are used to compute values: * One Ki = 2^10 * One Mi = 2^20 * One Gi = 2^30 * One Ti = 2^40 * One Pi = 2^50 * One Ei = 2^60 * * Java's type long can contain max value of 2^64, enough for a few Exbibytes. * Following equation is used to convert values: * * kubernetesGivenNumber << (10 * i), where << is implicit multiplication by powers of 2 and (10 * i) is the order of magnitude. * Example: convert 1Ti to Mi = 1Ti << (10*2) = 1 * 2^20 = 2^20 * * @param capacity the capacity string * @param designatedUnit the designated unit of type {@link MemoryUnit}. Possible values 'Ki, Mi, Gi, Ti, Pi, Ei'. * @return 0 if conversion is not possible, otherwise the numeric representation converted to designatedUnit. * Conversion is not possible when the designatedUnit cannot contain the unit given by Kubernetes in integral form. */ private static long convert(final String capacity, final MemoryUnit designatedUnit) { long number = parseCapacityValue(capacity); MemoryUnit currentUnit = parseCapacityUnit(capacity); int operation = currentUnit.compareTo(designatedUnit); if(operation == 0L) { return number; } else if(operation > 0L) { return safeMultiplicationBy1024(number, operation); //number << (10 * operation); } else { return 0L; } } /** * @throws ArithmeticException if computation overflows */ private static long safeMultiplicationBy1024(long value, long times) { long multiplicand = (long) Math.pow(1024L, times); return Math.multiplyExact(value, multiplicand); } private static long parseCapacityValue(String capacityString) { if(capacityString == null) { return 0L; } Pattern pattern = Pattern.compile("(\\d+)(\\w{2})"); Matcher m = pattern.matcher(capacityString); if(!m.find()) { return 0L; } return Long.parseLong(m.group(1)); } private static MemoryUnit parseCapacityUnit(String capacityString) { if(capacityString == null) { return null; } Pattern pattern = Pattern.compile("(\\d+)(\\w{2})"); Matcher m = pattern.matcher(capacityString); if(!m.find()) { return null; } return MemoryUnit.valueOf(m.group(2)); } }