@@ -27,7 +27,7 @@ price comparative price
2727package dx .api
2828
2929import dx .api
30- import dx .util .{Enum , JsUtils , Logger }
30+ import dx .util .{Enum , JsUtils , LinkedList , LinkedListInterface , LinkedNil , Logger }
3131import dx .util .Enum .enumFormat
3232import spray .json .{RootJsonFormat , _ }
3333
@@ -70,7 +70,7 @@ case class InstanceTypeRequest(dxInstanceType: Option[String] = None,
7070 os : Option [ExecutionEnvironment ] = None ,
7171 optional : Boolean = false ) {
7272 override def toString : String = {
73- s """ memory=( ${minMemoryMB}, ${maxMemoryMB}) disk=( ${minDiskGB}, ${maxDiskGB}) diskType= ${diskType}
73+ s """ memory=( ${minMemoryMB}, ${maxMemoryMB}) disk=( ${minDiskGB}, ${maxDiskGB}) diskType= ${diskType}
7474 |cores=( ${minCpu}, ${maxCpu}) gpu= ${gpu} os= ${os} instancetype= ${dxInstanceType}
7575 |optional= ${optional}""" .stripMargin.replaceAll(" \n " , " " )
7676 }
@@ -118,8 +118,6 @@ case class DxInstanceType(name: String,
118118 priceRank : Option [Int ] = None )
119119 extends Ordered [DxInstanceType ] {
120120
121- @ transient private lazy val version : Int = DxInstanceType .typeVersion(name)
122-
123121 /**
124122 * Returns true if this instance type satisfies the requirements of `query`,
125123 * which happens if 1) this instance type has the same name as specified in
@@ -220,11 +218,12 @@ case class DxInstanceType(name: String,
220218 }
221219
222220 /**
223- * Compare instances based on version (v1 vs v2 vs v3 vs ...).
224- * Higher version number is always better.
221+ * Compare instances based on version (v1 vs v2) -
222+ * v2 instances are always better than v1 instances .
225223 */
226224 def compareByType (that : DxInstanceType ): Int = {
227- this .version.compareTo(that.version)
225+ def typeVersion (name : String ): Int = if (name contains DxInstanceType .Version2Suffix ) 2 else 1
226+ typeVersion(this .name).compareTo(typeVersion(that.name))
228227 }
229228
230229 override def compare (that : DxInstanceType ): Int = {
@@ -238,7 +237,7 @@ case class DxInstanceType(name: String,
238237 if (resCmp != 0 ) {
239238 resCmp
240239 } else {
241- // all else being equal, choose newer versions (higher version number is better)
240+ // all else being equal, choose v2 instances over v1
242241 - compareByType(that)
243242 }
244243 }
@@ -254,33 +253,12 @@ object DxInstanceType extends DefaultJsonProtocol {
254253 implicit val dxInstanceTypeFormat : RootJsonFormat [DxInstanceType ] = jsonFormat8(
255254 DxInstanceType .apply
256255 )
257-
258- // Use a regex to dynamically find any version suffix like '_vN'
259- val VersionRegex = " _v(\\ d+)_" .r
256+ val Version2Suffix = " _v2"
257+ val NewestVersion = " v2"
260258 val CpuSuffixStart = " x"
261259 val InstanceNameSeparator = " _"
262260 private val MemoryNormFactor : Double = 1024.0
263261 private val DiskNormFactor : Double = 16.0
264-
265- /**
266- * Extracts the numeric version from the instance type name.
267- * Examples:
268- * "mem1_ssd1_x4" -> 1 (default)
269- * "mem1_ssd1_v2_x4" -> 2
270- * "mem1_ssd1_v3_x4" -> 3
271- */
272- def typeVersion (name : String ): Int = {
273- // Iterate over matches to find the last (most relevant) version suffix
274- VersionRegex .findFirstMatchIn(name) match {
275- case Some (m) =>
276- try {
277- m.group(1 ).toInt
278- } catch {
279- case _ : NumberFormatException => 1
280- }
281- case _ => 1
282- }
283- }
284262}
285263
286264case class InstanceTypeDB (instanceTypes : Map [String , DxInstanceType ]) {
@@ -292,63 +270,30 @@ case class InstanceTypeDB(instanceTypes: Map[String, DxInstanceType]) {
292270 instanceTypes.toVector.sortWith(_ < _).headOption
293271 }
294272
295- /**
296- * Generates the name of the next highest version of the current instance name.
297- * If the current version is vN, it returns the string for v(N+1).
298- * E.g., "mem1_ssd1_x4" (v1) -> "mem1_ssd1_v2_x4"
299- * E.g., "mem1_ssd1_v2_x4" -> "mem1_ssd1_v3_x4"
300- *
301- * @param instance The DxInstanceType to upgrade.
302- * @return The potential name of the next version instance.
303- */
304- private def getNextVersionName (instance : DxInstanceType ): String = {
305- val currentName = instance.name
306- val currentVersion = DxInstanceType .typeVersion(currentName)
307- val nextVersion = currentVersion + 1
308- val nextVersionSuffix = s " _v ${nextVersion}"
309-
310- // Explicitly match the type for the regex result to access start/end
311- DxInstanceType .VersionRegex .findFirstMatchIn(currentName) match {
312- case Some (m : scala.util.matching.Regex .Match ) =>
313- // Replace the existing version suffix with the next version suffix
314- currentName.substring(0 , m.start) + nextVersionSuffix + currentName.substring(m.end)
315- case None =>
316- // Append the version suffix before the CPU part
317- val parts = currentName.split(DxInstanceType .InstanceNameSeparator ).toVector
318- val (prefix, suffix) = parts.partition(! _.startsWith(DxInstanceType .CpuSuffixStart ))
319-
320- (prefix :+ nextVersionSuffix.stripPrefix(" _" ) :+ suffix.head)
321- .mkString(DxInstanceType .InstanceNameSeparator )
322- }
323- }
324-
325273 private def newerVersionAvailable (instance : DxInstanceType ): Boolean = {
326- val nextVersionName = getNextVersionName(instance)
327- instanceTypes.contains(nextVersionName)
274+ if (instance.name contains DxInstanceType .Version2Suffix ) true
275+ else {
276+ instanceTypes.contains(upgradeToLatestVersion(instance))
277+ }
328278 }
329279
330280 private def upgradeToLatestVersion (instance : DxInstanceType ): String = {
281+ val instanceNameElements = instance.name.split(DxInstanceType .InstanceNameSeparator ).toVector
282+ val linkedElements = LinkedList .create(instanceNameElements)
331283 @ tailrec
332- def findLatest (currentName : String ): String = {
333- instanceTypes.get(currentName) match {
334- case None =>
335- currentName
336- case Some (tempInstance) =>
337- val nextVersionName = getNextVersionName(tempInstance)
338- if (nextVersionName != currentName && instanceTypes.contains(nextVersionName)) {
339- findLatest(nextVersionName)
340- } else {
341- currentName
342- }
284+ def insertVersion (linkedList : LinkedListInterface [String ],
285+ accu : Vector [String ] = Vector .empty): Vector [String ] = {
286+ linkedList match {
287+ case LinkedNil => accu
288+ case l : LinkedList [String ] if l.next == LinkedNil => l.value +: accu
289+ case l : LinkedList [String ]
290+ if l.value.startsWith(DxInstanceType .CpuSuffixStart )
291+ && l.next.value != DxInstanceType .NewestVersion =>
292+ insertVersion(l.next, DxInstanceType .NewestVersion +: l.value +: accu)
293+ case l : LinkedList [(String )] => insertVersion(l.next, l.value +: accu)
343294 }
344295 }
345-
346- val nextVersionName = getNextVersionName(instance)
347- if (instanceTypes.contains(nextVersionName)) {
348- findLatest(nextVersionName)
349- } else {
350- instance.name
351- }
296+ insertVersion(linkedElements).mkString(" _" )
352297 }
353298
354299 /**
@@ -367,21 +312,12 @@ case class InstanceTypeDB(instanceTypes: Map[String, DxInstanceType]) {
367312 val preferredInstances = eligibleInstances.filterNot { instance =>
368313 instance.gpu || instance.name.contains(" fpga" )
369314 }
315+ val (v2Instances, v1Instances) =
316+ preferredInstances.partition(_.name.contains(DxInstanceType .Version2Suffix ))
370317
371- val instancesToConsider =
372- if (preferredInstances.nonEmpty) preferredInstances else eligibleInstances
373-
374- // Sort by version first (descending), then by price/resources (ascending)
375- instancesToConsider.toVector
376- .sortWith { (a, b) =>
377- val versionCmp = - a.compareByType(b)
378- if (versionCmp != 0 ) {
379- versionCmp < 0
380- } else {
381- a.compare(b) < 0
382- }
383- }
384- .headOption
318+ selectMinimalInstanceType(v2Instances) // Try preferred v2 non-GPU/FPGA
319+ .orElse(selectMinimalInstanceType(v1Instances)) // Then try v1 non-GPU/FPGA
320+ .orElse(selectMinimalInstanceType(eligibleInstances)) // As a last resort, consider all instances (including GPU/FPGA)
385321 .getOrElse(
386322 throw new Exception (
387323 s """ no instance types meet the minimal requirements memory >= ${InstanceTypeDB .MinMemory }
@@ -430,7 +366,8 @@ case class InstanceTypeDB(instanceTypes: Map[String, DxInstanceType]) {
430366 val instance = instanceTypes.get(name)
431367 instance match {
432368 case None => return instance
433- case Some (x) if newerVersionAvailable(x) =>
369+ case Some (x)
370+ if newerVersionAvailable(x) && ! (x.name contains DxInstanceType .Version2Suffix ) =>
434371 Logger .get.warning(
435372 s """
436373 |WARNING: an older version of the instance ${x.name} is specified.
0 commit comments