diff --git a/Standards/scs-0100-v3-flavor-naming.md b/Standards/scs-0100-v3-flavor-naming.md index 636c730f0..4d4447ac7 100644 --- a/Standards/scs-0100-v3-flavor-naming.md +++ b/Standards/scs-0100-v3-flavor-naming.md @@ -9,7 +9,7 @@ replaces: scs-0100-v2-flavor-naming.md ## Introduction -This is the standard v3.0 for SCS Release 5. +This is the standard v3.1 for SCS Release 5. Note that we intend to only extend it (so it's always backwards compatible), but try to avoid changing in incompatible ways. (See at the end for the v1 to v2 transition where we have not met that @@ -22,10 +22,7 @@ The flavors are pre-defined by the operator, the customer can not change these. OpenStack providers thus typically offer a large selection of flavors. While flavors can be discovered (`openstack flavor list`), it is helpful for users (DevOps teams), -to have - -- A naming scheme that is used across all SCS flavors, so flavor names have the same meaning everywhere. -- Have a guaranteed set of flavors available on all SCS clouds, so these do not need to be discovered. +to have a naming scheme that is used across all SCS flavors, so flavor names have the same meaning everywhere. While not all details will be encoded in the name, the key features should be obvious: Number of vCPUs, RAM, Root Disk. @@ -226,56 +223,6 @@ so users can expect some level of parallelism and independence. - SCS-2C-4-3x10 <- Cloud decides type and creates three 10GB volumes - ~~SCS-2C-4-**1.5n**~~ <- You must not specify disk sizes which are not in full GiBs -## Standard SCS flavors - -These are flavors that must exist on standard SCS clouds (x86-64). - -We recommend disk sizes to be 5, 10, 20, 50, 100, 200, 500, 1000GB, 2000GB. -We expect the most used vCPU:Mem[GiB] ratio to be 1:4. - -| vCPU:RAM ratio | Mandatory Flavors | Recommended Flavors | -| -------------- | ------------------------- | ------------------- | -| 1:4 | SCS-1V-4 | SCS-1V-4-10 | -| 2:8 | SCS-2V-8 | SCS-2V-8-20 | -| 4:16 | SCS-4V-16, SCS-4V-16-100s | SCS-4V-16-50 | -| 8:32 | SCS-8V-32 | SCS-8V-32-100 | -| 1:2 | SCS-1V-2 | SCS-1V-2-5 | -| 2:4 | SCS-2V-4, SCS-2V-4-20s | SCS-2V-4-10 | -| 4:8 | SCS-4V-8 | SCS-4V-8-20 | -| 8:16 | SCS-8V-16 | SCS-8V-16-50 | -| 16:32 | SCS-16V-32 | SCS-16V-32-100 | -| 1:8 | SCS-1V-8 | SCS-1V-8-20 | -| 2:16 | SCS-2V-16 | SCS-2V-16-50 | -| 4:32 | SCS-4V-32 | SCS-4V-32-100 | -| 1:1 | SCS-1L-1 | SCS-1L-1-5 | - -Note that all vCPUs of SCS standard flavors are oversubscribed — the smallest `1L-1` -flavor allows for heavy oversubscription (note the `L`), and thus can be offered very -cheaply — imagine jump hosts ... - -The design allows for small clouds (with CPUs with 16 Threads, 64GiB RAM -compute hosts) to offer all flavors. - -Note that the flavors with fixed size root disks have all moved to Recommended -in version 3 of the standard. This means that they are not a certification requirement any longer, -but we still recommend implementing these for backwards compatibility reasons. -Disks types are not specified (and expected to be n or h typically). - -However, two flavors with SSD+ root disks have been added in v3, as defined in -[scs-0110-v1-ssd-flavors.md](https://github.com/SovereignCloudStack/standards/blob/main/Standards/scs-0110-v1-ssd-flavors.md) - -Note: Compared to previous drafts, we have heavily reduced the variations -on disk sizes — this reflects that for the standard networked cinder -disks, you can pass `block_device_mapping_v2` on server (VM) creation to -allocate a boot disk of any size you desire. We have scaled the few -recommended disk sizes with the amount of RAM. For each flavor there is -also one _without_ a pre-attached disk — these are meant to be used -to boot from a volume (either created beforehand or allocated on-the-fly -with `block_device_mapping_v2`, e.g. -`openstack server create --flavor SCS-1V:2 --block-device-mapping sda=IMGUUID:image:12:true` -to create a bootable 12G cinder volume from image `IMGUUID` that gets tied to the VM -instance life cycle.) - ## Naming policy compliance You are allowed to understate your performance; you may implement a SCS-1V-1-5 flavor with @@ -306,7 +253,7 @@ You must not offer flavors with the `SCS-` prefix which do not follow this namin You must not extend the SCS naming scheme with your own extensions; you are encouraged however to suggest extensions that we can discuss and add to the official scheme. -## Validation +## Conformance Tests There is a script in [`flavor-name-check.py`](https://github.com/SovereignCloudStack/standards/blob/main/Tests/iaas/flavor-naming/flavor-name-check.py) which can be used to decode, validate and construct flavor names. @@ -319,17 +266,11 @@ on the flavor list compliance of the cloud environment. The script `flavor-names-openstack.py` talks to the OpenStack API of the cloud specified by the `OS_CLOUD` environment and queries properties and checks -the names for standards compliance and completeness w.r.t. the mandatory -flavor list. It goes beyond the above example in checking that the discoverable +the names for standards compliance. +It goes beyond the above example in checking that the discoverable features of flavors (vCPUs, RAM, Disk) match what the flavor names claim. This is used for SCS-compatible compliance testing. -## Operational tooling - -The [openstack-flavor-manager](https://github.com/osism/openstack-flavor-manager) is able to -create all standard, mandatory SCS flavors for you. It takes input that can be generated by -`flavor-manager-input.py`. - ## Extensions Extensions provide a possibility for providers that offer a very differentiated set @@ -526,6 +467,10 @@ an image is considered broken by the SCS team. ## Previous standard versions +Previous versions up to version 3.0 contained the list of +mandatory/recommended flavors, which has been moved to +[a standard of its own](scs-0103-v1-standard-flavors.md). + [Version 1 of the standard](scs-0100-v1-flavor-naming.md) used a slightly different naming syntax while the logic was exactly the same. What is a `-` in v2 used to be a `:`; `_` used to be `-`. The reason for diff --git a/Standards/scs-0103-v1-standard-flavors.md b/Standards/scs-0103-v1-standard-flavors.md new file mode 100644 index 000000000..098ef4dac --- /dev/null +++ b/Standards/scs-0103-v1-standard-flavors.md @@ -0,0 +1,150 @@ +--- +title: SCS Standard Flavors and Properties +type: Standard +status: Draft +track: IaaS +--- + +## Introduction + +## Motivation + +In OpenStack environments there is a need to define different flavors for instances. +The flavors are pre-defined by the operator, the customer can not change these. +OpenStack providers thus typically offer a large selection of flavors. + +While flavors can be discovered (`openstack flavor list`), it is helpful for users (DevOps teams), +to have a guaranteed set of flavors available on all SCS clouds, so these need not be discovered. + +## Properties (extra specs) + +The following extra specs are recognized, together with the respective semantics: + +- `scs:name-vN=NAME` (where `N` is `1` or `2`, and `NAME` is some string) means that the + flavor is one of the + standard SCS flavors, and the requirements of Section "Standard SCS flavors" below apply. +- `scs:cpu-type=shared-core` means that _at least 20% of a core in >99% of the time_, + measured over the course of one month (1% is 7,2 h/month). The `cpu-type=shared-core` + corresponds to the `V` cpu modifier in the [flavor-naming spec](./scs-0100-v3-flavor-naming.md), + other options are `crowded-core` (`L`), `dedicated-thread` (`T`) and `dedicated-core` (`C`). +- `scs:diskN-type=ssd` (where `N` is a nonnegative integer, usually `0`) means that the + root disk `N` must support 1000 _sequential_ IOPS per VM and it must be equipped with + power-loss protection; see [scs-0110-v1-ssd-flavors](./scs-0110-v1-ssd-flavors.md). + The `disk`N`-type=ssd` setting corresponds to the `s` disk modifier, other options + are `nvme` (`p`), `hdd` (`h`) and `network` (`n`). Only flavors without disk and + those with `diskN-type=network` can be expected to support live-migration. + +Whenever ANY of these are present on ANY flavor, the corresponding semantics must be satisfied. + +## Standard SCS flavors + +These are flavors that must exist on standard SCS clouds (x86-64). + +### Mandatory + +| Recommended name | vCPUs | vCPU type | RAM [GiB] | Root disk [GB] | Disk type | +| ---------------- | ------ | ------------- | ---------- | --------------- | ---------- | +| SCS-1V-4 | 1 | shared-core | 4 | | | +| SCS-2V-8 | 2 | shared-core | 8 | | | +| SCS-4V-16 | 4 | shared-core | 16 | | | +| SCS-4V-16-100s | 4 | shared-core | 16 | 100 | ssd | +| SCS-8V-32 | 8 | shared-core | 32 | | | +| SCS-1V-2 | 1 | shared-core | 2 | | | +| SCS-2V-4 | 2 | shared-core | 4 | | | +| SCS-2V-4-20s | 2 | shared-core | 4 | 20 | ssd | +| SCS-4V-8 | 4 | shared-core | 8 | | | +| SCS-8V-16 | 8 | shared-core | 16 | | | +| SCS-16V-32 | 16 | shared-core | 32 | | | +| SCS-1V-8 | 1 | shared-core | 8 | | | +| SCS-2V-16 | 2 | shared-core | 16 | | | +| SCS-4V-32 | 4 | shared-core | 32 | | | +| SCS-1L-1 | 1 | crowded-core | 1 | | | + +### Recommended + +| Recommended name | vCPUs | vCPU type | RAM [GiB] | Root disk [GB] | Disk type | +| ---------------- | ------ | ------------- | ---------- | --------------- | ---------- | +| SCS-1V-4-10 | 1 | shared-core | 4 | 10 | (any) | +| SCS-2V-8-20 | 2 | shared-core | 8 | 20 | (any) | +| SCS-4V-16-50 | 4 | shared-core | 16 | 50 | (any) | +| SCS-8V-32-100 | 8 | shared-core | 32 | 100 | (any) | +| SCS-1V-2-5 | 1 | shared-core | 2 | 5 | (any) | +| SCS-2V-4-10 | 2 | shared-core | 4 | 10 | (any) | +| SCS-4V-8-20 | 4 | shared-core | 8 | 20 | (any) | +| SCS-8V-16-50 | 8 | shared-core | 16 | 50 | (any) | +| SCS-16V-32-100 | 16 | shared-core | 32 | 100 | (any) | +| SCS-1V-8-20 | 1 | shared-core | 8 | 20 | (any) | +| SCS-2V-16-50 | 2 | shared-core | 16 | 50 | (any) | +| SCS-4V-32-100 | 4 | shared-core | 32 | 100 | (any) | +| SCS-1L-1-5 | 1 | crowded-core | 1 | 5 | (any) | + +### Guarantees and properties + +The figures given in the table (number of CPUs, amount of RAM, root disk size) must match +precisely the corresponding figures in the flavor. + +In addition, the following properties must be set (in the `extra_specs`): + +- `scs:name-v1` to the recommended name, but with each dash AFTER the first one replaced by a colon, +- `scs:name-v2` to the recommended name, +- `scs:cpu-type` to `shared-core` or `crowded-core`, reflecting the vCPU type, +- `scs:disk0-type` not set if no disk is provided, otherwise set to `ssd` or some other + value, reflecting the disk type. + +### Remarks + +We expect the most used vCPU:RAM[GiB] ratio to be 1:4. + +Note that all vCPUs of SCS standard flavors are oversubscribed — the smallest `1L-1` +flavor allows for heavy oversubscription (note the `L`), and thus can be offered very +cheaply — imagine jump hosts ... + +The design allows for small clouds (with CPUs with 16 Threads, 64GiB RAM +compute hosts) to offer all flavors. + +Except for the two flavors with SSD root volume, disks types are not specified +(and expected to be network disks (Ceph/Cinder) or local SATA/SAS disks typically). + +We only included a limited variation of disk sizes — this reflects that +for the standard networked cinder +disks, you can pass `block_device_mapping_v2` on server (VM) creation to +allocate a boot disk of any size you desire. We have scaled the few +recommended disk sizes with the amount of RAM. For each flavor there is +also one _without_ a pre-attached disk — these are meant to be used +to boot from a volume (either created beforehand or allocated on-the-fly +with `block_device_mapping_v2`, e.g. +`openstack server create --flavor SCS-1V-2 --block-device-mapping sda=IMGUUID:image:12:true` +to create a bootable 12G cinder volume from image `IMGUUID` that gets tied to the VM +instance life cycle.) + +## Conformance Tests + +The script `flavors-openstack.py` will read the lists of mandatory and recommended flavors +from a yaml file provided as command-line argument, connect to an OpenStack installation, +and check whether the flavors are present and their extra specs are correct. Missing +flavors will be reported on various logging channels: error for mandatory, info for +recommended flavors. Incorrect extra specs will be reported as error in any case. +The return code will be non-zero if the test could not be performed or if any error was +reported. + +## Operational tooling + +The [openstack-flavor-manager](https://github.com/osism/openstack-flavor-manager) is able to +create all standard, mandatory SCS flavors for you. It takes input that can be generated by +`flavor-manager-input.py`. + +## Previous standard versions + +The list of standard flavors used to be part of the flavor naming standard up until +[version 3](scs-0100-v3-flavor-naming.md). The following changes have been made to +the list in comparison with said standard: + +- the flavor names have been turned into recommendations, and +- the properties have been introduced in order to help discoverability. + +Note that the flavors with fixed size root disks have all moved to Recommended +with [scs-0100-v3](scs-0100-v3-flavor-naming.md). +This means that they are not a certification requirement any longer, +but we still recommend implementing these for backwards compatibility reasons. +Also in that standard, two flavors with SSD+ root disks have been added, as defined in +[scs-0110-v1-ssd-flavors.md](scs-0110-v1-ssd-flavors.md) diff --git a/Tests/iaas/scs-0100-v3-flavors.yaml b/Tests/iaas/scs-0100-v3-flavors.yaml new file mode 100644 index 000000000..a481a9cda --- /dev/null +++ b/Tests/iaas/scs-0100-v3-flavors.yaml @@ -0,0 +1,5 @@ +SCS-Spec: + # Empty lists so flavor-names-openstack.py only checks the names for scs-0100. + # The list of flavors will be checked with flavors-openstack.py for scs-0103. + MandatoryFlavors: [] + RecommendedFlavors: [] diff --git a/Tests/iaas/scs-0103-v1-flavors.yaml b/Tests/iaas/scs-0103-v1-flavors.yaml new file mode 100644 index 000000000..45c44b304 --- /dev/null +++ b/Tests/iaas/scs-0103-v1-flavors.yaml @@ -0,0 +1,192 @@ +meta: + name_key: name-v2 +flavor_groups: + - status: mandatory + list: + - name: SCS-1V-4 + cpus: 1 + cpu-type: shared-core + ram: 4 + name-v1: SCS-1V:4 + name-v2: SCS-1V-4 + - name: SCS-2V-8 + cpus: 2 + cpu-type: shared-core + ram: 8 + name-v1: SCS-2V:8 + name-v2: SCS-2V-8 + - name: SCS-4V-16 + cpus: 4 + cpu-type: shared-core + ram: 16 + name-v1: SCS-4V:16 + name-v2: SCS-4V-16 + - name: SCS-4V-16-100s + cpus: 4 + cpu-type: shared-core + ram: 16 + disk: 100 + disk0-type: ssd + name-v1: SCS-4V:16:100s + name-v2: SCS-4V-16-100s + - name: SCS-8V-32 + cpus: 8 + cpu-type: shared-core + ram: 32 + name-v1: SCS-8V:32 + name-v2: SCS-8V-32 + - name: SCS-1V-2 + cpus: 1 + cpu-type: shared-core + ram: 2 + name-v1: SCS-1V:2 + name-v2: SCS-1V-2 + - name: SCS-2V-4 + cpus: 2 + cpu-type: shared-core + ram: 4 + name-v1: SCS-2V:4 + name-v2: SCS-2V-4 + - name: SCS-2V-4-20s + cpus: 2 + cpu-type: shared-core + ram: 4 + disk: 20 + disk0-type: ssd + name-v1: SCS-2V:4:20s + name-v2: SCS-2V-4-20s + - name: SCS-4V-8 + cpus: 4 + cpu-type: shared-core + ram: 8 + name-v1: SCS-4V:8 + name-v2: SCS-4V-8 + - name: SCS-8V-16 + cpus: 8 + cpu-type: shared-core + ram: 16 + name-v1: SCS-8V:16 + name-v2: SCS-8V-16 + - name: SCS-16V-32 + cpus: 16 + cpu-type: shared-core + ram: 32 + name-v1: SCS-16V:32 + name-v2: SCS-16V-32 + - name: SCS-1V-8 + cpus: 1 + cpu-type: shared-core + ram: 8 + name-v1: SCS-1V:8 + name-v2: SCS-1V-8 + - name: SCS-2V-16 + cpus: 2 + cpu-type: shared-core + ram: 16 + name-v1: SCS-2V:16 + name-v2: SCS-2V-16 + - name: SCS-4V-32 + cpus: 4 + cpu-type: shared-core + ram: 32 + name-v1: SCS-4V:32 + name-v2: SCS-4V-32 + - name: SCS-1L-1 + cpus: 1 + cpu-type: crowded-core + ram: 1 + name-v1: SCS-1L:1 + name-v2: SCS-1L-1 + - status: recommended + list: + - name: SCS-1V-4-10 + cpus: 1 + cpu-type: shared-core + ram: 4 + disk: 10 + name-v1: SCS-1V:4:10 + name-v2: SCS-1V-4-10 + - name: SCS-2V-8-20 + cpus: 2 + cpu-type: shared-core + ram: 8 + disk: 20 + name-v1: SCS-2V:8:20 + name-v2: SCS-2V-8-20 + - name: SCS-4V-16-50 + cpus: 4 + cpu-type: shared-core + ram: 16 + disk: 50 + name-v1: SCS-4V:16:50 + name-v2: SCS-4V-16-50 + - name: SCS-8V-32-100 + cpus: 8 + cpu-type: shared-core + ram: 32 + disk: 100 + name-v1: SCS-8V:32:100 + name-v2: SCS-8V-32-100 + - name: SCS-1V-2-5 + cpus: 1 + cpu-type: shared-core + ram: 2 + disk: 5 + name-v1: SCS-1V:2:5 + name-v2: SCS-1V-2-5 + - name: SCS-2V-4-10 + cpus: 2 + cpu-type: shared-core + ram: 4 + disk: 10 + name-v1: SCS-2V:4:10 + name-v2: SCS-2V-4-10 + - name: SCS-4V-8-20 + cpus: 4 + cpu-type: shared-core + ram: 8 + disk: 20 + name-v1: SCS-4V:8:20 + name-v2: SCS-4V-8-20 + - name: SCS-8V-16-50 + cpus: 8 + cpu-type: shared-core + ram: 16 + disk: 50 + name-v1: SCS-8V:16:50 + name-v2: SCS-8V-16-50 + - name: SCS-16V-32-100 + cpus: 16 + cpu-type: shared-core + ram: 32 + disk: 100 + name-v1: SCS-16V:32:100 + name-v2: SCS-16V-32-100 + - name: SCS-1V-8-20 + cpus: 1 + cpu-type: shared-core + ram: 8 + disk: 20 + name-v1: SCS-1V:8:20 + name-v2: SCS-1V-8-20 + - name: SCS-2V-16-50 + cpus: 2 + cpu-type: shared-core + ram: 16 + disk: 50 + name-v1: SCS-2V:16:50 + name-v2: SCS-2V-16-50 + - name: SCS-4V-32-100 + cpus: 4 + cpu-type: shared-core + ram: 32 + disk: 100 + name-v1: SCS-4V:32:100 + name-v2: SCS-4V-32-100 + - name: SCS-1L-1-5 + cpus: 1 + cpu-type: crowded-core + ram: 1 + disk: 5 + name-v1: SCS-1L:1:5 + name-v2: SCS-1L-1-5 diff --git a/Tests/iaas/standard-flavors/flavor-manager-input.py b/Tests/iaas/standard-flavors/flavor-manager-input.py new file mode 100755 index 000000000..b8bb5da60 --- /dev/null +++ b/Tests/iaas/standard-flavors/flavor-manager-input.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +Convert yaml format from the one expected by flavors-openstack.py to the one expected +by osism's flavor manager, cf. https://github.com/osism/openstack-flavor-manager . + +The conversion is performed from stdin to stdout. +""" +import logging +import sys + +import yaml + + +logger = logging.getLogger(__name__) + + +def main(argv): + logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) + + # load the yaml, checking for sanity + try: + flavor_spec_data = yaml.safe_load(sys.stdin) + except Exception as e: + logger.critical(f"Unable to load: {e!r}") + logger.debug("Exception info", exc_info=True) + return 1 + + if 'meta' not in flavor_spec_data or 'name_key' not in flavor_spec_data['meta']: + logger.critical("Flavor definition missing 'meta' field or field incomplete") + return 1 + + if 'flavor_groups' not in flavor_spec_data: + logger.critical("Flavor definition missing 'flavor_groups' field") + + # boilerplate / scaffolding + result = yaml.safe_load(""" +reference: + - field: name + mandatory_prefix: SCS- + optional_prefix: SCSX- + - field: cpus + - field: ram + - field: disk + - field: public + default: true + - field: disabled + default: false +mandatory: [] +recommended: [] +""") + + # transfer the info into the result yaml, again checking for sanity + for flavor_group in flavor_spec_data['flavor_groups']: + group_info = dict(flavor_group) + group_info.pop('list') + missing = {'status'} - set(group_info) + if missing: + logging.critical(f"Flavor group missing attributes: {', '.join(missing)}") + return 1 + group_info.pop('status') + result_list = result[flavor_group['status']] + for flavor_spec in flavor_group['list']: + missing = {'name', 'cpus', 'ram'} - set(flavor_spec) + if missing: + logging.critical(f"Flavor spec missing attributes: {', '.join(missing)}") + return 1 + flavor_spec = {**group_info, **flavor_spec} + extra_specs = { + f"scs:{key}": value + for key, value in flavor_spec.items() + if key not in ('name', 'cpus', 'ram', 'disk') + } + flavor_spec = { + key: value + for key, value in flavor_spec.items() + if key in ('name', 'cpus', 'ram', 'disk') + } + result_list.append({**flavor_spec, **extra_specs}) + + print(yaml.dump(result, sort_keys=False)) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/Tests/iaas/standard-flavors/flavors-openstack.py b/Tests/iaas/standard-flavors/flavors-openstack.py new file mode 100755 index 000000000..c3a1d206d --- /dev/null +++ b/Tests/iaas/standard-flavors/flavors-openstack.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Standard flavors checker for OpenStack + +Check given cloud for conformance with SCS standard regarding standard flavors, +to be found under /Standards/scs-0103-v1-standard-flavors.md + +The respective list of flavors is defined in a corresponding yaml file; this script +expects the path of such a file as its only positional argument. + +Return code is 0 precisely when it could be verified that the standard is satisfied. +Otherwise the return code is the number of errors that occurred (up to 127 due to OS +restrictions); for further information, see the log messages on various channels: + CRITICAL for problems preventing the test to complete, + ERROR for violations of requirements, + INFO for violations of recommendations, + DEBUG for background information and problems that don't hinder the test. +""" +from collections import Counter +import getopt +import logging +import os +import sys + +import openstack +import openstack.cloud +import yaml + + +logger = logging.getLogger(__name__) + + +def print_usage(file=sys.stderr): + """Help output""" + print("""Usage: flavors-openstack.py [options] YAML +This tool checks the flavors according to the SCS Standard 0103 "Standard Flavors". +Arguments: + YAML path to the file containing the flavor definitions corresponding to the version + of the standard to be tested +Options: + [-c/--os-cloud OS_CLOUD] sets cloud environment (default from OS_CLOUD env) + [-d/--debug] enables DEBUG logging channel +""", end='', file=file) + + +class CountingHandler(logging.Handler): + def __init__(self, level=logging.NOTSET): + super().__init__(level=level) + self.bylevel = Counter() + + def handle(self, record): + self.bylevel[record.levelno] += 1 + + +def main(argv): + # configure logging, disable verbose library logging + logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) + openstack.enable_logging(debug=False) + # count the number of log records per level (used for summary and return code) + counting_handler = CountingHandler(level=logging.INFO) + logger.addHandler(counting_handler) + + try: + opts, args = getopt.gnu_getopt(argv, "c:hd", ["os-cloud=", "help", "debug"]) + except getopt.GetoptError as exc: + logger.critical(f"{exc}") + print_usage() + return 1 + + if len(args) != 1: + logger.critical("Missing YAML argument, or too many arguments") + print_usage() + return 1 + + yaml_path = args[0] + cloud = os.environ.get("OS_CLOUD") + for opt in opts: + if opt[0] == "-h" or opt[0] == "--help": + print_usage() + return 0 + if opt[0] == "-c" or opt[0] == "--os-cloud": + cloud = opt[1] + if opt[0] == "-d" or opt[0] == "--debug": + logging.getLogger().setLevel(logging.DEBUG) + + if not cloud: + logger.critical("You need to have OS_CLOUD set or pass --os-cloud=CLOUD.") + return 1 + + try: + with open(yaml_path, "rb") as fileobj: + flavor_spec_data = yaml.safe_load(fileobj) + except Exception as e: + logger.critical(f"Unable to load '{yaml_path}': {e!r}") + logger.debug("Exception info", exc_info=True) + return 1 + + if 'meta' not in flavor_spec_data or 'name_key' not in flavor_spec_data['meta']: + logger.critical("Flavor definition missing 'meta' field or field incomplete") + return 1 + + if 'flavor_groups' not in flavor_spec_data: + logger.critical("Flavor definition missing 'flavor_groups' field") + + name_key = flavor_spec_data['meta']['name_key'] + es_name_key = f"scs:{name_key}" + # compute union of all flavor groups, copying group info (mainly "status") to each flavor + # check if the spec is complete while we are at it + flavor_specs = [] + for flavor_group in flavor_spec_data['flavor_groups']: + group_info = dict(flavor_group) + group_info.pop('list') + missing = {'status'} - set(group_info) + if missing: + logging.critical(f"Flavor group missing attributes: {', '.join(missing)}") + return 1 + for flavor_spec in flavor_group['list']: + missing = {'name', 'cpus', 'ram'} - set(flavor_spec) + if missing: + logging.critical(f"Flavor spec missing attributes: {', '.join(missing)}") + return 1 + flavor_specs.append({"_group": group_info, **flavor_spec}) + + try: + logger.debug(f"Fetching flavors from cloud '{cloud}'") + with openstack.connect(cloud=cloud, timeout=32) as conn: + present_flavors = conn.list_flavors(get_extra=True) + by_name = { + flavor.extra_specs[es_name_key]: flavor + for flavor in present_flavors + if es_name_key in flavor.extra_specs + } + + logger.debug(f"Checking {len(flavor_specs)} flavor specs against {len(present_flavors)} flavors") + for flavor_spec in flavor_specs: + flavor = by_name.get(flavor_spec[name_key]) + if not flavor: + status = flavor_spec['_group']['status'] + level = {"mandatory": logging.ERROR}.get(status, logging.INFO) + logger.log(level, f"Missing {status} flavor '{flavor_spec['name']}'") + continue + # check that flavor matches flavor_spec + # cpu, ram, and disk should match, and they should match precisely for discoverability + if flavor.vcpus != flavor_spec['cpus']: + logger.error(f"Flavor '{flavor.name}' violating CPU constraint: {flavor.vcpus} != {flavor_spec['cpus']}") + if flavor.ram != 1024 * flavor_spec['ram']: + logger.error(f"Flavor '{flavor.name}' violating RAM constraint: {flavor.ram} != {1024 * flavor_spec['ram']}") + if flavor.disk != flavor_spec.get('disk', 0): + logger.error(f"Flavor '{flavor.name}' violating disk constraint: {flavor.disk} != {flavor_spec.get('disk', 0)}") + # other fields besides name, cpu, ram, and disk should also match + report = [ + f"{key}: {es_value!r} should be {value!r}" + for key, value, es_value in [ + (key, value, flavor.extra_specs.get(f"scs:{key}")) + for key, value in flavor_spec.items() + if key not in ('_group', 'name', 'cpus', 'ram', 'disk') + ] + if value != es_value + ] + if report: + logger.error(f"Flavor '{flavor.name}' violating property constraints: {'; '.join(report)}") + except BaseException as e: + logger.critical(f"{e!r}") + logger.debug("Exception info", exc_info=True) + + c = counting_handler.bylevel + logger.debug(f"Total critical / error / info: {c[logging.CRITICAL]} / {c[logging.ERROR]} / {c[logging.INFO]}") + return min(127, c[logging.CRITICAL] + c[logging.ERROR]) # cap at 127 due to OS restrictions + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/Tests/testing/scs-compatible-test.yaml b/Tests/testing/scs-compatible-test.yaml index e712705b6..7652ef4c5 100644 --- a/Tests/testing/scs-compatible-test.yaml +++ b/Tests/testing/scs-compatible-test.yaml @@ -1,15 +1,37 @@ name: SCS Compatible url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Tests/scs-compatible.yaml iaas: + - version: v4 + standards: + - name: Standard flavors + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0103-v1-standard-flavors.md + check_tools: + - executable: ./iaas/standard-flavors/flavors-openstack.py + args: "./iaas/scs-0103-v1-flavors.yaml" + - name: Flavor naming + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v3-flavor-naming.md + check_tools: + - executable: ./iaas/flavor-naming/flavor-names-openstack.py + args: "--mand=./iaas/scs-0100-v3-flavors.yaml" + - name: Image metadata + url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0102-v1-image-metadata.md + check_tools: + - executable: ./iaas/image-metadata/image-md-check.py + args: -v + - name: OpenStack Powered Compute v2022.11 + url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json + condition: mandatory + # Unfortunately, no wrapper to run refstack yet, needs to be added - version: v3 - stabilized_at: 2023-08-01 - # obsoleted_at: 2024-10-30 + stabilized_at: 2023-06-15 + obsoleted_at: 2024-04-30 standards: - name: Flavor naming url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0100-v3-flavor-naming.md check_tools: - executable: ./iaas/flavor-naming/flavor-names-openstack.py - args: "--v3 --v2plus" + args: "--v3" + # Note: "--v3 --v2plus" would outlaw the v1 flavor names. Don't do this yet. - name: Image metadata url: https://raw.githubusercontent.com/SovereignCloudStack/standards/main/Standards/scs-0102-v1-image-metadata.md check_tools: @@ -18,7 +40,7 @@ iaas: - name: OpenStack Powered Compute v2022.11 url: https://opendev.org/openinfra/interop/src/branch/master/guidelines/2022.11.json condition: mandatory - # Unfortunately, no wrapper to run refstack yet, needs to be added - version: v3 + # Unfortunately, no wrapper to run refstack yet, needs to be added - version: v2 stabilized_at: 2023-03-20 obsoleted_at: 2024-04-30