diff --git a/docs/README.md b/docs/README.md index 030e23f92..16051e2e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,12 +11,12 @@ of containers running various device OSes. This document is meant to serve as a How-To guide for various KNE usage. The guide is broken up into multiple sections spanning multiple documents. -* [Setup](setup.md): A guide to first time setup for KNE. -* [Create a topology](create_topology.md): A guide to deploying a KNE cluster +- [Setup](setup.md): A guide to first time setup for KNE. +- [Create a topology](create_topology.md): A guide to deploying a KNE cluster and creating a topology. -* [Interact with a topology](interact_topology.md): A guide to interacting with +- [Interact with a topology](interact_topology.md): A guide to interacting with a KNE topology after creation. -* [Troubleshooting](troubleshoot.md): A troubleshooting guide if anything goes +- [Troubleshooting](troubleshoot.md): A troubleshooting guide if anything goes wrong along the way. They are recommended to be done in order. @@ -33,7 +33,7 @@ and get a 150 node KNE topology up and running. [Vendor Image Requirements](vendor.md) -KNE uses vendor supplied images. This document describes the expectations +KNE uses vendor supplied images. This document describes the expectations for those images. ## Kubernetes Reference @@ -45,7 +45,9 @@ concepts and how they are used in KNE by running through an example topology creation. ## Support for AlpineVS in KNE + [AlpineVS](https://github.com/sonic-net/sonic-alpine/blob/master/README.md) (AVS) is a SONiC Virtual Switch with dataplane deployed as a k8s Pod within KNE. It provides switch capabilities in a simulated environment with following key features: -* **Dual-Container Architecture:** Encloses a SwitchStack container (running SONiC VM) and an ASIC Simulation container through vendor node definition for [alpine](../topo/node/alpine/alpine.go). -* **Multiple Dataplanes:** Integrates with Lucius (default gRPC-based SAI implementation) as well as vendor ASIC simulations. -* **Natively in KNE:** Runs natively in KNE with simple [2-switch topologies](https://github.com/sonic-net/sonic-alpine/blob/master/src/deploy/kne/twodut-alpine-vs.pb.txt) and scaled topologies for automated testing of the SONiC stack. + +- **Dual-Container Architecture:** Encloses a SwitchStack container (running SONiC VM) and an ASIC Simulation container through vendor node definition for [alpine](../topo/node/alpine/alpine.go). +- **Multiple Dataplanes:** Integrates with Lucius (default gRPC-based SAI implementation) as well as vendor ASIC simulations. +- **Natively in KNE:** Runs natively in KNE with simple [2-switch topologies](https://github.com/sonic-net/sonic-alpine/blob/master/src/deploy/kne/twodut-alpine-vs.pb.txt) and scaled topologies for automated testing of the SONiC stack. diff --git a/docs/create_topology.md b/docs/create_topology.md index 3f0ce0181..8e813d98d 100644 --- a/docs/create_topology.md +++ b/docs/create_topology.md @@ -31,12 +31,12 @@ Global Flags: -v, --verbosity string log level (default "info") ``` -A deployment yaml file specifies 4 things (*optional in italics*): +A deployment yaml file specifies 4 things (_optional in italics_): 1. A cluster spec 2. An ingress spec 3. A CNI spec -4. *A list of controller specs* +4. _A list of controller specs_ Expand the below section for a full description of all fields in the deployment yaml. @@ -48,106 +48,106 @@ yaml. > NOTE: ~~Strikethrough~~ fields are DEPRECATED and should not be used. -Field | Type | Description -------------- | ---------------- | --------------------------------------------- -`cluster` | ClusterSpec | Spec for the cluster. -`ingress` | IngressSpec | Spec for the ingress. -`cni` | CNISpec | Spec for the CNI. -`controllers` | []ControllerSpec | List of specs for the additional controllers. +| Field | Type | Description | +| ------------- | ---------------- | --------------------------------------------- | +| `cluster` | ClusterSpec | Spec for the cluster. | +| `ingress` | IngressSpec | Spec for the ingress. | +| `cni` | CNISpec | Spec for the CNI. | +| `controllers` | []ControllerSpec | List of specs for the additional controllers. | #### Cluster -Field | Type | Description ------- | --------- | --------------------------------------------------- -`kind` | string | Name of the cluster type. The options currently are `Kind` or `External`. -`spec` | yaml.Node | Fields that set the options for the cluster type. +| Field | Type | Description | +| ------ | --------- | ------------------------------------------------------------------------- | +| `kind` | string | Name of the cluster type. The options currently are `Kind` or `External`. | +| `spec` | yaml.Node | Fields that set the options for the cluster type. | ##### Kind -Field | Type | Description --------------------------- | ----------------- | -------------------------- -`name` | string | Cluster name, overrides `KIND_CLUSTER_NAME`, config (default `kind`). -`recycle` | bool | Reuse an existing cluster of the same name if it exists. -`version` | string | Desired version of the `kubectl` client. -`image` | string | Node docker image to use for booting the cluster. -`retain` | bool | Retain nodes for debugging when cluster creation fails. -`wait` | time.Duration | Wait for control plane node to be ready (default 0s). -`kubecfg` | string | Sets kubeconfig path instead of `$KUBECONFIG` or `$HOME/.kube/config`. -`googleArtifactRegistries` | []string | List of Google Artifact Registries to setup credentials for in the cluster. Example value for registry would be `us-west1-docker.pkg.dev`. Credentials used are associated with the configured `gcloud` user on the host. -`containerImages` | map[string]string | Map of source images to target images for containers to load in the cluster. Empty values cause the source image to be loaded into the cluster without being renamed. -`config` | string | Path to a kind config file. -`additionalManifests` | []string | List of paths to manifests to be applied using `kubectl` directly after cluster creation. +| Field | Type | Description | +| -------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | string | Cluster name, overrides `KIND_CLUSTER_NAME`, config (default `kind`). | +| `recycle` | bool | Reuse an existing cluster of the same name if it exists. | +| `version` | string | Desired version of the `kubectl` client. | +| `image` | string | Node docker image to use for booting the cluster. | +| `retain` | bool | Retain nodes for debugging when cluster creation fails. | +| `wait` | time.Duration | Wait for control plane node to be ready (default 0s). | +| `kubecfg` | string | Sets kubeconfig path instead of `$KUBECONFIG` or `$HOME/.kube/config`. | +| `googleArtifactRegistries` | []string | List of Google Artifact Registries to setup credentials for in the cluster. Example value for registry would be `us-west1-docker.pkg.dev`. Credentials used are associated with the configured `gcloud` user on the host. | +| `containerImages` | map[string]string | Map of source images to target images for containers to load in the cluster. Empty values cause the source image to be loaded into the cluster without being renamed. | +| `config` | string | Path to a kind config file. | +| `additionalManifests` | []string | List of paths to manifests to be applied using `kubectl` directly after cluster creation. | ##### External -Field | Type | Description ---------- | ------ | ------------------------------------------------------- -`network` | string | Name of the docker network to create a pool of external IP addresses for ingress to assign to services. +| Field | Type | Description | +| --------- | ------ | ------------------------------------------------------------------------------------------------------- | +| `network` | string | Name of the docker network to create a pool of external IP addresses for ingress to assign to services. | #### Ingress -Field | Type | Description ------- | --------- | ------------------------------------------------------ -`kind` | string | Name of the ingress type. The only option currently is `MetalLB`. -`spec` | yaml.Node | Fields that set the options for the ingress type. +| Field | Type | Description | +| ------ | --------- | ----------------------------------------------------------------- | +| `kind` | string | Name of the ingress type. The only option currently is `MetalLB`. | +| `spec` | yaml.Node | Fields that set the options for the ingress type. | ##### MetalLB -Field | Type | Description ---------------- | ---------- | ----------- -`ip_count` | int | Number of IP addresses to include in the available pool. -`manifest` | string | Path of the manifest yaml file to create MetalLB in the cluster. The validated manifest for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/metallb/manifest.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create MetalLB in the cluster. The directory is expected to contain a file with the name `metallb-native.yaml`.~~ +| Field | Type | Description | +| --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ip_count` | int | Number of IP addresses to include in the available pool. | +| `manifest` | string | Path of the manifest yaml file to create MetalLB in the cluster. See the [validated MetalLB manifest](https://github.com/openconfig/kne/tree/main/manifests/metallb/manifest.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create MetalLB in the cluster. The directory is expected to contain a file with the name `metallb-native.yaml`.~~ | #### CNI -Field | Type | Description ------- | --------- | -------------------------------------------------- -`kind` | string | Name of the CNI type. The only option currently is `Meshnet`. -`spec` | yaml.Node | Fields that set the options for the CNI type. +| Field | Type | Description | +| ------ | --------- | ------------------------------------------------------------- | +| `kind` | string | Name of the CNI type. The only option currently is `Meshnet`. | +| `spec` | yaml.Node | Fields that set the options for the CNI type. | ##### Meshnet -Field | Type | Description ---------------- | ---------- | ----------- -`manifest` | string | Path of the manifest yaml file to create Meshnet in the cluster. The validated manifest for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/meshnet/grpc/manifest.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create Meshnet in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ +| Field | Type | Description | +| --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `manifest` | string | Path of the manifest yaml file to create Meshnet in the cluster. See the [validated Meshnet manifest](https://github.com/openconfig/kne/tree/main/manifests/meshnet/grpc/manifest.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create Meshnet in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ | #### Controllers -Field | Type | Description ------- | --------- | ---------------------------------------------------- -`kind` | string | Name of the controller type. The current options currently are `IxiaTG`, `SRLinux`, `CEOSLab`, and `Lemming`. -`spec` | yaml.Node | Fields that set the options for the controller type. +| Field | Type | Description | +| ------ | --------- | ------------------------------------------------------------------------------------------------------------- | +| `kind` | string | Name of the controller type. The current options currently are `IxiaTG`, `SRLinux`, `CEOSLab`, and `Lemming`. | +| `spec` | yaml.Node | Fields that set the options for the controller type. | ##### IxiaTG -Field | Type | Description ---------------- | ---------- | ----------- -`operator` | string | Path of the yaml file to create an IxiaTG operator in the cluster. The validated operator for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/keysight/ixiatg-operator.yaml). -`configMap` | string | Path of the yaml file to create an IxiaTG config map in the cluster. The validated config map for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/keysight/ixiatg-configmap.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create an IxiaTG operator in the cluster. The directory is expected to contain a file with the name `ixiatg-operator.yaml`. Optionally the directory can contain a file with the name `ixiatg-configmap.yaml` to apply a config map of the desired container images used by the controller.~~ +| Field | Type | Description | +| --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `operator` | string | Path of the yaml file to create an IxiaTG operator in the cluster. See the [validated IxiaTG operator manifest](https://github.com/openconfig/kne/tree/main/manifests/keysight/ixiatg-operator.yaml). | +| `configMap` | string | Path of the yaml file to create an IxiaTG config map in the cluster. See the [validated IxiaTG config map manifest](https://github.com/openconfig/kne/tree/main/manifests/keysight/ixiatg-configmap.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create an IxiaTG operator in the cluster. The directory is expected to contain a file with the name `ixiatg-operator.yaml`. Optionally the directory can contain a file with the name `ixiatg-configmap.yaml` to apply a config map of the desired container images used by the controller.~~ | ##### SRLinux -Field | Type | Description ---------------- | ---------- | ----------- -`operator` | string | Path of the yaml file to create an SRLinux operator in the cluster. The validated operator for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/controllers/srlinux/manifest.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create an SRLinux operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ +| Field | Type | Description | +| --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `operator` | string | Path of the yaml file to create an SRLinux operator in the cluster. See the [validated SRLinux operator manifest](https://github.com/openconfig/kne/tree/main/manifests/controllers/srlinux/manifest.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create an SRLinux operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ | ##### CEOSLab -Field | Type | Description ---------------- | ---------- | ----------- -`operator` | string | Path of the yaml file to create a CEOSLab operator in the cluster. The validated operator for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/controllers/ceoslab/manifest.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create a CEOSLab operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ +| Field | Type | Description | +| --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `operator` | string | Path of the yaml file to create a CEOSLab operator in the cluster. See the [validated CEOSLab operator manifest](https://github.com/openconfig/kne/tree/main/manifests/controllers/ceoslab/manifest.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create a CEOSLab operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ | ##### Lemming -Field | Type | Description ---------------- | ---------- | ----------- -`operator` | string | Path of the yaml file to create a Lemming operator in the cluster. The validated operator for use with KNE can be found [here](https://github.com/openconfig/kne/tree/main/manifests/controllers/lemming/manifest.yaml). -~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create a Lemming operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ +| Field | Type | Description | +| --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `operator` | string | Path of the yaml file to create a Lemming operator in the cluster. See the [validated Lemming operator manifest](https://github.com/openconfig/kne/tree/main/manifests/controllers/lemming/manifest.yaml). | +| ~~`manifests`~~ | ~~string~~ | ~~Path of the directory holding the manifests to create a Lemming operator in the cluster. The directory is expected to contain a file with the name `manifest.yaml`.~~ | @@ -256,21 +256,21 @@ To load an image into a `kind` cluster there is a 3 step process: 1. Pull the desired image: - ```bash - docker pull src_image:src_tag - ``` + ```bash + docker pull src_image:src_tag + ``` 2. Tag the image with the desired in-cluster name: - ```bash - docker tag src_image:src_tag dst_image:dst_tag - ``` + ```bash + docker tag src_image:src_tag dst_image:dst_tag + ``` 3. Load the image into the `kind` cluster: - ```bash - kind load docker-image dst_image:dst_tag --name=kne - ``` + ```bash + kind load docker-image dst_image:dst_tag --name=kne + ``` Now the `dst_image:dst_tag` image will be present for use in the `kind` cluster. @@ -315,8 +315,7 @@ node definitions interfaces, services, and initial configs can be specified. An example topology containing 4 DUT nodes (Arista, Cisco, Nokia, and Juniper) and 1 ATE node (Keysight) can be found under the examples directory at [examples/multivendor/multivendor.pb.txt](https://github.com/openconfig/kne/blob/main/examples/multivendor/multivendor.pb.txt). -The initial vendor router configs referenced in the topology are found -[here](https://github.com/openconfig/kne/tree/main/examples/multivendor) +The initial vendor router configs referenced in the topology are found in the [multivendor example directory](https://github.com/openconfig/kne/tree/main/examples/multivendor). See the [push config](interact_topology.md#push_config) section for details about pushing config after initial creation. diff --git a/docs/interact_topology.md b/docs/interact_topology.md index 90509c241..3c390bb93 100644 --- a/docs/interact_topology.md +++ b/docs/interact_topology.md @@ -66,32 +66,32 @@ $ ssh admin@192.168.11.50 1. Get the IP range used by KNE services: - ```bash - $ kubectl get services -n multivendor - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - service-gnmi-otg-controller LoadBalancer 10.96.179.48 192.168.11.55 50051:30901/TCP 4m9s - service-grpc-otg-controller LoadBalancer 10.96.33.245 192.168.11.56 40051:30449/TCP 4m9s - service-https-otg-controller LoadBalancer 10.96.215.225 192.168.11.54 443:32556/TCP 4m9s - service-otg-port-eth1 LoadBalancer 10.96.82.37 192.168.11.58 5555:30886/TCP,50071:30286/TCP 4m9s - service-otg-port-eth2 LoadBalancer 10.96.204.154 192.168.11.59 5555:31326/TCP,50071:31860/TCP 4m9s - service-otg-port-eth3 LoadBalancer 10.96.136.253 192.168.11.60 5555:30181/TCP,50071:31619/TCP 4m9s - service-otg-port-eth4 LoadBalancer 10.96.205.227 192.168.11.57 5555:32636/TCP,50071:31247/TCP 4m9s - service-r1 LoadBalancer 10.96.130.198 192.168.11.50 443:32101/TCP,22:32304/TCP,6030:32011/TCP 4m12s - service-r2 LoadBalancer 10.96.107.2 192.168.11.51 443:31942/TCP,22:30785/TCP,57400:30921/TCP 4m11s - service-r3 LoadBalancer 10.96.80.18 192.168.11.52 22:32410/TCP 4m11s - service-r4 LoadBalancer 10.96.138.204 192.168.11.53 22:31932/TCP,50051:32666/TCP 4m10s - ``` - - In this case the IP range would be `192.168.11.*`. + ```bash + $ kubectl get services -n multivendor + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + service-gnmi-otg-controller LoadBalancer 10.96.179.48 192.168.11.55 50051:30901/TCP 4m9s + service-grpc-otg-controller LoadBalancer 10.96.33.245 192.168.11.56 40051:30449/TCP 4m9s + service-https-otg-controller LoadBalancer 10.96.215.225 192.168.11.54 443:32556/TCP 4m9s + service-otg-port-eth1 LoadBalancer 10.96.82.37 192.168.11.58 5555:30886/TCP,50071:30286/TCP 4m9s + service-otg-port-eth2 LoadBalancer 10.96.204.154 192.168.11.59 5555:31326/TCP,50071:31860/TCP 4m9s + service-otg-port-eth3 LoadBalancer 10.96.136.253 192.168.11.60 5555:30181/TCP,50071:31619/TCP 4m9s + service-otg-port-eth4 LoadBalancer 10.96.205.227 192.168.11.57 5555:32636/TCP,50071:31247/TCP 4m9s + service-r1 LoadBalancer 10.96.130.198 192.168.11.50 443:32101/TCP,22:32304/TCP,6030:32011/TCP 4m12s + service-r2 LoadBalancer 10.96.107.2 192.168.11.51 443:31942/TCP,22:30785/TCP,57400:30921/TCP 4m11s + service-r3 LoadBalancer 10.96.80.18 192.168.11.52 22:32410/TCP 4m11s + service-r4 LoadBalancer 10.96.138.204 192.168.11.53 22:31932/TCP,50051:32666/TCP 4m10s + ``` + + In this case the IP range would be `192.168.11.*`. 1. Edit your SSH config found at `~/.ssh/config` to include: - ```bash - Host 192.168.11.* - UserKnownHostsFile /dev/null - StrictHostKeyChecking no - ProxyCommand none - ``` + ```bash + Host 192.168.11.* + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + ProxyCommand none + ``` @@ -252,7 +252,7 @@ See the external cptx with services -### Using OpenConfig g* services +### Using OpenConfig g\* services #### Using the CLI diff --git a/docs/kubernetes_reference.md b/docs/kubernetes_reference.md index db36dc85d..d318883a5 100644 --- a/docs/kubernetes_reference.md +++ b/docs/kubernetes_reference.md @@ -74,12 +74,12 @@ cluster creation, but regardless of which is chosen a k8s cluster will be created ready for topology creation. Currently, the most used cluster tool in KNE is **kind**. This tool actually hosts a single node k8s cluster inside of a docker container. The details here are not important for the purpose of this -reference, but if you see the term *kind* then know it may be referring to a +reference, but if you see the term _kind_ then know it may be referring to a tool for creating a k8s cluster. You can also bring your own cluster for use with KNE, this is convenient for users with custom k8s setups. -NOTE: *kind* is also a field in kubeyaml used to specify resource type, you may -see this inside of k8s manifests. However when we refer to *kind*, it's likely +NOTE: _kind_ is also a field in kubeyaml used to specify resource type, you may +see this inside of k8s manifests. However when we refer to _kind_, it's likely the cluster tool. After the cluster is created, several k8s deployments are created to initialize diff --git a/docs/multinode.md b/docs/multinode.md index 7e56ac3f7..dfbf04e98 100644 --- a/docs/multinode.md +++ b/docs/multinode.md @@ -3,8 +3,7 @@ ## Background A k8s cluster is made up of 1 or more nodes. Each node can hold up to 110 pods. -See the official large cluster considerations -[here](https://kubernetes.io/docs/setup/best-practices/cluster-large/). An +See the [official large cluster considerations](https://kubernetes.io/docs/setup/best-practices/cluster-large/). An emulated DUT in KNE brings up 1 pod. An emulated ATE in KNE brings up 1 pod per port. Together with the controller pods and other dependency pods, this in turn restricts a KNE user using kind (a single node cluster) to less than ~100 DUTs + diff --git a/docs/setup.md b/docs/setup.md index 32ad2ad05..ccbe9bc6f 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -7,11 +7,11 @@ to work with your Linux distribution. The following dependencies and required to use KNE: -* Golang -* Docker -* Kubectl -* Kind -* Make +- Golang +- Docker +- Kubectl +- Kind +- Make ## Install Golang diff --git a/docs/vendor.md b/docs/vendor.md index 093a8fe3b..c6346147b 100644 --- a/docs/vendor.md +++ b/docs/vendor.md @@ -1,17 +1,17 @@ # Vendor Image Requirements A Vendor Image is a docker container that can be used with KNE to emulate a -vendor's devices. A Vendor Image might also be a fully virtual device with +vendor's devices. A Vendor Image might also be a fully virtual device with no physical version, such as openconfig/lemming. Without vendor images KNE is just an empty virtual machine rack that does -nothing. Vendor supplied images are what the user of KNE sees and is interested -in. This document describes the requirements and expectations of vendor images +nothing. Vendor supplied images are what the user of KNE sees and is interested +in. This document describes the requirements and expectations of vendor images and the associated code that is included in the KNE repository A vendor image requires a corresponding node implementation in topo/node/vendor -and should have working examples in examples/vendor. A single node -implementation may support multiple vendor images (e.g., cisco-xrd and cisco-8000e). A maintainer is the person or organization that maintains the vendor +and should have working examples in examples/vendor. A single node +implementation may support multiple vendor images (e.g., cisco-xrd and cisco-8000e). A maintainer is the person or organization that maintains the vendor specific node implementation and examples. In this document a vendor is considered the person or organization that makes @@ -20,14 +20,14 @@ image containers available for use by others. ## KNE Uses KNE was built to enable testing the functionality of networks without physical -hardware. Due to the obvious limitations of emulation, KNE is not designed to -test bandwidth and latency of connections. KNE is designed to enable testing of -the control protocols and interaction between devices. There are several +hardware. Due to the obvious limitations of emulation, KNE is not designed to +test bandwidth and latency of connections. KNE is designed to enable testing of +the control protocols and interaction between devices. There are several different types of testing. ### Testing new Topologies -KNE is used to test changes in network topology. Changes in network topology +KNE is used to test changes in network topology. Changes in network topology can impact various protocols use in the network (e.g. BGP). ### Testing Changes in Protocol or Configuration @@ -36,7 +36,7 @@ KNE is used to test protocol changes or other configuration changes. ### Testing Device Functionality -KNE is used to test changes to a device's Network Operating System (NOS). This +KNE is used to test changes to a device's Network Operating System (NOS). This is a crucial step in validating a devices usability for a particular purpose when a new NOS is released. @@ -45,43 +45,43 @@ when a new NOS is released. A network device in KNE can be viewed as two main components, the control plane and the data plane (the ASIC). -KNE is used to test the control plane of the NOS. This requires the control -software in the virtual device behave the same as in the hardware. It is +KNE is used to test the control plane of the NOS. This requires the control +software in the virtual device behave the same as in the hardware. It is expected that the control software used in an image is the same as the software used on the physical device and that it is configured and reacts in the same way as the hardware. -KNE is not designed to test the data plane or ASIC. The emulated data plane -must support routing and packet forwarding. ASIC specific commands and features +KNE is not designed to test the data plane or ASIC. The emulated data plane +must support routing and packet forwarding. ASIC specific commands and features do not need to be supported as long as the data plane provides basic functionality. All of these use cases require that the vendor images to behave functionally as -if it were the hardware. The image is expected to be built from the same source -code base as the NOS used in the hardware. Faithful emulation of the ASIC is -not a requirement. The emulated ASIC (data plane) must correctly handle routing +if it were the hardware. The image is expected to be built from the same source +code base as the NOS used in the hardware. Faithful emulation of the ASIC is +not a requirement. The emulated ASIC (data plane) must correctly handle routing changes and packet forwarding. ### Deviations The vendor should supply a document that describes what series of devices the -image emulates as well as known limits and deviations. These include +image emulates as well as known limits and deviations. These include -* Protocols not supported -* Protocols that deviate from the hardware (and how) -* OpenConfig paths only supported by hardware -* OpenConfig paths that report different results compared to the hardware. -* Known limitations of the emulated device -* Supported port configurations (e.g, number of ports, line cards, etc). +- Protocols not supported +- Protocols that deviate from the hardware (and how) +- OpenConfig paths only supported by hardware +- OpenConfig paths that report different results compared to the hardware. +- Known limitations of the emulated device +- Supported port configurations (e.g, number of ports, line cards, etc). -The listed OpenConfig paths need not be leaf nodes. Wildcards may be used in +The listed OpenConfig paths need not be leaf nodes. Wildcards may be used in the path where applicable. ## Testing -Vendor images must be tested prior to publication. A standard set of tests is -found at . At a minimum, a KNE node using that image should -start and not cause the KNE emulation to hang. It should work in both a single +Vendor images must be tested prior to publication. A standard set of tests is +found at . At a minimum, a KNE node using that image should +start and not cause the KNE emulation to hang. It should work in both a single Kubernetes Worker Node environment as well as a multi-worker node environment. It is expected that released images undergo repeated testing to identify @@ -89,13 +89,13 @@ non-deterministic errors. ## Support -Vendors are responsible for support of their images. The maintainer (typically +Vendors are responsible for support of their images. The maintainer (typically the person or organization that provides the associated container images) is responsible for the support of the vendor image specific node implementation in [kne/topo/node](https://github.com/openconfig/kne/tree/main/topo/node), the vendor specific examples in [kne/examples](https://github.com/openconfig/kne/tree/main/examples), as well as -other vendor software reqiured by the node implementation (e.g., controller or -operator). The maintainer should be responsive to community contributions. In +other vendor software required by the node implementation (e.g., controller or +operator). The maintainer should be responsive to community contributions. In the event the maintainer of a particular node implementation is unresponsive a new maintainer may take over that implementation. diff --git a/examples/ciena/README.md b/examples/ciena/README.md new file mode 100644 index 000000000..520f5fe7a --- /dev/null +++ b/examples/ciena/README.md @@ -0,0 +1,115 @@ +# How to configure Ciena simulators in KNE + +## Interface naming + +- `eth0` - Node management interface + +### For saos + +- `1` - First dataplane interface +- `X` - Subsequent dataplane interfaces will count onwards from 1. For example, the third dataplane interface will be `3` + +### For waverouter + +Waverouter port numbering format is: `//`. In the example below, `1/5/1`, this is housing 1, slot 5, port 1. + +- `1/5/1` - First dataplane interface +- `1/5/X` - Subsequent dataplane interfaces will count onwards from 1. For example, the third dataplane interface will be `1/5/3` + +### Notes + +- You can also use interface aliases of `ethX` (count onwards from 1) for both saos and waverouter +- We only support one waverouter interface box (using the `wr-qbox` type in JSON) at this time + +### [wr13_example.json](./wr13_example.json) + +```json +{ + "WR1": { + "1": { + "type": "wr13", + "7": { + "type": "wr-ctm" + }, + "5": { + "type": "wr-qbox" + } + } + } +} +``` + +## [saos.pbtxt topology](./saos.pbtxt) + +This topology includes 2 saos which has 2 connections, saos-1 1----1 saos-2, saos-1 2----2 saos-2. + +```yaml +name: "saos-example" +nodes: { + name: "saos-1" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +nodes: { + name: "saos-2" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +links: { + a_node: "saos-1" + a_int: "1" + z_node: "saos-2" + z_int: "1" +} +links: { + a_node: "saos-1" + a_int: "2" + z_node: "saos-2" + z_int: "2" +} +``` + +## [waverouter.pbtxt topology](./waverouter.pbtxt) + +This topology includes one saos and one waverouter which has 2 connections, saos-1 1----1/5/1 wr-1, saos-1 2----1/5/2 wr-1. + +- Waverouter model requires an additional file ([wr13_example.json](./wr13_example.json)) to describe the internal system topology. + +```yaml +name: "waverouter-saos-example" +nodes: { + name: "wr-1" + vendor: CIENA + model: "waverouter" + config: { + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/rw-containerlab:latest" container image is used by default + vendor_data { + [type.googleapis.com/ciena.CienaConfig] { + system_equipment: { + equipment_json: "wr13_example.json" + } + } + } + } +} +nodes: { + name: "saos-1" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +links: { + a_node: "saos-1" + a_int: "1" + z_node: "wr-1" + z_int: "1/5/1" +} +links: { + a_node: "saos-1" + a_int: "2" + z_node: "wr-1" + z_int: "1/5/2" +} +``` diff --git a/examples/ciena/saos.pbtxt b/examples/ciena/saos.pbtxt new file mode 100644 index 000000000..961863a62 --- /dev/null +++ b/examples/ciena/saos.pbtxt @@ -0,0 +1,25 @@ +name: "saos-example" +nodes: { + name: "saos-1" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +nodes: { + name: "saos-2" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +links: { + a_node: "saos-1" + a_int: "1" + z_node: "saos-2" + z_int: "1" +} +links: { + a_node: "saos-1" + a_int: "2" + z_node: "saos-2" + z_int: "2" +} \ No newline at end of file diff --git a/examples/ciena/waverouter.pbtxt b/examples/ciena/waverouter.pbtxt new file mode 100644 index 000000000..38c88356f --- /dev/null +++ b/examples/ciena/waverouter.pbtxt @@ -0,0 +1,34 @@ +name: "waverouter-saos-example" +nodes: { + name: "wr-1" + vendor: CIENA + model: "waverouter" + config: { + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/rw-containerlab:latest" container image is used by default + vendor_data { + [type.googleapis.com/ciena.CienaConfig] { + system_equipment: { + equipment_json: "wr13_example.json" + } + } + } + } +} +nodes: { + name: "saos-1" + vendor: CIENA + model: "5132" + # when `image` is not specified under `config`, the "artifactory.ciena.com/psa/saos-containerlab:latest" container image is used by default +} +links: { + a_node: "saos-1" + a_int: "1" + z_node: "wr-1" + z_int: "1/5/1" +} +links: { + a_node: "saos-1" + a_int: "2" + z_node: "wr-1" + z_int: "1/5/2" +} \ No newline at end of file diff --git a/examples/ciena/wr13_example.json b/examples/ciena/wr13_example.json new file mode 100644 index 000000000..2a8cf78cb --- /dev/null +++ b/examples/ciena/wr13_example.json @@ -0,0 +1,13 @@ +{ + "WR1": { + "1": { + "type": "wr13", + "7": { + "type": "wr-ctm" + }, + "5": { + "type": "wr-qbox" + } + } + } +} diff --git a/examples/cisco/8000e/README.md b/examples/cisco/8000e/README.md index 7ea53bd90..3441bf518 100644 --- a/examples/cisco/8000e/README.md +++ b/examples/cisco/8000e/README.md @@ -6,15 +6,15 @@ - Ensure you have a healthy kind cluster. Please refer [setup](../../../docs/setup.md) and [topology](../../../docs/create_topology.md) documents for the detailed instructions. - Verify if nested virtualization is configured correctly by checking presence of /dev/kvm (`ls /dev/kvm`). -- Verify if Open vSwitch is installed by running `ovs-vswitchd --version` and install if it is missing by running `sudo apt-get install openvswitch-switch-dpdk`. -- Set pid_max <= 1048575 using `echo "kernel.pid_max=1048575" >> /etc/sysctl.conf` or `sysctl kernel.pid_max=1048575`. +- Verify if Open vSwitch is installed by running `ovs-vswitchd --version` and install if it is missing by running `sudo apt-get install openvswitch-switch-dpdk`. +- Set pid_max <= 1048575 using `echo "kernel.pid_max=1048575" >> /etc/sysctl.conf` or `sysctl kernel.pid_max=1048575`. - Create a KNE topology using `kne create path/to/8000e-ixia.pb.txt` ## Make sure the topology is healthy - Make sure nodes are up and running by running command `kubectl get pods -A`. The output of the command should contain namespace `cisco-ixia` and 6 nodes with status `Running`. 4 otg ports (`otg-port-*`), one otg controller (`otg-controller`), and one cisco 8000e (`8000e`) are expected to be shown if the topology is created successfully. - -``` bash + +```bash kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE cisco-ixia 8000e 1/1 Running 0 4m28s @@ -23,13 +23,13 @@ cisco-ixia otg-port-eth1 2/2 Run cisco-ixia otg-port-eth2 2/2 Running 0 4m28s cisco-ixia otg-port-eth3 2/2 Running 0 4m28s cisco-ixia otg-port-eth4 2/2 Running 0 4m27s --- omitted -- - +-- omitted -- + ``` -- Make sure external ip are mapped correctly by running command `kubectl get services -n cisco-ixia`. It is expected an external ip is assigned to each of the six nodes mentioned above. Also, the port mapping of the gnmi/gnoi/gribi/p4rt/ssh services for 8000e should match the port mapping in the topology file. - -``` bash +- Make sure external ip are mapped correctly by running command `kubectl get services -n cisco-ixia`. It is expected an external ip is assigned to each of the six nodes mentioned above. Also, the port mapping of the gnmi/gnoi/gribi/p4rt/ssh services for 8000e should match the port mapping in the topology file. + +```bash kubectl get services -n cisco-ixia NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service-8000e LoadBalancer 10.96.195.26 172.18.0.50 22:31633/TCP,9339:31543/TCP,9340:30751/TCP,9337:30331/TCP,9559:31439/TCP 7m45s @@ -40,23 +40,23 @@ service-otg-port-eth1 LoadBalancer 10.96.70.21 172.18.0.54 5555 service-otg-port-eth2 LoadBalancer 10.96.166.2 172.18.0.55 5555:31205/TCP,50071:32376/TCP 7m45s service-otg-port-eth3 LoadBalancer 10.96.108.38 172.18.0.56 5555:32396/TCP,50071:30361/TCP 7m45s service-otg-port-eth4 LoadBalancer 10.96.24.228 172.18.0.57 5555:31664/TCP,50071:30416/TCP 7m44s - ``` +``` ## Check 8000e status - Based on the above output, you may use `ssh cisco@172.18.0.50` with user/pass (`cisco/cisco123`) to access cisco e8000 instance (`e8000`). -``` bash -ssh cisco@172.18.0.50 -Password: +```bash +ssh cisco@172.18.0.50 +Password: Last login: Thu Feb 23 05:03:48 2023 from 10.244.0.1 -RP/0/RP0/CPU0:ios# +RP/0/RP0/CPU0:ios# ``` -- You can also use `kubectl exec -it -n cisco-ixia 8000e -- telnet 0 60000` to get console access to the device. - -``` bash +- You can also use `kubectl exec -it -n cisco-ixia 8000e -- telnet 0 60000` to get console access to the device. + +```bash kubectl exec -it -n cisco-ixia 8000e -- telnet 0 60000 Defaulted container "vxr" out of: vxr, init-vxr (init) Trying 0.0.0.0... @@ -64,23 +64,23 @@ Connected to 0. Escape character is '^]'. RP/0/RP0/CPU0:ios# - ``` +``` **Note:** Depending on the model, it may takes around 6 minutes for the 8000e to be fully up. You may check `startup.log` and `startup.err` using the following steps if the telnet fails: -``` bash -$ kubectl exec -it -n cisco-ixia 8000e -- bash +```bash +$ kubectl exec -it -n cisco-ixia 8000e -- bash Defaulted container "8000e" out of: 8000e, init-8000e (init) root@8000e:/# cd /nobackup/ root@8000e:/nobackup# ls -ltr -rw-r--r-- 1 root root 0 Feb 23 14:14 startup.err -rw-r--r-- 1 root root 4894 Feb 23 14:18 startup.log -root@8000e:/nobackup# +root@8000e:/nobackup# ``` To check if the grpc is configured, you may use `show running-config grpc` after login to the router. By default grpc for 8000e is configured using tls without authentication (`insecure: false & skip_verify: true`). -``` bash +```bash RP/0/RP0/CPU0:ios#show running-config grpc Thu Feb 23 06:04:04.562 UTC grpc @@ -97,7 +97,7 @@ RP/0/RP0/CPU0:ios# ## Test GNMI using external service ip and outside port -``` bash +```bash gnmic -a 172.18.0.50:9339 -u cisco -p cisco123 capabilities --skip-verify gNMI version: 0.8.0 supported encodings: @@ -130,12 +130,12 @@ supported models: ] } ] - + ``` ## Test gNOI service using external service ip and outside port -``` bash +```bash gnoic -a 172.18.0.50:9337 --skip-verify -u cisco -p cisco123 system ping --destination 44.44.44.44 100 bytes from 44.44.44.44: icmp_seq=1 ttl=255 time=3ns 100 bytes from 44.44.44.44: icmp_seq=2 ttl=255 time=1ns @@ -149,11 +149,11 @@ round-trip min/avg/max/stddev = 1.000/1.000/3.000/1.000 ms ## Test gRIBI service using external service ip and outside port -``` bash -gribic -a 172.18.0.50:9340 -u cisco -p cisco --skip-verify flush --ns DEFAULT -INFO[0000] got 1 results +```bash +gribic -a 172.18.0.50:9340 -u cisco -p cisco --skip-verify flush --ns DEFAULT +INFO[0000] got 1 results INFO[0000] "172.18.0.50:9340": timestamp: 1677161921484040943 -result: OK -$ +result: OK +$ ``` diff --git a/examples/juniper/cptx-ixia/README.md b/examples/juniper/cptx-ixia/README.md index df981f7c5..f88719bed 100644 --- a/examples/juniper/cptx-ixia/README.md +++ b/examples/juniper/cptx-ixia/README.md @@ -167,35 +167,39 @@ entry: { - cPTX can be configured in a channelized or non-channelized mode. - cPTX will be started in channelized mode if any of the interfaces in the interface mapping of KNE config are channelized. - cPTX ethernet interfaces to software wire interface mapping (channelized). Follow the `juniper.config` for more info. Here is an example. - ```bash - et-0/0/0:0 (eth4) - et-0/0/0:1 (eth5) - -- snip -- - et-0/0/1:0 (eth12) - et-0/0/1:1 (eth13) - -- snip -- - et-0/0/2:0 (eth20) - -- snip -- - et-0/0/3:0 (eth28) - et-0/0/4:0 (eth36) - et-0/0/5:0 (unused) - et-0/0/6:0 (eth40) - -- snip -- - et-0/0/7:0 (unused) - -- snip -- - et-0/0/11:0 (eth68) - ``` + + ```bash + et-0/0/0:0 (eth4) + et-0/0/0:1 (eth5) + -- snip -- + et-0/0/1:0 (eth12) + et-0/0/1:1 (eth13) + -- snip -- + et-0/0/2:0 (eth20) + -- snip -- + et-0/0/3:0 (eth28) + et-0/0/4:0 (eth36) + et-0/0/5:0 (unused) + et-0/0/6:0 (eth40) + -- snip -- + et-0/0/7:0 (unused) + -- snip -- + et-0/0/11:0 (eth68) + ``` + - cPTX ethernet interfaces to software wire interface mapping (non-channelized). Here is an example. - ```bash - et-0/0/0 (eth4) - et-0/0/1 (eth5) - et-0/0/2 (eth6) - -- snip -- - et-0/0/5 (unused) - et-0/0/6 (eth10) - et-0/0/7 (unused) - et-0/0/8 (eth12) - -- snip -- - et-0/0/11 (eth15) - ``` + + ```bash + et-0/0/0 (eth4) + et-0/0/1 (eth5) + et-0/0/2 (eth6) + -- snip -- + et-0/0/5 (unused) + et-0/0/6 (eth10) + et-0/0/7 (unused) + et-0/0/8 (eth12) + -- snip -- + et-0/0/11 (eth15) + ``` + - Pass gRPC client option `-skip-verify` as only self-signed TLS certificates are configured as of today. diff --git a/proto/ciena.proto b/proto/ciena.proto new file mode 100644 index 000000000..c65673a55 --- /dev/null +++ b/proto/ciena.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ciena; + +option go_package = "github.com/openconfig/kne/proto/ciena"; + +message SystemEquipment { + // Mount point for the files inside the pod. + string mount_dir = 1; + string equipment_json = 2; +} + +// Ciena specific vendor data for KNE +message CienaConfig { + SystemEquipment system_equipment = 1; +} diff --git a/proto/ciena/ciena.pb.go b/proto/ciena/ciena.pb.go new file mode 100644 index 000000000..f680b861a --- /dev/null +++ b/proto/ciena/ciena.pb.go @@ -0,0 +1,222 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v6.31.1 +// source: ciena.proto + +package ciena + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SystemEquipment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Mount point for the files inside the pod. + MountDir string `protobuf:"bytes,1,opt,name=mount_dir,json=mountDir,proto3" json:"mount_dir,omitempty"` + EquipmentJson string `protobuf:"bytes,2,opt,name=equipment_json,json=equipmentJson,proto3" json:"equipment_json,omitempty"` +} + +func (x *SystemEquipment) Reset() { + *x = SystemEquipment{} + if protoimpl.UnsafeEnabled { + mi := &file_ciena_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemEquipment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemEquipment) ProtoMessage() {} + +func (x *SystemEquipment) ProtoReflect() protoreflect.Message { + mi := &file_ciena_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemEquipment.ProtoReflect.Descriptor instead. +func (*SystemEquipment) Descriptor() ([]byte, []int) { + return file_ciena_proto_rawDescGZIP(), []int{0} +} + +func (x *SystemEquipment) GetMountDir() string { + if x != nil { + return x.MountDir + } + return "" +} + +func (x *SystemEquipment) GetEquipmentJson() string { + if x != nil { + return x.EquipmentJson + } + return "" +} + +// Ciena specific vendor data for KNE +type CienaConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SystemEquipment *SystemEquipment `protobuf:"bytes,1,opt,name=system_equipment,json=systemEquipment,proto3" json:"system_equipment,omitempty"` +} + +func (x *CienaConfig) Reset() { + *x = CienaConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_ciena_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CienaConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CienaConfig) ProtoMessage() {} + +func (x *CienaConfig) ProtoReflect() protoreflect.Message { + mi := &file_ciena_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CienaConfig.ProtoReflect.Descriptor instead. +func (*CienaConfig) Descriptor() ([]byte, []int) { + return file_ciena_proto_rawDescGZIP(), []int{1} +} + +func (x *CienaConfig) GetSystemEquipment() *SystemEquipment { + if x != nil { + return x.SystemEquipment + } + return nil +} + +var File_ciena_proto protoreflect.FileDescriptor + +var file_ciena_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x63, 0x69, 0x65, 0x6e, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, + 0x69, 0x65, 0x6e, 0x61, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x45, 0x71, + 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x5f, 0x64, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x44, 0x69, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x71, 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x71, + 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x0b, 0x43, + 0x69, 0x65, 0x6e, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x10, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x69, 0x65, 0x6e, 0x61, 0x2e, 0x53, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x45, 0x71, 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x45, 0x71, 0x75, 0x69, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x27, 0x5a, + 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6b, 0x6e, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x63, 0x69, 0x65, 0x6e, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_ciena_proto_rawDescOnce sync.Once + file_ciena_proto_rawDescData = file_ciena_proto_rawDesc +) + +func file_ciena_proto_rawDescGZIP() []byte { + file_ciena_proto_rawDescOnce.Do(func() { + file_ciena_proto_rawDescData = protoimpl.X.CompressGZIP(file_ciena_proto_rawDescData) + }) + return file_ciena_proto_rawDescData +} + +var file_ciena_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_ciena_proto_goTypes = []interface{}{ + (*SystemEquipment)(nil), // 0: ciena.SystemEquipment + (*CienaConfig)(nil), // 1: ciena.CienaConfig +} +var file_ciena_proto_depIdxs = []int32{ + 0, // 0: ciena.CienaConfig.system_equipment:type_name -> ciena.SystemEquipment + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_ciena_proto_init() } +func file_ciena_proto_init() { + if File_ciena_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_ciena_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SystemEquipment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ciena_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CienaConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_ciena_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_ciena_proto_goTypes, + DependencyIndexes: file_ciena_proto_depIdxs, + MessageInfos: file_ciena_proto_msgTypes, + }.Build() + File_ciena_proto = out.File + file_ciena_proto_rawDesc = nil + file_ciena_proto_goTypes = nil + file_ciena_proto_depIdxs = nil +} diff --git a/proto/generate.go b/proto/generate.go index e47e9d0b1..9899e2116 100644 --- a/proto/generate.go +++ b/proto/generate.go @@ -7,3 +7,4 @@ package proto //go:generate protoc --go_out=./wire --go-grpc_out=./wire --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative ./wire.proto //go:generate protoc --go_out=./forward --go-grpc_out=./forward --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative ./forward.proto //go:generate protoc --go_out=./event --go-grpc_out=./event --go-grpc_opt=paths=source_relative --go_opt=paths=source_relative ./event.proto +//go:generate protoc --go_out=./ciena --go_opt=paths=source_relative ./ciena.proto diff --git a/proto/topo.proto b/proto/topo.proto index 6292c2e6b..4229c8d77 100644 --- a/proto/topo.proto +++ b/proto/topo.proto @@ -46,6 +46,7 @@ enum Vendor { FORWARD = 13; IN_CLUSTER_PROXY = 14; SONIC = 15; + CIENA = 16; } // Node is a single container inside the topology @@ -68,6 +69,7 @@ message Node { CISCO_E8000 = 13; LEMMING = 14; IN_CLUSTER_PROXY = 15; + CIENA_SAOS = 16; } string name = 1; // Name of the node in the topology. Must be unique. Type type = 2 [deprecated = true]; diff --git a/proto/topo/topo.pb.go b/proto/topo/topo.pb.go index e712bbcfa..5db844a55 100644 --- a/proto/topo/topo.pb.go +++ b/proto/topo/topo.pb.go @@ -56,6 +56,7 @@ const ( Vendor_FORWARD Vendor = 13 Vendor_IN_CLUSTER_PROXY Vendor = 14 Vendor_SONIC Vendor = 15 + Vendor_CIENA Vendor = 16 ) // Enum value maps for Vendor. @@ -77,6 +78,7 @@ var ( 13: "FORWARD", 14: "IN_CLUSTER_PROXY", 15: "SONIC", + 16: "CIENA", } Vendor_value = map[string]int32{ "UNKNOWN": 0, @@ -95,6 +97,7 @@ var ( "FORWARD": 13, "IN_CLUSTER_PROXY": 14, "SONIC": 15, + "CIENA": 16, } ) @@ -144,6 +147,7 @@ const ( Node_CISCO_E8000 Node_Type = 13 Node_LEMMING Node_Type = 14 Node_IN_CLUSTER_PROXY Node_Type = 15 + Node_CIENA_SAOS Node_Type = 16 ) // Enum value maps for Node_Type. @@ -165,6 +169,7 @@ var ( 13: "CISCO_E8000", 14: "LEMMING", 15: "IN_CLUSTER_PROXY", + 16: "CIENA_SAOS", } Node_Type_value = map[string]int32{ "UNKNOWN": 0, @@ -183,6 +188,7 @@ var ( "CISCO_E8000": 13, "LEMMING": 14, "IN_CLUSTER_PROXY": 15, + "CIENA_SAOS": 16, } ) @@ -1330,7 +1336,7 @@ var file_topo_proto_rawDesc = []byte{ 0x6f, 0x70, 0x6f, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, - 0x73, 0x22, 0x96, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x73, 0x22, 0xa6, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x02, 0x18, @@ -1380,7 +1386,7 @@ var file_topo_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0xee, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, + 0x02, 0x38, 0x01, 0x22, 0xfe, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x52, 0x49, 0x53, 0x54, 0x41, 0x5f, 0x43, 0x45, 0x4f, 0x53, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4a, 0x55, 0x4e, 0x49, 0x50, 0x45, 0x52, 0x5f, @@ -1395,7 +1401,8 @@ var file_topo_proto_rawDesc = []byte{ 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x49, 0x53, 0x43, 0x4f, 0x5f, 0x45, 0x38, 0x30, 0x30, 0x30, 0x10, 0x0d, 0x12, 0x0b, 0x0a, 0x07, 0x4c, 0x45, 0x4d, 0x4d, 0x49, 0x4e, 0x47, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x50, 0x52, 0x4f, - 0x58, 0x59, 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x60, 0x0a, 0x0e, 0x48, 0x6f, + 0x58, 0x59, 0x10, 0x0f, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x49, 0x45, 0x4e, 0x41, 0x5f, 0x53, 0x41, + 0x4f, 0x53, 0x10, 0x10, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x60, 0x0a, 0x0e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x11, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x6f, 0x70, 0x6f, 0x2e, 0x4b, @@ -1497,7 +1504,7 @@ var file_topo_proto_rawDesc = []byte{ 0x73, 0x69, 0x64, 0x65, 0x49, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2a, 0xd5, 0x01, 0x0a, 0x06, 0x56, 0x65, + 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2a, 0xe0, 0x01, 0x0a, 0x06, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x52, 0x49, 0x53, 0x54, 0x41, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x49, 0x53, 0x43, 0x4f, @@ -1511,10 +1518,10 @@ var file_topo_proto_rawDesc = []byte{ 0x53, 0x10, 0x0c, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x0e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x4f, 0x4e, 0x49, 0x43, 0x10, - 0x0f, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6b, 0x6e, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x6f, 0x70, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x0f, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x49, 0x45, 0x4e, 0x41, 0x10, 0x10, 0x42, 0x26, 0x5a, 0x24, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6b, 0x6e, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x74, 0x6f, 0x70, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/topo/node/ciena/ciena.go b/topo/node/ciena/ciena.go new file mode 100644 index 000000000..84cb56672 --- /dev/null +++ b/topo/node/ciena/ciena.go @@ -0,0 +1,350 @@ +// Copyright 2025 Ciena Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ciena + +import ( + "context" + "fmt" + "os" + "path/filepath" + "regexp" + + cpb "github.com/openconfig/kne/proto/ciena" + tpb "github.com/openconfig/kne/proto/topo" + "github.com/openconfig/kne/topo/node" + "google.golang.org/protobuf/proto" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + log "k8s.io/klog/v2" + "k8s.io/utils/ptr" +) + +const ( + ModelWR = "waverouter" + ModelSAOS = "5132" // Default SAOS model type +) + +var ( + defaultNode = tpb.Node{ + Name: "default_waverouter_node", + Services: map[uint32]*tpb.Service{ + 22: { + Names: []string{"ssh"}, + Inside: 22, + }, + 179: { + Names: []string{"bgp"}, + Inside: 179, + }, + 225: { + Names: []string{"debug"}, + Inside: 225, + }, + 443: { + Names: []string{"ssl"}, + Inside: 443, + }, + 830: { + Names: []string{"netconf"}, + Inside: 830, + }, + 4243: { + Names: []string{"docker-daemon"}, + Inside: 4243, + }, + 9339: { + Names: []string{"gnmi"}, + Inside: 9339, + }, + 9340: { + Names: []string{"gribi"}, + Inside: 9340, + }, + 9559: { + Names: []string{"p4rt"}, + Inside: 9559, + }, + 10161: { + Names: []string{"gnmi-gnoi-alternate"}, + Inside: 10161, + }, + }, + Model: ModelSAOS, + Os: "saos", + Labels: map[string]string{ + "vendor": tpb.Vendor_CIENA.String(), + node.OndatraRoleLabel: node.OndatraRoleDUT, + }, + Config: &tpb.Config{ + ConfigPath: "config/", + ConfigFile: "config.cfg", + Image: "artifactory.ciena.com/psa/saos-containerlab:latest", + }, + } +) + +func New(nodeImpl *node.Impl) (node.Node, error) { + if nodeImpl == nil { + return nil, fmt.Errorf("nodeImpl cannot be nil") + } + if nodeImpl.Proto == nil { + return nil, fmt.Errorf("nodeImpl.Proto cannot be nil") + } + cfg, err := defaults(nodeImpl.Proto) + if err != nil { + return nil, err + } + nodeImpl.Proto = cfg + n := &Node{ + Impl: nodeImpl, + } + n.Proto.Interfaces = renameInterfaces(nodeImpl.Proto.Interfaces) + return n, nil +} + +// renameInterfaces renames the interfaces port naming to ethX format +// e.g., eth1 -> eth1, 1/5/1 -> eth1, 2 -> eth2, etc +func renameInterfaces(interfaces map[string]*tpb.Interface) map[string]*tpb.Interface { + intf := map[string]*tpb.Interface{} + reFull := regexp.MustCompile(`^\d+/\d+/\d+$`) + reSingle := regexp.MustCompile(`^\d+$`) + for k, v := range interfaces { + switch { + case reFull.MatchString(k): + parts := regexp.MustCompile(`/`).Split(k, -1) + lastNum := parts[len(parts)-1] + name := fmt.Sprintf("eth%s", lastNum) + intf[name] = v + case reSingle.MatchString(k): + name := fmt.Sprintf("eth%s", k) + intf[name] = v + default: + intf[k] = v + } + } + return intf +} + +type Node struct { + *node.Impl +} + +func (n *Node) Create(ctx context.Context) error { + if err := n.ValidateConstraints(); err != nil { + return fmt.Errorf("node %s failed to validate node with errors: %w", n.Name(), err) + } + if err := n.CreatePod(ctx); err != nil { + return fmt.Errorf("node %s failed to create pod %w", n.Name(), err) + } + if err := n.CreateService(ctx); err != nil { + return fmt.Errorf("node %s failed to create service %w", n.Name(), err) + } + return nil +} + +// Delete removes the node resources from the cluster. +func (n *Node) Delete(ctx context.Context) error { + cmName := fmt.Sprintf("%s-file", n.Name()) + if err := n.KubeClient.CoreV1().ConfigMaps(n.Namespace).Delete(ctx, cmName, metav1.DeleteOptions{}); err != nil && !errors.IsNotFound(err) { + log.Warningf("Error deleting equipment file configmap %q: %v", cmName, err) + } + return n.Impl.Delete(ctx) +} + +// CreatePod creates a Pod for the Node based on the underlying proto. +func (n *Node) CreatePod(ctx context.Context) error { + pb := n.Proto + log.Infof("Creating Pod:\n %+v", pb) + + var extraVolumes []corev1.Volume + var extraMounts []corev1.VolumeMount + mountPath := "/" + fileName := "setup.json" + + if vendorData := pb.Config.GetVendorData(); vendorData != nil { + cienaConfig := &cpb.CienaConfig{} + + if err := vendorData.UnmarshalTo(cienaConfig); err != nil { + return err + } + + if cienaConfig.GetSystemEquipment().GetMountDir() != "" { + mountPath = cienaConfig.GetSystemEquipment().GetMountDir() + } + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-file", n.Proto.Name), + }, + Data: map[string]string{}, + } + equipmentFile := cienaConfig.GetSystemEquipment().GetEquipmentJson() + + contents, err := os.ReadFile(filepath.Join(n.BasePath, equipmentFile)) + if err != nil { + return err + } + cm.Data[fileName] = string(contents) + extraMounts = append(extraMounts, corev1.VolumeMount{ + Name: "equipment-file", + MountPath: fmt.Sprintf("%s/%s", mountPath, fileName), + SubPath: fileName, + ReadOnly: true, + }) + extraVolumes = append(extraVolumes, corev1.Volume{ + Name: "equipment-file", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: fmt.Sprintf("%s-file", n.Proto.Name), + }, + }}, + }) + + _, err = n.KubeClient.CoreV1().ConfigMaps(n.Namespace).Create(ctx, cm, metav1.CreateOptions{}) + if err != nil { + return err + } + } + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pb.Name, + Labels: map[string]string{ + "app": pb.Name, + "topo": n.Namespace, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: pb.Name, + Image: pb.Config.Image, + Command: pb.Config.Command, + Args: pb.Config.Args, + Env: node.ToEnvVar(pb.Config.Env), + Resources: node.ToResourceRequirements(pb.Constraints), + ImagePullPolicy: corev1.PullIfNotPresent, + SecurityContext: &corev1.SecurityContext{ + Privileged: ptr.To(true), + }, + VolumeMounts: extraMounts, + }}, + Volumes: extraVolumes, + TerminationGracePeriodSeconds: ptr.To[int64](0), + NodeSelector: map[string]string{}, + Affinity: &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{{ + Weight: 100, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "topo", + Operator: "In", + Values: []string{pb.Name}, + }}, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }}, + }, + }, + }, + } + for label, v := range n.GetProto().GetLabels() { + pod.Labels[label] = v + } + if pb.Config.ConfigData != nil { + vol, err := n.CreateConfig(ctx) + if err != nil { + return err + } + pod.Spec.Volumes = append(pod.Spec.Volumes, *vol) + vm := corev1.VolumeMount{ + Name: node.ConfigVolumeName, + MountPath: fmt.Sprintf("%s/%s", pb.Config.ConfigPath, pb.Config.ConfigFile), + ReadOnly: true, + } + if vol.ConfigMap != nil { + vm.SubPath = pb.Config.ConfigFile + } + for i, c := range pod.Spec.Containers { + pod.Spec.Containers[i].VolumeMounts = append(c.VolumeMounts, vm) + } + } + sPod, err := n.KubeClient.CoreV1().Pods(n.Namespace).Create(ctx, pod, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create pod for %q: %w", pb.Name, err) + } + log.V(1).Infof("Pod created:\n%+v\n", sPod) + return nil +} + +func defaults(pb *tpb.Node) (*tpb.Node, error) { + defaultNodeClone := proto.Clone(&defaultNode).(*tpb.Node) + if pb == nil { + pb = &tpb.Node{ + Name: defaultNodeClone.Name, + } + } + if pb.Config == nil { + pb.Config = &tpb.Config{} + } + if pb.Config.ConfigFile == "" { + pb.Config.ConfigFile = defaultNodeClone.Config.ConfigFile + } + if pb.Config.ConfigPath == "" { + pb.Config.ConfigPath = defaultNodeClone.Config.ConfigPath + } + if pb.Model == "" { + pb.Model = defaultNodeClone.Model + } + if pb.Os == "" { + pb.Os = defaultNodeClone.Os + } + if pb.Services == nil { + pb.Services = defaultNodeClone.Services + } + if pb.Labels == nil { + pb.Labels = map[string]string{} + } + if pb.Labels["vendor"] == "" { + pb.Labels["vendor"] = defaultNodeClone.Labels["vendor"] + } + if pb.Labels["model"] == "" { + pb.Labels["model"] = pb.Model + } + if pb.Labels["os"] == "" { + pb.Labels["os"] = pb.Os + } + if pb.Labels[node.OndatraRoleLabel] == "" { + pb.Labels[node.OndatraRoleLabel] = defaultNodeClone.Labels[node.OndatraRoleLabel] + } + if pb.Config.Env == nil { + pb.Config.Env = map[string]string{} + } + pb.Config.Env["CLAB_LABEL_CLAB_NODE_NAME"] = pb.Name + pb.Config.Env["CLAB_LABEL_CLAB_NODE_TYPE"] = pb.Model + + if pb.Model == ModelWR && pb.Config.Image == "" { + pb.Config.Image = "artifactory.ciena.com/psa/rw-containerlab:latest" + } else if pb.Config.Image == "" { + pb.Config.Image = defaultNodeClone.Config.Image + } + return pb, nil +} + +func init() { + node.Vendor(tpb.Vendor_CIENA, New) +} diff --git a/topo/node/ciena/ciena_test.go b/topo/node/ciena/ciena_test.go new file mode 100644 index 000000000..6789e6cfb --- /dev/null +++ b/topo/node/ciena/ciena_test.go @@ -0,0 +1,541 @@ +// Copyright 2025 Ciena Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ciena + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + cpb "github.com/openconfig/kne/proto/ciena" + tpb "github.com/openconfig/kne/proto/topo" + "github.com/openconfig/kne/topo/node" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kfake "k8s.io/client-go/kubernetes/fake" +) + +func TestNew(t *testing.T) { + tests := []struct { + desc string + nImpl *node.Impl + want *tpb.Node + wantErr string + }{ + { + desc: "nil nodeImpl", + nImpl: nil, + wantErr: "nodeImpl cannot be nil", + }, + { + desc: "nil nodeImpl.Proto", + nImpl: &node.Impl{}, + wantErr: "nodeImpl.Proto cannot be nil", + }, + { + desc: "empty proto, expect defaults", + nImpl: &node.Impl{ + Proto: &tpb.Node{}, + }, + want: &tpb.Node{ + // Name: "default_waverouter_node", + Model: "5132", + Os: "saos", + Labels: map[string]string{ + "vendor": tpb.Vendor_CIENA.String(), + "ondatra-role": "DUT", + "model": "5132", + "os": "saos", + }, + Config: &tpb.Config{ + ConfigPath: "config/", + ConfigFile: "config.cfg", + Image: "artifactory.ciena.com/psa/saos-containerlab:latest", + Env: map[string]string{ + "CLAB_LABEL_CLAB_NODE_NAME": "", + "CLAB_LABEL_CLAB_NODE_TYPE": "5132", + }, + }, + Services: map[uint32]*tpb.Service{ + 22: {Names: []string{"ssh"}, Inside: 22}, + 179: {Names: []string{"bgp"}, Inside: 179}, + 225: {Names: []string{"debug"}, Inside: 225}, + 443: {Names: []string{"ssl"}, Inside: 443}, + 830: {Names: []string{"netconf"}, Inside: 830}, + 4243: {Names: []string{"docker-daemon"}, Inside: 4243}, + 9339: {Names: []string{"gnmi"}, Inside: 9339}, + 9340: {Names: []string{"gribi"}, Inside: 9340}, + 9559: {Names: []string{"p4rt"}, Inside: 9559}, + 10161: {Names: []string{"gnmi-gnoi-alternate"}, Inside: 10161}, + }, + Interfaces: map[string]*tpb.Interface{}, + }, + }, + { + desc: "proto with interfaces, expect rename", + nImpl: &node.Impl{ + Proto: &tpb.Node{ + Interfaces: map[string]*tpb.Interface{ + "1/2/3": {IntName: "1/2/3"}, + "5": {IntName: "5"}, + "eth7": {IntName: "eth7"}, + "mgmt": {IntName: "mgmt"}, + }, + }, + }, + want: &tpb.Node{ + // Name: "default_waverouter_node", + Model: "5132", + Os: "saos", + Labels: map[string]string{ + "vendor": tpb.Vendor_CIENA.String(), + "ondatra-role": "DUT", + "model": "5132", + "os": "saos", + }, + Config: &tpb.Config{ + ConfigPath: "config/", + ConfigFile: "config.cfg", + Image: "artifactory.ciena.com/psa/saos-containerlab:latest", + Env: map[string]string{ + "CLAB_LABEL_CLAB_NODE_NAME": "", + "CLAB_LABEL_CLAB_NODE_TYPE": "5132", + }, + }, + Services: map[uint32]*tpb.Service{ + 22: {Names: []string{"ssh"}, Inside: 22}, + 179: {Names: []string{"bgp"}, Inside: 179}, + 225: {Names: []string{"debug"}, Inside: 225}, + 443: {Names: []string{"ssl"}, Inside: 443}, + 830: {Names: []string{"netconf"}, Inside: 830}, + 4243: {Names: []string{"docker-daemon"}, Inside: 4243}, + 9339: {Names: []string{"gnmi"}, Inside: 9339}, + 9340: {Names: []string{"gribi"}, Inside: 9340}, + 9559: {Names: []string{"p4rt"}, Inside: 9559}, + 10161: {Names: []string{"gnmi-gnoi-alternate"}, Inside: 10161}, + }, + Interfaces: map[string]*tpb.Interface{ + "eth3": {IntName: "1/2/3"}, + "eth5": {IntName: "5"}, + "eth7": {IntName: "eth7"}, + "mgmt": {IntName: "mgmt"}, + }, + }, + }, + { + desc: "waverouter model, expect defaults", + nImpl: &node.Impl{ + Proto: &tpb.Node{ + Model: ModelWR, + }, + }, + want: &tpb.Node{ + Model: ModelWR, + Os: "saos", + Labels: map[string]string{ + "vendor": tpb.Vendor_CIENA.String(), + "ondatra-role": "DUT", + "model": ModelWR, + "os": "saos", + }, + Config: &tpb.Config{ + ConfigPath: "config/", + ConfigFile: "config.cfg", + Image: "artifactory.ciena.com/psa/rw-containerlab:latest", + Env: map[string]string{ + "CLAB_LABEL_CLAB_NODE_NAME": "", + "CLAB_LABEL_CLAB_NODE_TYPE": ModelWR, + }, + }, + Services: map[uint32]*tpb.Service{ + 22: {Names: []string{"ssh"}, Inside: 22}, + 179: {Names: []string{"bgp"}, Inside: 179}, + 225: {Names: []string{"debug"}, Inside: 225}, + 443: {Names: []string{"ssl"}, Inside: 443}, + 830: {Names: []string{"netconf"}, Inside: 830}, + 4243: {Names: []string{"docker-daemon"}, Inside: 4243}, + 9339: {Names: []string{"gnmi"}, Inside: 9339}, + 9340: {Names: []string{"gribi"}, Inside: 9340}, + 9559: {Names: []string{"p4rt"}, Inside: 9559}, + 10161: {Names: []string{"gnmi-gnoi-alternate"}, Inside: 10161}, + }, + Interfaces: map[string]*tpb.Interface{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + n, err := New(tt.nImpl) + if tt.wantErr != "" { + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("got err %v, want %v", err, tt.wantErr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + got := n.GetProto() + if !proto.Equal(got, tt.want) { + t.Errorf("proto mismatch (-want +got):\nwant: %v\ngot: %v", tt.want, got) + } + }) + } +} +func TestRenameInterfaces(t *testing.T) { + tests := []struct { + desc string + input map[string]*tpb.Interface + want map[string]*tpb.Interface + }{ + { + desc: "empty interfaces", + input: map[string]*tpb.Interface{}, + want: map[string]*tpb.Interface{}, + }, + { + desc: "single full format", + input: map[string]*tpb.Interface{ + "1/5/2": {IntName: "1/5/2"}, + }, + want: map[string]*tpb.Interface{ + "eth2": {IntName: "1/5/2"}, + }, + }, + { + desc: "single digit format", + input: map[string]*tpb.Interface{ + "3": {IntName: "3"}, + }, + want: map[string]*tpb.Interface{ + "eth3": {IntName: "3"}, + }, + }, + { + desc: "already eth format", + input: map[string]*tpb.Interface{ + "eth1": {IntName: "eth1"}, + }, + want: map[string]*tpb.Interface{ + "eth1": {IntName: "eth1"}, + }, + }, + { + desc: "mgmt interface", + input: map[string]*tpb.Interface{ + "mgmt": {IntName: "mgmt"}, + }, + want: map[string]*tpb.Interface{ + "mgmt": {IntName: "mgmt"}, + }, + }, + { + desc: "multiple mixed formats", + input: map[string]*tpb.Interface{ + "1/2/3": {IntName: "1/2/3"}, + "5": {IntName: "5"}, + "eth7": {IntName: "eth7"}, + "mgmt": {IntName: "mgmt"}, + }, + want: map[string]*tpb.Interface{ + "eth3": {IntName: "1/2/3"}, + "eth5": {IntName: "5"}, + "eth7": {IntName: "eth7"}, + "mgmt": {IntName: "mgmt"}, + }, + }, + { + desc: "non-matching string", + input: map[string]*tpb.Interface{ + "foo": {IntName: "foo"}, + }, + want: map[string]*tpb.Interface{ + "foo": {IntName: "foo"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := renameInterfaces(tt.input) + if diff := cmp.Diff(tt.want, got, cmp.Comparer(proto.Equal)); diff != "" { + t.Errorf("renameInterfaces() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestNode_CreatePod_EquipmentFile(t *testing.T) { + tmpDir := t.TempDir() + equipmentFile := "equipment.json" + equipmentContent := `{"equipment": "test"}` + equipmentPath := filepath.Join(tmpDir, equipmentFile) + if err := os.WriteFile(equipmentPath, []byte(equipmentContent), 0644); err != nil { + t.Fatalf("failed to write equipment file: %v", err) + } + + vendorData, err := anypb.New(&cpb.CienaConfig{ + SystemEquipment: &cpb.SystemEquipment{ + MountDir: "/equipment", + EquipmentJson: equipmentFile, + }, + }) + if err != nil { + t.Fatalf("failed to marshal vendor data: %v", err) + } + + nodeProto := &tpb.Node{ + Name: "wr-1", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + VendorData: vendorData, + }, + } + + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + BasePath: tmpDir, + }, + } + + err = n.CreatePod(context.Background()) + if err != nil { + t.Fatalf("CreatePod() failed: %v", err) + } + + pod, err := n.KubeClient.CoreV1().Pods(n.Namespace).Get(context.Background(), n.Name(), metav1.GetOptions{}) + if err != nil { + t.Fatalf("Could not get the pod: %v", err) + } + + // Check container + if len(pod.Spec.Containers) != 1 { + t.Fatalf("expected 1 container, got %d", len(pod.Spec.Containers)) + } + ctr := pod.Spec.Containers[0] + if ctr.Name != "wr-1" { + t.Errorf("container name: got %q, want %q", ctr.Name, "wr-1") + } + if ctr.Image != "vrnetlab/ciena_waverouter:config" { + t.Errorf("container image: got %q, want %q", ctr.Image, "vrnetlab/ciena_waverouter:config") + } + foundMount := false + for _, m := range ctr.VolumeMounts { + if m.Name == "equipment-file" && m.MountPath == "/equipment/setup.json" && m.SubPath == "setup.json" && m.ReadOnly { + foundMount = true + } + } + if !foundMount { + t.Errorf("expected equipment-file mount not found in container: %+v", ctr.VolumeMounts) + } + + // Check volumes + foundVol := false + for _, v := range pod.Spec.Volumes { + if v.Name == "equipment-file" && v.ConfigMap != nil && v.ConfigMap.Name == "wr-1-file" { + foundVol = true + } + } + if !foundVol { + t.Errorf("expected equipment-file volume not found in pod: %+v", pod.Spec.Volumes) + } +} + +func TestNode_CreatePod_MissingEquipmentFile(t *testing.T) { + tmpDir := t.TempDir() + vendorData, err := anypb.New(&cpb.CienaConfig{ + SystemEquipment: &cpb.SystemEquipment{ + MountDir: "/equipment", + EquipmentJson: "nonexistent.json", + }, + }) + if err != nil { + t.Fatalf("failed to marshal vendor data: %v", err) + } + + nodeProto := &tpb.Node{ + Name: "wr-2", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + VendorData: vendorData, + }, + } + + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + BasePath: tmpDir, + }, + } + + err = n.CreatePod(context.Background()) + if err == nil { + t.Fatalf("expected error due to missing equipment file, got nil") + } +} + +func TestNode_CreatePod_NoVendorData(t *testing.T) { + nodeProto := &tpb.Node{ + Name: "wr-3", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + }, + } + + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + }, + } + + err := n.CreatePod(context.Background()) + if err != nil { + t.Fatalf("CreatePod() failed: %v", err) + } + + pod, err := n.KubeClient.CoreV1().Pods(n.Namespace).Get(context.Background(), n.Name(), metav1.GetOptions{}) + if err != nil { + t.Fatalf("Could not get the pod: %v", err) + } + + if len(pod.Spec.Containers) != 1 { + t.Fatalf("expected 1 container, got %d", len(pod.Spec.Containers)) + } + ctr := pod.Spec.Containers[0] + if ctr.Name != "wr-3" { + t.Errorf("container name: got %q, want %q", ctr.Name, "wr-3") + } + if ctr.Image != "vrnetlab/ciena_waverouter:config" { + t.Errorf("container image: got %q, want %q", ctr.Image, "vrnetlab/ciena_waverouter:config") + } + if len(ctr.VolumeMounts) != 0 { + t.Errorf("expected no volume mounts, got %+v", ctr.VolumeMounts) + } +} + +func TestNode_Create(t *testing.T) { + nodeProto := &tpb.Node{ + Name: "wr-create", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + }, + } + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + }, + } + if err := n.Create(context.Background()); err != nil { + t.Fatalf("Create() failed: %v", err) + } +} + +func TestNode_CreatePod_ConfigData(t *testing.T) { + nodeProto := &tpb.Node{ + Name: "wr-cfg", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + ConfigPath: "/etc/config", + ConfigFile: "startup.cfg", + ConfigData: &tpb.Config_Data{ + Data: []byte("hostname ciena-router"), + }, + }, + } + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + }, + } + if err := n.CreatePod(context.Background()); err != nil { + t.Fatalf("CreatePod() with ConfigData failed: %v", err) + } +} + +func TestNode_Delete(t *testing.T) { + tmpDir := t.TempDir() + equipmentFile := "equipment.json" + equipmentContent := `{"equipment": "test"}` + equipmentPath := filepath.Join(tmpDir, equipmentFile) + if err := os.WriteFile(equipmentPath, []byte(equipmentContent), 0644); err != nil { + t.Fatalf("failed to write equipment file: %v", err) + } + + vendorData, err := anypb.New(&cpb.CienaConfig{ + SystemEquipment: &cpb.SystemEquipment{ + MountDir: "/equipment", + EquipmentJson: equipmentFile, + }, + }) + if err != nil { + t.Fatalf("failed to marshal vendor data: %v", err) + } + + nodeProto := &tpb.Node{ + Name: "wr-del", + Config: &tpb.Config{ + Image: "vrnetlab/ciena_waverouter:config", + VendorData: vendorData, + }, + } + + n := &Node{ + Impl: &node.Impl{ + Namespace: "testns", + KubeClient: kfake.NewSimpleClientset(), + Proto: nodeProto, + BasePath: tmpDir, + }, + } + + if err := n.CreatePod(context.Background()); err != nil { + t.Fatalf("CreatePod() failed: %v", err) + } + + // Verify equipment ConfigMap exists + cmName := "wr-del-file" + if _, err := n.KubeClient.CoreV1().ConfigMaps(n.Namespace).Get(context.Background(), cmName, metav1.GetOptions{}); err != nil { + t.Fatalf("expected ConfigMap %q to exist before delete, got error: %v", cmName, err) + } + + // Delete node + if err := n.Delete(context.Background()); err != nil { + t.Fatalf("Delete() failed: %v", err) + } + + // Verify equipment ConfigMap is deleted + if _, err := n.KubeClient.CoreV1().ConfigMaps(n.Namespace).Get(context.Background(), cmName, metav1.GetOptions{}); err == nil { + t.Errorf("expected ConfigMap %q to be deleted, but it still exists", cmName) + } + + // Delete node without equipment ConfigMap existing (should not error) + if err := n.Delete(context.Background()); err != nil { + t.Errorf("Delete() failed when ConfigMap does not exist: %v", err) + } +} diff --git a/topo/node/inclusterproxy/README.md b/topo/node/inclusterproxy/README.md index 4e030f12c..3c8bc9cbf 100644 --- a/topo/node/inclusterproxy/README.md +++ b/topo/node/inclusterproxy/README.md @@ -12,21 +12,21 @@ The `IN_CLUSTER_PROXY` node type is a specialized KNE node designed to act as a To pass static validation, the node **must** meet the following conditions: -| Parameter | Constraint | -| :--- | :--- | -| **Interfaces** | Exactly one interface named `eth1`. | -| **Links** | `eth1` **must** link directly to the node specified in the `proxy-pool-for` label. | -| **Services** | Exactly one Service mapping must be provided inside `Services` map. | -| **Labels** | The node must have the `proxy-pool-for` label correctly populated. | +| Parameter | Constraint | +| :------------- | :--------------------------------------------------------------------------------- | +| **Interfaces** | Exactly one interface named `eth1`. | +| **Links** | `eth1` **must** link directly to the node specified in the `proxy-pool-for` label. | +| **Services** | Exactly one Service mapping must be provided inside `Services` map. | +| **Labels** | The node must have the `proxy-pool-for` label correctly populated. | ## Configuration Labels -| Label Key | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `proxy-pool-for` | String | **Yes** | Name of the target node this proxy is mediating. | -| `peer-ip` | IP | No (Opt-in) | IP address of the peer (DUT) connected over `eth1` (e.g. `192.168.100.1` or `2001:db8::1`). | -| `peer-prefix` | String | No (Opt-in) | Prefix length of the peer IP (e.g. `31` or `127`). | -| `target-port` | Integer | No (Opt-in) | Port on the peer node to forward proxy streams to. | +| Label Key | Type | Required | Description | +| :--------------- | :------ | :---------- | :------------------------------------------------------------------------------------------ | +| `proxy-pool-for` | String | **Yes** | Name of the target node this proxy is mediating. | +| `peer-ip` | IP | No (Opt-in) | IP address of the peer (DUT) connected over `eth1` (e.g. `192.168.100.1` or `2001:db8::1`). | +| `peer-prefix` | String | No (Opt-in) | Prefix length of the peer IP (e.g. `31` or `127`). | +| `target-port` | Integer | No (Opt-in) | Port on the peer node to forward proxy streams to. | > **Note on Opt-in Automatic Setup**: > If **all three of** `peer-ip`, `peer-prefix`, and `target-port` are provided, the controller will automatically calculate the inverse IP (your side of the `/31` or `/127` link) and generate full commands addressing `socat`. If omitted, users must configure `command` and `args` in `.Config` structures manually. @@ -59,9 +59,9 @@ nodes: { } services: { key: 1790 - value: { - name: "bgp-proxy" - inside: 1790 + value: { + name: "bgp-proxy" + inside: 1790 } } } diff --git a/topo/topo.go b/topo/topo.go index a2c5432df..5553c20e2 100644 --- a/topo/topo.go +++ b/topo/topo.go @@ -53,6 +53,7 @@ import ( _ "github.com/openconfig/kne/topo/node/alpine" _ "github.com/openconfig/kne/topo/node/arista" + _ "github.com/openconfig/kne/topo/node/ciena" _ "github.com/openconfig/kne/topo/node/cisco" _ "github.com/openconfig/kne/topo/node/drivenets" _ "github.com/openconfig/kne/topo/node/forward" diff --git a/x/webhook/README.md b/x/webhook/README.md index 2b82d2bd2..991fc93f4 100644 --- a/x/webhook/README.md +++ b/x/webhook/README.md @@ -79,7 +79,7 @@ default kne-assembly-webhook-f5b8cf987-lpxjt We can now create the KNE topology. -*Note* The KNE topology must have the label `webhook:enabled` for each node, as in +_Note_ The KNE topology must have the label `webhook:enabled` for each node, as in [this example](examples/topology.textproto), otherwise the webhook will ignore the pod upon create. diff --git a/x/webhook/examples/addcontainer/testdata/in.json b/x/webhook/examples/addcontainer/testdata/in.json index 1b573ce20..7030db4ac 100644 --- a/x/webhook/examples/addcontainer/testdata/in.json +++ b/x/webhook/examples/addcontainer/testdata/in.json @@ -1,322 +1,313 @@ { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "creationTimestamp": "2023-11-07T14:19:54Z", - "labels": { - "webhook": "enabled", - "app": "r0", - "model": "ceos", - "ondatra-role": "DUT", - "os": "eos", - "topo": "b2b", - "vendor": "ARISTA", - "version": "" - }, - "name": "r0", - "namespace": "b2b", - "ownerReferences": [ - { - "apiVersion": "ceoslab.arista.com/v1alpha1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "CEosLabDevice", - "name": "r0", - "uid": "7faf439d-befe-4df4-b9f4-42dd35659927" - } - ], - "resourceVersion": "21744", - "uid": "4cc873e1-4166-415e-89a1-9692270e260d" + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "creationTimestamp": "2023-11-07T14:19:54Z", + "labels": { + "webhook": "enabled", + "app": "r0", + "model": "ceos", + "ondatra-role": "DUT", + "os": "eos", + "topo": "b2b", + "vendor": "ARISTA", + "version": "" }, - "spec": { - "containers": [ - { - "args": [ - "systemd.setenv=CEOS=1", - "systemd.setenv=EOS_PLATFORM=ceoslab", - "systemd.setenv=ETBA=1", - "systemd.setenv=INTFTYPE=eth", - "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", - "systemd.setenv=container=docker" - ], - "command": [ - "/sbin/init" - ], - "env": [ - { - "name": "CEOS", - "value": "1" - }, - { - "name": "EOS_PLATFORM", - "value": "ceoslab" - }, - { - "name": "ETBA", - "value": "1" - }, - { - "name": "INTFTYPE", - "value": "eth" - }, - { - "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", - "value": "1" - }, - { - "name": "container", - "value": "docker" - } - ], - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imagePullPolicy": "IfNotPresent", - "name": "ceos", - "resources": { - "requests": { - "cpu": "500m", - "memory": "1Gi" - } - }, - "securityContext": { - "privileged": true - }, - "startupProbe": { - "exec": { - "command": [ - "wfw", - "-t", - "5" - ] - }, - "failureThreshold": 24, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 5 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/mnt/flash/EosIntfMapping.json", - "name": "volume-configmap-intfmapping-r0", - "subPath": "EosIntfMapping.json" - }, - { - "mountPath": "/mnt/flash/rc.eos", - "name": "volume-configmap-rceos-r0", - "subPath": "rc.eos" - }, - { - "mountPath": "/mnt/flash/startup-config", - "name": "volume-r0-config", - "subPath": "startup-config" - }, - { - "mountPath": "/mnt/flash/gnmiCert.pem", - "name": "volume-secret-selfsigned-r0-0", - "subPath": "gnmiCert.pem" - }, - { - "mountPath": "/mnt/flash/gnmiCertKey.pem", - "name": "volume-secret-selfsigned-r0-0", - "subPath": "gnmiCertKey.pem" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-4txwv", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "initContainers": [ - { - "args": [ - "4", - "0" - ], - "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", - "imagePullPolicy": "IfNotPresent", - "name": "init-r0", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-4txwv", - "readOnly": true - } - ] - } + "name": "r0", + "namespace": "b2b", + "ownerReferences": [ + { + "apiVersion": "ceoslab.arista.com/v1alpha1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "CEosLabDevice", + "name": "r0", + "uid": "7faf439d-befe-4df4-b9f4-42dd35659927" + } + ], + "resourceVersion": "21744", + "uid": "4cc873e1-4166-415e-89a1-9692270e260d" + }, + "spec": { + "containers": [ + { + "args": [ + "systemd.setenv=CEOS=1", + "systemd.setenv=EOS_PLATFORM=ceoslab", + "systemd.setenv=ETBA=1", + "systemd.setenv=INTFTYPE=eth", + "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", + "systemd.setenv=container=docker" ], - "nodeName": "kne-control-plane", - "preemptionPolicy": "PreemptLowerPriority", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 0, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } + "command": ["/sbin/init"], + "env": [ + { + "name": "CEOS", + "value": "1" + }, + { + "name": "EOS_PLATFORM", + "value": "ceoslab" + }, + { + "name": "ETBA", + "value": "1" + }, + { + "name": "INTFTYPE", + "value": "eth" + }, + { + "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", + "value": "1" + }, + { + "name": "container", + "value": "docker" + } ], - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "configmap-intfmapping-r0" - }, - "name": "volume-configmap-intfmapping-r0" - }, - { - "configMap": { - "defaultMode": 509, - "name": "configmap-rceos-r0" - }, - "name": "volume-configmap-rceos-r0" - }, - { - "configMap": { - "defaultMode": 420, - "name": "r0-config" - }, - "name": "volume-r0-config" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "secret": { - "defaultMode": 420, - "secretName": "secret-selfsigned-r0-0" - } - }, - { - "name": "kube-api-access-4txwv", - "projected": { - "defaultMode": 420, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - }, - { - "downwardAPI": { - "items": [ - { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - }, - "path": "namespace" - } - ] - } - } - ] - } - } + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imagePullPolicy": "IfNotPresent", + "name": "ceos", + "resources": { + "requests": { + "cpu": "500m", + "memory": "1Gi" + } + }, + "securityContext": { + "privileged": true + }, + "startupProbe": { + "exec": { + "command": ["wfw", "-t", "5"] + }, + "failureThreshold": 24, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/mnt/flash/EosIntfMapping.json", + "name": "volume-configmap-intfmapping-r0", + "subPath": "EosIntfMapping.json" + }, + { + "mountPath": "/mnt/flash/rc.eos", + "name": "volume-configmap-rceos-r0", + "subPath": "rc.eos" + }, + { + "mountPath": "/mnt/flash/startup-config", + "name": "volume-r0-config", + "subPath": "startup-config" + }, + { + "mountPath": "/mnt/flash/gnmiCert.pem", + "name": "volume-secret-selfsigned-r0-0", + "subPath": "gnmiCert.pem" + }, + { + "mountPath": "/mnt/flash/gnmiCertKey.pem", + "name": "volume-secret-selfsigned-r0-0", + "subPath": "gnmiCertKey.pem" + }, + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-4txwv", + "readOnly": true + } ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:03Z", - "status": "True", - "type": "Initialized" - }, + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "initContainers": [ + { + "args": ["4", "0"], + "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", + "imagePullPolicy": "IfNotPresent", + "name": "init-r0", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-4txwv", + "readOnly": true + } + ] + } + ], + "nodeName": "kne-control-plane", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 0, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "configMap": { + "defaultMode": 420, + "name": "configmap-intfmapping-r0" + }, + "name": "volume-configmap-intfmapping-r0" + }, + { + "configMap": { + "defaultMode": 509, + "name": "configmap-rceos-r0" + }, + "name": "volume-configmap-rceos-r0" + }, + { + "configMap": { + "defaultMode": 420, + "name": "r0-config" + }, + "name": "volume-r0-config" + }, + { + "name": "volume-secret-selfsigned-r0-0", + "secret": { + "defaultMode": 420, + "secretName": "secret-selfsigned-r0-0" + } + }, + { + "name": "kube-api-access-4txwv", + "projected": { + "defaultMode": 420, + "sources": [ { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z", - "status": "True", - "type": "Ready" + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } }, { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z", - "status": "True", - "type": "ContainersReady" + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-root-ca.crt" + } }, { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:19:54Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", - "lastState": {}, - "name": "ceos", - "ready": true, - "restartCount": 0, - "started": true, - "state": { - "running": { - "startedAt": "2023-11-07T14:20:03Z" - } - } - } - ], - "hostIP": "192.168.16.2", - "initContainerStatuses": [ - { - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", - "image": "ghcr.io/srl-labs/init-wait:latest", - "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", - "lastState": {}, - "name": "init-r0", - "ready": true, - "restartCount": 0, - "state": { - "terminated": { - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", - "exitCode": 0, - "finishedAt": "2023-11-07T14:20:02Z", - "reason": "Completed", - "startedAt": "2023-11-07T14:19:55Z" - } - } - } - ], - "phase": "Running", - "podIP": "10.244.0.31", - "podIPs": [ - { - "ip": "10.244.0.31" + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } } - ], - "qosClass": "Burstable", - "startTime": "2023-11-07T14:19:54Z" - } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:03Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:19:54Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", + "lastState": {}, + "name": "ceos", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2023-11-07T14:20:03Z" + } + } + } + ], + "hostIP": "192.168.16.2", + "initContainerStatuses": [ + { + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", + "image": "ghcr.io/srl-labs/init-wait:latest", + "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", + "lastState": {}, + "name": "init-r0", + "ready": true, + "restartCount": 0, + "state": { + "terminated": { + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", + "exitCode": 0, + "finishedAt": "2023-11-07T14:20:02Z", + "reason": "Completed", + "startedAt": "2023-11-07T14:19:55Z" + } + } + } + ], + "phase": "Running", + "podIP": "10.244.0.31", + "podIPs": [ + { + "ip": "10.244.0.31" + } + ], + "qosClass": "Burstable", + "startTime": "2023-11-07T14:19:54Z" + } } diff --git a/x/webhook/examples/addcontainer/testdata/in_nomod.json b/x/webhook/examples/addcontainer/testdata/in_nomod.json index fc8c2cb64..0db45e173 100644 --- a/x/webhook/examples/addcontainer/testdata/in_nomod.json +++ b/x/webhook/examples/addcontainer/testdata/in_nomod.json @@ -1,321 +1,312 @@ { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "creationTimestamp": "2023-11-07T14:19:54Z", - "labels": { - "app": "r0", - "model": "ceos", - "ondatra-role": "DUT", - "os": "eos", - "topo": "b2b", - "vendor": "ARISTA", - "version": "" - }, - "name": "r0", - "namespace": "b2b", - "ownerReferences": [ - { - "apiVersion": "ceoslab.arista.com/v1alpha1", - "blockOwnerDeletion": true, - "controller": true, - "kind": "CEosLabDevice", - "name": "r0", - "uid": "7faf439d-befe-4df4-b9f4-42dd35659927" - } - ], - "resourceVersion": "21744", - "uid": "4cc873e1-4166-415e-89a1-9692270e260d" + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "creationTimestamp": "2023-11-07T14:19:54Z", + "labels": { + "app": "r0", + "model": "ceos", + "ondatra-role": "DUT", + "os": "eos", + "topo": "b2b", + "vendor": "ARISTA", + "version": "" }, - "spec": { - "containers": [ - { - "args": [ - "systemd.setenv=CEOS=1", - "systemd.setenv=EOS_PLATFORM=ceoslab", - "systemd.setenv=ETBA=1", - "systemd.setenv=INTFTYPE=eth", - "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", - "systemd.setenv=container=docker" - ], - "command": [ - "/sbin/init" - ], - "env": [ - { - "name": "CEOS", - "value": "1" - }, - { - "name": "EOS_PLATFORM", - "value": "ceoslab" - }, - { - "name": "ETBA", - "value": "1" - }, - { - "name": "INTFTYPE", - "value": "eth" - }, - { - "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", - "value": "1" - }, - { - "name": "container", - "value": "docker" - } - ], - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imagePullPolicy": "IfNotPresent", - "name": "ceos", - "resources": { - "requests": { - "cpu": "500m", - "memory": "1Gi" - } - }, - "securityContext": { - "privileged": true - }, - "startupProbe": { - "exec": { - "command": [ - "wfw", - "-t", - "5" - ] - }, - "failureThreshold": 24, - "periodSeconds": 5, - "successThreshold": 1, - "timeoutSeconds": 5 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/mnt/flash/EosIntfMapping.json", - "name": "volume-configmap-intfmapping-r0", - "subPath": "EosIntfMapping.json" - }, - { - "mountPath": "/mnt/flash/rc.eos", - "name": "volume-configmap-rceos-r0", - "subPath": "rc.eos" - }, - { - "mountPath": "/mnt/flash/startup-config", - "name": "volume-r0-config", - "subPath": "startup-config" - }, - { - "mountPath": "/mnt/flash/gnmiCert.pem", - "name": "volume-secret-selfsigned-r0-0", - "subPath": "gnmiCert.pem" - }, - { - "mountPath": "/mnt/flash/gnmiCertKey.pem", - "name": "volume-secret-selfsigned-r0-0", - "subPath": "gnmiCertKey.pem" - }, - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-4txwv", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "initContainers": [ - { - "args": [ - "4", - "0" - ], - "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", - "imagePullPolicy": "IfNotPresent", - "name": "init-r0", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "kube-api-access-4txwv", - "readOnly": true - } - ] - } + "name": "r0", + "namespace": "b2b", + "ownerReferences": [ + { + "apiVersion": "ceoslab.arista.com/v1alpha1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "CEosLabDevice", + "name": "r0", + "uid": "7faf439d-befe-4df4-b9f4-42dd35659927" + } + ], + "resourceVersion": "21744", + "uid": "4cc873e1-4166-415e-89a1-9692270e260d" + }, + "spec": { + "containers": [ + { + "args": [ + "systemd.setenv=CEOS=1", + "systemd.setenv=EOS_PLATFORM=ceoslab", + "systemd.setenv=ETBA=1", + "systemd.setenv=INTFTYPE=eth", + "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", + "systemd.setenv=container=docker" ], - "nodeName": "kne-control-plane", - "preemptionPolicy": "PreemptLowerPriority", - "priority": 0, - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "default", - "serviceAccountName": "default", - "terminationGracePeriodSeconds": 0, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } + "command": ["/sbin/init"], + "env": [ + { + "name": "CEOS", + "value": "1" + }, + { + "name": "EOS_PLATFORM", + "value": "ceoslab" + }, + { + "name": "ETBA", + "value": "1" + }, + { + "name": "INTFTYPE", + "value": "eth" + }, + { + "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", + "value": "1" + }, + { + "name": "container", + "value": "docker" + } ], - "volumes": [ - { - "configMap": { - "defaultMode": 420, - "name": "configmap-intfmapping-r0" - }, - "name": "volume-configmap-intfmapping-r0" - }, - { - "configMap": { - "defaultMode": 509, - "name": "configmap-rceos-r0" - }, - "name": "volume-configmap-rceos-r0" - }, - { - "configMap": { - "defaultMode": 420, - "name": "r0-config" - }, - "name": "volume-r0-config" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "secret": { - "defaultMode": 420, - "secretName": "secret-selfsigned-r0-0" - } - }, - { - "name": "kube-api-access-4txwv", - "projected": { - "defaultMode": 420, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - }, - { - "downwardAPI": { - "items": [ - { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - }, - "path": "namespace" - } - ] - } - } - ] - } - } + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imagePullPolicy": "IfNotPresent", + "name": "ceos", + "resources": { + "requests": { + "cpu": "500m", + "memory": "1Gi" + } + }, + "securityContext": { + "privileged": true + }, + "startupProbe": { + "exec": { + "command": ["wfw", "-t", "5"] + }, + "failureThreshold": 24, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/mnt/flash/EosIntfMapping.json", + "name": "volume-configmap-intfmapping-r0", + "subPath": "EosIntfMapping.json" + }, + { + "mountPath": "/mnt/flash/rc.eos", + "name": "volume-configmap-rceos-r0", + "subPath": "rc.eos" + }, + { + "mountPath": "/mnt/flash/startup-config", + "name": "volume-r0-config", + "subPath": "startup-config" + }, + { + "mountPath": "/mnt/flash/gnmiCert.pem", + "name": "volume-secret-selfsigned-r0-0", + "subPath": "gnmiCert.pem" + }, + { + "mountPath": "/mnt/flash/gnmiCertKey.pem", + "name": "volume-secret-selfsigned-r0-0", + "subPath": "gnmiCertKey.pem" + }, + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-4txwv", + "readOnly": true + } ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:03Z", - "status": "True", - "type": "Initialized" - }, + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "initContainers": [ + { + "args": ["4", "0"], + "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", + "imagePullPolicy": "IfNotPresent", + "name": "init-r0", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "kube-api-access-4txwv", + "readOnly": true + } + ] + } + ], + "nodeName": "kne-control-plane", + "preemptionPolicy": "PreemptLowerPriority", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 0, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "configMap": { + "defaultMode": 420, + "name": "configmap-intfmapping-r0" + }, + "name": "volume-configmap-intfmapping-r0" + }, + { + "configMap": { + "defaultMode": 509, + "name": "configmap-rceos-r0" + }, + "name": "volume-configmap-rceos-r0" + }, + { + "configMap": { + "defaultMode": 420, + "name": "r0-config" + }, + "name": "volume-r0-config" + }, + { + "name": "volume-secret-selfsigned-r0-0", + "secret": { + "defaultMode": 420, + "secretName": "secret-selfsigned-r0-0" + } + }, + { + "name": "kube-api-access-4txwv", + "projected": { + "defaultMode": 420, + "sources": [ { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z", - "status": "True", - "type": "Ready" + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } }, { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z", - "status": "True", - "type": "ContainersReady" + "configMap": { + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ], + "name": "kube-root-ca.crt" + } }, { - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:19:54Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", - "lastState": {}, - "name": "ceos", - "ready": true, - "restartCount": 0, - "started": true, - "state": { - "running": { - "startedAt": "2023-11-07T14:20:03Z" - } - } - } - ], - "hostIP": "192.168.16.2", - "initContainerStatuses": [ - { - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", - "image": "ghcr.io/srl-labs/init-wait:latest", - "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", - "lastState": {}, - "name": "init-r0", - "ready": true, - "restartCount": 0, - "state": { - "terminated": { - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", - "exitCode": 0, - "finishedAt": "2023-11-07T14:20:02Z", - "reason": "Completed", - "startedAt": "2023-11-07T14:19:55Z" - } - } - } - ], - "phase": "Running", - "podIP": "10.244.0.31", - "podIPs": [ - { - "ip": "10.244.0.31" + "downwardAPI": { + "items": [ + { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + }, + "path": "namespace" + } + ] + } } - ], - "qosClass": "Burstable", - "startTime": "2023-11-07T14:19:54Z" - } + ] + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:03Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:19:54Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", + "lastState": {}, + "name": "ceos", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2023-11-07T14:20:03Z" + } + } + } + ], + "hostIP": "192.168.16.2", + "initContainerStatuses": [ + { + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", + "image": "ghcr.io/srl-labs/init-wait:latest", + "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", + "lastState": {}, + "name": "init-r0", + "ready": true, + "restartCount": 0, + "state": { + "terminated": { + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9", + "exitCode": 0, + "finishedAt": "2023-11-07T14:20:02Z", + "reason": "Completed", + "startedAt": "2023-11-07T14:19:55Z" + } + } + } + ], + "phase": "Running", + "podIP": "10.244.0.31", + "podIPs": [ + { + "ip": "10.244.0.31" + } + ], + "qosClass": "Burstable", + "startTime": "2023-11-07T14:19:54Z" + } } diff --git a/x/webhook/examples/addcontainer/testdata/out.json b/x/webhook/examples/addcontainer/testdata/out.json index de7956e4e..0c699c0e7 100644 --- a/x/webhook/examples/addcontainer/testdata/out.json +++ b/x/webhook/examples/addcontainer/testdata/out.json @@ -2,340 +2,327 @@ "kind": "Pod", "apiVersion": "v1", "metadata": { - "name": "r0", - "namespace": "b2b", - "uid": "4cc873e1-4166-415e-89a1-9692270e260d", - "resourceVersion": "21744", - "creationTimestamp": "2023-11-07T14:19:54Z", - "labels": { - "webhook": "enabled", - "app": "r0", - "model": "ceos", - "ondatra-role": "DUT", - "os": "eos", - "topo": "b2b", - "vendor": "ARISTA", - "version": "" - }, - "ownerReferences": [ - { - "apiVersion": "ceoslab.arista.com/v1alpha1", - "kind": "CEosLabDevice", - "name": "r0", - "uid": "7faf439d-befe-4df4-b9f4-42dd35659927", - "controller": true, - "blockOwnerDeletion": true - } - ] + "name": "r0", + "namespace": "b2b", + "uid": "4cc873e1-4166-415e-89a1-9692270e260d", + "resourceVersion": "21744", + "creationTimestamp": "2023-11-07T14:19:54Z", + "labels": { + "webhook": "enabled", + "app": "r0", + "model": "ceos", + "ondatra-role": "DUT", + "os": "eos", + "topo": "b2b", + "vendor": "ARISTA", + "version": "" + }, + "ownerReferences": [ + { + "apiVersion": "ceoslab.arista.com/v1alpha1", + "kind": "CEosLabDevice", + "name": "r0", + "uid": "7faf439d-befe-4df4-b9f4-42dd35659927", + "controller": true, + "blockOwnerDeletion": true + } + ] }, "spec": { - "volumes": [ - { - "name": "volume-configmap-intfmapping-r0", - "configMap": { - "name": "configmap-intfmapping-r0", - "defaultMode": 420 + "volumes": [ + { + "name": "volume-configmap-intfmapping-r0", + "configMap": { + "name": "configmap-intfmapping-r0", + "defaultMode": 420 + } + }, + { + "name": "volume-configmap-rceos-r0", + "configMap": { + "name": "configmap-rceos-r0", + "defaultMode": 509 + } + }, + { + "name": "volume-r0-config", + "configMap": { + "name": "r0-config", + "defaultMode": 420 + } + }, + { + "name": "volume-secret-selfsigned-r0-0", + "secret": { + "secretName": "secret-selfsigned-r0-0", + "defaultMode": 420 + } + }, + { + "name": "kube-api-access-4txwv", + "projected": { + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" } - }, - { - "name": "volume-configmap-rceos-r0", + }, + { "configMap": { - "name": "configmap-rceos-r0", - "defaultMode": 509 + "name": "kube-root-ca.crt", + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ] } - }, - { - "name": "volume-r0-config", - "configMap": { - "name": "r0-config", - "defaultMode": 420 + }, + { + "downwardAPI": { + "items": [ + { + "path": "namespace", + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } + ] } + } + ], + "defaultMode": 420 + } + } + ], + "initContainers": [ + { + "name": "init-r0", + "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", + "args": ["4", "0"], + "resources": {}, + "volumeMounts": [ + { + "name": "kube-api-access-4txwv", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + } + ], + "containers": [ + { + "name": "ceos", + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "command": ["/sbin/init"], + "args": [ + "systemd.setenv=CEOS=1", + "systemd.setenv=EOS_PLATFORM=ceoslab", + "systemd.setenv=ETBA=1", + "systemd.setenv=INTFTYPE=eth", + "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", + "systemd.setenv=container=docker" + ], + "env": [ + { + "name": "CEOS", + "value": "1" }, { - "name": "volume-secret-selfsigned-r0-0", - "secret": { - "secretName": "secret-selfsigned-r0-0", - "defaultMode": 420 - } + "name": "EOS_PLATFORM", + "value": "ceoslab" }, { - "name": "kube-api-access-4txwv", - "projected": { - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } - }, - { - "configMap": { - "name": "kube-root-ca.crt", - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ] - } - }, - { - "downwardAPI": { - "items": [ - { - "path": "namespace", - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - } - } - ] - } - } - ], - "defaultMode": 420 - } - } - ], - "initContainers": [ + "name": "ETBA", + "value": "1" + }, { - "name": "init-r0", - "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", - "args": [ - "4", - "0" - ], - "resources": {}, - "volumeMounts": [ - { - "name": "kube-api-access-4txwv", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent" - } - ], - "containers": [ + "name": "INTFTYPE", + "value": "eth" + }, { - "name": "ceos", - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "command": [ - "/sbin/init" - ], - "args": [ - "systemd.setenv=CEOS=1", - "systemd.setenv=EOS_PLATFORM=ceoslab", - "systemd.setenv=ETBA=1", - "systemd.setenv=INTFTYPE=eth", - "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", - "systemd.setenv=container=docker" - ], - "env": [ - { - "name": "CEOS", - "value": "1" - }, - { - "name": "EOS_PLATFORM", - "value": "ceoslab" - }, - { - "name": "ETBA", - "value": "1" - }, - { - "name": "INTFTYPE", - "value": "eth" - }, - { - "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", - "value": "1" - }, - { - "name": "container", - "value": "docker" - } - ], - "resources": { - "requests": { - "cpu": "500m", - "memory": "1Gi" - } - }, - "volumeMounts": [ - { - "name": "volume-configmap-intfmapping-r0", - "mountPath": "/mnt/flash/EosIntfMapping.json", - "subPath": "EosIntfMapping.json" - }, - { - "name": "volume-configmap-rceos-r0", - "mountPath": "/mnt/flash/rc.eos", - "subPath": "rc.eos" - }, - { - "name": "volume-r0-config", - "mountPath": "/mnt/flash/startup-config", - "subPath": "startup-config" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "mountPath": "/mnt/flash/gnmiCert.pem", - "subPath": "gnmiCert.pem" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "mountPath": "/mnt/flash/gnmiCertKey.pem", - "subPath": "gnmiCertKey.pem" - }, - { - "name": "kube-api-access-4txwv", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "startupProbe": { - "exec": { - "command": [ - "wfw", - "-t", - "5" - ] - }, - "timeoutSeconds": 5, - "periodSeconds": 5, - "successThreshold": 1, - "failureThreshold": 24 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent", - "securityContext": { - "privileged": true - } + "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", + "value": "1" }, { - "name": "alpine", - "image": "alpine:latest", - "command": [ - "/bin/sh", - "-c", - "sleep 2000000000000" - ], - "resources": { - "requests": { - "cpu": "500m", - "memory": "1Gi" - } - }, - "imagePullPolicy": "IfNotPresent", - "securityContext": { - "privileged": true - } + "name": "container", + "value": "docker" + } + ], + "resources": { + "requests": { + "cpu": "500m", + "memory": "1Gi" } - ], - "restartPolicy": "Always", - "terminationGracePeriodSeconds": 0, - "dnsPolicy": "ClusterFirst", - "serviceAccountName": "default", - "serviceAccount": "default", - "nodeName": "kne-control-plane", - "securityContext": {}, - "schedulerName": "default-scheduler", - "tolerations": [ + }, + "volumeMounts": [ { - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "effect": "NoExecute", - "tolerationSeconds": 300 + "name": "volume-configmap-intfmapping-r0", + "mountPath": "/mnt/flash/EosIntfMapping.json", + "subPath": "EosIntfMapping.json" }, { - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "effect": "NoExecute", - "tolerationSeconds": 300 - } - ], - "priority": 0, - "enableServiceLinks": true, - "preemptionPolicy": "PreemptLowerPriority" - }, - "status": { - "phase": "Running", - "conditions": [ + "name": "volume-configmap-rceos-r0", + "mountPath": "/mnt/flash/rc.eos", + "subPath": "rc.eos" + }, { - "type": "Initialized", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:03Z" + "name": "volume-r0-config", + "mountPath": "/mnt/flash/startup-config", + "subPath": "startup-config" }, { - "type": "Ready", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z" + "name": "volume-secret-selfsigned-r0-0", + "mountPath": "/mnt/flash/gnmiCert.pem", + "subPath": "gnmiCert.pem" }, { - "type": "ContainersReady", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z" + "name": "volume-secret-selfsigned-r0-0", + "mountPath": "/mnt/flash/gnmiCertKey.pem", + "subPath": "gnmiCertKey.pem" }, { - "type": "PodScheduled", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:19:54Z" + "name": "kube-api-access-4txwv", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" } - ], - "hostIP": "192.168.16.2", - "podIP": "10.244.0.31", - "podIPs": [ - { - "ip": "10.244.0.31" + ], + "startupProbe": { + "exec": { + "command": ["wfw", "-t", "5"] + }, + "timeoutSeconds": 5, + "periodSeconds": 5, + "successThreshold": 1, + "failureThreshold": 24 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "privileged": true + } + }, + { + "name": "alpine", + "image": "alpine:latest", + "command": ["/bin/sh", "-c", "sleep 2000000000000"], + "resources": { + "requests": { + "cpu": "500m", + "memory": "1Gi" } - ], - "startTime": "2023-11-07T14:19:54Z", - "initContainerStatuses": [ - { - "name": "init-r0", - "state": { - "terminated": { - "exitCode": 0, - "reason": "Completed", - "startedAt": "2023-11-07T14:19:55Z", - "finishedAt": "2023-11-07T14:20:02Z", - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" - } - }, - "lastState": {}, - "ready": true, - "restartCount": 0, - "image": "ghcr.io/srl-labs/init-wait:latest", - "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" + }, + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "privileged": true + } + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 0, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "default", + "serviceAccount": "default", + "nodeName": "kne-control-plane", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + } + ], + "priority": 0, + "enableServiceLinks": true, + "preemptionPolicy": "PreemptLowerPriority" + }, + "status": { + "phase": "Running", + "conditions": [ + { + "type": "Initialized", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:03Z" + }, + { + "type": "Ready", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z" + }, + { + "type": "ContainersReady", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z" + }, + { + "type": "PodScheduled", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:19:54Z" + } + ], + "hostIP": "192.168.16.2", + "podIP": "10.244.0.31", + "podIPs": [ + { + "ip": "10.244.0.31" + } + ], + "startTime": "2023-11-07T14:19:54Z", + "initContainerStatuses": [ + { + "name": "init-r0", + "state": { + "terminated": { + "exitCode": 0, + "reason": "Completed", + "startedAt": "2023-11-07T14:19:55Z", + "finishedAt": "2023-11-07T14:20:02Z", + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" } - ], - "containerStatuses": [ - { - "name": "ceos", - "state": { - "running": { - "startedAt": "2023-11-07T14:20:03Z" - } - }, - "lastState": {}, - "ready": true, - "restartCount": 0, - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", - "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", - "started": true + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "ghcr.io/srl-labs/init-wait:latest", + "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" + } + ], + "containerStatuses": [ + { + "name": "ceos", + "state": { + "running": { + "startedAt": "2023-11-07T14:20:03Z" } - ], - "qosClass": "Burstable" + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", + "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", + "started": true + } + ], + "qosClass": "Burstable" } } diff --git a/x/webhook/examples/addcontainer/testdata/out_nomod.json b/x/webhook/examples/addcontainer/testdata/out_nomod.json index e6d84cfa8..db8308a6d 100644 --- a/x/webhook/examples/addcontainer/testdata/out_nomod.json +++ b/x/webhook/examples/addcontainer/testdata/out_nomod.json @@ -2,320 +2,311 @@ "kind": "Pod", "apiVersion": "v1", "metadata": { - "name": "r0", - "namespace": "b2b", - "uid": "4cc873e1-4166-415e-89a1-9692270e260d", - "resourceVersion": "21744", - "creationTimestamp": "2023-11-07T14:19:54Z", - "labels": { - "app": "r0", - "model": "ceos", - "ondatra-role": "DUT", - "os": "eos", - "topo": "b2b", - "vendor": "ARISTA", - "version": "" - }, - "ownerReferences": [ - { - "apiVersion": "ceoslab.arista.com/v1alpha1", - "kind": "CEosLabDevice", - "name": "r0", - "uid": "7faf439d-befe-4df4-b9f4-42dd35659927", - "controller": true, - "blockOwnerDeletion": true - } - ] + "name": "r0", + "namespace": "b2b", + "uid": "4cc873e1-4166-415e-89a1-9692270e260d", + "resourceVersion": "21744", + "creationTimestamp": "2023-11-07T14:19:54Z", + "labels": { + "app": "r0", + "model": "ceos", + "ondatra-role": "DUT", + "os": "eos", + "topo": "b2b", + "vendor": "ARISTA", + "version": "" + }, + "ownerReferences": [ + { + "apiVersion": "ceoslab.arista.com/v1alpha1", + "kind": "CEosLabDevice", + "name": "r0", + "uid": "7faf439d-befe-4df4-b9f4-42dd35659927", + "controller": true, + "blockOwnerDeletion": true + } + ] }, "spec": { - "volumes": [ - { - "name": "volume-configmap-intfmapping-r0", + "volumes": [ + { + "name": "volume-configmap-intfmapping-r0", + "configMap": { + "name": "configmap-intfmapping-r0", + "defaultMode": 420 + } + }, + { + "name": "volume-configmap-rceos-r0", + "configMap": { + "name": "configmap-rceos-r0", + "defaultMode": 509 + } + }, + { + "name": "volume-r0-config", + "configMap": { + "name": "r0-config", + "defaultMode": 420 + } + }, + { + "name": "volume-secret-selfsigned-r0-0", + "secret": { + "secretName": "secret-selfsigned-r0-0", + "defaultMode": 420 + } + }, + { + "name": "kube-api-access-4txwv", + "projected": { + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { "configMap": { - "name": "configmap-intfmapping-r0", - "defaultMode": 420 + "name": "kube-root-ca.crt", + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ] + } + }, + { + "downwardAPI": { + "items": [ + { + "path": "namespace", + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } + ] } + } + ], + "defaultMode": 420 + } + } + ], + "initContainers": [ + { + "name": "init-r0", + "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", + "args": ["4", "0"], + "resources": {}, + "volumeMounts": [ + { + "name": "kube-api-access-4txwv", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + } + ], + "containers": [ + { + "name": "ceos", + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "command": ["/sbin/init"], + "args": [ + "systemd.setenv=CEOS=1", + "systemd.setenv=EOS_PLATFORM=ceoslab", + "systemd.setenv=ETBA=1", + "systemd.setenv=INTFTYPE=eth", + "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", + "systemd.setenv=container=docker" + ], + "env": [ + { + "name": "CEOS", + "value": "1" }, { - "name": "volume-configmap-rceos-r0", - "configMap": { - "name": "configmap-rceos-r0", - "defaultMode": 509 - } + "name": "EOS_PLATFORM", + "value": "ceoslab" }, { - "name": "volume-r0-config", - "configMap": { - "name": "r0-config", - "defaultMode": 420 - } + "name": "ETBA", + "value": "1" }, { - "name": "volume-secret-selfsigned-r0-0", - "secret": { - "secretName": "secret-selfsigned-r0-0", - "defaultMode": 420 - } + "name": "INTFTYPE", + "value": "eth" }, { - "name": "kube-api-access-4txwv", - "projected": { - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 3607, - "path": "token" - } - }, - { - "configMap": { - "name": "kube-root-ca.crt", - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ] - } - }, - { - "downwardAPI": { - "items": [ - { - "path": "namespace", - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" - } - } - ] - } - } - ], - "defaultMode": 420 - } - } - ], - "initContainers": [ + "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", + "value": "1" + }, { - "name": "init-r0", - "image": "us-west1-docker.pkg.dev/gep-kne/kne/networkop/init-wait:ga", - "args": [ - "4", - "0" - ], - "resources": {}, - "volumeMounts": [ - { - "name": "kube-api-access-4txwv", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent" + "name": "container", + "value": "docker" } - ], - "containers": [ - { - "name": "ceos", - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "command": [ - "/sbin/init" - ], - "args": [ - "systemd.setenv=CEOS=1", - "systemd.setenv=EOS_PLATFORM=ceoslab", - "systemd.setenv=ETBA=1", - "systemd.setenv=INTFTYPE=eth", - "systemd.setenv=SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT=1", - "systemd.setenv=container=docker" - ], - "env": [ - { - "name": "CEOS", - "value": "1" - }, - { - "name": "EOS_PLATFORM", - "value": "ceoslab" - }, - { - "name": "ETBA", - "value": "1" - }, - { - "name": "INTFTYPE", - "value": "eth" - }, - { - "name": "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT", - "value": "1" - }, - { - "name": "container", - "value": "docker" - } - ], - "resources": { - "requests": { - "cpu": "500m", - "memory": "1Gi" - } - }, - "volumeMounts": [ - { - "name": "volume-configmap-intfmapping-r0", - "mountPath": "/mnt/flash/EosIntfMapping.json", - "subPath": "EosIntfMapping.json" - }, - { - "name": "volume-configmap-rceos-r0", - "mountPath": "/mnt/flash/rc.eos", - "subPath": "rc.eos" - }, - { - "name": "volume-r0-config", - "mountPath": "/mnt/flash/startup-config", - "subPath": "startup-config" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "mountPath": "/mnt/flash/gnmiCert.pem", - "subPath": "gnmiCert.pem" - }, - { - "name": "volume-secret-selfsigned-r0-0", - "mountPath": "/mnt/flash/gnmiCertKey.pem", - "subPath": "gnmiCertKey.pem" - }, - { - "name": "kube-api-access-4txwv", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "startupProbe": { - "exec": { - "command": [ - "wfw", - "-t", - "5" - ] - }, - "timeoutSeconds": 5, - "periodSeconds": 5, - "successThreshold": 1, - "failureThreshold": 24 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent", - "securityContext": { - "privileged": true - } + ], + "resources": { + "requests": { + "cpu": "500m", + "memory": "1Gi" } - ], - "restartPolicy": "Always", - "terminationGracePeriodSeconds": 0, - "dnsPolicy": "ClusterFirst", - "serviceAccountName": "default", - "serviceAccount": "default", - "nodeName": "kne-control-plane", - "securityContext": {}, - "schedulerName": "default-scheduler", - "tolerations": [ + }, + "volumeMounts": [ { - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "effect": "NoExecute", - "tolerationSeconds": 300 + "name": "volume-configmap-intfmapping-r0", + "mountPath": "/mnt/flash/EosIntfMapping.json", + "subPath": "EosIntfMapping.json" }, { - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "effect": "NoExecute", - "tolerationSeconds": 300 - } - ], - "priority": 0, - "enableServiceLinks": true, - "preemptionPolicy": "PreemptLowerPriority" - }, - "status": { - "phase": "Running", - "conditions": [ - { - "type": "Initialized", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:03Z" + "name": "volume-configmap-rceos-r0", + "mountPath": "/mnt/flash/rc.eos", + "subPath": "rc.eos" }, { - "type": "Ready", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z" + "name": "volume-r0-config", + "mountPath": "/mnt/flash/startup-config", + "subPath": "startup-config" }, { - "type": "ContainersReady", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:20:10Z" + "name": "volume-secret-selfsigned-r0-0", + "mountPath": "/mnt/flash/gnmiCert.pem", + "subPath": "gnmiCert.pem" }, { - "type": "PodScheduled", - "status": "True", - "lastProbeTime": null, - "lastTransitionTime": "2023-11-07T14:19:54Z" - } - ], - "hostIP": "192.168.16.2", - "podIP": "10.244.0.31", - "podIPs": [ + "name": "volume-secret-selfsigned-r0-0", + "mountPath": "/mnt/flash/gnmiCertKey.pem", + "subPath": "gnmiCertKey.pem" + }, { - "ip": "10.244.0.31" + "name": "kube-api-access-4txwv", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" } - ], - "startTime": "2023-11-07T14:19:54Z", - "initContainerStatuses": [ - { - "name": "init-r0", - "state": { - "terminated": { - "exitCode": 0, - "reason": "Completed", - "startedAt": "2023-11-07T14:19:55Z", - "finishedAt": "2023-11-07T14:20:02Z", - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" - } - }, - "lastState": {}, - "ready": true, - "restartCount": 0, - "image": "ghcr.io/srl-labs/init-wait:latest", - "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", - "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" + ], + "startupProbe": { + "exec": { + "command": ["wfw", "-t", "5"] + }, + "timeoutSeconds": 5, + "periodSeconds": 5, + "successThreshold": 1, + "failureThreshold": 24 + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "privileged": true + } + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 0, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "default", + "serviceAccount": "default", + "nodeName": "kne-control-plane", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + } + ], + "priority": 0, + "enableServiceLinks": true, + "preemptionPolicy": "PreemptLowerPriority" + }, + "status": { + "phase": "Running", + "conditions": [ + { + "type": "Initialized", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:03Z" + }, + { + "type": "Ready", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z" + }, + { + "type": "ContainersReady", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:20:10Z" + }, + { + "type": "PodScheduled", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2023-11-07T14:19:54Z" + } + ], + "hostIP": "192.168.16.2", + "podIP": "10.244.0.31", + "podIPs": [ + { + "ip": "10.244.0.31" + } + ], + "startTime": "2023-11-07T14:19:54Z", + "initContainerStatuses": [ + { + "name": "init-r0", + "state": { + "terminated": { + "exitCode": 0, + "reason": "Completed", + "startedAt": "2023-11-07T14:19:55Z", + "finishedAt": "2023-11-07T14:20:02Z", + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" } - ], - "containerStatuses": [ - { - "name": "ceos", - "state": { - "running": { - "startedAt": "2023-11-07T14:20:03Z" - } - }, - "lastState": {}, - "ready": true, - "restartCount": 0, - "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", - "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", - "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", - "started": true + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "ghcr.io/srl-labs/init-wait:latest", + "imageID": "docker.io/library/import-2023-11-07@sha256:6826c6c65984870f2c56cda40ff2adf5830b36d8c45738129807e1fd523059a5", + "containerID": "containerd://68119d6b9c97273d7a5f4b9ddc8d9bc2a09f447685ccfec616f5220c95fce7d9" + } + ], + "containerStatuses": [ + { + "name": "ceos", + "state": { + "running": { + "startedAt": "2023-11-07T14:20:03Z" } - ], - "qosClass": "Burstable" + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "us-west1-docker.pkg.dev/gep-kne/ceos:ga", + "imageID": "docker.io/library/import-2023-11-07@sha256:c778a7bcd90456754797022daf173e157e92b7cc30310638f2c00953db61ad08", + "containerID": "containerd://0905e616570eb080cf572a5f632f90abd6fc115234e2059551e8c9338cedbe45", + "started": true + } + ], + "qosClass": "Burstable" } }