Skip to content

Commit

Permalink
Intermediate revision
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias Büchse <[email protected]>
  • Loading branch information
mbuechse committed Jan 22, 2024
1 parent 63dd8b9 commit 20a37d2
Showing 1 changed file with 337 additions and 0 deletions.
337 changes: 337 additions & 0 deletions Tests/iaas/flavor-naming/flavor_name_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
#!/usr/bin/env python3
import re


class TypeCheck:
def __call__(self, attr, value):
raise ValueError(f"{attr} can not be set to {value}")


class OptionalCheck(TypeCheck):
def __init__(self, check):
self.check = check

def __call__(self, attr, value):
if value is None:
return
self.check(attr, value)


class TblCheck(TypeCheck):
def __init__(self, tbl):
self.tbl = tbl

def __call__(self, attr, value):
if value not in self.tbl:
raise ValueError(f"{attr} can not be set to {value}; must be one of {tuple(self.tbl)}")


class BoolCheck(TypeCheck):
def __call__(self, attr, value):
if not isinstance(value, bool):
raise ValueError(f"{attr} can not be set to {value}; must be boolean")


class IntCheck(TypeCheck):
def __call__(self, attr, value):
if not isinstance(value, int) or value <= 0:
raise ValueError(f"{attr} can not be set to {value}; must be positive integer")


class FloatCheck(TypeCheck):
def __call__(self, attr, value):
if not isinstance(value, float) or value <= 0 or int(2 * value) != 2 * value:
raise ValueError(f"{attr} can not be set to {value}; must be positive multiple of 0.5")


class Attr:
typ = None
default = None

def __init__(self, name, default=None):
self.name = name
if default != self.default:
self.default = default # instance attribute will override class attibute
# the following will be set via __set_name__
self.attr = None
self._attr = None

def validate(self, val):
if self.typ is None:
return
self.typ(self.attr, val)

def __set_name__(self, owner, name):
self.attr = name
self._attr = '_' + name

def __get__(self, obj, objclass=None):
if obj is None:
return self
return getattr(obj, self._attr, self.default)

def __set__(self, obj, value):
self.validate(value)
setattr(obj, self._attr, value)


class IntAttr(Attr):
typ = IntCheck()


class OptIntAttr(Attr):
typ = OptionalCheck(IntCheck())


class FloatAttr(Attr):
typ = FloatCheck()


class BoolAttr(Attr):
typ = BoolCheck()


class TblAttr(Attr):
def __init__(self, name, tbl, default=None):
super().__init__(name, default)
self.typ = TblCheck(tbl)


class DepTblAttr(Attr):
def __init__(self, name, key_attr, deptbl, default=None):
super().__init__(name, default)
self.key_attr = key_attr
self.typs = {key: TblCheck(tbl) for key, tbl in deptbl.items()}

def __set__(self, obj, value):
self.typs[self.key_attr.__get__(obj)](self.attr, value)
super().__set__(obj, value)


class Main:
"""Class representing the first part (CPU+RAM)"""
type = "CPU-RAM"
cpus = IntAttr("#vCPUs")
cputype = TblAttr("CPU type", {"L": "LowPerf vCPUs", "V": "vCPUs", "T": "SMT Threads", "C": "Dedicated Cores"})
cpuinsecure = BoolAttr("?Insec SMT")
ram = FloatAttr("##GiB RAM")
raminsecure = BoolAttr("?no ECC")
ramoversubscribed = BoolAttr("?RAM Over")


class Disk:
"""Class representing the disk part"""
type = "Disk"
nrdisks = IntAttr("#:NrDisks")
nrdisks.default = 1
disksize = OptIntAttr("#.GB Disk")
disktype = TblAttr(".Disk type", {"n": "Networked", "h": "Local HDD", "s": "SSD", "p": "HiPerf NVMe"})


class Hype:
"""Class repesenting Hypervisor"""
type = "Hypervisor"
hype = TblAttr(".Hypervisor", {None: None, "kvm": "KVM", "xen": "Xen", "hyv": "Hyper-V", "vmw": "VMware", "bms": "Bare Metal System"})


