// Copyright 2016 Twitter. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.twitter.heron.packing;
import java.util.HashSet;
import com.google.common.base.Optional;
import com.twitter.heron.spi.packing.PackingPlan;
import com.twitter.heron.spi.packing.Resource;
/**
* Class that describes a container used to place Heron Instances with specific memory, Cpu and disk
* requirements. Each container has limited ram, CpuCores and disk resources.
*/
public class Container {
private HashSet<PackingPlan.InstancePlan> instances;
private Resource capacity;
private int paddingPercentage;
public HashSet<PackingPlan.InstancePlan> getInstances() {
return instances;
}
public Resource getCapacity() {
return capacity;
}
public int getPaddingPercentage() {
return paddingPercentage;
}
/**
* Creates a container with a specific capacity which will maintain a specific percentage
* of its resources for padding.
*
* @param capacity the capacity of the container in terms of cpu, ram and disk
* @param paddingPercentage the padding percentage
*/
public Container(Resource capacity, int paddingPercentage) {
this.capacity = capacity;
this.instances = new HashSet<PackingPlan.InstancePlan>();
this.paddingPercentage = paddingPercentage;
}
/**
* Check whether the container can accommodate a new instance with specific resource requirements
*
* @return true if the container has space otherwise return false
*/
private boolean hasSpace(Resource resource) {
Resource usedResources = this.getTotalUsedResources();
long newRam = usedResources.getRam() + resource.getRam();
double newCpu = usedResources.getCpu() + resource.getCpu();
long newDisk = usedResources.getDisk() + resource.getDisk();
return PackingUtils.increaseBy(newRam, paddingPercentage) <= this.capacity.getRam()
&& Math.round(PackingUtils.increaseBy(newCpu, paddingPercentage)) <= this.capacity.getCpu()
&& PackingUtils.increaseBy(newDisk, paddingPercentage) <= this.capacity.getDisk();
}
/**
* Computes the used resources of the container by taking into account the resources
* allocated for each instance.
*
* @return a Resource object that describes the used cpu, ram and disk in the container.
*/
private Resource getTotalUsedResources() {
long usedRam = 0;
double usedCpuCores = 0;
long usedDisk = 0;
for (PackingPlan.InstancePlan instancePlan : this.instances) {
Resource resource = instancePlan.getResource();
usedRam += resource.getRam();
usedCpuCores += resource.getCpu();
usedDisk += resource.getDisk();
}
return new Resource(usedCpuCores, usedRam, usedDisk);
}
/**
* Update the resources currently used by the container, when a new instance with specific
* resource requirements has been assigned to the container.
*
* @return true if the instance can be added to the container, false otherwise
*/
public boolean add(PackingPlan.InstancePlan instancePlan) {
if (this.hasSpace(instancePlan.getResource())) {
this.instances.add(instancePlan);
return true;
} else {
return false;
}
}
/**
* Remove an instance of a particular component from a container and update its
* corresponding resources.
*
* @return the corresponding instance plan if the instance is removed the container.
* Return void if an instance is not found
*/
public Optional<PackingPlan.InstancePlan> removeAnyInstanceOfComponent(String component) {
Optional<PackingPlan.InstancePlan> instancePlan = getAnyInstanceOfComponent(component);
if (instancePlan.isPresent()) {
PackingPlan.InstancePlan plan = instancePlan.get();
this.instances.remove(plan);
return instancePlan;
}
return Optional.absent();
}
/**
* Find whether any instance of a particular component is assigned to the container
*
* @return the instancePlan that corresponds to the instance if it is found, void otherwise
*/
public Optional<PackingPlan.InstancePlan> getAnyInstanceOfComponent(String component) {
for (PackingPlan.InstancePlan instancePlan : this.instances) {
if (instancePlan.getComponentName().equals(component)) {
return Optional.of(instancePlan);
}
}
return Optional.absent();
}
}