diff --git a/README.md b/README.md index 202faf0..227bcce 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The provided methods and data models ensure easy handling, robust validation, co ### Prerequisites - Access to a VideoIPath Server (version 2023.4.2 or higher, LTS versions recommended) -- A user account with API access credentials +- Username and Password for a user account with API access - Python 3.11 or higher ### Installation @@ -39,7 +39,7 @@ The package is available via the [Python Package Index (PyPI)](https://pypi.org/ #### Install the package using pip ```bash -pip3 install videoipath-automation-tool +pip install videoipath-automation-tool ``` ### A Simple Example: Adding a Device to the Inventory @@ -49,7 +49,7 @@ pip3 install videoipath-automation-tool from videoipath_automation_tool import VideoIPathApp # Initialize the VideoIPathApp -app = VideoIPathApp(server_address="10.1.100.10", username="api-user", password="veryStrongPassword") +app = VideoIPathApp(server_address="10.1.100.10", username="api-user", password="veryStrongPassword", use_https=False, verify_ssl_cert=False) # Create a device object with NMOS Multidevice driver staged_device = app.inventory.create_device(driver="com.nevion.NMOS_multidevice-0.1.0") diff --git a/docs/development-and-release.md b/docs/development-and-release.md index 4ad8a6c..0077964 100644 --- a/docs/development-and-release.md +++ b/docs/development-and-release.md @@ -22,7 +22,7 @@ For stable/official versions, the common semver pattern is used: For development versions, there are multiple options - since this package doesn't have a complex testing and verification process for release, a simple format is used: -`...dev+` +`...dev` Pip won't treat those versions as stable, they can only be installed by specifying the exact version or using the `--pre` flag. @@ -71,7 +71,7 @@ flowchart TD subgraph run-mr-pipeline["Run Merge-Request Pipeline"] mr-tests["Run Tests [Pipeline]"] mr-build["Build the Package [Pipeline]"] - publish-dev-version["Publish Dev Version, e.g. 1.3.6.dev+d882293d [Pipeline]"] + publish-dev-version["Publish Dev Version, e.g. 1.3.6.dev256 [Pipeline]"] mr-tests --> mr-build mr-build --> publish-dev-version diff --git a/pyproject.toml b/pyproject.toml index b607ef8..3a1738f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "videoipath-automation-tool" -version = "0.1.6" +version = "0.2.0" description = "A Python package for automating VideoIPath configuration workflows." authors = ["Paul Winterstein "] maintainers = ["SWR Media-over-IP Team ", "Josia Hildebrandt "] diff --git a/src/videoipath_automation_tool/apps/inventory/model/device_status.py b/src/videoipath_automation_tool/apps/inventory/model/device_status.py index 3522513..5c11310 100644 --- a/src/videoipath_automation_tool/apps/inventory/model/device_status.py +++ b/src/videoipath_automation_tool/apps/inventory/model/device_status.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from pydantic import BaseModel, Field @@ -65,3 +65,30 @@ class DeviceStatus(BaseModel): reachable: bool softwareInfo: dict url: str + + def list_modules_label(self) -> List[str]: + """List all modules label.""" + return [module.label for module in self.modules] + + def list_modules_id(self) -> List[str]: + """List all modules id.""" + return [module.id for module in self.modules] + + def get_module_by_id(self, module_id: str) -> Optional[ModulesEntry]: + """Get module by id.""" + for module in self.modules: + if module.id == module_id: + return module + return None + + def get_module_by_label(self, module_label: str) -> Optional[ModulesEntry]: + """Returns the module with the specified label, or None if not found. + + Raises: + ValueError: If multiple modules with the same label are found. + """ + matches = [module for module in self.modules if module.label == module_label] + + if len(matches) > 1: + raise ValueError(f"Multiple modules found with label '{module_label}'") + return matches[0] if matches else None diff --git a/src/videoipath_automation_tool/apps/topology/model/topology_device.py b/src/videoipath_automation_tool/apps/topology/model/topology_device.py index 0f61e28..10be307 100644 --- a/src/videoipath_automation_tool/apps/topology/model/topology_device.py +++ b/src/videoipath_automation_tool/apps/topology/model/topology_device.py @@ -180,7 +180,7 @@ def add_virtual_module(self) -> GenericVertex: "_vid": module_id, "descriptor": {"label": "", "desc": ""}, "fDescriptor": { - "label": f"Virtual Device {device_number+1} Virtual Switching Core {module_number}", + "label": f"Virtual Device {device_number + 1} Virtual Switching Core {module_number}", "desc": "", }, "deviceId": f"{device_id}", @@ -248,7 +248,7 @@ def add_virtual_codec_vertex( "descriptor": {"label": "", "desc": ""}, "fDescriptor": { "label": f"Vertex {vertex_number}", - "desc": f"Virtual Device {device_number+1} Vertex {vertex_number}", + "desc": f"Virtual Device {device_number + 1} Vertex {vertex_number}", }, "deviceId": f"{device_id}", "gpid": { @@ -305,7 +305,7 @@ def add_virtual_codec_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Vertex {vertex_number} -> Virtual Device {device_number+1} Virtual Switching Core {module_number}", + "label": f"Vertex {vertex_number} -> Virtual Device {device_number + 1} Virtual Switching Core {module_number}", "desc": "", }, "fromId": f"{vertex_id}", @@ -332,7 +332,7 @@ def add_virtual_codec_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Virtual Device {device_number+1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", + "label": f"Virtual Device {device_number + 1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", "desc": "", }, "fromId": f"{module_id}", @@ -390,7 +390,7 @@ def add_virtual_ip_vertex( "descriptor": {"label": "", "desc": ""}, "fDescriptor": { "label": f"Vertex {vertex_number}", - "desc": f"Virtual Device {device_number+1} Vertex {vertex_number}", + "desc": f"Virtual Device {device_number + 1} Vertex {vertex_number}", }, "deviceId": f"{device_id}", "gpid": { @@ -436,7 +436,7 @@ def add_virtual_ip_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Vertex {vertex_number} -> Virtual Device {device_number+1} Virtual Switching Core {module_number}", + "label": f"Vertex {vertex_number} -> Virtual Device {device_number + 1} Virtual Switching Core {module_number}", "desc": "", }, "fromId": f"{vertex_id}", @@ -463,7 +463,7 @@ def add_virtual_ip_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Virtual Device {device_number+1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", + "label": f"Virtual Device {device_number + 1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", "desc": "", }, "fromId": f"{module_id}", @@ -521,7 +521,7 @@ def add_virtual_generic_vertex( "descriptor": {"label": "", "desc": ""}, "fDescriptor": { "label": f"Vertex {vertex_number}", - "desc": f"Virtual Device {device_number+1} Vertex {vertex_number}", + "desc": f"Virtual Device {device_number + 1} Vertex {vertex_number}", }, "deviceId": f"{device_id}", "gpid": { @@ -556,7 +556,7 @@ def add_virtual_generic_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Vertex {vertex_number} -> Virtual Device {device_number+1} Virtual Switching Core {module_number}", + "label": f"Vertex {vertex_number} -> Virtual Device {device_number + 1} Virtual Switching Core {module_number}", "desc": "", }, "fromId": f"{vertex_id}", @@ -583,7 +583,7 @@ def add_virtual_generic_vertex( "descriptor": {"label": "", "desc": ""}, "excludeFormats": [], "fDescriptor": { - "label": f"Virtual Device {device_number+1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", + "label": f"Virtual Device {device_number + 1} Virtual Switching Core {module_number} -> Vertex {vertex_number}", "desc": "", }, "fromId": f"{module_id}", diff --git a/src/videoipath_automation_tool/apps/topology/model/topology_device_configuration.py b/src/videoipath_automation_tool/apps/topology/model/topology_device_configuration.py index 33541b4..31218d8 100644 --- a/src/videoipath_automation_tool/apps/topology/model/topology_device_configuration.py +++ b/src/videoipath_automation_tool/apps/topology/model/topology_device_configuration.py @@ -5,6 +5,8 @@ from pydantic import BaseModel, Field from typing_extensions import deprecated +from videoipath_automation_tool.apps.inventory.model.device_status import DeviceStatus +from videoipath_automation_tool.apps.inventory.model.inventory_device import InventoryDevice from videoipath_automation_tool.apps.topology.model.n_graph_elements.topology_base_device import BaseDevice from videoipath_automation_tool.apps.topology.model.n_graph_elements.topology_codec_vertex import CodecVertex from videoipath_automation_tool.apps.topology.model.n_graph_elements.topology_generic_vertex import GenericVertex @@ -166,6 +168,78 @@ def site_id(self, value: str) -> None: self.base_device.siteId = value # --- Methods --- + def get_vertices_by_module_label( + self, + module_label: str, + inventory_device_status: InventoryDevice | DeviceStatus, + vertex_type: Literal["all", "codec_vertex", "ip_vertex", "generic_vertex"] = "all", + ): + """Get all vertices by module label. + Optionally, the search can be filtered by vertex type. + + Args: + module_label (str): The module label to search for (e.g. `Slot 3`). + inventory_device_status (InventoryDevice | DeviceStatus): The inventory device status object. + + Returns: + List[BaseDevice | GenericVertex | IpVertex | CodecVertex | UnidirectionalEdge]: A list of matching nGraphElements. + """ + + if vertex_type == "all": + combined_vertex_list = chain( + self.generic_vertices, + self.ip_vertices, + self.codec_vertices, + ) # Note: Base Device and Edges are not included in this search + elif vertex_type == "codec_vertex": + combined_vertex_list = self.codec_vertices + elif vertex_type == "ip_vertex": + combined_vertex_list = self.ip_vertices + elif vertex_type == "generic_vertex": + combined_vertex_list = self.generic_vertices + else: + raise ValueError( + f"Invalid vertex type: {vertex_type}. Valid options are 'all', 'codec_vertex', 'ip_vertex', or 'generic_vertex'." + ) + + if isinstance(inventory_device_status, InventoryDevice): + device_status = inventory_device_status + else: + device_status = inventory_device_status + + if not device_status: + raise ValueError("Device status is not available.") + + # Check if the device ID matches the topology device ID + if device_status.id != self.base_device.id: + raise ValueError( + f"Device ID mismatch: Inventory device ID '{device_status.id}' does not match topology device ID '{self.base_device.id}'." + ) + + module = device_status.get_module_by_label(module_label) + + if not module: + raise ValueError(f"Module with label '{module_label}' not found in device status.") + + # Build the list of element IDs to search for + # .. + # e.g. 'device66.4.6200000' + + element_id_list = [] + device_id = device_status.id + module_id = module.id + for port in module.ports: + port_id = port.id + element_id = f"{device_id}.{module_id}.{port_id}" + element_id_list.append(element_id) + + vertex_list = [] + for element in combined_vertex_list: + if element.id in element_id_list: + vertex_list.append(element) + + return vertex_list + def get_nGraphElement_by_id( self, element_id: str ) -> Optional[BaseDevice | GenericVertex | IpVertex | CodecVertex | UnidirectionalEdge]: