diff --git a/scripts/playbooks.py b/scripts/playbooks.py index 99839bcb..6cb8ed76 100755 --- a/scripts/playbooks.py +++ b/scripts/playbooks.py @@ -23,7 +23,7 @@ from tdp.cli.params import collections_option from tdp.core.constants import DEFAULT_SERVICE_PRIORITY, SERVICE_PRIORITY from tdp.core.dag import Dag -from tdp.core.operation import Operation +from tdp.core.entities.operation import OperationName @click.command() @@ -51,14 +51,14 @@ def playbooks(services, output_dir, for_collection, collections): # For each service, get all operations with DAG topological_sort order dag_service_operations = {} for operation in dag.get_all_operations(): - dag_services.add_node(operation.service_name) + dag_services.add_node(operation.name.service) for dependency in operation.depends_on: - dependency_operation = Operation(dependency) - if dependency_operation.service_name != operation.service_name: + dependency_operation = OperationName.from_name(dependency) + if dependency_operation.service != operation.name.service: dag_services.add_edge( - dependency_operation.service_name, operation.service_name + dependency_operation.service, operation.name.service ) - dag_service_operations.setdefault(operation.service_name, []).append(operation) + dag_service_operations.setdefault(operation.name.service, []).append(operation) if not nx.is_directed_acyclic_graph(dag_services): raise RuntimeError("dag_services is not a DAG.") diff --git a/tdp/cli/commands/dag.py b/tdp/cli/commands/dag.py index 90e5e2c9..48464d03 100644 --- a/tdp/cli/commands/dag.py +++ b/tdp/cli/commands/dag.py @@ -101,12 +101,18 @@ def dag( if color_to: nodes_to_color.update( list( - map(lambda o: o.name, dag.get_operations_to_nodes(color_to.split(","))) + map( + lambda o: o.name.name, + dag.get_operations_to_nodes(color_to.split(",")), + ) ) ) if color_from: nodes_from = list( - map(lambda o: o.name, dag.get_operations_from_nodes(color_from.split(","))) + map( + lambda o: o.name.name, + dag.get_operations_from_nodes(color_from.split(",")), + ) ) if nodes_to_color: nodes_to_color = nodes_to_color.intersection(nodes_from) diff --git a/tdp/cli/commands/ops.py b/tdp/cli/commands/ops.py index 5a30bfb5..473bd9f2 100644 --- a/tdp/cli/commands/ops.py +++ b/tdp/cli/commands/ops.py @@ -1,15 +1,20 @@ # Copyright 2022 TOSIT.IO # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from collections.abc import Iterable +from typing import TYPE_CHECKING import click from tabulate import tabulate from tdp.cli.params import collections_option, hosts_option -from tdp.core.collections import Collections from tdp.core.dag import Dag -from tdp.core.operation import Operation + +if TYPE_CHECKING: + from tdp.core.collections import Collections + from tdp.core.entities.operation import Operation @click.command() @@ -49,10 +54,12 @@ def ops( ] if topo_sort: sorted_operations = dag.topological_sort_key( - operations, key=lambda operation: operation.name + operations, key=lambda operation: operation.name.name ) else: - sorted_operations = sorted(operations, key=lambda operation: operation.name) + sorted_operations = sorted( + operations, key=lambda operation: operation.name.name + ) _print_operations(sorted_operations) else: operations = [ @@ -60,7 +67,9 @@ def ops( for operation in collections.operations.values() if len(hosts) == 0 or bool(set(operation.host_names) & set(hosts)) ] - sorted_operations = sorted(operations, key=lambda operation: operation.name) + sorted_operations = sorted( + operations, key=lambda operation: operation.name.name + ) _print_operations(sorted_operations) @@ -68,7 +77,10 @@ def _print_operations(operations: Iterable[Operation], /): """Prints a list of operations.""" click.echo( tabulate( - [[operation.name, operation.host_names or ""] for operation in operations], + [ + [operation.name.name, operation.host_names or ""] + for operation in operations + ], headers=["Operation name", "Hosts"], ) ) diff --git a/tdp/cli/commands/vars/edit.py b/tdp/cli/commands/vars/edit.py index 8246442d..d1f1f65f 100644 --- a/tdp/cli/commands/vars/edit.py +++ b/tdp/cli/commands/vars/edit.py @@ -96,7 +96,7 @@ def edit( # Check if component exists entity_name = parse_entity_name(variables_file.stem) if isinstance(entity_name, ServiceComponentName): - if entity_name not in collections.hostable_entities[service_name]: + if entity_name not in collections.entities[service_name]: raise click.ClickException( f"Error unknown component '{entity_name.component}' for service '{entity_name.service}'" ) diff --git a/tdp/cli/params/status/component_argument.py b/tdp/cli/params/status/component_argument.py index 42dcf150..3fac64e0 100644 --- a/tdp/cli/params/status/component_argument.py +++ b/tdp/cli/params/status/component_argument.py @@ -19,7 +19,7 @@ def _check_component( collections: Collections = ctx.params["collections"] service: str = ctx.params["service"] if value and value not in [ - sc_name.component for sc_name in collections.hostable_entities[service] + sc_name.component for sc_name in collections.entities[service] ]: raise click.UsageError( f"Component '{value}' does not exists in service '{service}'." diff --git a/tdp/core/cluster_status.py b/tdp/core/cluster_status.py index afaceabb..d1f6aec1 100644 --- a/tdp/core/cluster_status.py +++ b/tdp/core/cluster_status.py @@ -97,9 +97,9 @@ def generate_stale_sch_logs( # Add the config and start operations to the set to get their descendants if config_operation: - source_reconfigure_operations.add(config_operation.name) + source_reconfigure_operations.add(config_operation.name.name) if start_operation: - source_reconfigure_operations.add(start_operation.name) + source_reconfigure_operations.add(start_operation.name.name) # Create a log to update the stale status of the entity if a config and/or # restart operations are available @@ -129,7 +129,7 @@ def generate_stale_sch_logs( nodes=list(source_reconfigure_operations), restart=True ): # Only create a log when config or restart operation is available - if operation.action_name not in ["config", "restart"]: + if operation.name.action not in ["config", "restart"]: continue # Create a log for each host where the entity is deployed @@ -137,20 +137,20 @@ def generate_stale_sch_logs( log = logs.setdefault( create_hosted_entity( create_entity_name( - operation.service_name, operation.component_name + operation.name.service, operation.name.component ), host, ), SCHStatusLogModel( - service=operation.service_name, - component=operation.component_name, + service=operation.name.service, + component=operation.name.component, host=host, source=SCHStatusLogSourceEnum.STALE, ), ) - if operation.action_name == "config": + if operation.name.action == "config": log.to_config = True - elif operation.action_name == "restart": + elif operation.name.action == "restart": log.to_restart = True return set(logs.values()) diff --git a/tdp/core/collections/collections.py b/tdp/core/collections/collections.py index bb2d5eb1..c9e222c8 100644 --- a/tdp/core/collections/collections.py +++ b/tdp/core/collections/collections.py @@ -18,9 +18,8 @@ from typing import TYPE_CHECKING, Optional from tdp.core.entities.entity_name import ServiceComponentName -from tdp.core.entities.operation import Operations, Playbook +from tdp.core.entities.operation import Operation, Operations, Playbook from tdp.core.inventory_reader import InventoryReader -from tdp.core.operation import Operation from tdp.core.variables.schema.service_schema import ServiceSchema from .collection_reader import CollectionReader @@ -52,7 +51,7 @@ def __init__(self, collections: Iterable[CollectionReader]): self._dag_operations, self._other_operations = self._init_operations() self._default_var_directories = self._init_default_vars_dirs() self._schemas = self._init_schemas() - self._services_components = self._init_hostable_entities() + self._services_components = self._init_entities() @staticmethod def from_collection_paths( @@ -102,7 +101,7 @@ def schemas(self) -> dict[str, ServiceSchema]: # ? The mapping is using service name as a string for convenience. Should we keep # ? this or change it to ServiceName? @property - def hostable_entities(self) -> dict[str, set[ServiceComponentName]]: + def entities(self) -> dict[str, set[ServiceComponentName]]: """Mapping of services to their set of components.""" return self._services_components @@ -232,13 +231,10 @@ def _init_schemas(self) -> dict[str, ServiceSchema]: schemas.setdefault(schema.service, ServiceSchema()).add_schema(schema) return schemas - def _init_hostable_entities(self) -> dict[str, set[ServiceComponentName]]: + def _init_entities(self) -> dict[str, set[ServiceComponentName]]: services_components: dict[str, set[ServiceComponentName]] = {} for operation in self.operations.values(): - service = services_components.setdefault(operation.service_name, set()) - if not operation.component_name: - continue - service.add( - ServiceComponentName(operation.service_name, operation.component_name) - ) + service = services_components.setdefault(operation.name.service, set()) + if isinstance(operation.name, ServiceComponentName): + service.add(operation.name) return services_components diff --git a/tdp/core/dag.py b/tdp/core/dag.py index 84584a00..53d30c40 100644 --- a/tdp/core/dag.py +++ b/tdp/core/dag.py @@ -21,10 +21,10 @@ from tdp.core.constants import DEFAULT_SERVICE_PRIORITY, SERVICE_PRIORITY from tdp.core.entities.operation import Operations -from tdp.core.operation import Operation if TYPE_CHECKING: from tdp.core.collections import Collections + from tdp.core.entities.operation import Operation T = TypeVar("T") @@ -122,7 +122,7 @@ def topological_sort_key( def priority_key(node: str) -> str: operation = self.operations[node] operation_priority = SERVICE_PRIORITY.get( - operation.service_name, DEFAULT_SERVICE_PRIORITY + operation.name.service, DEFAULT_SERVICE_PRIORITY ) return f"{operation_priority:02d}_{node}" @@ -278,13 +278,13 @@ def warning(collection_name: str, message: str) -> None: c_warning = functools.partial(warning, operation.collection_name) for dependency in operation.depends_on: # *_start operations can only be required from within its own service - dependency_service = nodes[dependency].service_name + dependency_service = nodes[dependency].name.service if ( dependency.endswith("_start") - and dependency_service != operation.service_name + and dependency_service != operation.name.service ): c_warning( - f"Operation '{operation_name}' is in service '{operation.service_name}', depends on " + f"Operation '{operation_name}' is in service '{operation.name.service}', depends on " f"'{dependency}' which is a start action in service '{dependency_service}' and should " f"only depends on start action within its own service" ) @@ -301,22 +301,22 @@ def warning(collection_name: str, message: str) -> None: # Each service (HDFS, HBase, Hive, etc) should have *_install, *_config, *_init and *_start actions # even if they are "empty" (tagged with noop) # Part 1 - service_actions = services_actions.setdefault(operation.service_name, set()) + service_actions = services_actions.setdefault(operation.name.service, set()) if operation.is_service_operation(): - service_actions.add(operation.action_name) + service_actions.add(operation.name.action) # Each service action (config, start, init) except the first (install) must have an explicit # dependency with the previous service action within the same service actions_order = ["install", "config", "start", "init"] # Check only if the action is in actions_order and is not the first if ( - operation.action_name in actions_order - and operation.action_name != actions_order[0] + operation.name.action in actions_order + and operation.name.action != actions_order[0] ): previous_action = actions_order[ - actions_order.index(operation.action_name) - 1 + actions_order.index(operation.name.action) - 1 ] - previous_service_action = f"{operation.service_name}_{previous_action}" + previous_service_action = f"{operation.name.service}_{previous_action}" previous_service_action_found = False # Loop over dependency and check if the service previous action is found for dependency in operation.depends_on: @@ -325,7 +325,7 @@ def warning(collection_name: str, message: str) -> None: if not previous_service_action_found: c_warning( f"Operation '{operation_name}' is a service action and has to depend on " - f"'{operation.service_name}_{previous_action}'" + f"'{operation.name.service}_{previous_action}'" ) # Operations tagged with the noop flag should not have a playbook defined in the collection diff --git a/tdp/core/dag_dot.py b/tdp/core/dag_dot.py index ec789f87..de79cdcd 100644 --- a/tdp/core/dag_dot.py +++ b/tdp/core/dag_dot.py @@ -3,7 +3,7 @@ import networkx as nx -from tdp.core.operation import Operation +from tdp.core.entities.operation import OperationName # Needed : @@ -54,15 +54,15 @@ def to_pydot( for dot_node in dot_nodes: # Dot node name can be quoted, remove it operation_name = dot_node.get_name().strip('"') - operation = Operation(operation_name) + operation_name = OperationName.from_name(operation_name) subgraphs.setdefault( - operation.service_name, + operation_name.service, pydot.Cluster( - operation.service_name, - label=operation.service_name, + operation_name.service, + label=operation_name.service, fontname="Roboto", ), - ).add_node(pydot.Node(operation_name)) + ).add_node(pydot.Node(operation_name.name)) for service_name, subgraph in sorted(subgraphs.items()): pydot_graph.add_subgraph(subgraph) diff --git a/tdp/core/deployment/deployment_iterator.py b/tdp/core/deployment/deployment_iterator.py index facc7608..bae3e17b 100644 --- a/tdp/core/deployment/deployment_iterator.py +++ b/tdp/core/deployment/deployment_iterator.py @@ -161,12 +161,12 @@ def _process_operation_fn( # ===== Update the cluster status if success ===== # Skip sleep operation - if operation.name == OPERATION_SLEEP_NAME: + if operation.name.name == OPERATION_SLEEP_NAME: return sch_status_logs: list[SCHStatusLogModel] = [] entity_name = create_entity_name( - operation.service_name, operation.component_name + operation.name.service, operation.name.component ) if self._cluster_status.is_sc_stale(entity_name, hosts=operation.host_names): @@ -205,8 +205,8 @@ def _process_operation_fn( for host in hosts: sch_status_log = self._cluster_status.update_hosted_entity( create_hosted_entity(entity_name, host), - action_name=operation.action_name, - version=self._cluster_variables[operation.service_name].version, + action_name=operation.name.action, + version=self._cluster_variables[operation.name.service].version, can_update_stale=can_update_stale, ) if sch_status_log: diff --git a/tdp/core/deployment/deployment_runner.py b/tdp/core/deployment/deployment_runner.py index 4ff52950..53ed0e10 100644 --- a/tdp/core/deployment/deployment_runner.py +++ b/tdp/core/deployment/deployment_runner.py @@ -66,7 +66,7 @@ def _run_operation(self, operation_rec: OperationModel) -> None: return # Execute the operation - playbook_file = self._collections.playbooks[operation.name].path + playbook_file = self._collections.playbooks[operation.name.name].path state, logs = self._executor.execute( playbook=playbook_file, host=operation_rec.host, diff --git a/tdp/core/entities/operation.py b/tdp/core/entities/operation.py index fd161459..0609da20 100644 --- a/tdp/core/entities/operation.py +++ b/tdp/core/entities/operation.py @@ -1,12 +1,135 @@ # Copyright 2022 TOSIT.IO # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + from collections.abc import MutableMapping from dataclasses import dataclass from pathlib import Path +from typing import Any, Optional, Union + +from tdp.core.constants import ( + ACTION_NAME_MAX_LENGTH, + HOST_NAME_MAX_LENGTH, + OPERATION_NAME_MAX_LENGTH, +) +from tdp.core.entities.entity_name import ( + ServiceComponentName, + ServiceName, + parse_entity_name, +) + + +@dataclass(frozen=True) +class OperationName: + entity: Union[ServiceName, ServiceComponentName] + action: str + + def __post_init__(self): + if len(self.action) > ACTION_NAME_MAX_LENGTH: + raise ValueError( + f"Action '{self.action}' must be less than {ACTION_NAME_MAX_LENGTH} " + "characters." + ) + if not self.action: + raise ValueError("Action name cannot be empty.") + if len(self.name) > OPERATION_NAME_MAX_LENGTH: + raise ValueError( + f"Operation '{self.name}' must be less than {OPERATION_NAME_MAX_LENGTH}" + " characters." + ) + + @property + def service(self) -> str: + return self.entity.service + + @property + def component(self) -> Optional[str]: + return getattr(self.entity, "component", None) + + @property + def name(self) -> str: + return f"{self.entity.name}_{self.action}" + + @classmethod + def from_name(cls, name: str) -> OperationName: + entity_name, action_name = name.rsplit("_", 1) + entity = parse_entity_name(entity_name) + return cls(entity, action_name) + + def __repr__(self): + return self.name + + def __str__(self): + return self.name + + +class Operation: + """A task that can be executed by Ansible. + + The name of the operation is composed of the service name, the component name and + the action name (__). The component name is optional. + + Args: + action: Name of the action. + name: Name of the operation. + collection_name: Name of the collection where the operation is defined. + component: Name of the component. + depends_on: List of operations that must be executed before this one. + noop: If True, the operation will not be executed. + service: Name of the service. + host_names: Set of host names where the operation can be launched. + """ + + def __init__( + self, + name: str, + collection_name: Optional[str] = None, + depends_on: Optional[list[str]] = None, + noop: bool = False, + host_names: Optional[set[str]] = None, + ): + """Create a new Operation. + + Args: + name: Name of the operation. + collection_name: Name of the collection where the operation is defined. + depends_on: List of operations that must be executed before this one. + noop: If True, the operation will not be executed. + host_names: Set of host names where the operation can be launched. + """ + self.name = OperationName.from_name(name) + self.collection_name = collection_name + self.depends_on = depends_on or [] + self.noop = noop + self.host_names = host_names or set() + + for host_name in self.host_names: + if len(host_name) > HOST_NAME_MAX_LENGTH: + raise ValueError( + f"host {host_name} is longer than {HOST_NAME_MAX_LENGTH}" + ) + + def is_service_operation(self) -> bool: + """Return True if the operation is about a service, False otherwise.""" + return isinstance(self.name.entity, ServiceName) + + def __repr__(self): + return ( + f"Operation(name={self.name}, " + f"collection_name={self.collection_name}, " + f"depends_on={self.depends_on}, " + f"noop={self.noop}, " + f"host_names={self.host_names})" + ) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Operation): + return NotImplemented + return repr(self) == repr(other) -from tdp.core.constants import HOST_NAME_MAX_LENGTH -from tdp.core.operation import Operation + def __hash__(self) -> int: + return hash(repr(self)) @dataclass(frozen=True) @@ -36,7 +159,7 @@ def __getitem__(self, key: str): raise KeyError(f"Operation '{key}' not found") def __setitem__(self, key: str, value: Operation): - if key != value.name: + if key != value.name.name: raise ValueError( f"Operation name '{value.name}' does not match key '{key}'" ) diff --git a/tdp/core/filters.py b/tdp/core/filters.py index 816c026c..dfa053b0 100644 --- a/tdp/core/filters.py +++ b/tdp/core/filters.py @@ -1,14 +1,18 @@ # Copyright 2022 TOSIT.IO # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + import fnmatch import re from abc import ABC, abstractmethod from collections.abc import Callable, Iterable -from typing import Type +from typing import TYPE_CHECKING, Type from tdp.core.models.enums import FilterTypeEnum -from tdp.core.operation import Operation + +if TYPE_CHECKING: + from tdp.core.entities.operation import Operation class FilterStrategy(ABC): @@ -27,14 +31,14 @@ class RegexFilterStrategy(FilterStrategy): def apply_filter(self, operations, expression): compiled_regex = re.compile(expression) - return [o for o in operations if compiled_regex.match(o.name)] + return [o for o in operations if compiled_regex.match(o.name.name)] class GlobFilterStrategy(FilterStrategy): """Filter strategy that uses glob patterns.""" def apply_filter(self, operations, expression): - return [o for o in operations if fnmatch.fnmatch(o.name, expression)] + return [o for o in operations if fnmatch.fnmatch(o.name.name, expression)] class FilterFactory: diff --git a/tdp/core/models/deployment_model.py b/tdp/core/models/deployment_model.py index c64fbb1f..b037e3dd 100644 --- a/tdp/core/models/deployment_model.py +++ b/tdp/core/models/deployment_model.py @@ -27,7 +27,7 @@ if TYPE_CHECKING: from tdp.core.collections import Collections from tdp.core.entities.hosted_entity_status import HostedEntityStatus - from tdp.core.operation import Operation + from tdp.core.entities.operation import Operation logger = logging.getLogger(__name__) @@ -175,12 +175,12 @@ def from_dag( for operation in operations: can_perform_rolling_restart = ( rolling_interval is not None - and operation.action_name == "restart" + and operation.name.action == "restart" and operation.host_names ) deployment.operations.append( OperationModel( - operation=operation.name, + operation=operation.name.name, operation_order=operation_order, host=None, extra_vars=None, @@ -245,7 +245,7 @@ def from_operations( for operation in operations: can_perform_rolling_restart = ( rolling_interval is not None - and operation.action_name == "restart" + and operation.name.action == "restart" and operation.host_names ) for host_name in host_names or ( @@ -257,7 +257,7 @@ def from_operations( ): deployment.operations.append( OperationModel( - operation=operation.name, + operation=operation.name.name, operation_order=operation_order, host=host_name, extra_vars=list(extra_vars) if extra_vars else None, @@ -370,7 +370,7 @@ def from_stale_hosted_entities( for operation, host in reconfigure_operations_sorted: deployment.operations.append( OperationModel( - operation=operation.name, + operation=operation.name.name, operation_order=operation_order, host=host, extra_vars=None, @@ -378,7 +378,7 @@ def from_stale_hosted_entities( ) ) # Add sleep operation after each "restart" - if rolling_interval is not None and operation.action_name == "restart": + if rolling_interval is not None and operation.name.action == "restart": operation_order += 1 deployment.operations.append( OperationModel( diff --git a/tdp/core/operation.py b/tdp/core/operation.py deleted file mode 100644 index 2d740942..00000000 --- a/tdp/core/operation.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2022 TOSIT.IO -# SPDX-License-Identifier: Apache-2.0 - -import re -from typing import Any, Optional - -from tdp.core.constants import ( - ACTION_NAME_MAX_LENGTH, - COMPONENT_NAME_MAX_LENGTH, - HOST_NAME_MAX_LENGTH, - OPERATION_NAME_MAX_LENGTH, - SERVICE_NAME_MAX_LENGTH, -) - -# service operation: _ -RE_IS_SERVICE = re.compile("^([^_]+)_[^_]+$") -# component operation: __ -RE_GET_SERVICE = re.compile("^([^_]+)_.*") -RE_GET_COMPONENT = re.compile("^[^_]+_(.*)_[^_]+$") -RE_GET_ACTION = re.compile(".*_([^_]+)$") - - -class Operation: - """A task that can be executed by Ansible. - - The name of the operation is composed of the service name, the component name and - the action name (__). The component name is optional. - - Args: - action: Name of the action. - name: Name of the operation. - collection_name: Name of the collection where the operation is defined. - component: Name of the component. - depends_on: List of operations that must be executed before this one. - noop: If True, the operation will not be executed. - service: Name of the service. - host_names: Set of host names where the operation can be launched. - """ - - def __init__( - self, - name: str, - collection_name: Optional[str] = None, - depends_on: Optional[list[str]] = None, - noop: bool = False, - host_names: Optional[set[str]] = None, - ): - """Create a new Operation. - - Args: - name: Name of the operation. - collection_name: Name of the collection where the operation is defined. - depends_on: List of operations that must be executed before this one. - noop: If True, the operation will not be executed. - host_names: Set of host names where the operation can be launched. - """ - self.name = name - self.collection_name = collection_name - self.depends_on = depends_on or [] - self.noop = noop - self.host_names = host_names or set() - - if len(name) > OPERATION_NAME_MAX_LENGTH: - raise ValueError(f"{name} is longer than {OPERATION_NAME_MAX_LENGTH}") - - match = RE_GET_SERVICE.search(self.name) - if not match: - raise ValueError(f"Fail to parse service name from '{self.name}'") - self.service_name = match.group(1) - - if len(self.service_name) > SERVICE_NAME_MAX_LENGTH: - raise ValueError( - f"service {self.service_name} is longer than {SERVICE_NAME_MAX_LENGTH}" - ) - - match = RE_GET_ACTION.search(self.name) - if not match: - raise ValueError(f"Fail to parse action name from '{self.name}'") - self.action_name = match.group(1) - - if len(self.action_name) > ACTION_NAME_MAX_LENGTH: - raise ValueError( - f"action {self.action_name} is longer than {ACTION_NAME_MAX_LENGTH}" - ) - - match = RE_GET_COMPONENT.search(self.name) - if not match: - self.component_name = None - else: - self.component_name = match.group(1) - if ( - self.component_name is not None - and len(self.component_name) > COMPONENT_NAME_MAX_LENGTH - ): - raise ValueError( - f"component {self.component_name} is longer than {COMPONENT_NAME_MAX_LENGTH}" - ) - - for host_name in self.host_names: - if len(host_name) > HOST_NAME_MAX_LENGTH: - raise ValueError( - f"host {host_name} is longer than {HOST_NAME_MAX_LENGTH}" - ) - - def is_service_operation(self) -> bool: - """Return True if the operation is about a service, False otherwise.""" - return bool(RE_IS_SERVICE.search(self.name)) - - def __repr__(self): - return ( - f"Operation(name={self.name}, " - f"collection_name={self.collection_name}, " - f"depends_on={self.depends_on}, " - f"noop={self.noop}, " - f"host_names={self.host_names})" - ) - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Operation): - return NotImplemented - return repr(self) == repr(other) - - def __hash__(self) -> int: - return hash(repr(self)) diff --git a/test_dag_order/conftest.py b/test_dag_order/conftest.py index 5bcb9313..344bc49e 100644 --- a/test_dag_order/conftest.py +++ b/test_dag_order/conftest.py @@ -18,8 +18,8 @@ from tdp.core.constants import YML_EXTENSION from tdp.core.dag import Dag from tdp.core.deployment import DeploymentRunner +from tdp.core.entities.operation import OperationName from tdp.core.models import DeploymentModel, init_database -from tdp.core.operation import Operation from tdp.core.variables import ClusterVariables from tdp.dao import Dao from tests.unit.core.deployment.test_deployment_runner import MockExecutor @@ -204,15 +204,11 @@ def plan_reconfigure( ) +# TODO: store a set of entities instead of strings @pytest.fixture def stale_sc(plan_reconfigure: DeploymentModel) -> set[str]: """Set of stale service_components""" sc: set[str] = set() for operation in plan_reconfigure.operations: - # TODO: would be nice to use a dedicated class to parse the operation name - operation = Operation(operation.operation) - if operation.component_name is None: - sc.add(operation.service_name) - else: - sc.add(operation.service_name + "_" + operation.component_name) + sc.add(OperationName.from_name(operation.operation).entity.name) return sc diff --git a/test_dag_order/helpers.py b/test_dag_order/helpers.py index ac375686..efe6d2be 100644 --- a/test_dag_order/helpers.py +++ b/test_dag_order/helpers.py @@ -187,7 +187,7 @@ def resolve_components( service_component_map: dict[str, str] = {} for service_component in service_components: if isinstance(parse_entity_name(service_component), ServiceName): - for component in collections.hostable_entities[service_component]: + for component in collections.entities[service_component]: resolved_components.add(component.name) service_component_map[component.name] = service_component else: diff --git a/tests/unit/core/test_filters.py b/tests/unit/core/test_filters.py index 0dc89336..ac74b2e2 100644 --- a/tests/unit/core/test_filters.py +++ b/tests/unit/core/test_filters.py @@ -3,9 +3,9 @@ import pytest +from tdp.core.entities.operation import Operation from tdp.core.filters import FilterFactory, GlobFilterStrategy, RegexFilterStrategy from tdp.core.models.enums import FilterTypeEnum -from tdp.core.operation import Operation @pytest.fixture @@ -22,14 +22,14 @@ def test_regex_filter_strategy(operations): regex_filter = RegexFilterStrategy() filtered_operations = regex_filter.apply_filter(operations, r"^.+_config$") assert len(filtered_operations) == 2 - assert all(op.name.endswith("_config") for op in filtered_operations) + assert all(op.name.name.endswith("_config") for op in filtered_operations) def test_glob_filter_strategy(operations): glob_filter = GlobFilterStrategy() filtered_operations = glob_filter.apply_filter(operations, "*config") assert len(filtered_operations) == 2 - assert all(op.name.endswith("_config") for op in filtered_operations) + assert all(op.name.name.endswith("_config") for op in filtered_operations) def test_filter_factory_with_regex(operations):