acex-devkit 2.10.0__tar.gz → 2.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/PKG-INFO +1 -1
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/pyproject.toml +1 -1
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/__init__.py +3 -1
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/drivers/base.py +6 -7
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/drivers/base_driver.py +3 -4
- acex_devkit-2.11.0/src/acex_devkit/models/job.py +96 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/node_response.py +11 -7
- acex_devkit-2.11.0/src/acex_devkit/models/worker.py +44 -0
- acex_devkit-2.11.0/src/acex_devkit/models/ztp.py +434 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/README.md +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/command.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/configdiffer.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/diff.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/old_configdiffer.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/configdiffer/old_diff.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/base.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst2950.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst2960.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3500.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3560.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3650.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3750.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3850.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst4500.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst6500.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst6800.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9200.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9300.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9400.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9500.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/generic.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/industrial.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/modules.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/nexus9000.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/routers.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/fortinet/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/fortinet/base.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/fortinet/fortigate_f.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/base.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/ex2300.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/ex3400.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/ex4100.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/ex4400.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/ex4650.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/juniper/modules.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/drivers/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/exceptions/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/acl_model.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/agent_manifest.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/ai_ops.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/asset.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/assignment.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/attribute_value.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/augment.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/auth_config.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/base.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/capability.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/collection_agent.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/compliance.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/composed_configuration.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/config_components.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/config_snapshot.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/contact.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/container_entry.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/credential.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/device_type.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/external_value.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/lldp_neighbor.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/lldp_topology.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/logging.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/logical_node.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/management_connection.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/ned.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/os_version.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/pagination.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/platform.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/reference.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/region.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/site.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/spanning_tree.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/models/telemetry_agent.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/normalizer/__init__.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/normalizer/base.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/normalizer/engine.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/os_versions.py +0 -0
- {acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/types/__init__.py +0 -0
|
@@ -4,7 +4,6 @@ from abc import ABC, abstractmethod
|
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from acex_devkit.models.management_connection import ManagementConnection
|
|
8
7
|
from acex_devkit.models.node_response import NodeListItem
|
|
9
8
|
|
|
10
9
|
|
|
@@ -50,12 +49,12 @@ class TransportBase(ABC):
|
|
|
50
49
|
|
|
51
50
|
Args:
|
|
52
51
|
node: The node instance (identity, hostname, vendor, os, ned_id)
|
|
53
|
-
|
|
52
|
+
target_ip: Management IP or hostname of the device
|
|
54
53
|
**kwargs: Future use (credentials, options, etc.)
|
|
55
54
|
"""
|
|
56
55
|
|
|
57
56
|
@contextmanager
|
|
58
|
-
def session(self,
|
|
57
|
+
def session(self, target_ip: str, **kwargs):
|
|
59
58
|
"""Open a reusable session for multiple ops on one device.
|
|
60
59
|
|
|
61
60
|
Default is a no-op: methods continue to open/close per call.
|
|
@@ -65,20 +64,20 @@ class TransportBase(ABC):
|
|
|
65
64
|
yield self
|
|
66
65
|
|
|
67
66
|
@abstractmethod
|
|
68
|
-
def get_config(self, node: NodeListItem,
|
|
67
|
+
def get_config(self, node: NodeListItem, target_ip: str, **kwargs) -> str:
|
|
69
68
|
"""Fetch the full running configuration from a device."""
|
|
70
69
|
pass
|
|
71
70
|
|
|
72
71
|
@abstractmethod
|
|
73
|
-
def send_config(self, node: NodeListItem,
|
|
72
|
+
def send_config(self, node: NodeListItem, target_ip: str, commands: list[str], **kwargs) -> str:
|
|
74
73
|
"""Apply configuration commands to a device."""
|
|
75
74
|
pass
|
|
76
75
|
|
|
77
|
-
def execute(self, node: NodeListItem,
|
|
76
|
+
def execute(self, node: NodeListItem, target_ip: str, commands: list[str], **kwargs) -> list[str]:
|
|
78
77
|
"""Run arbitrary commands and return output per command. Opt-in per driver."""
|
|
79
78
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement execute()")
|
|
80
79
|
|
|
81
|
-
def get_lldp_neighbors(self, node: NodeListItem,
|
|
80
|
+
def get_lldp_neighbors(self, node: NodeListItem, target_ip: str, **kwargs) -> list[dict]:
|
|
82
81
|
"""Fetch LLDP/CDP neighbor table from a device.
|
|
83
82
|
|
|
84
83
|
Returns list of dicts with keys:
|
|
@@ -3,7 +3,6 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from acex_devkit.models.asset import Asset
|
|
5
5
|
from acex_devkit.models.composed_configuration import ComposedConfiguration
|
|
6
|
-
from acex_devkit.models.management_connection import ManagementConnection
|
|
7
6
|
from acex_devkit.models.node_response import NodeListItem
|
|
8
7
|
|
|
9
8
|
|
|
@@ -22,16 +21,16 @@ class RendererBase(ABC):
|
|
|
22
21
|
|
|
23
22
|
class TransportBase(ABC):
|
|
24
23
|
@abstractmethod
|
|
25
|
-
def get_config(self, node: NodeListItem,
|
|
24
|
+
def get_config(self, node: NodeListItem, target_ip: str, **kwargs) -> str:
|
|
26
25
|
"""Fetch the full running configuration from a device."""
|
|
27
26
|
pass
|
|
28
27
|
|
|
29
28
|
@abstractmethod
|
|
30
|
-
def send_config(self, node: NodeListItem,
|
|
29
|
+
def send_config(self, node: NodeListItem, target_ip: str, commands: list[str], **kwargs) -> str:
|
|
31
30
|
"""Apply configuration commands to a device."""
|
|
32
31
|
pass
|
|
33
32
|
|
|
34
|
-
def execute(self, node: NodeListItem,
|
|
33
|
+
def execute(self, node: NodeListItem, target_ip: str, commands: list[str], **kwargs) -> list[str]:
|
|
35
34
|
"""Run arbitrary commands. Opt-in per driver."""
|
|
36
35
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement execute()")
|
|
37
36
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
from acex_devkit.models.base import PersistedResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JobState(StrEnum):
|
|
11
|
+
queued = "queued"
|
|
12
|
+
running = "running"
|
|
13
|
+
succeeded = "succeeded"
|
|
14
|
+
failed = "failed"
|
|
15
|
+
cancelled = "cancelled"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JobSubjectType(StrEnum):
|
|
19
|
+
"""The kind of object a job is about, for finding every job that concerns it."""
|
|
20
|
+
|
|
21
|
+
node = "node"
|
|
22
|
+
logical_node = "logical_node"
|
|
23
|
+
asset = "asset"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class JobBase(BaseModel):
|
|
27
|
+
#: The job type, e.g. "acex.ztp.discover". Also the Celery task name.
|
|
28
|
+
type: str
|
|
29
|
+
#: Set on the jobs of a batch. Only one level: a child has no children.
|
|
30
|
+
parent_id: int | None = None
|
|
31
|
+
#: Derived from the job type and its data, never set by the caller.
|
|
32
|
+
subject_type: JobSubjectType | None = None
|
|
33
|
+
subject_id: int | None = None
|
|
34
|
+
#: Typed per job type, and only ids: workers fetch the rest from the API.
|
|
35
|
+
data: dict | None = None
|
|
36
|
+
#: Typed per job type; set when a worker reports success.
|
|
37
|
+
result: dict | None = None
|
|
38
|
+
state: JobState = JobState.queued
|
|
39
|
+
attempts: int = 0
|
|
40
|
+
error: str | None = None
|
|
41
|
+
#: The user's `sub`, or "system" for jobs the backend creates itself.
|
|
42
|
+
created_by: str
|
|
43
|
+
claimed_by: str | None = None
|
|
44
|
+
#: The `sub` of whoever cancelled the job.
|
|
45
|
+
cancelled_by: str | None = None
|
|
46
|
+
created_at: datetime
|
|
47
|
+
started_at: datetime | None = None
|
|
48
|
+
finished_at: datetime | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class JobResponse(PersistedResponse, JobBase):
|
|
52
|
+
#: On a batch's parent: how many of its jobs are in each state. The
|
|
53
|
+
#: parent's own state is derived from these.
|
|
54
|
+
children: dict[JobState, int] | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class JobSummary(PersistedResponse):
|
|
58
|
+
"""A job in a listing. Its data, result and error are fetched one job at a time."""
|
|
59
|
+
|
|
60
|
+
type: str
|
|
61
|
+
state: JobState
|
|
62
|
+
parent_id: int | None = None
|
|
63
|
+
subject_type: JobSubjectType | None = None
|
|
64
|
+
subject_id: int | None = None
|
|
65
|
+
attempts: int
|
|
66
|
+
claimed_by: str | None = None
|
|
67
|
+
created_at: datetime
|
|
68
|
+
finished_at: datetime | None = None
|
|
69
|
+
children: dict[JobState, int] | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class JobPurge(BaseModel):
|
|
73
|
+
"""How many jobs a purge deleted, batch parents included."""
|
|
74
|
+
|
|
75
|
+
deleted: int
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class JobUpdate(BaseModel):
|
|
79
|
+
"""What a worker reports on a job it runs: that it has claimed it, or how it ended."""
|
|
80
|
+
|
|
81
|
+
#: running claims the job; succeeded and failed finish it.
|
|
82
|
+
state: Literal[JobState.running, JobState.succeeded, JobState.failed]
|
|
83
|
+
#: With succeeded, for job types that give a result.
|
|
84
|
+
result: dict | None = None
|
|
85
|
+
#: With failed: why.
|
|
86
|
+
error: str | None = None
|
|
87
|
+
|
|
88
|
+
@model_validator(mode="after")
|
|
89
|
+
def _fits_state(self) -> "JobUpdate":
|
|
90
|
+
if self.result is not None and self.state != JobState.succeeded:
|
|
91
|
+
raise ValueError("a result is only reported with state succeeded")
|
|
92
|
+
if self.state == JobState.failed and not self.error:
|
|
93
|
+
raise ValueError("state failed needs an error")
|
|
94
|
+
if self.error is not None and self.state != JobState.failed:
|
|
95
|
+
raise ValueError("an error is only reported with state failed")
|
|
96
|
+
return self
|
|
@@ -27,15 +27,19 @@ class NodeAdminStatus(StrEnum):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class NodeProvisionStatus(StrEnum):
|
|
30
|
-
"""Where the node's device is in the provisioning lifecycle. Set by the provisioning flow, not by operators.
|
|
30
|
+
"""Where the node's device is in the provisioning lifecycle. Set by the provisioning flow, not by operators.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
With no provisioning under way the node sits in a steady state (unprovisioned, adopted,
|
|
33
|
+
provisioned). While it is in ZTP it is enrolled, provisioning or failed; an enrolled
|
|
34
|
+
node's ZTP stage (awaiting device, awaiting approval, ready) comes from its asset.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
unprovisioned = "unprovisioned" # steady: no provisioning requested, never provisioned by ACEX
|
|
38
|
+
adopted = "adopted" # steady: brownfield, already running when brought under management
|
|
39
|
+
enrolled = "enrolled" # in ZTP, waiting for its device to be discovered, approved and provisioned
|
|
36
40
|
provisioning = "provisioning" # full config being pushed and verified
|
|
37
|
-
provisioned = "provisioned" # provisioned by ACEX and verified
|
|
38
|
-
failed = "failed"
|
|
41
|
+
provisioned = "provisioned" # steady: provisioned by ACEX and verified
|
|
42
|
+
failed = "failed" # in ZTP, stuck until retried or cancelled; the reason is on the job
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
class NodeBase(BaseModel):
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, SecretStr, field_serializer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JobTypeInfo(BaseModel):
|
|
5
|
+
"""A job type the backend creates, and the queue that carries it."""
|
|
6
|
+
|
|
7
|
+
name: str
|
|
8
|
+
queue: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorkerConnectRequest(BaseModel):
|
|
12
|
+
"""What a starting worker can run, so it is told which queues carry those jobs."""
|
|
13
|
+
|
|
14
|
+
job_types: list[str] = Field(min_length=1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BrokerConnection(BaseModel):
|
|
18
|
+
"""The RabbitMQ broker a worker consumes from."""
|
|
19
|
+
|
|
20
|
+
host: str
|
|
21
|
+
port: int
|
|
22
|
+
vhost: str
|
|
23
|
+
user: str | None = None
|
|
24
|
+
#: Hidden in repr and logs, but sent in full: the worker has to log in with it.
|
|
25
|
+
password: SecretStr | None = None
|
|
26
|
+
|
|
27
|
+
@field_serializer("password", when_used="json")
|
|
28
|
+
def _send_password(self, password: SecretStr | None) -> str | None:
|
|
29
|
+
return password.get_secret_value() if password is not None else None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class QueueDeclaration(BaseModel):
|
|
33
|
+
"""A queue as the backend declares it. A worker must declare it the same
|
|
34
|
+
way: RabbitMQ refuses a second declaration with different arguments."""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
durable: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WorkerConnection(BaseModel):
|
|
41
|
+
"""Everything a worker needs to start consuming, handed out by the API."""
|
|
42
|
+
|
|
43
|
+
broker: BrokerConnection
|
|
44
|
+
queues: list[QueueDeclaration]
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from ipaddress import IPv4Address, IPv4Network
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, IPvAnyAddress
|
|
6
|
+
|
|
7
|
+
from acex_devkit.models.base import PersistedResponse
|
|
8
|
+
from acex_devkit.models.job import JobResponse, JobState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ZtpMethod(StrEnum):
|
|
12
|
+
"""How a device bootstraps itself. One OS can have several methods."""
|
|
13
|
+
|
|
14
|
+
#: The device downloads and runs a Python script (Cisco IOS XE guestshell).
|
|
15
|
+
cisco_iosxe_python = "cisco_iosxe_python"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ZtpMethodInfo(BaseModel):
|
|
19
|
+
"""How a ZTP method is presented to an administrator."""
|
|
20
|
+
|
|
21
|
+
label: str
|
|
22
|
+
description: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#: One entry per ZtpMethod; add one with every new method.
|
|
26
|
+
ZTP_METHOD_INFO: dict[ZtpMethod, ZtpMethodInfo] = {
|
|
27
|
+
ZtpMethod.cisco_iosxe_python: ZtpMethodInfo(
|
|
28
|
+
label="Cisco IOS XE — Python",
|
|
29
|
+
description="The device downloads ztp.py over HTTP and runs it in guestshell.",
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ZtpMethodResponse(ZtpMethodInfo):
|
|
35
|
+
"""A ZTP method, and what an administrator has chosen for it."""
|
|
36
|
+
|
|
37
|
+
method: ZtpMethod
|
|
38
|
+
#: The NED discovery uses for devices that bootstrap this way. None until one is chosen.
|
|
39
|
+
ned: str | None = None
|
|
40
|
+
#: The temporary login the bootstrap gives a device and discovery logs in with,
|
|
41
|
+
#: until onboarding rotates it. Not a secret: the bootstrap is public.
|
|
42
|
+
bootstrap_username: str | None = None
|
|
43
|
+
bootstrap_password: str | None = None
|
|
44
|
+
#: Devices whose vendor class matches no mapping bootstrap with this method. At most one method is.
|
|
45
|
+
is_default: bool = False
|
|
46
|
+
updated_by: str | None = None
|
|
47
|
+
updated_at: datetime | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ZtpMethodUpdate(BaseModel):
|
|
51
|
+
"""Change what is chosen for a ZTP method. Only the fields sent change; None clears one."""
|
|
52
|
+
|
|
53
|
+
ned: str | None = None
|
|
54
|
+
bootstrap_username: str | None = None
|
|
55
|
+
bootstrap_password: str | None = None
|
|
56
|
+
#: True makes this the default method, and no other; false leaves no default.
|
|
57
|
+
is_default: bool | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ZtpVendorClassMatch(StrEnum):
|
|
61
|
+
"""How the vendor class (option 60) a device sends is compared to a mapped one."""
|
|
62
|
+
|
|
63
|
+
exact = "exact"
|
|
64
|
+
#: The device's vendor class starts with it, for vendors that add model or serial.
|
|
65
|
+
prefix = "prefix"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ZtpVendorClassCreate(BaseModel):
|
|
69
|
+
"""A DHCP vendor class (option 60) a device may send, and the ZTP method it bootstraps with."""
|
|
70
|
+
|
|
71
|
+
#: Free text, matched by the DHCP server as `match` says.
|
|
72
|
+
vendor_class: str
|
|
73
|
+
match: ZtpVendorClassMatch = ZtpVendorClassMatch.exact
|
|
74
|
+
#: None until an administrator maps it.
|
|
75
|
+
method: ZtpMethod | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ZtpVendorClassUpdate(BaseModel):
|
|
79
|
+
"""Change a vendor class. Only the fields sent change; method None unmaps it."""
|
|
80
|
+
|
|
81
|
+
vendor_class: str | None = None
|
|
82
|
+
match: ZtpVendorClassMatch | None = None
|
|
83
|
+
method: ZtpMethod | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ZtpVendorClassResponse(PersistedResponse):
|
|
87
|
+
vendor_class: str
|
|
88
|
+
match: ZtpVendorClassMatch
|
|
89
|
+
method: ZtpMethod | None = None
|
|
90
|
+
updated_by: str | None = None
|
|
91
|
+
updated_at: datetime | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ZtpDhcpScopeCreate(BaseModel):
|
|
95
|
+
"""An IPv4 network the ZTP DHCP server hands out addresses in."""
|
|
96
|
+
|
|
97
|
+
network: IPv4Network
|
|
98
|
+
#: The addresses handed out, both inclusive, inside the network.
|
|
99
|
+
pool_start: IPv4Address
|
|
100
|
+
pool_end: IPv4Address
|
|
101
|
+
#: Option 3. Inside the network, outside the pool.
|
|
102
|
+
gateway: IPv4Address
|
|
103
|
+
#: Option 6.
|
|
104
|
+
dns_servers: list[IPv4Address] = []
|
|
105
|
+
#: Option 15.
|
|
106
|
+
domain_name: str | None = None
|
|
107
|
+
description: str | None = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ZtpDhcpScopeUpdate(BaseModel):
|
|
111
|
+
"""Change a scope. Only the fields sent change; None clears an optional one."""
|
|
112
|
+
|
|
113
|
+
network: IPv4Network | None = None
|
|
114
|
+
pool_start: IPv4Address | None = None
|
|
115
|
+
pool_end: IPv4Address | None = None
|
|
116
|
+
gateway: IPv4Address | None = None
|
|
117
|
+
dns_servers: list[IPv4Address] | None = None
|
|
118
|
+
domain_name: str | None = None
|
|
119
|
+
description: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ZtpDhcpScopeResponse(PersistedResponse, ZtpDhcpScopeCreate):
|
|
123
|
+
updated_by: str | None = None
|
|
124
|
+
updated_at: datetime | None = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ZtpDiscoverData(BaseModel):
|
|
128
|
+
"""Data of an acex.ztp.discover job: a device that fetched its ZTP bootstrap."""
|
|
129
|
+
|
|
130
|
+
#: The address the device fetched its bootstrap from, which discovery logs in to.
|
|
131
|
+
ip: IPvAnyAddress
|
|
132
|
+
#: The bootstrap method it used. Decides how it is discovered.
|
|
133
|
+
method: ZtpMethod
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ZtpProvisionData(BaseModel):
|
|
137
|
+
"""Data of an acex.ztp.provision job: a node whose approved device gets its full configuration."""
|
|
138
|
+
|
|
139
|
+
node_id: int
|
|
140
|
+
#: The bootstrap method the device used. Decides which bootstrap login provisioning logs in with.
|
|
141
|
+
method: ZtpMethod
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ZtpProvisionResult(BaseModel):
|
|
145
|
+
"""Worker response from accessing the node
|
|
146
|
+
during provision"""
|
|
147
|
+
|
|
148
|
+
lines_sent: int
|
|
149
|
+
output: str
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ZtpDiscoverResult(BaseModel):
|
|
153
|
+
"""Result of an acex.ztp.discover job: what the device says it is.
|
|
154
|
+
|
|
155
|
+
Self-reported and unverified, so plain strings: a value ACEX has no enum
|
|
156
|
+
member for is still worth showing to the administrator who reviews it.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
serial_number: str
|
|
160
|
+
vendor: str | None = None
|
|
161
|
+
hardware_model: str | None = None
|
|
162
|
+
os: str | None = None
|
|
163
|
+
os_version: str | None = None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ZtpReviewStatus(StrEnum):
|
|
167
|
+
"""Where a discovery stands with the administrator who has to approve it."""
|
|
168
|
+
|
|
169
|
+
unreviewed = "unreviewed"
|
|
170
|
+
approved = "approved"
|
|
171
|
+
rejected = "rejected"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ZtpConflictKind(StrEnum):
|
|
175
|
+
#: The same serial number was reported from another IP.
|
|
176
|
+
serial_on_other_ip = "serial_on_other_ip"
|
|
177
|
+
#: The same IP has reported another serial number.
|
|
178
|
+
ip_with_other_serial = "ip_with_other_serial"
|
|
179
|
+
#: The serial belongs to a provisioned node: the device may have been reset.
|
|
180
|
+
serial_of_provisioned_node = "serial_of_provisioned_node"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
#: Conflicts that put the device's identity in doubt. A bulk approval skips a
|
|
184
|
+
#: discovery with one; it can still be approved on its own.
|
|
185
|
+
IDENTITY_CONFLICTS = frozenset({ZtpConflictKind.serial_on_other_ip, ZtpConflictKind.ip_with_other_serial})
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ZtpConflict(BaseModel):
|
|
189
|
+
"""Something about a discovery an administrator should look at before approving it."""
|
|
190
|
+
|
|
191
|
+
kind: ZtpConflictKind
|
|
192
|
+
message: str
|
|
193
|
+
#: The other discovery involved, for the kinds that compare two.
|
|
194
|
+
discovery_id: int | None = None
|
|
195
|
+
#: The node involved, for serial_of_provisioned_node.
|
|
196
|
+
node_id: int | None = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ZtpApprovalAction(StrEnum):
|
|
200
|
+
"""What approving a discovery does to the asset with its serial number."""
|
|
201
|
+
|
|
202
|
+
#: No asset has the serial; one is created from the reported facts.
|
|
203
|
+
create = "create"
|
|
204
|
+
#: The asset exists; the reported facts that differ are written to it.
|
|
205
|
+
update = "update"
|
|
206
|
+
#: The asset exists and already matches.
|
|
207
|
+
unchanged = "unchanged"
|
|
208
|
+
#: The discovery cannot be approved; `blocked_by` says why.
|
|
209
|
+
blocked = "blocked"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ZtpFactChange(BaseModel):
|
|
213
|
+
field: str
|
|
214
|
+
current: str | None = None
|
|
215
|
+
reported: str | None = None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class ZtpProposedAsset(BaseModel):
|
|
219
|
+
"""The asset as it will be once the discovery is approved."""
|
|
220
|
+
|
|
221
|
+
serial_number: str
|
|
222
|
+
vendor: str | None = None
|
|
223
|
+
hardware_model: str | None = None
|
|
224
|
+
os: str | None = None
|
|
225
|
+
os_version: str | None = None
|
|
226
|
+
ned_id: str | None = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class ZtpApprovalProposal(BaseModel):
|
|
230
|
+
"""What approving the discovery would do, computed by the same code that approves it."""
|
|
231
|
+
|
|
232
|
+
action: ZtpApprovalAction
|
|
233
|
+
#: The existing asset; None for create and for some blocked proposals.
|
|
234
|
+
asset_id: int | None = None
|
|
235
|
+
asset: ZtpProposedAsset | None = None
|
|
236
|
+
#: For update: the facts that will be written.
|
|
237
|
+
changes: list[ZtpFactChange] = []
|
|
238
|
+
blocked_by: list[str] = []
|
|
239
|
+
#: Identifies this exact proposal. Sent back on approve, so nothing is
|
|
240
|
+
#: written that differs from what the administrator saw.
|
|
241
|
+
hash: str
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ZtpDiscoveryResponse(PersistedResponse, ZtpDiscoverResult):
|
|
245
|
+
"""A discovery: the device at `source_ip` says it is `serial_number`."""
|
|
246
|
+
|
|
247
|
+
job_id: int | None = None
|
|
248
|
+
source_ip: str
|
|
249
|
+
#: The asset the discovery was approved as; None until approved.
|
|
250
|
+
asset_id: int | None = None
|
|
251
|
+
#: The node the asset with this serial is bound to, if any. Approval never changes it.
|
|
252
|
+
node_id: int | None = None
|
|
253
|
+
#: Hostname of that node's logical node.
|
|
254
|
+
node_hostname: str | None = None
|
|
255
|
+
review_status: ZtpReviewStatus
|
|
256
|
+
reviewed_by: str | None = None
|
|
257
|
+
reviewed_at: datetime | None = None
|
|
258
|
+
created_at: datetime
|
|
259
|
+
conflicts: list[ZtpConflict] = []
|
|
260
|
+
#: What approving would do; None once reviewed.
|
|
261
|
+
proposal: ZtpApprovalProposal | None = None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ZtpApproveRequest(BaseModel):
|
|
265
|
+
#: The hash of the proposal the administrator saw. Approval is refused if it
|
|
266
|
+
#: no longer matches. Leave out to approve whatever the proposal now is.
|
|
267
|
+
proposal_hash: str | None = None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class ZtpBulkApproveItem(BaseModel):
|
|
271
|
+
discovery_id: int
|
|
272
|
+
proposal_hash: str | None = None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class ZtpBulkApprove(BaseModel):
|
|
276
|
+
items: list[ZtpBulkApproveItem]
|
|
277
|
+
#: Work out what would happen without writing anything.
|
|
278
|
+
dry_run: bool = False
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class ZtpBulkReject(BaseModel):
|
|
282
|
+
discovery_ids: list[int]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class ZtpBulkOutcome(StrEnum):
|
|
286
|
+
approved = "approved"
|
|
287
|
+
#: A dry run would approve it.
|
|
288
|
+
would_approve = "would_approve"
|
|
289
|
+
rejected = "rejected"
|
|
290
|
+
skipped = "skipped"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ZtpBulkItemResult(BaseModel):
|
|
294
|
+
discovery_id: int
|
|
295
|
+
outcome: ZtpBulkOutcome
|
|
296
|
+
#: Why it was skipped.
|
|
297
|
+
reason: str | None = None
|
|
298
|
+
proposal: ZtpApprovalProposal | None = None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class ZtpBulkResult(BaseModel):
|
|
302
|
+
results: list[ZtpBulkItemResult]
|
|
303
|
+
#: Counts per proposal action (approve) and per outcome.
|
|
304
|
+
summary: dict[str, int]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class ZtpCallStage(StrEnum):
|
|
308
|
+
"""Where a device that called in stands before anyone reviews it, from its latest discovery job."""
|
|
309
|
+
|
|
310
|
+
#: Fetched its bootstrap; no worker has picked up the discovery yet.
|
|
311
|
+
waiting = "waiting"
|
|
312
|
+
#: A worker is logging in to it.
|
|
313
|
+
discovering = "discovering"
|
|
314
|
+
#: The worker could not find out what it is.
|
|
315
|
+
failed = "failed"
|
|
316
|
+
#: It said what it is; the discovery waits in review.
|
|
317
|
+
reported = "reported"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class ZtpCallAttempt(BaseModel):
|
|
321
|
+
"""One bootstrap fetch and the discovery job it started."""
|
|
322
|
+
|
|
323
|
+
job_id: int
|
|
324
|
+
state: JobState
|
|
325
|
+
attempts: int
|
|
326
|
+
error: str | None = None
|
|
327
|
+
created_at: datetime
|
|
328
|
+
started_at: datetime | None = None
|
|
329
|
+
finished_at: datetime | None = None
|
|
330
|
+
#: Set once the worker reported what the device is.
|
|
331
|
+
discovery_id: int | None = None
|
|
332
|
+
serial_number: str | None = None
|
|
333
|
+
review_status: ZtpReviewStatus | None = None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class ZtpCall(BaseModel):
|
|
337
|
+
"""A device that called in, by the IP it fetched its bootstrap from.
|
|
338
|
+
|
|
339
|
+
A device that reboots calls in again, so one IP gathers several attempts.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
source_ip: str
|
|
343
|
+
method: ZtpMethod
|
|
344
|
+
stage: ZtpCallStage
|
|
345
|
+
#: When it last fetched its bootstrap.
|
|
346
|
+
last_seen: datetime
|
|
347
|
+
#: Newest first.
|
|
348
|
+
attempts: list[ZtpCallAttempt]
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class ZtpAssetReadiness(StrEnum):
|
|
352
|
+
"""How far a node's device has come before provisioning, from the discoveries of its serial."""
|
|
353
|
+
|
|
354
|
+
#: No discovery of the serial, or only rejected ones.
|
|
355
|
+
unseen = "unseen"
|
|
356
|
+
#: The latest discovery waits for an administrator.
|
|
357
|
+
awaiting_review = "awaiting_review"
|
|
358
|
+
#: The latest discovery is approved: identity and address are trusted.
|
|
359
|
+
ready = "ready"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class ZtpStage(StrEnum):
|
|
363
|
+
"""Where a node stands in ZTP.
|
|
364
|
+
|
|
365
|
+
Before provisioning starts this comes from the asset's readiness; from then
|
|
366
|
+
on it is the node's provision_status.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
awaiting_device = "awaiting_device"
|
|
370
|
+
awaiting_approval = "awaiting_approval"
|
|
371
|
+
ready = "ready"
|
|
372
|
+
provisioning = "provisioning"
|
|
373
|
+
provisioned = "provisioned"
|
|
374
|
+
failed = "failed"
|
|
375
|
+
adopted = "adopted"
|
|
376
|
+
unprovisioned = "unprovisioned"
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
#: The stages a node is in the pipeline for.
|
|
380
|
+
PIPELINE_STAGES = (
|
|
381
|
+
ZtpStage.awaiting_device,
|
|
382
|
+
ZtpStage.awaiting_approval,
|
|
383
|
+
ZtpStage.ready,
|
|
384
|
+
ZtpStage.provisioning,
|
|
385
|
+
ZtpStage.failed,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class ZtpNodeAsset(BaseModel):
|
|
390
|
+
id: int
|
|
391
|
+
serial_number: str
|
|
392
|
+
vendor: str | None = None
|
|
393
|
+
hardware_model: str | None = None
|
|
394
|
+
os: str | None = None
|
|
395
|
+
os_version: str | None = None
|
|
396
|
+
ned_id: str | None = None
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class ZtpPipelineNode(BaseModel):
|
|
400
|
+
"""A node and where it stands in ZTP."""
|
|
401
|
+
|
|
402
|
+
node_id: int
|
|
403
|
+
hostname: str | None = None
|
|
404
|
+
site: str | None = None
|
|
405
|
+
admin_status: str
|
|
406
|
+
provision_status: str
|
|
407
|
+
#: The status the node had when it was enrolled; cancelling ZTP returns it there.
|
|
408
|
+
ztp_previous_status: str | None = None
|
|
409
|
+
stage: ZtpStage
|
|
410
|
+
#: Why a provisioning node is not getting anywhere: no job, or a job lost on its queue or its worker.
|
|
411
|
+
stalled: str | None = None
|
|
412
|
+
#: None when the node is bound to an asset cluster.
|
|
413
|
+
asset: ZtpNodeAsset | None = None
|
|
414
|
+
readiness: ZtpAssetReadiness | None = None
|
|
415
|
+
#: When the device was last discovered.
|
|
416
|
+
last_seen: datetime | None = None
|
|
417
|
+
#: The address of the approved discovery, which provisioning uses.
|
|
418
|
+
approved_ip: str | None = None
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class ZtpNodeDetail(ZtpPipelineNode):
|
|
422
|
+
#: Discoveries of the asset's serial, newest first.
|
|
423
|
+
discoveries: list[ZtpDiscoveryResponse] = []
|
|
424
|
+
#: Provisioning jobs of the node, newest first.
|
|
425
|
+
provision_jobs: list[JobResponse] = []
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class ZtpPipelineSummary(BaseModel):
|
|
429
|
+
#: Nodes per stage, every node counted once.
|
|
430
|
+
stages: dict[ZtpStage, int]
|
|
431
|
+
#: Approved assets not bound to any node: ready for a node to be assigned.
|
|
432
|
+
pool: int
|
|
433
|
+
#: Provisioning nodes that are not getting anywhere.
|
|
434
|
+
stalled: int = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst2950.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst2960.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3500.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3560.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3650.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3750.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst3850.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst4500.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst6500.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst6800.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9200.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9300.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9400.py
RENAMED
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/cisco/catalyst9500.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acex_devkit-2.10.0 → acex_devkit-2.11.0}/src/acex_devkit/device_types/fortinet/fortigate_f.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|