Skip to content

Commit

Permalink
Yet another intermediate revision
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias Büchse <matthias.buechse@cloudandheat.com>
  • Loading branch information
mbuechse committed Jan 23, 2024
1 parent 6a3b925 commit bb4e51c
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 36 deletions.
5 changes: 2 additions & 3 deletions Tests/iaas/flavor-naming/flavor-names-openstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def main(argv):
"""Entry point -- main loop going over flavors"""
fnmck = flavor_name_check.CompatLayer()
cloud = None
verbose = False
v3mode = False
accept_old_mand = False
scsMandFile = fnmck.mandFlavorFile
Expand Down Expand Up @@ -79,9 +80,7 @@ def main(argv):
accept_old_mand = True
elif opt[0] == "-v" or opt[0] == "--verbose":
verbose = True
# fnmck.verbose = True
elif opt[0] == "-q" or opt[0] == "--quiet":
quiet = True
fnmck.quiet = True
else:
usage(2)
Expand Down Expand Up @@ -241,7 +240,7 @@ def main(argv):
"Warnings": len(warnFlv)+len(scsRecommended),
}
Report = {cloud: {"TotalSummary": totSummary}}
if not quiet:
if not fnmck.quiet:
Report[cloud]["SCSFlavorSummary"] = flvSCSRep
Report[cloud]["OtherFlavorSummary"] = flvOthRep
if verbose:
Expand Down
98 changes: 65 additions & 33 deletions Tests/iaas/flavor-naming/flavor_name_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import os.path
import re
import sys
from typing import Optional

import yaml


class TypeCheck:
def __call__(self, attr, value):
"""class for validating the type of some attribute within a flavor name"""
def __call__(self, attr: str, value):
raise ValueError(f"{attr} can not be set to {value}")


Expand Down Expand Up @@ -50,14 +52,20 @@ def __call__(self, attr, value):


class Attr:
"""class to represent one attribute, such as brand, of one component, such as gpu, of a flavor name"""
typ = None
default = None

@staticmethod
def collect(cls):
"""return all instances of `Attr` in the dict of given cls"""
return [att for att in cls.__dict__.values() if isinstance(att, Attr)]

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__
# the following will be set automatically via __set_name__
self.attr = None
self._attr = None

Expand All @@ -69,6 +77,9 @@ def validate(self, val):
return
self.typ(self.attr, val)

# the following methods make this class a `Descriptor`,
# see <https://docs.python.org/3/howto/descriptor.html>

def __set_name__(self, owner, name):
self.attr = name
self._attr = '_' + name
Expand Down Expand Up @@ -192,19 +203,22 @@ class IB:


class Flavorname:
"""A flavor name; merely a bunch of components"""
def __init__(self):
self.cpuram = None
self.disk = None
self.hype = None
self.hwvirt = None
self.cpubrand = None
self.gpu = None
self.ib = None
self.cpuram: Main = None
self.disk: Disk = None
self.hype: Hype = None
self.hwvirt: HWVirt = None
self.cpubrand: CPUBrand = None
self.gpu: GPU = None
self.ib: IB = None