class HWVirt:
"""Class repesenting support for hardware virtualization"""
type = "Hardware/NestedVirtualization"
hwvirt = BoolAttr("?HardwareVirt")


class CPUBrand:
"""Class repesenting CPU brand"""
type = "CPUBrand"
cpuvendor = TblAttr(".CPU Vendor", {"i": "Intel", "z": "AMD", "a": "ARM", "r": "RISC-V"})
cpugen = DepTblAttr("#.CPU Gen", cpuvendor, {
"i": {0: "Unspec/Pre-Skylake", 1: "Skylake", 2: "Cascade Lake", 3: "Ice Lake", 4: "Sapphire Rapids"},
"z": {0: "Unspec/Pre-Zen", 1: "Zen 1", 2: "Zen 2", 3: "Zen 3", 4: "Zen 4"},
"a": {0: "Unspec/Pre-A76", 1: "A76/NeoN1", 2: "A78/X1/NeoV1", 3: "A710/NeoN2"},
})
perf = TblAttr("Performance", {"": "Std Perf", "h": "High Perf", "hh": "Very High Perf", "hhh": "Very Very High Perf"})


class GPU:
"""Class repesenting GPU support"""
type = "GPU"
gputype = TblAttr(".Type", {"g": "vGPU", "G": "Pass-Through GPU"})
brand = TblAttr(".Brand", {"N": "nVidia", "A": "AMD", "I": "Intel"})
gen = DepTblAttr(".Gen", brand, {
"N": {"f": "Fermi", "k": "Kepler", "m": "Maxwell", "p": "Pascal",
"v": "Volta", "t": "Turing", "a": "Ampere", "l": "AdaLovelace"},
"A": {"0.4": "GCN4.0/Polaris", "0.5": "GCN5.0/Vega", "1": "RDNA1/Navi1x", "2": "RDNA2/Navi2x", "3": "RDNA3/Navi3x"},
"I": {"0.9": "Gen9/Skylake", "0.95": "Gen9.5/KabyLake", "1": "Xe1/Gen12.1", "2": "Xe2"},
})
cu = OptIntAttr("#.CU/EU/SM")
perf = TblAttr("Performance", {"": "Std Perf", "h": "High Perf", "hh": "Very High Perf", "hhh": "Very Very High Perf"})


class IB:
"""Class representing Infiniband"""
type = "Infiniband"
ib = BoolAttr("?IB")


class Flavorname:
def __init__(self):
self.cpuram = None
self.disk = None
self.hype = None
self.hwvirt = None
self.cpubrand = None
self.gpu = None
self.ib = None


class ComponentParser:
def __init__(self, parsestr, targetcls):
self.parsestr = parsestr
self.targetcls = targetcls

def parse(self, s, pos):
m = self.parsestr.match(s, pos)
if m is None:
return None, pos
match_attr = [att for att in self.targetcls.__dict__.values() if isinstance(att, Attr)]
groups = m.groups()
if len(groups) != len(match_attr):
raise ValueError(f"unexpected number of matching groups: {match_attr} vs {groups}")
t = self.targetcls()
for attr, group in zip(match_attr, groups):
if attr.name[:2] == "##":
attr.__set__(t, float(group))
elif attr.name[:1] == "#":
attr.__set__(t, int(group) if group else attr.default)
elif attr.name[:1] == "?":
attr.__set__(t, bool(group))
else:
attr.__set__(t, group)
return t, pos + len(m.group(0))


class Output:
prefix = "SCS-"
cpuram = "%i%s%?i-%f%?u%?o"
disk = "-%1x%0i%s"
hype = "_%s"
hwvirt = "_%?hwv"
cpubrand = "_%s%0i%s"
gpu = "_%s%s%s%:i%s"
ib = "_%?ib"

