Add rack-types (#3026)
* feat: add front image * feat: startech 4post rack * feat: add digitus DN-19 07U-I-OD * feat: add digitus DN-19 07U-I-OD * Revert "feat: add front image" This reverts commit bdd27607f821fa942d40fb01774cab6053f83ada. * fix: change form_factor * feat: add racktype schema * feat: add racktype schema * feat: upload generated known-racks.pickle * fix: change filename * feat: add rack type * refactor: change tests to work with racks * refactor: use the correct device class * fix: rename file correctly * Update racktype.json * fix: add missing desc_units schema property * fix: add missing desc_units value * chore: update readme with rack-types documentation --------- Co-authored-by: Harry <Harry@cadby.co.uk>
This commit is contained in:
42
README.md
42
README.md
@@ -103,6 +103,48 @@ The following fields may **optionally** be declared:
|
|||||||
For further detail on these attributes and those listed below, please reference the
|
For further detail on these attributes and those listed below, please reference the
|
||||||
[schema definitions](schema/) and the [Component Definitions](#component-definitions) below.
|
[schema definitions](schema/) and the [Component Definitions](#component-definitions) below.
|
||||||
|
|
||||||
|
## Rack Type Definitions
|
||||||
|
|
||||||
|
Each definition **must** include at minimum the following fields:
|
||||||
|
|
||||||
|
- `manufacturer`: The name of the manufacturer which produces this rack type.
|
||||||
|
- Type: String
|
||||||
|
- `model`: The model number of the rack type. This must be unique per manufacturer.
|
||||||
|
- Type: String
|
||||||
|
- `slug`: A URL-friendly representation of the model number. Like the model number, this must be unique per
|
||||||
|
manufacturer. All slugs should have the manufacturers name prepended to it with a dash, please see the example below.
|
||||||
|
- Type: String
|
||||||
|
- Pattern: `"^[-a-z0-9_]+$"`. Must match the following characters: `-`, Lowercase `a` to `z`, Numbers `0` to `9`.
|
||||||
|
- `form_factor`: The form factor of the rack type. This is used to indicate the physical characteristics of the rack, such as whether it is a 4-post frame or a wall-cabinet etc.
|
||||||
|
- Type: String
|
||||||
|
- :test_tube: Example: `form_factor: 4-post-frame`
|
||||||
|
- `width`: The width of the rack type in zoll/inches. This is used to indicate the physical width of the rack, such as whether it is a 19" or 23" rack.
|
||||||
|
- Type: Integer
|
||||||
|
- :test_tube: Example: `width: 19`
|
||||||
|
- `u_height`: The height of the rack type in rack units.
|
||||||
|
- Type: Number
|
||||||
|
- :test_tube: Example: `u_height: 42`
|
||||||
|
- `starting_unit`: The unit number at which the rack starts. This is used to indicate the starting unit number of the rack, such as whether it starts at 1 or 42. The starting unit is normally defined from bottom to top, with the bottom unit being 1.
|
||||||
|
- Type: Number
|
||||||
|
- :test_tube: Example: `starting_unit: 1`
|
||||||
|
|
||||||
|
:test_tube: Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
manufacturer: Startech
|
||||||
|
model: 4 Post 42U
|
||||||
|
slug: startech-4postrack42
|
||||||
|
form_factor: 4-post-frame
|
||||||
|
width: 19
|
||||||
|
u_height: 42
|
||||||
|
starting_unit: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note: We are asking that all new racks also include the following optional fields: `outer_width`, `outer_height`, `outer_depth`, `outer_unit`, `weight`, `max_weight`, `weight_unit`, `mounting_depth`, and `desc_units`.**
|
||||||
|
|
||||||
|
For further detail on these attributes and those listed below, please reference the
|
||||||
|
[racktype schema definition](schema/racktype.json)
|
||||||
|
|
||||||
### Component Definitions
|
### Component Definitions
|
||||||
|
|
||||||
Valid component types are listed below. Each type of component must declare a list of the individual component templates
|
Valid component types are listed below. Each type of component must declare a list of the individual component templates
|
||||||
|
|||||||
16
rack-types/Digitus/dn-19-07u-i-od.yml
Normal file
16
rack-types/Digitus/dn-19-07u-i-od.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
manufacturer: Digitus
|
||||||
|
model: DN-19 07U-I-OD
|
||||||
|
slug: digitus-dn-19-07u-i-od
|
||||||
|
width: 19
|
||||||
|
u_height: 7
|
||||||
|
form_factor: wall-cabinet
|
||||||
|
description: '[Datasheet](https://www.assmann.com/product-pdf/4016032360971?PL=de)'
|
||||||
|
starting_unit: 1
|
||||||
|
outer_width: 600
|
||||||
|
outer_unit: mm
|
||||||
|
mounting_depth: 450
|
||||||
|
weight: 31
|
||||||
|
max_weight: 100
|
||||||
|
weight_unit: kg
|
||||||
|
desc_units: false
|
||||||
24
rack-types/Startech/4-post-42u.yaml
Normal file
24
rack-types/Startech/4-post-42u.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
manufacturer: Startech
|
||||||
|
model: 4 Post 42U
|
||||||
|
slug: startech-4postrack42
|
||||||
|
width: 19
|
||||||
|
u_height: 42
|
||||||
|
form_factor: 4-post-frame
|
||||||
|
description: Startech 4 Post 42U 19in rack with optional casters
|
||||||
|
starting_unit: 1
|
||||||
|
outer_width: 600
|
||||||
|
outer_unit: mm
|
||||||
|
# Adjustable depth, do we want the minimum or maximum depth?
|
||||||
|
# Minimum adjusted depth
|
||||||
|
mounting_depth: 560
|
||||||
|
# Maximum adjusted depth
|
||||||
|
# mounting_depth: 1017
|
||||||
|
weight: 38.5
|
||||||
|
# Different weights between stationary and on casters, which one?
|
||||||
|
# Stationary
|
||||||
|
# max_weight: 600
|
||||||
|
# Rolling
|
||||||
|
max_weight: 360
|
||||||
|
weight_unit: kg
|
||||||
|
desc_units: false
|
||||||
100
schema/racktype.json
Normal file
100
schema/racktype.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"$id": "urn:devicetype-library:rack-type",
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"properties": {
|
||||||
|
"manufacturer": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[-a-z0-9_]+$"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"form_factor": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"wall-cabinet",
|
||||||
|
"4-post-frame",
|
||||||
|
"2-post-frame",
|
||||||
|
"4-post-cabinet",
|
||||||
|
"wall-frame",
|
||||||
|
"wall-frame-vertical",
|
||||||
|
"wall-cabinet-vertical"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "integer",
|
||||||
|
"enum": [
|
||||||
|
10,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
23
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"u_height": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 1
|
||||||
|
},
|
||||||
|
"outer_width": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"outer_height": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"outer_depth": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"outer_unit": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"mm",
|
||||||
|
"in"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"max_weight": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"weight_unit": {
|
||||||
|
"$ref": "urn:devicetype-library:generated-schema#/definitions/weight-unit"
|
||||||
|
},
|
||||||
|
"mounting_depth": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"multipleOf": 0.01
|
||||||
|
},
|
||||||
|
"starting_unit": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1,
|
||||||
|
"multipleOf": 1
|
||||||
|
},
|
||||||
|
"desc_units": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["manufacturer", "model", "slug", "form_factor", "width", "u_height", "starting_unit"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from test_configuration import COMPONENT_TYPES, IMAGE_FILETYPES, SCHEMAS, SCHEMAS_BASEPATH, KNOWN_SLUGS, ROOT_DIR, USE_LOCAL_KNOWN_SLUGS, NETBOX_DT_LIBRARY_URL, KNOWN_MODULES, USE_UPSTREAM_DIFF, PRECOMMIT_ALL_SWITCHES
|
from test_configuration import COMPONENT_TYPES, IMAGE_FILETYPES, SCHEMAS, SCHEMAS_BASEPATH, KNOWN_SLUGS, ROOT_DIR, USE_LOCAL_KNOWN_SLUGS, NETBOX_DT_LIBRARY_URL, KNOWN_MODULES, USE_UPSTREAM_DIFF, PRECOMMIT_ALL_SWITCHES
|
||||||
import pickle_operations
|
import pickle_operations
|
||||||
from yaml_loader import DecimalSafeLoader
|
from yaml_loader import DecimalSafeLoader
|
||||||
from device_types import DeviceType, ModuleType, verify_filename, validate_components
|
from device_types import DeviceType, ModuleType, RackType, verify_filename, validate_components
|
||||||
import decimal
|
import decimal
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
@@ -134,11 +134,13 @@ image_files = _get_image_files()
|
|||||||
if USE_LOCAL_KNOWN_SLUGS:
|
if USE_LOCAL_KNOWN_SLUGS:
|
||||||
KNOWN_SLUGS = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-slugs.pickle')
|
KNOWN_SLUGS = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-slugs.pickle')
|
||||||
KNOWN_MODULES = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-modules.pickle')
|
KNOWN_MODULES = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-modules.pickle')
|
||||||
|
KNOWN_RACKS = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-racks.pickle')
|
||||||
else:
|
else:
|
||||||
temp_dir = tempfile.TemporaryDirectory()
|
temp_dir = tempfile.TemporaryDirectory()
|
||||||
repo = Repo.clone_from(url=NETBOX_DT_LIBRARY_URL, to_path=temp_dir.name)
|
repo = Repo.clone_from(url=NETBOX_DT_LIBRARY_URL, to_path=temp_dir.name)
|
||||||
KNOWN_SLUGS = pickle_operations.read_pickle_data(f'{temp_dir.name}/tests/known-slugs.pickle')
|
KNOWN_SLUGS = pickle_operations.read_pickle_data(f'{temp_dir.name}/tests/known-slugs.pickle')
|
||||||
KNOWN_MODULES = pickle_operations.read_pickle_data(f'{temp_dir.name}/tests/known-modules.pickle')
|
KNOWN_MODULES = pickle_operations.read_pickle_data(f'{temp_dir.name}/tests/known-modules.pickle')
|
||||||
|
KNOWN_RACKS = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-racks.pickle')
|
||||||
|
|
||||||
SCHEMA_REGISTRY = _generate_schema_registry()
|
SCHEMA_REGISTRY = _generate_schema_registry()
|
||||||
|
|
||||||
@@ -181,6 +183,12 @@ def test_definitions(file_path, schema, change_type):
|
|||||||
if "device-types" in file_path:
|
if "device-types" in file_path:
|
||||||
# A device
|
# A device
|
||||||
this_device = DeviceType(definition, file_path, change_type)
|
this_device = DeviceType(definition, file_path, change_type)
|
||||||
|
elif "module-types" in file_path:
|
||||||
|
# A module type
|
||||||
|
this_device = ModuleType(definition, file_path, change_type)
|
||||||
|
elif "rack-types" in file_path:
|
||||||
|
# A rack type
|
||||||
|
this_device = RackType(definition, file_path, change_type)
|
||||||
else:
|
else:
|
||||||
# A module
|
# A module
|
||||||
this_device = ModuleType(definition, file_path, change_type)
|
this_device = ModuleType(definition, file_path, change_type)
|
||||||
|
|||||||
@@ -179,6 +179,28 @@ class ModuleType:
|
|||||||
slugified = slugified[:-1]
|
slugified = slugified[:-1]
|
||||||
return slugified
|
return slugified
|
||||||
|
|
||||||
|
class RackType:
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return super().__new__(cls)
|
||||||
|
|
||||||
|
def __init__(self, definition, file_path, change_type):
|
||||||
|
self.file_path = file_path
|
||||||
|
self.isDevice = False
|
||||||
|
self.definition = definition
|
||||||
|
self.manufacturer = definition.get('manufacturer')
|
||||||
|
self.model = definition.get('model')
|
||||||
|
self._slug_model = self._slugify_model()
|
||||||
|
self.change_type = change_type
|
||||||
|
|
||||||
|
def get_filepath(self):
|
||||||
|
return self.file_path
|
||||||
|
|
||||||
|
def _slugify_model(self):
|
||||||
|
slugified = self.model.casefold().replace(" ", "-").replace("sfp+", "sfpp").replace("poe+", "poep").replace("-+", "-plus").replace("+", "-plus-").replace("_", "-").replace("&", "-and-").replace("!", "").replace("/", "-").replace(",", "").replace("'", "").replace("*", "-")
|
||||||
|
if slugified.endswith("-"):
|
||||||
|
slugified = slugified[:-1]
|
||||||
|
return slugified
|
||||||
|
|
||||||
def validate_component_names(component_names: (set or None)):
|
def validate_component_names(component_names: (set or None)):
|
||||||
if len(component_names) > 1:
|
if len(component_names) > 1:
|
||||||
verify_name = list(component_names[0])
|
verify_name = list(component_names[0])
|
||||||
@@ -197,10 +219,17 @@ def validate_component_names(component_names: (set or None)):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def verify_filename(device: (DeviceType or ModuleType), KNOWN_MODULES: (set or None)):
|
def verify_filename(device: (DeviceType or ModuleType or RackType), KNOWN_MODULES: (set or None)):
|
||||||
head, tail = os.path.split(device.get_filepath())
|
head, tail = os.path.split(device.get_filepath())
|
||||||
filename = tail.rsplit(".", 1)[0].casefold()
|
filename = tail.rsplit(".", 1)[0].casefold()
|
||||||
|
|
||||||
|
# Check if file is RackType
|
||||||
|
if "rack-types" in device.file_path:
|
||||||
|
if not filename == device._slug_model:
|
||||||
|
device.failureMessage = f'{device.file_path} file name is invalid. Must be the model "{device._slug_model}"'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
if not (filename == device._slug_model or filename == device._slug_part_number or filename == device.part_number.casefold()):
|
if not (filename == device._slug_model or filename == device._slug_part_number or filename == device.part_number.casefold()):
|
||||||
device.failureMessage = f'{device.file_path} file name is invalid. Must be either the model "{device._slug_model}" or part_number "{device.part_number} / {device._slug_part_number}"'
|
device.failureMessage = f'{device.file_path} file name is invalid. Must be either the model "{device._slug_model}" or part_number "{device.part_number} / {device._slug_part_number}"'
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -107,3 +107,6 @@ pickle_operations.write_pickle_data(KNOWN_SLUGS, f'{ROOT_DIR}/tests/known-slugs.
|
|||||||
|
|
||||||
_generate_knowns('module')
|
_generate_knowns('module')
|
||||||
pickle_operations.write_pickle_data(KNOWN_MODULES, f'{ROOT_DIR}/tests/known-modules.pickle')
|
pickle_operations.write_pickle_data(KNOWN_MODULES, f'{ROOT_DIR}/tests/known-modules.pickle')
|
||||||
|
|
||||||
|
_generate_knowns('rack')
|
||||||
|
pickle_operations.write_pickle_data(KNOWN_MODULES, f'{ROOT_DIR}/tests/known-racks.pickle')
|
||||||
|
|||||||
BIN
tests/known-racks.pickle
Normal file
BIN
tests/known-racks.pickle
Normal file
Binary file not shown.
@@ -3,6 +3,7 @@ import os
|
|||||||
SCHEMAS = (
|
SCHEMAS = (
|
||||||
('device-types', 'devicetype.json'),
|
('device-types', 'devicetype.json'),
|
||||||
('module-types', 'moduletype.json'),
|
('module-types', 'moduletype.json'),
|
||||||
|
('rack-types', 'racktype.json'),
|
||||||
)
|
)
|
||||||
SCHEMAS_BASEPATH = f"{os.getcwd()}/schema/"
|
SCHEMAS_BASEPATH = f"{os.getcwd()}/schema/"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user