class Outputter:
"""
Auxiliary class for serializing the Flavorname instance.
Auxiliary class for serializing `Flavorname` instances.
Use the global instance `outputter` (defined below) like so: `namestr = outputter(flavorname)`.
Using templating language with std C/Python % formatting and a few extras:
%? outputs following word (until next non-alnum char) if the parameter is True, otherwise nothing
Expand All @@ -226,7 +240,7 @@ class Outputter:
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)])
attr_iter = iter(Attr.collect(component.__class__))
i = 0
while i < len(pattern):
j = i
Expand Down Expand Up @@ -272,7 +286,7 @@ def output_component(self, pattern, component, parts):
raise RuntimeError("Pattern problem")
i += 1

def __call__(self, flavorname):
def __call__(self, flavorname: Flavorname) -> str:
parts = [self.prefix]
self.output_component(self.cpuram, flavorname.cpuram, parts)
self.output_component(self.disk, flavorname.disk, parts)
Expand All @@ -299,7 +313,7 @@ class SyntaxV1:
ib = re.compile(r"\-(ib)")

@staticmethod
def from_v2(nm):
def from_v2(nm: str) -> str:
"""v2 to v1 flavor name transformation"""
return nm.replace('-', ':').replace('_', '-').replace('SCS:', 'SCS-')

Expand All @@ -324,21 +338,29 @@ class SyntaxV2:
ib = re.compile(r"_(ib)")

@staticmethod
def from_v1(nm):
def from_v1(nm: str) -> str:
"""v1 to v2 flavor name transformation"""
return nm.replace('-', '_').replace(':', '-').replace('SCS_', 'SCS-')


class ParseCtx:
"""Auxiliary class used during parsing to hold current position in the string"""
def __init__(self, s: str, pos=0):
self.s = s
self.pos = pos


class ComponentParser:
def __init__(self, parsestr, targetcls):
self.parsestr = parsestr
self.targetcls = targetcls
"""Auxiliary class for parsing a single component of a flavor name"""
def __init__(self, parsestr: re.Pattern, targetcls):
self.parsestr = parsestr # re.Pattern as defined in `SyntaxV1` or `SyntaxV2`
self.targetcls = targetcls # component class such as `Main` or `Disk`

def parse(self, s, pos):
m = self.parsestr.match(s, pos)
def parse(self, ctx: ParseCtx):
m = self.parsestr.match(ctx.s, ctx.pos)
if m is None:
return None, pos
match_attr = [att for att in self.targetcls.__dict__.values() if isinstance(att, Attr)]
return
match_attr = Attr.collect(self.targetcls)
groups = m.groups()
if len(groups) != len(match_attr):
raise ValueError(f"unexpected number of matching groups: {match_attr} vs {groups}")
Expand All @@ -352,10 +374,18 @@ def parse(self, s, pos):
attr.__set__(t, bool(group))
else:
attr.__set__(t, group)
return t, pos + len(m.group(0))
ctx.pos += len(m.group(0))
return t


class Parser:
"""
Auxiliary class for parsing flavorname strings.
Use the global instances `parser_v1` and `parser_v2` (defined below) like so:
`flavorname = parser_v2(namestr)`.
"""

def __init__(self, syntax):
self.prefix = syntax.prefix
self.cpuram = ComponentParser(syntax.cpuram, Main)
Expand All @@ -366,20 +396,22 @@ def __init__(self, syntax):
self.gpu = ComponentParser(syntax.gpu, GPU)
self.ib = ComponentParser(syntax.ib, IB)

def __call__(self, s, pos=0):
def __call__(self, s: str, pos=0) -> Flavorname:
if not s[pos:].startswith(self.prefix):
return
pos += len(self.prefix)
ctx = ParseCtx(s, 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)
flavorname.cpuram = self.cpuram.parse(ctx)
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})")
raise ValueError(f"Failed to parse main part of {s}")
flavorname.disk = self.disk.parse(ctx)
flavorname.hype = self.hype.parse(ctx)
flavorname.hwvirt = self.hwvirt.parse(ctx)
flavorname.cpubrand = self.cpubrand.parse(ctx)
flavorname.gpu = self.gpu.parse(ctx)
flavorname.ib = self.ib.parse(ctx)
if ctx.pos != len(s):
raise ValueError(f"Failed to parse name {s} to completion; remainder: {s[ctx.pos:]}")
return flavorname


Expand Down Expand Up @@ -412,7 +444,7 @@ def __init__(self):
bindir = os.path.basename(sys.argv[0])
self.searchpath = (bindir, ) if bindir else os.environ['PATH'].split(':')

def parsename(self, namestr):
def parsename(self, namestr: str) -> Optional[Flavorname]:
"""
Parse flavor name: returns None (if non-SCS) or Flavorname instance
raises exception if name appears SCS, but not conforming to syntax
Expand Down

0 comments on commit bb4e51c

Please sign in to comment.