def output_component(self, pattern, component, parts):
if component is None:
return
attr_iter = iter([att for att in component.__class__.__dict__.values() if isinstance(att, Attr)])
i = 0
while i < len(pattern):
j = i
while i < len(pattern) and pattern[i] != "%":
i += 1
if i > j:
parts.append(pattern[j:i])
if i == len(pattern):
break
i += 1 # skip %
attr = next(attr_iter)
value = attr.__get__(component)
if pattern[i] == "?":
j = i + 1
while j < len(pattern) and pattern[j].isalnum():
j += 1
if value:
parts.append(pattern[i + 1:j])
i = j - 1
elif pattern[i] == "s":
parts.append(str(value))
elif pattern[i] == "i":
parts.append(str(value))
elif pattern[i:i+2] == "0i":
if value != int(pattern[i]):
parts.append(str(value))
i += 1
elif pattern[i:i+2] == ":i":
if value:
parts.append(f"-{value}")
i += 1
elif pattern[i:i+2] == "1x":
if value != int(pattern[i]):
parts.append(f"{value}x")
i += 1
elif pattern[i] == "f":
if value == int(value):
parts.append(f"{value:.0f}")
else:
parts.append(f"{value:.1f}")
else:
print(pattern, i)
raise RuntimeError("Pattern problem")
i += 1

def output(self, flavorname):
parts = [self.prefix]
self.output_component(self.cpuram, flavorname.cpuram, parts)
self.output_component(self.disk, flavorname.disk, parts)
self.output_component(self.hype, flavorname.hype, parts)
self.output_component(self.hwvirt, flavorname.hwvirt, parts)
self.output_component(self.cpubrand, flavorname.cpubrand, parts)
self.output_component(self.gpu, flavorname.gpu, parts)
self.output_component(self.ib, flavorname.ib, parts)
return "".join(parts)


class SyntaxV1:
prefix = "SCS-"
cpuram = re.compile(r"([0-9]*)([LVTC])(i|):([0-9\.]*)(u|)(o|)")
disk = re.compile(r":(?:([0-9]*)x|)([0-9]*)([nhsp]|)")
hype = re.compile(r"\-(kvm|xen|vmw|hyv|bms)")
hwvirt = re.compile(r"\-(hwv)")
cpubrand = re.compile(r"\-([izar])([0-9]*)(h*)")
gpu = re.compile(r"\-([gG])([NAI])([^:-]*)(?::([0-9]*)|)(h*)")
ib = re.compile(r"\-(ib)")


class SyntaxV2:
prefix = "SCS-"
cpuram = re.compile(r"([0-9]*)([LVTC])(i|)\-([0-9\.]*)(u|)(o|)")
disk = re.compile(r"\-(?:([0-9]*)x|)([0-9]*)([nhsp]|)")
hype = re.compile(r"_(kvm|xen|vmw|hyv|bms)")
hwvirt = re.compile(r"_(hwv)")
cpubrand = re.compile(r"_([izar])([0-9]*)(h*)")
gpu = re.compile(r"_([gG])([NAI])([^-]*)(?:\-([0-9]*)|)(h*)")
ib = re.compile(r"_(ib)")


class Parser:
def __init__(self, syntax):
self.prefix = syntax.prefix
self.cpuram = ComponentParser(syntax.cpuram, Main)
self.disk = ComponentParser(syntax.disk, Disk)
self.hype = ComponentParser(syntax.hype, Hype)
self.hwvirt = ComponentParser(syntax.hwvirt, HWVirt)
self.cpubrand = ComponentParser(syntax.cpubrand, CPUBrand)
self.gpu = ComponentParser(syntax.gpu, GPU)
self.ib = ComponentParser(syntax.ib, IB)

def parse(self, s, pos=0):
if not s[pos:].startswith(self.prefix):
return
pos += len(self.prefix)
flavorname = Flavorname()
for key, p in self.__dict__.items():
if not isinstance(p, ComponentParser):
continue
t, pos = p.parse(s, pos)
setattr(flavorname, key, t)
if flavorname.cpuram is None:
raise ValueError(f"Error 10: Failed to parse main part of {s}")
if pos != len(s):
raise ValueError(f"Failed to parse name {s} to completion (after {pos})")
return flavorname

if __name__ == "__main__":
namestr = "SCS-16T-64-3x10s_GNa-64_ib"
print(namestr)
print(Output().output(Parser(SyntaxV1).parse("SCS-16T:64:3x10s-GNa:64-ib")))
print(namestr == Output().output(Parser(SyntaxV2).parse(namestr)))

0 comments on commit 20a37d2

Please sign in to comment.