From d25353cbdf82be7d338ac8353fb586f5616ca8af Mon Sep 17 00:00:00 2001 From: Tom Gianos Date: Mon, 4 Apr 2022 13:56:47 -0700 Subject: [PATCH] Add ComputeResource and Image DTOs Will be used to transfer information for commands and jobs within business logic --- config/checkstyle/checkstyle.xml | 2 +- .../internal/dtos/ComputeResources.java | 220 ++++++++++++++++++ .../genie/common/internal/dtos/Image.java | 137 +++++++++++ .../internal/dtos/ComputeResourcesSpec.groovy | 84 +++++++ .../common/internal/dtos/ImageSpec.groovy | 68 ++++++ 5 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/ComputeResources.java create mode 100644 genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/Image.java create mode 100644 genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ComputeResourcesSpec.groovy create mode 100644 genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ImageSpec.groovy diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 21a379efbe7..0598b3d6b2e 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -104,7 +104,7 @@ - + diff --git a/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/ComputeResources.java b/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/ComputeResources.java new file mode 100644 index 00000000000..9577098892a --- /dev/null +++ b/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/ComputeResources.java @@ -0,0 +1,220 @@ +/* + * + * Copyright 2022 Netflix, Inc. + * + * 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.netflix.genie.common.internal.dtos; + +import javax.annotation.Nullable; +import javax.validation.constraints.Min; +import java.util.Objects; +import java.util.Optional; + +/** + * A representation of compute resources that a Genie entity (job/command/etc.) may request or use. + * + * @author tgianos + * @since 4.3.0 + */ +public class ComputeResources { + + @Min(value = 1, message = "Must have at least one CPU") + private final Integer cpu; + + @Min(value = 1, message = "Must have at least one GPU") + private final Integer gpu; + + @Min(value = 1, message = "Must have at least 1 MB of memory") + private final Integer memoryMb; + + @Min(value = 1, message = "Must have at least 1 MB of disk space") + private final Integer diskMb; + + @Min(value = 1, message = "Must have at least 1 Mbps of network bandwidth") + private final Integer networkMbps; + + private ComputeResources(final Builder builder) { + this.cpu = builder.bCpu; + this.gpu = builder.bGpu; + this.memoryMb = builder.bMemoryMb; + this.diskMb = builder.bDiskMb; + this.networkMbps = builder.bNetworkMbps; + } + + /** + * Get the number of CPUs. + * + * @return The amount or {@link Optional#empty()} + */ + public Optional getCpu() { + return Optional.ofNullable(this.cpu); + } + + /** + * Get the number of GPUs. + * + * @return The amount or {@link Optional#empty()} + */ + public Optional getGpu() { + return Optional.ofNullable(this.gpu); + } + + /** + * Get the amount of memory. + * + * @return The amount or {@link Optional#empty()} + */ + public Optional getMemoryMb() { + return Optional.ofNullable(this.memoryMb); + } + + /** + * Get the amount of disk space. + * + * @return The amount or {@link Optional#empty()} + */ + public Optional getDiskMb() { + return Optional.ofNullable(this.diskMb); + } + + /** + * Get the network bandwidth size. + * + * @return The size or {@link Optional#empty()} + */ + public Optional getNetworkMbps() { + return Optional.ofNullable(this.networkMbps); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(this.cpu, this.gpu, this.memoryMb, this.diskMb, this.networkMbps); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ComputeResources)) { + return false; + } + final ComputeResources that = (ComputeResources) o; + return Objects.equals(this.cpu, that.cpu) + && Objects.equals(this.gpu, that.gpu) + && Objects.equals(this.memoryMb, that.memoryMb) + && Objects.equals(this.diskMb, that.diskMb) + && Objects.equals(this.networkMbps, that.networkMbps); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "ComputeResources{" + + "cpu=" + this.cpu + + ", gpu=" + this.gpu + + ", memoryMb=" + this.memoryMb + + ", diskMb=" + this.diskMb + + ", networkMbps=" + this.networkMbps + + '}'; + } + + /** + * Builder for generating immutable {@link ComputeResources} instances. + * + * @author tgianos + * @since 4.3.0 + */ + public static class Builder { + + private Integer bCpu; + private Integer bGpu; + private Integer bMemoryMb; + private Integer bDiskMb; + private Integer bNetworkMbps; + + /** + * Set the number of CPUs. + * + * @param cpu The number must be at least 1 or {@literal null} + * @return The {@link Builder} + */ + public Builder withCpu(@Nullable final Integer cpu) { + this.bCpu = cpu; + return this; + } + + /** + * Set the number of GPUs. + * + * @param gpu The number must be at least 1 or {@literal null} + * @return The {@link Builder} + */ + public Builder withGpu(@Nullable final Integer gpu) { + this.bGpu = gpu; + return this; + } + + /** + * Set amount of memory in MB. + * + * @param memoryMb The number must be at least 1 or {@literal null} + * @return The {@link Builder} + */ + public Builder withMemoryMb(@Nullable final Integer memoryMb) { + this.bMemoryMb = memoryMb; + return this; + } + + /** + * Set amount of disk space in MB. + * + * @param diskMb The number must be at least 1 or {@literal null} + * @return The {@link Builder} + */ + public Builder withDiskMb(@Nullable final Integer diskMb) { + this.bDiskMb = diskMb; + return this; + } + + /** + * Set amount of network bandwidth in Mbps. + * + * @param networkMbps The number must be at least 1 or {@literal null} + * @return The {@link Builder} + */ + public Builder withNetworkMbps(@Nullable final Integer networkMbps) { + this.bNetworkMbps = networkMbps; + return this; + } + + /** + * Create a new immutable {@link ComputeResources} instance based on the current state of this builder instance. + * + * @return A {@link ComputeResources} instance + */ + public ComputeResources build() { + return new ComputeResources(this); + } + } +} diff --git a/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/Image.java b/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/Image.java new file mode 100644 index 00000000000..e888fd593f3 --- /dev/null +++ b/genie-common-internal/src/main/java/com/netflix/genie/common/internal/dtos/Image.java @@ -0,0 +1,137 @@ +/* + * + * Copyright 2022 Netflix, Inc. + * + * 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.netflix.genie.common.internal.dtos; + +import javax.annotation.Nullable; +import javax.validation.constraints.Size; +import java.util.Objects; +import java.util.Optional; + +/** + * Representation of metadata corresponding to the container image (docker, etc.) that the job should be launched in. + * + * @author tgianos + * @since 4.3.0 + */ +public class Image { + + @Size(max = 1024, message = "Maximum length of a container image name is 1024 characters") + private final String name; + @Size(max = 1024, message = "Maximum length of a container image tag is 1024 characters") + private final String tag; + + private Image(final Builder builder) { + this.name = builder.bName; + this.tag = builder.bTag; + } + + /** + * Get the name of the image to use for the job if one was specified. + * + * @return The name or {@link Optional#empty()} + */ + public Optional getName() { + return Optional.ofNullable(this.name); + } + + /** + * Get the tag of the image to use for the job if one was specified. + * + * @return The tag or {@link Optional#empty()} + */ + public Optional getTag() { + return Optional.ofNullable(this.tag); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(this.name, this.tag); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Image)) { + return false; + } + final Image image = (Image) o; + return Objects.equals(this.name, image.name) && Objects.equals(this.tag, image.tag); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "Image{" + + "name='" + name + '\'' + + ", tag='" + tag + '\'' + + '}'; + } + + /** + * Builder for immutable instances of {@link Image}. + * + * @author tgianos + * @since 4.3.0 + */ + public static class Builder { + private String bName; + private String bTag; + + + /** + * Set the name of the image to use. + * + * @param name The name or {@literal null} + * @return This {@link Builder} instance + */ + public Builder withName(@Nullable final String name) { + this.bName = name; + return this; + } + + /** + * Set the tag of the image to use. + * + * @param tag The tag or {@literal null} + * @return This {@link Builder} instance + */ + public Builder withTag(@Nullable final String tag) { + this.bTag = tag; + return this; + } + + /** + * Create an immutable instance of {@link Image} based on the current contents of this builder instance. + * + * @return A new {@link Image} instance + */ + public Image build() { + return new Image(this); + } + } +} diff --git a/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ComputeResourcesSpec.groovy b/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ComputeResourcesSpec.groovy new file mode 100644 index 00000000000..8ab7ecabf6c --- /dev/null +++ b/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ComputeResourcesSpec.groovy @@ -0,0 +1,84 @@ +/* + * + * Copyright 2022 Netflix, Inc. + * + * 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.netflix.genie.common.internal.dtos + +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Specifications for {@link ComputeResources}. + * + * @author tgianos + */ +@SuppressWarnings("checkstyle:finalclass") +class ComputeResourcesSpec extends Specification { + + @Unroll + def "can validate for #cpu, #gpu, #memoryMb, #diskMb, #networkMbps"() { + when: + def computeResources0 = new ComputeResources.Builder() + .withCpu(cpu) + .withGpu(gpu) + .withMemoryMb(memoryMb) + .withDiskMb(diskMb) + .withNetworkMbps(networkMbps) + .build() + + def computeResources1 = new ComputeResources.Builder() + .withCpu(cpu) + .withGpu(gpu) + .withMemoryMb(memoryMb) + .withDiskMb(diskMb) + .withNetworkMbps(networkMbps) + .build() + + def computeResources2 = new ComputeResources.Builder() + .withCpu(cpu == null ? 1 : cpu + 1) + .withGpu(gpu == null ? 1 : gpu + 1) + .withMemoryMb(memoryMb == null ? 1 : memoryMb + 1) + .withDiskMb(diskMb == null ? 1 : diskMb + 1) + .withNetworkMbps(networkMbps == null ? 1 : networkMbps + 1) + .build() + + then: + computeResources0.getCpu() == Optional.ofNullable(cpu) + computeResources0.getGpu() == Optional.ofNullable(gpu) + computeResources0.getMemoryMb() == Optional.ofNullable(memoryMb) + computeResources0.getDiskMb() == Optional.ofNullable(diskMb) + computeResources0.getNetworkMbps() == Optional.ofNullable(networkMbps) + + computeResources0 == computeResources1 + computeResources0 != computeResources2 + + computeResources0.hashCode() == computeResources1.hashCode() + computeResources0.hashCode() != computeResources2.hashCode() + + computeResources0.toString() == computeResources1.toString() + computeResources0.toString() != computeResources2.toString() + + where: + cpu | gpu | memoryMb | diskMb | networkMbps + 1 | 2 | 3 | 4 | 5 + 1 | null | null | null | null + null | 2 | null | null | null + null | null | 3 | null | null + null | null | null | 4 | null + null | null | null | null | 5 + null | null | null | null | null + } +} diff --git a/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ImageSpec.groovy b/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ImageSpec.groovy new file mode 100644 index 00000000000..2d28a29c0d4 --- /dev/null +++ b/genie-common-internal/src/test/groovy/com/netflix/genie/common/internal/dtos/ImageSpec.groovy @@ -0,0 +1,68 @@ +/* + * + * Copyright 2022 Netflix, Inc. + * + * 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.netflix.genie.common.internal.dtos + +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Specifications for {@link Image}. + * + * @author tgianos + */ +class ImageSpec extends Specification { + + @Unroll + def "can validate for #name, #tag"() { + when: + def image0 = new Image.Builder() + .withName(name) + .withTag(tag) + .build() + + def image1 = new Image.Builder() + .withName(name) + .withTag(tag) + .build() + + def image2 = new Image.Builder() + .withName(name == null ? UUID.randomUUID().toString() : name + "blah") + .withTag(tag == null ? UUID.randomUUID().toString() : tag + "blah") + .build() + + then: + image0.getName() == Optional.ofNullable(name) + image0.getTag() == Optional.ofNullable(tag) + + image0 == image1 + image0 != image2 + + image0.hashCode() == image1.hashCode() + image0.hashCode() != image2.hashCode() + + image0.toString() == image1.toString() + image0.toString() != image2.toString() + + where: + name | tag + UUID.randomUUID().toString() | UUID.randomUUID().toString() + UUID.randomUUID().toString() | null + null | UUID.randomUUID().toString() + null | null + } +}