From 76616a02e20c0e080f6f32b88dbb5a38f371b446 Mon Sep 17 00:00:00 2001 From: Leonardo Di Giovanna Date: Tue, 21 Jun 2022 13:55:34 +0200 Subject: [PATCH] Add pcn-k8sdispatcher service Signed-off-by: Leonardo Di Giovanna --- AUTHORS | 1 + .../services/pcn-k8sdispatcher/egress.png | Bin 0 -> 54042 bytes .../services/pcn-k8sdispatcher/ingress.png | Bin 0 -> 63865 bytes .../pcn-k8sdispatcher/k8sdispatcher.md | 40 + scripts/install.sh | 6 +- src/services/CMakeLists.txt | 1 + .../pcn-k8sdispatcher/.swagger-codegen-ignore | 13 + src/services/pcn-k8sdispatcher/CMakeLists.txt | 5 + .../datamodel/k8sdispatcher.yang | 173 +++ .../pcn-k8sdispatcher/src/CMakeLists.txt | 50 + .../pcn-k8sdispatcher/src/HashTuple.h | 42 + .../src/K8sdispatcher-lib.cpp | 21 + .../pcn-k8sdispatcher/src/K8sdispatcher.cpp | 497 +++++++ .../pcn-k8sdispatcher/src/K8sdispatcher.h | 139 ++ .../pcn-k8sdispatcher/src/K8sdispatcher_dp.c | 521 +++++++ .../pcn-k8sdispatcher/src/NodeportRule.cpp | 99 ++ .../pcn-k8sdispatcher/src/NodeportRule.h | 59 + src/services/pcn-k8sdispatcher/src/Ports.cpp | 57 + src/services/pcn-k8sdispatcher/src/Ports.h | 45 + .../pcn-k8sdispatcher/src/SessionRule.cpp | 106 ++ .../pcn-k8sdispatcher/src/SessionRule.h | 111 ++ src/services/pcn-k8sdispatcher/src/Utils.cpp | 129 ++ src/services/pcn-k8sdispatcher/src/Utils.h | 48 + .../src/api/K8sdispatcherApi.cpp | 1271 +++++++++++++++++ .../src/api/K8sdispatcherApi.h | 86 ++ .../src/api/K8sdispatcherApiImpl.cpp | 855 +++++++++++ .../src/api/K8sdispatcherApiImpl.h | 90 ++ .../src/base/K8sdispatcherBase.cpp | 171 +++ .../src/base/K8sdispatcherBase.h | 91 ++ .../src/base/NodeportRuleBase.cpp | 42 + .../src/base/NodeportRuleBase.h | 65 + .../pcn-k8sdispatcher/src/base/PortsBase.cpp | 41 + .../pcn-k8sdispatcher/src/base/PortsBase.h | 59 + .../src/base/SessionRuleBase.cpp | 45 + .../src/base/SessionRuleBase.h | 94 ++ .../src/serializer/JsonObjectBase.cpp | 69 + .../src/serializer/JsonObjectBase.h | 55 + .../serializer/K8sdispatcherJsonObject.cpp | 239 ++++ .../src/serializer/K8sdispatcherJsonObject.h | 108 ++ .../src/serializer/NodeportRuleJsonObject.cpp | 186 +++ .../src/serializer/NodeportRuleJsonObject.h | 96 ++ .../src/serializer/PortsJsonObject.cpp | 136 ++ .../src/serializer/PortsJsonObject.h | 79 + .../src/serializer/SessionRuleJsonObject.cpp | 375 +++++ .../src/serializer/SessionRuleJsonObject.h | 162 +++ src/services/pcn-k8sdispatcher/test/test.sh | 24 + 46 files changed, 6600 insertions(+), 2 deletions(-) create mode 100644 Documentation/services/pcn-k8sdispatcher/egress.png create mode 100644 Documentation/services/pcn-k8sdispatcher/ingress.png create mode 100644 Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md create mode 100644 src/services/pcn-k8sdispatcher/.swagger-codegen-ignore create mode 100644 src/services/pcn-k8sdispatcher/CMakeLists.txt create mode 100644 src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang create mode 100644 src/services/pcn-k8sdispatcher/src/CMakeLists.txt create mode 100644 src/services/pcn-k8sdispatcher/src/HashTuple.h create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher.h create mode 100644 src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c create mode 100644 src/services/pcn-k8sdispatcher/src/NodeportRule.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/NodeportRule.h create mode 100644 src/services/pcn-k8sdispatcher/src/Ports.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/Ports.h create mode 100644 src/services/pcn-k8sdispatcher/src/SessionRule.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/SessionRule.h create mode 100644 src/services/pcn-k8sdispatcher/src/Utils.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/Utils.h create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/PortsBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp create mode 100644 src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h create mode 100755 src/services/pcn-k8sdispatcher/test/test.sh diff --git a/AUTHORS b/AUTHORS index dd5f4889b..b5b9ad058 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ off on commits in the Polycube repository: Gianluca Scopelliti gianlu.1033@gmail.com Giuseppe Ognibebe ognibenegiuseppe8@gmail.com Jianwen Pi jianwpi@gmail.com + Leonardo Di Giovanna leonardodigiovanna1@gmail.com Matteo Bertrone m.bertrone@gmail.com Mauricio Vásquez Bernal mauriciovasquezbernal@gmail.com Nico Caprioli nico.caprioli@gmail.com diff --git a/Documentation/services/pcn-k8sdispatcher/egress.png b/Documentation/services/pcn-k8sdispatcher/egress.png new file mode 100644 index 0000000000000000000000000000000000000000..761c89309528c10a7a097637b45026192f1621bd GIT binary patch literal 54042 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 literal 0 HcmV?d00001 diff --git a/Documentation/services/pcn-k8sdispatcher/ingress.png b/Documentation/services/pcn-k8sdispatcher/ingress.png new file mode 100644 index 0000000000000000000000000000000000000000..59aa9b2732a9ac2887626ae72c7c08ccc89e15ee GIT binary patch literal 63865 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ð9rB4GP^WOA-m39^_jNh7b$|`3^_;o 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 W@%XqWM(pn*1%70r3!xkzaP}`2yj)uV literal 0 HcmV?d00001 diff --git a/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md b/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md new file mode 100644 index 000000000..2e8f50af1 --- /dev/null +++ b/Documentation/services/pcn-k8sdispatcher/k8sdispatcher.md @@ -0,0 +1,40 @@ +# K8dispatcher + +The ``pcn-k8sdispatcher`` service is specifically designed as part of our Kubernetes networking solution (please see [polykube](https://github.com/polycube-network/polykube) to get more information about it). The service provides an eBPF implementation of a custom NAT: it performs different actions depending on the type and on the direction of the traffic. + +For Egress Traffic, the following flow chart can be used to explain the functioning of the service: + +![K8sdispatcher egress flow chart](egress.png) + +The Egress Traffic is the traffic generated by Pods and directed to the external world. +This traffic can be generated by an internal Pod that wants to contact the external world or as a response to an +external world request. For this traffic, the service maintains an egress session table containing information +about the active egress sessions. The first time a Pod wants to contact the external world, no active egress session +will be present in the table: in this scenario, the service performs SNAT, replacing the address of the Pod +with the address of the node, and creates entries in the ingress and egress session table accordingly. +If the outgoing traffic is generated as a response to an external request, it can only be originated as a response to +a request made to a NodePort Service. For traffic related to NodePort Services with a CLUSTER ExternalTrafficPolicy, +if an egress session table hit happens, the destination IP address and port are replaced accordingly to the session data. +The traffic related to NodePort Services with a LOCAL ExternalTrafficPolicy is forwarded as it is to the next cube. + +For Ingress Traffic, the following flow chart can be used to explain the functioning of the service: + +![K8sdispatcher ingress flow chart](ingress.png) + +The Ingress Traffic can be differentiated in traffic directed to the host (either directly or because it needs VxLAN +processing) and traffic directed to Pods. The traffic directed to Pods can be the traffic generated by an external host +trying to contact a NodePort service or the return traffic generated by an external host providing a response to an +internal Pod request. The service uses an ingress session table containing all the active ingress sessions. +If a session table hit happens, the service apply NAT according to the session data. If no session table entry is +associated with the incoming packet, the service tries to determine if a NodePort rule matches the packet +characteristics. In case of no NodePort rule matching, the packet is sent to the Linux stack for further processing. +In case of NodePort rule matching, different actions are applied according to the ExternalTrafficPolicy of the +Kubernetes NodePort Service associated to the rule. If the policy is LOCAL, the traffic is allowed to reach only +backend Pods located on the current node: in this case the packet can proceed towards the Pod without modifications. +In case the policy is CLUSTER, the packet can also reach backend Pods located on other nodes: since later in +the chain the packet will be processed by a load balancer and the return packet will have to transit through +the same load balancer, SNAT is applied by replacing the source IP address with a specific reserved address belonging +to the Pod CIDR of the node on which the k8sdispatcher is deployed. In this way the two nodes (the one that +receives the request and the one running the selected backend Pod) will exchange the packets of the flow over +the VxLAN interconnect. In this latter case, corresponding session entries are stored into the ingress and egress +sessions tables. \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index d3ade0dce..5275260f7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -125,7 +125,8 @@ if [ "$MODE" == "pcn-iptables" ]; then -DENABLE_SERVICE_SIMPLEFORWARDER=OFF \ -DENABLE_SERVICE_TRANSPARENTHELLOWORLD=OFF \ -DENABLE_SERVICE_SYNFLOOD=OFF \ - -DENABLE_SERVICE_PACKETCAPTURE=OFF + -DENABLE_SERVICE_PACKETCAPTURE=OFF \ + -DENABLE_SERVICE_K8SDISPATCHER=OFF elif [ "$MODE" == "pcn-k8s" ]; then cmake .. -DENABLE_SERVICE_BRIDGE=OFF \ -DENABLE_SERVICE_DDOSMITIGATOR=ON \ @@ -143,7 +144,8 @@ elif [ "$MODE" == "pcn-k8s" ]; then -DENABLE_SERVICE_SIMPLEFORWARDER=OFF \ -DENABLE_SERVICE_TRANSPARENTHELLOWORLD=OFF \ -DENABLE_SERVICE_SYNFLOOD=OFF \ - -DENABLE_SERVICE_PACKETCAPTURE=ON + -DENABLE_SERVICE_PACKETCAPTURE=ON \ + -DENABLE_SERVICE_K8SDISPATCHER=OFF else cmake .. -DENABLE_PCN_IPTABLES=ON fi diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 55ca7e4ef..5f450cd5c 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -37,6 +37,7 @@ add_service(transparenthelloworld pcn-transparent-helloworld) add_service(synflood pcn-synflood) add_service(packetcapture pcn-packetcapture) add_service(dynmon pcn-dynmon) +add_service(k8sdispatcher pcn-k8sdispatcher) # save string to create code that load the services SET_PROPERTY(GLOBAL PROPERTY LOAD_SERVICES_ ${LOAD_SERVICES}) diff --git a/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore b/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore new file mode 100644 index 000000000..1bc8b75ce --- /dev/null +++ b/src/services/pcn-k8sdispatcher/.swagger-codegen-ignore @@ -0,0 +1,13 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. + +.swagger-codegen-ignore + +src/*.cpp +src/*.h + +!src/*Interface.h +!src/*JsonObject.h +!src/*JsonObject.cpp diff --git a/src/services/pcn-k8sdispatcher/CMakeLists.txt b/src/services/pcn-k8sdispatcher/CMakeLists.txt new file mode 100644 index 000000000..9857094f7 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required (VERSION 3.2) + +set (CMAKE_CXX_STANDARD 11) + +add_subdirectory(src) diff --git a/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang b/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang new file mode 100644 index 000000000..ae36d590d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/datamodel/k8sdispatcher.yang @@ -0,0 +1,173 @@ +module k8sdispatcher { + yang-version 1.1; + namespace "http://polycube.network/k8sdispatcher"; + prefix "k8sdispatcher"; + + import polycube-base { prefix "polycube-base"; } + import polycube-standard-base { prefix "polycube-standard-base"; } + + import ietf-inet-types { prefix "inet"; } + + organization "Polycube open source project"; + description "YANG data model for the Polycube K8s Dispatcher"; + + polycube-base:service-description "K8s Dispatcher Service"; + polycube-base:service-version "2.0.0"; + polycube-base:service-name "k8sdispatcher"; + polycube-base:service-min-kernel-version "4.14.0"; + + typedef l4-proto { + type enumeration { + enum "TCP" { + value 6; + description "The TCP protocol type"; + } + enum "UDP" { + value 17; + description "The UDP protocol type"; + } + enum "ICMP" { + value 1; + description "The ICMP protocol type"; + } + } + description "L4 protocol"; + } + + uses "polycube-standard-base:standard-base-yang-module" { + augment ports { + leaf type { + type enumeration { + enum BACKEND { description "Port connected to the internal CNI topology"; } + enum FRONTEND { description "Port connected to the node NIC"; } + } + description "Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND)"; + mandatory true; + polycube-base:init-only-config; + } + leaf ip { + type inet:ipv4-address; + description "IP address of the node interface (only for FRONTEND port)"; + polycube-base:cli-example "10.10.1.1"; + polycube-base:init-only-config; + } + } + } + + leaf internal-src-ip { + type inet:ipv4-address; + description "Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy"; + mandatory true; + polycube-base:cli-example "10.10.1.1"; + polycube-base:init-only-config; + } + + leaf nodeport-range { + type string; + description "Port range used for NodePort Services"; + default "30000-32767"; + polycube-base:cli-example "30000-32767"; + } + + list session-rule { + key "direction src-ip dst-ip src-port dst-port proto"; + description "Session entry related to a specific traffic direction"; + config false; + + leaf direction { + type enumeration { + enum INGRESS { + description "Direction of traffic going from the internal topology to the external world"; + } + enum EGRESS { + description "Direction of traffic going from the external world to the internal CNI topology"; + } + } + description "Session entry direction (e.g. INGRESS or EGRESS)"; + } + leaf src-ip { + type inet:ipv4-address; + description "Session entry source IP address"; + } + leaf dst-ip { + type inet:ipv4-address; + description "Session entry destination IP address"; + } + leaf src-port { + type inet:port-number; + description "Session entry source L4 port number"; + } + leaf dst-port { + type inet:port-number; + description "Session entry destination L4 port number"; + } + leaf proto { + type l4-proto; + description "Session entry L4 protocol"; + polycube-base:cli-example "TCP, UDP, ICMP"; + } + + leaf new-ip { + type inet:ipv4-address; + description "Translated IP address"; + config false; + } + leaf new-port { + type inet:port-number; + description "Translated L4 port number"; + config false; + } + leaf operation { + type enumeration { + enum XLATE_SRC { description "The source IP and port are replaced"; } + enum XLATE_DST { description "The destination IP and port are replaced"; } + } + description "Operation applied on the original packet"; + config false; + } + leaf originating-rule { + type enumeration { + enum POD_TO_EXT { + description "Traffic related to communication between a Pod and the external world"; + } + enum NODEPORT_CLUSTER { + description "Traffic related to communication involving a NodePort Service with having a CLUSTER external traffic policy"; + } + } + description "Rule originating the session entry"; + config false; + } + } + + list nodeport-rule { + key "nodeport-port proto"; + description "NodePort rule associated with a Kubernetes NodePort Service"; + + leaf nodeport-port { + type inet:port-number; + description "NodePort rule nodeport port number"; + polycube-base:cli-example "30500"; + } + leaf proto { + type l4-proto; + description "NodePort rule L4 protocol"; + polycube-base:cli-example "TCP, UDP, ICMP"; + } + + leaf external-traffic-policy { + type enumeration { + enum LOCAL { description "Incoming traffic is allowed to be served only by local backends"; } + enum CLUSTER { description "Incoming traffic is allowed to be served by any backend of the cluster"; } + } + default CLUSTER; + description "The external traffic policy of the Kubernetes NodePort Service"; + } + leaf rule-name { + type string; + description "An optional name for the NodePort rule"; + polycube-base:cli-example "my-nodeport-rule"; + polycube-base:init-only-config; + } + } +} + diff --git a/src/services/pcn-k8sdispatcher/src/CMakeLists.txt b/src/services/pcn-k8sdispatcher/src/CMakeLists.txt new file mode 100644 index 000000000..7acb5ccfb --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/CMakeLists.txt @@ -0,0 +1,50 @@ +include(${PROJECT_SOURCE_DIR}/cmake/LoadFileAsVariable.cmake) + +aux_source_directory(serializer SERIALIZER_SOURCES) +aux_source_directory(api API_SOURCES) +aux_source_directory(base BASE_SOURCES) + +include_directories(serializer) + +if (NOT DEFINED POLYCUBE_STANDALONE_SERVICE OR POLYCUBE_STANDALONE_SERVICE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(POLYCUBE libpolycube) + include_directories(${POLYCUBE_INCLUDE_DIRS}) +endif (NOT DEFINED POLYCUBE_STANDALONE_SERVICE OR POLYCUBE_STANDALONE_SERVICE) + +# Needed to load files as variables +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_library(pcn-k8sdispatcher SHARED + ${SERIALIZER_SOURCES} + ${API_SOURCES} + ${BASE_SOURCES} + K8sdispatcher.cpp + NodeportRule.cpp + Ports.cpp + SessionRule.cpp + K8sdispatcher-lib.cpp + Utils.cpp) + +# load ebpf datapath code a variable +load_file_as_variable(pcn-k8sdispatcher + K8sdispatcher_dp.c + k8sdispatcher_code) + +# load datamodel in a variable +load_file_as_variable(pcn-k8sdispatcher + ../datamodel/k8sdispatcher.yang + k8sdispatcher_datamodel) + +target_link_libraries(pcn-k8sdispatcher ${POLYCUBE_LIBRARIES}) + +# Specify shared library install directory + +set(CMAKE_INSTALL_LIBDIR /usr/lib) + +install( + TARGETS + pcn-k8sdispatcher + DESTINATION + "${CMAKE_INSTALL_LIBDIR}" +) diff --git a/src/services/pcn-k8sdispatcher/src/HashTuple.h b/src/services/pcn-k8sdispatcher/src/HashTuple.h new file mode 100644 index 000000000..e72cb2897 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/HashTuple.h @@ -0,0 +1,42 @@ +#include +// function has to live in the std namespace +// so that it is picked up by argument-dependent name lookup (ADL). +namespace std { +namespace { +// Code from boost +// Reciprocal of the golden ratio helps spread entropy +// and handles duplicates. +// See Mike Seymour in magic-numbers-in-boosthash-combine: +// https://stackoverflow.com/questions/4948780 + +template +inline void hash_combine(std::size_t &seed, T const &v) { + seed ^= hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +// Recursive template code derived from Matthieu M. +template ::value - 1> +struct HashValueImpl { + static void apply(size_t &seed, Tuple const &tuple) { + HashValueImpl::apply(seed, tuple); + hash_combine(seed, get(tuple)); + } +}; + +template +struct HashValueImpl { + static void apply(size_t &seed, Tuple const &tuple) { + hash_combine(seed, get<0>(tuple)); + } +}; +} + +template +struct hash> { + size_t operator()(std::tuple const &tt) const { + size_t seed = 0; + HashValueImpl>::apply(seed, tt); + return seed; + } +}; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp b/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp new file mode 100644 index 000000000..8c9bc73b6 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher-lib.cpp @@ -0,0 +1,21 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +#include "api/K8sdispatcherApiImpl.h" +#include "../datamodel/k8sdispatcher.h" // generated from datamodel + +#define SERVICE_PYANG_GIT "" +#define SERVICE_SWAGGER_CODEGEN_GIT "GIT_REPO_ID" + +#include + +extern "C" const char *data_model() { + return k8sdispatcher_datamodel.c_str(); +} diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp new file mode 100644 index 000000000..3bd8c588b --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.cpp @@ -0,0 +1,497 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include "K8sdispatcher.h" + +#include +#include + +#include "K8sdispatcher_dp.h" +#include "Utils.h" + +using namespace Tins; +namespace poly_utils = polycube::service::utils; + +const std::string K8sdispatcher::EBPF_EGRESS_SESSION_TABLE = + "egress_session_table"; +const std::string K8sdispatcher::EBPF_INGRESS_SESSION_TABLE = + "ingress_session_table"; +const std::string K8sdispatcher::EBPF_NPR_TABLE_MAP = "npr_table"; + +K8sdispatcher::K8sdispatcher(const std::string name, + const K8sdispatcherJsonObject &conf) + : Cube(conf.getBase(), {k8sdispatcher_code}, {}), + K8sdispatcherBase(name), + internalSrcIp_{conf.getInternalSrcIp()}, + internalSrcIpNboInt_{poly_utils::ip_string_to_nbo_uint(internalSrcIp_)}, + nodeportRange_{"30000-32767"}, + nodeportRangeTuple_{30000, 32767}, + nodeIpNboInt_{0} { + logger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [K8sDispatcher] [%n] [%l] %v"); + logger()->info("Creating K8sDispatcher instance"); + + if (conf.nodeportRangeIsSet()) { + this->setNodeportRange(conf.getNodeportRange()); + } + this->addNodeportRuleList(conf.getNodeportRule()); + this->addPortsList(conf.getPorts()); + + logger()->trace("Created K8sDispatcher instance"); +} + +K8sdispatcher::~K8sdispatcher() { + logger()->info("Destroying K8sDispatcher instance"); +} + +void K8sdispatcher::packet_in(Ports &port, + polycube::service::PacketInMetadata &md, + const std::vector &packet) { + try { + switch (static_cast(md.reason)) { + case SlowPathReason::ARP_REPLY: { + EthernetII pkt(&packet[0], packet.size()); + auto backendPort = this->getBackendPort(); + if (backendPort != nullptr) { + backendPort->send_packet_out(pkt); + } + break; + } + default: { + logger()->error("Not valid slow path reason {} received", md.reason); + } + } + } catch (const std::exception &e) { + logger()->error("Exception during slow path packet processing: {}", + e.what()); + } +} + +std::shared_ptr K8sdispatcher::getPorts(const std::string &name) { + return this->get_port(name); +} + +std::vector> K8sdispatcher::getPortsList() { + return this->get_ports(); +} + +void K8sdispatcher::addPorts(const std::string &name, + const PortsJsonObject &conf) { + try { + PortsTypeEnum type = conf.getType(); + + // consistency check + if (get_ports().size() == 2) { + throw std::runtime_error("Reached maximum number of ports"); + } + if (type == PortsTypeEnum::FRONTEND) { + if (this->getFrontendPort() != nullptr) { + throw std::runtime_error("There is already a FRONTEND port"); + } + } else { + if (this->getBackendPort() != nullptr) { + throw std::runtime_error("There is already a BACKEND port"); + } + } + + add_port(name, conf); + + // save node IP if port_type == FRONTEND + if (type == PortsTypeEnum::FRONTEND) { + this->nodeIpNboInt_ = poly_utils::ip_string_to_nbo_uint(conf.getIp()); + } + // reload service dataplane if the configuration is complete + if (get_ports().size() == 2) { + this->reloadConfig(); + } + } catch (std::runtime_error &ex) { + logger()->error("Failed to add port {0}: {1}", name, ex.what()); + throw std::runtime_error("Failed to add port"); + } +} + +void K8sdispatcher::addPortsList(const std::vector &conf) { + for (auto &i : conf) { + std::string name_ = i.getName(); + this->addPorts(name_, i); + } +} + +void K8sdispatcher::replacePorts(const std::string &name, + const PortsJsonObject &conf) { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delPorts(const std::string &name) { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delPortsList() { + logger()->error("K8sdispatcher::delPorts: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +std::string K8sdispatcher::getInternalSrcIp() { + return this->internalSrcIp_; +} + +std::string K8sdispatcher::getNodeportRange() { + return this->nodeportRange_; +} + +void K8sdispatcher::setNodeportRange(const std::string &value) { + // Return immediately if the provided NodePort port range is the same of the + // already configured one + if (this->nodeportRange_ == value) { + return; + } + + // Parsing lower and upper port numbers + uint16_t low; + uint16_t high; + int res = std::sscanf(value.c_str(), "%hu-%hu", &low, &high); + if (res != 2) { + logger()->error("Failed to parse {} NodePort port range", value); + throw std::runtime_error("Failed to parse NodePort port range"); + } + + if (low >= high) { + logger()->error("Invalid {} NodePort port range", value); + throw std::runtime_error("Invalid NodePort port range"); + } + + try { + auto npr_table = get_hash_table( + K8sdispatcher::EBPF_NPR_TABLE_MAP); + + for (auto const &rule : this->getNodeportRuleList()) { + uint16_t port = rule->getNodeportPort(); + if (port < low || port > high) { + L4ProtoEnum proto = rule->getProto(); + nt_k npr_key{.port = htons(port), + .proto = utils::L4ProtoEnum_to_int(proto)}; + npr_table.remove(npr_key); + + if (this->nodePortRuleMap_.erase(NodeportKey(port, proto)) != 1) { + std::runtime_error{"Failed to delete NodePort rule from user map"}; + } + } + } + } catch (std::runtime_error &ex) { + logger()->error("Failed to flush out-of-range NodePort rules: {}", + ex.what()); + throw std::runtime_error("Failed to flush out-of-range NodePort rules"); + } + + this->nodeportRangeTuple_ = std::make_pair(low, high); + this->nodeportRange_ = value; +} + +std::shared_ptr K8sdispatcher::getSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto) { + try { + auto table = get_hash_table( + direction == SessionRuleDirectionEnum::EGRESS + ? K8sdispatcher::EBPF_EGRESS_SESSION_TABLE + : K8sdispatcher::EBPF_INGRESS_SESSION_TABLE); + st_k map_key{.src_ip = poly_utils::ip_string_to_nbo_uint(srcIp), + .dst_ip = poly_utils::ip_string_to_nbo_uint(dstIp), + .src_port = htons(srcPort), + .dst_port = htons(dstPort), + .proto = utils::L4ProtoEnum_to_int(proto)}; + + st_v value = table.get(map_key); + + std::string newIp = poly_utils::nbo_uint_to_ip_string(value.new_ip); + uint16_t newPort = ntohs(value.new_port); + SessionRuleOperationEnum operation = + utils::int_to_SessionRuleOperationEnum(value.operation); + SessionRuleOriginatingRuleEnum originatingRule = + utils::int_to_SessionRuleOriginatingRuleEnum(value.originating_rule); + + return std::make_shared(*this, direction, srcIp, dstIp, + srcPort, dstPort, proto, newIp, + newPort, operation, originatingRule); + } catch (std::exception &ex) { + logger()->error("Failed to get session rule: {}", ex.what()); + throw std::runtime_error("Failed to get session rule"); + } +} + +std::vector> K8sdispatcher::getSessionRuleList() { + std::vector> rules; + std::vector tableNames{ + K8sdispatcher::EBPF_EGRESS_SESSION_TABLE, + K8sdispatcher::EBPF_INGRESS_SESSION_TABLE}; + try { + for (auto &tableName : tableNames) { + auto table = get_hash_table(tableName); + auto direction = tableName == K8sdispatcher::EBPF_INGRESS_SESSION_TABLE + ? SessionRuleDirectionEnum::INGRESS + : SessionRuleDirectionEnum::EGRESS; + for (auto &entry : table.get_all()) { + auto key = entry.first; + auto value = entry.second; + + auto rule = std::make_shared( + *this, direction, poly_utils::nbo_uint_to_ip_string(key.src_ip), + poly_utils::nbo_uint_to_ip_string(key.dst_ip), ntohs(key.src_port), + ntohs(key.dst_port), utils::int_to_L4ProtoEnum(key.proto), + poly_utils::nbo_uint_to_ip_string(value.new_ip), + ntohs(value.new_port), + utils::int_to_SessionRuleOperationEnum(value.operation), + utils::int_to_SessionRuleOriginatingRuleEnum( + value.originating_rule)); + + rules.push_back(rule); + } + } + } catch (std::exception &ex) { + logger()->error("Failed to get session rules list: {0}", ex.what()); + throw std::runtime_error("Failed to get session rules list"); + } + return rules; +} + +void K8sdispatcher::addSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + logger()->error("K8sdispatcher::addSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::addSessionRuleList( + const std::vector &conf) { + logger()->error("K8sdispatcher::addSessionRuleList: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::replaceSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + logger()->error("K8sdispatcher::replaceSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, + const std::string &dstIp, + const uint16_t &srcPort, + const uint16_t &dstPort, + const L4ProtoEnum &proto) { + logger()->error("K8sdispatcher::delSessionRule: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +void K8sdispatcher::delSessionRuleList() { + logger()->error("K8sdispatcher::delSessionRuleList: Method not allowed"); + throw std::runtime_error{"Method not allowed"}; +} + +std::shared_ptr K8sdispatcher::getNodeportRule( + const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + NodeportKey key = NodeportKey(nodeportPort, proto); + auto it = nodePortRuleMap_.find(key); + if (it == nodePortRuleMap_.end()) { + logger()->info("No NodePort rule associated with key ({0}, {1})", + nodeportPort, + NodeportRuleJsonObject::L4ProtoEnum_to_string(proto)); + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + return it->second; +} + +std::vector> +K8sdispatcher::getNodeportRuleList() { + std::vector> rules; + for (auto &entry : nodePortRuleMap_) { + rules.push_back(entry.second); + } + return rules; +} + +void K8sdispatcher::addNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) { + logger()->trace("Received a request to add a NodePort rule"); + NodeportKey key = NodeportKey(nodeportPort, proto); + + if (nodeportPort < this->nodeportRangeTuple_.first || + nodeportPort > this->nodeportRangeTuple_.second) { + logger()->error( + "The NodePort rule port is not contained in the valid range"); + throw std::runtime_error( + "The NodePort rule port is not contained in the valid range"); + } + + if (this->nodePortRuleMap_.count(key)) { + logger()->error("The NodePort rule already exists"); + throw std::runtime_error("The NodePort rule already exists"); + } + + auto rule = std::make_shared(*this, conf); + if (!nodePortRuleMap_.insert(std::make_pair(key, rule)).second) { + logger()->error("Failed to add the NodePort rule"); + throw std::runtime_error("Failed to add the NodePort rule"); + } + + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = + get_hash_table(K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + nt_k npr_key{ + .port = htons(nodeportPort), + .proto = utils::L4ProtoEnum_to_int(proto), + }; + nt_v npr_value{.external_traffic_policy = + utils::NodeportRuleExternalTrafficPolicyEnum_to_int( + conf.getExternalTrafficPolicy())}; + + logger()->trace("Storing NodePort rule in NodePort rules kernel map"); + npr_table.set(npr_key, npr_value); + logger()->trace("Stored NodePort rule in NodePort rules kernel map"); + } catch (std::exception &ex) { + logger()->warn("Failed to store NodePort rule in kernel map: {}", + ex.what()); + if (this->nodePortRuleMap_.erase(key) != 1) { + logger()->error("Broken user space NodePort rule map"); + }; + throw std::runtime_error("Failed to store NodePort rule in kernel map"); + } + logger()->info("Added NodePort rule"); +} + +void K8sdispatcher::addNodeportRuleList( + const std::vector &conf) { + for (auto &i : conf) { + this->addNodeportRule(i.getNodeportPort(), i.getProto(), i); + } +} + +void K8sdispatcher::updateNodeportRuleList( + const std::vector &conf) { + logger()->trace("Received request for NodePort rules updating"); + try { + for (auto &i : conf) { + auto nodeportRule = getNodeportRule(i.getNodeportPort(), i.getProto()); + nodeportRule->update(i); + } + } catch (std::exception &ex) { + logger()->error("Failed update NodePort rule: {}", ex.what()); + throw std::runtime_error("Failed to update NodePort rule"); + } + logger()->info("Updated NodePort rules"); +} + +void K8sdispatcher::replaceNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) { + this->delNodeportRule(nodeportPort, proto); + this->addNodeportRule(nodeportPort, proto, conf); +} + +void K8sdispatcher::replaceNodeportRuleList( + const std::vector &conf) { + this->delNodeportRuleList(); + this->addNodeportRuleList(conf); +} + +void K8sdispatcher::delNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto) { + logger()->trace("Received a request to delete a NodePort rule"); + NodeportKey key = NodeportKey(nodeportPort, proto); + + if (!this->nodePortRuleMap_.count(key)) { + logger()->info("No NodePort rule associated with key ({0}, {1})", + nodeportPort, + NodeportRuleJsonObject::L4ProtoEnum_to_string(proto)); + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = + get_hash_table(K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + nt_k npr_key{.port = htons(nodeportPort), + .proto = utils::L4ProtoEnum_to_int(proto)}; + + npr_table.remove(npr_key); + + if (this->nodePortRuleMap_.erase(key) == 0) { + std::runtime_error{"No NodePort rule associated with the provided key"}; + } + } catch (std::exception &ex) { + logger()->error("Failed to delete NodePort rule: {}", ex.what()); + throw std::runtime_error("Failed to delete NodePort rule"); + } + logger()->info("Deleted NodePort rule"); +} + +void K8sdispatcher::delNodeportRuleList() { + for (auto &it : nodePortRuleMap_) { + NodeportKey key = it.first; + this->delNodeportRule(std::get<0>(key), std::get<1>(key)); + } +} + +void K8sdispatcher::reloadConfig() { + std::string flags; + + uint16_t frontend = 0; + uint16_t backend = 0; + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::FRONTEND) + frontend = it->index(); + else + backend = it->index(); + } + flags += "#define FRONTEND_PORT " + std::to_string(frontend) + "\n"; + flags += "#define BACKEND_PORT " + std::to_string(backend) + "\n"; + flags += "#define NODE_IP " + std::to_string(this->nodeIpNboInt_) + "\n"; + flags += + "#define INTERNAL_SRC_IP " + std::to_string(this->internalSrcIpNboInt_); + + logger()->trace("Reloading code with the following flags:\n{}", flags); + + reload(flags + '\n' + k8sdispatcher_code); + + logger()->trace("Reloaded K8sDispatcher code"); +} + +std::shared_ptr K8sdispatcher::getFrontendPort() { + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::FRONTEND) { + return it; + } + } + return nullptr; +} + +std::shared_ptr K8sdispatcher::getBackendPort() { + for (auto &it : get_ports()) { + if (it->getType() == PortsTypeEnum::BACKEND) { + return it; + } + } + return nullptr; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h new file mode 100644 index 000000000..462776a3d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher.h @@ -0,0 +1,139 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#pragma once + +#include "base/K8sdispatcherBase.h" +#include "SessionRule.h" +#include "NodeportRule.h" +#include "Ports.h" +#include "HashTuple.h" + +/* definitions copied from datapath */ +struct nt_k { + uint16_t port; + uint8_t proto; +} __attribute__((packed)); +struct nt_v { + uint16_t external_traffic_policy; +} __attribute__((packed)); + +enum class SlowPathReason { ARP_REPLY = 0 }; + +using namespace polycube::service::model; + +class K8sdispatcher : public K8sdispatcherBase { + public: + K8sdispatcher(const std::string name, const K8sdispatcherJsonObject &conf); + virtual ~K8sdispatcher(); + + void packet_in(Ports &port, polycube::service::PacketInMetadata &md, + const std::vector &packet) override; + + /// + /// Entry of the ports table + /// + std::shared_ptr getPorts(const std::string &name) override; + std::vector> getPortsList() override; + void addPorts(const std::string &name, const PortsJsonObject &conf) override; + void addPortsList(const std::vector &conf) override; + void replacePorts(const std::string &name, + const PortsJsonObject &conf) override; + void delPorts(const std::string &name) override; + void delPortsList() override; + + /// + /// Internal source IP address used for natting incoming packets directed to + /// Kubernetes Services with a CLUSTER external traffic policy + /// + std::string getInternalSrcIp() override; + + /// + /// Port range used for NodePort Services + /// + std::string getNodeportRange() override; + void setNodeportRange(const std::string &value) override; + + /// + /// Session entry related to a specific traffic direction + /// + std::shared_ptr getSessionRule( + const SessionRuleDirectionEnum &direction, const std::string &srcIp, + const std::string &dstIp, const uint16_t &srcPort, + const uint16_t &dstPort, const L4ProtoEnum &proto) override; + std::vector> getSessionRuleList() override; + void addSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, + const SessionRuleJsonObject &conf) override; + void addSessionRuleList( + const std::vector &conf) override; + void replaceSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto, + const SessionRuleJsonObject &conf) override; + void delSessionRule(const SessionRuleDirectionEnum &direction, + const std::string &srcIp, const std::string &dstIp, + const uint16_t &srcPort, const uint16_t &dstPort, + const L4ProtoEnum &proto) override; + void delSessionRuleList() override; + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + std::shared_ptr getNodeportRule( + const uint16_t &nodeportPort, const L4ProtoEnum &proto) override; + std::vector> getNodeportRuleList() override; + void addNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) override; + void addNodeportRuleList( + const std::vector &conf) override; + void updateNodeportRuleList( + const std::vector &conf) override; + void replaceNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto, + const NodeportRuleJsonObject &conf) override; + virtual void replaceNodeportRuleList( + const std::vector &conf) override; + void delNodeportRule(const uint16_t &nodeportPort, + const L4ProtoEnum &proto) override; + void delNodeportRuleList() override; + + static const std::string EBPF_EGRESS_SESSION_TABLE; + static const std::string EBPF_INGRESS_SESSION_TABLE; + static const std::string EBPF_NPR_TABLE_MAP; + + typedef std::tuple NodeportKey; + + private: + std::string internalSrcIp_; + uint32_t internalSrcIpNboInt_; + std::string nodeportRange_; + std::pair nodeportRangeTuple_; + + uint32_t nodeIpNboInt_; + + std::unordered_map> + nodePortRuleMap_; + + void reloadConfig(); + + std::shared_ptr getFrontendPort(); + + std::shared_ptr getBackendPort(); +}; diff --git a/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c b/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c new file mode 100644 index 000000000..dc0569e8a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/K8sdispatcher_dp.c @@ -0,0 +1,521 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef FRONTEND_PORT +#define FRONTEND_PORT 0 +#endif + +#ifndef BACKEND_PORT +#define BACKEND_PORT 0 +#endif + +#ifndef NODE_IP +#define NODE_IP 0 +#endif + +#ifndef INTERNAL_SRC_IP +#define INTERNAL_SRC_IP 0 +#endif + +#define SESSION_MAP_DIM 32768 + +enum OPERATION_TYPE { OP_XLATE_SRC = 0, OP_XLATE_DST = 1 }; + +enum ORIGINATING_RULE_TYPE { RULE_POD_TO_EXT = 0, RULE_NODEPORT_CLUSTER = 1 }; + +enum EXTERNAL_TRAFFIC_POLICY_TYPE { ETP_LOCAL = 0, ETP_CLUSTER = 1 }; + +enum SLOWPATH_REASON { REASON_ARP_REPLY = 0 }; + +#define IP_CSUM_OFFSET (sizeof(struct eth_hdr) + offsetof(struct iphdr, check)) +#define UDP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct udphdr, check)) +#define TCP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct tcphdr, check)) +#define ICMP_CSUM_OFFSET \ + (sizeof(struct eth_hdr) + sizeof(struct iphdr) + \ + offsetof(struct icmphdr, checksum)) +#define IS_PSEUDO 0x10 + +struct eth_hdr { + __be64 dst : 48; + __be64 src : 48; + __be16 proto; +} __attribute__((packed)); + +struct arp_hdr { + __be16 ar_hrd; /* format of hardware address */ + __be16 ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + __be16 ar_op; /* ARP opcode (command) */ + __be64 ar_sha : 48; /* sender hardware address */ + __be32 ar_sip; /* sender IP address */ + __be64 ar_tha : 48; /* target hardware address */ + __be32 ar_tip; /* target IP address */ +} __attribute__((packed)); + +// Session table +struct st_k { + uint32_t src_ip; + uint32_t dst_ip; + uint16_t src_port; + uint16_t dst_port; + uint8_t proto; +} __attribute__((packed)); +struct st_v { + uint32_t new_ip; + uint16_t new_port; + uint8_t operation; + uint8_t originating_rule; +} __attribute__((packed)); +BPF_TABLE("lru_hash", struct st_k, struct st_v, egress_session_table, + SESSION_MAP_DIM); +BPF_TABLE("lru_hash", struct st_k, struct st_v, ingress_session_table, + SESSION_MAP_DIM); + +// NodePort rules table +struct nt_k { + uint16_t port; + uint8_t proto; +} __attribute__((packed)); +; +struct nt_v { + uint16_t external_traffic_policy; +} __attribute__((packed)); +; + +BPF_F_TABLE("hash", struct nt_k, struct nt_v, npr_table, 1024, + BPF_F_NO_PREALLOC); + +// Port numbers +struct free_port_entry { + uint16_t free_port; + struct bpf_spin_lock lock; +}; + +BPF_TABLE("array", uint32_t, struct free_port_entry, free_port_map, 1); + +static inline __be16 get_free_port() { + uint32_t key = 0; + uint16_t free_port = 0; + struct free_port_entry *entry = free_port_map.lookup(&key); + if (!entry) { // never happen for array + return 0; + } + bpf_spin_lock(&entry->lock); + if (entry->free_port < 1024 || entry->free_port == 65535) { + entry->free_port = 1024; + } + free_port = entry->free_port; + entry->free_port++; + bpf_spin_unlock(&entry->lock); + return bpf_htons(free_port); +} + +static int handle_rx(struct CTXTYPE *ctx, struct pkt_metadata *md) { + // Disable data plane if initialization has not been completed yet + if (FRONTEND_PORT == BACKEND_PORT) { + return RX_DROP; + } + // NAT processing happens in 4 steps: + // 1) packet parsing + // 2) session table lookup + // 3) rule lookup + // 4) packet modification + + // 1) Parse packet + void *data = (void *)(long)ctx->data; + void *data_end = (void *)(long)ctx->data_end; + + struct eth_hdr *eth = data; + if ((void *)(eth + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped Ethernet pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + switch (eth->proto) { + case htons(ETH_P_IP): + goto IP; // ipv4 packet + case htons(ETH_P_ARP): { + goto ARP; // ARP packet + } + default: + if (md->in_port == BACKEND_PORT) { + pcn_log(ctx, LOG_TRACE, + "Received unsupported pkt from BACKEND port: sent to stack - " + "(in_port: %d) (proto: 0x%x)", + md->in_port, bpf_htons(eth->proto)); + } else { + pcn_log(ctx, LOG_TRACE, + "Received unsupported pkt from FRONTEND port: sent to stack - " + "(in_port: %d) (proto: 0x%x)", + md->in_port, bpf_htons(eth->proto)); + } + return RX_OK; + } + +IP:; + struct iphdr *ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + // check if ip dest is for this host + if (md->in_port == FRONTEND_PORT && ip->daddr != NODE_IP) { + pcn_log(ctx, LOG_TRACE, + "Pkt coming from FRONTEND port is not for host: sent to stack - " + "(in_port: %d) (dst_ip: %I)", + md->in_port, ip->daddr); + return RX_OK; + } + + // Extract L3 packet data + uint32_t src_ip = ip->saddr; + uint32_t dst_ip = ip->daddr; + uint8_t proto = ip->protocol; + + // Extract L4 segment data + uint16_t src_port = 0, dst_port = 0; + // The following pointers are used to update a TCP port, a UDP port or the + // ICMP echo id in the same way regardless the l4 protocol; same for the + // checksum offset + uint16_t *src_port_ptr = NULL, *dst_port_ptr = NULL; + int l4_csum_offset = 0; + + switch (ip->protocol) { + case IPPROTO_TCP: { + struct tcphdr *tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped TCP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + src_port = tcp->source; + dst_port = tcp->dest; + src_port_ptr = &tcp->source; + dst_port_ptr = &tcp->dest; + l4_csum_offset = TCP_CSUM_OFFSET; + break; + } + case IPPROTO_UDP: { + struct udphdr *udp = (void *)(ip + 1); + if ((void *)(udp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped UDP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + src_port = udp->source; + dst_port = udp->dest; + src_port_ptr = &udp->source; + dst_port_ptr = &udp->dest; + l4_csum_offset = UDP_CSUM_OFFSET; + break; + } + case IPPROTO_ICMP: { + struct icmphdr *icmp = (void *)(ip + 1); + if ((void *)(icmp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ICMP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + // Consider the ICMP ID as a "port" number for easier handling + src_port = icmp->un.echo.id; + dst_port = icmp->un.echo.id; + src_port_ptr = &icmp->un.echo.id; + dst_port_ptr = &icmp->un.echo.id; + l4_csum_offset = ICMP_CSUM_OFFSET; + break; + } + default: { + pcn_log(ctx, LOG_TRACE, + "Dropped IP pkt - (in_port: %d) (reason: unsupported_type) " + "(ip_proto: %d)", + md->in_port, ip->protocol); + return RX_DROP; + } + } + + // 2) Packet parsed, start session table lookup + // NAT data + uint32_t new_ip = 0; + uint16_t new_port = 0; + uint8_t operation = 0; + struct st_k session_key = {.src_ip = src_ip, + .dst_ip = dst_ip, + .src_port = src_port, + .dst_port = dst_port, + .proto = proto}; + struct st_v *session_entry = (md->in_port == BACKEND_PORT) + ? egress_session_table.lookup(&session_key) + : ingress_session_table.lookup(&session_key); + if (session_entry) { + // Extract NAT data + new_ip = session_entry->new_ip; + new_port = session_entry->new_port; + operation = session_entry->operation; + goto APPLY_NAT; + } + + uint8_t originating_rule = 0; + + // 3) Session table miss, start rule lookup + if (md->in_port == BACKEND_PORT) { // inside -> outside + // Check egress rules + + // Rule: RESP_NP_LOCAL + // Response for a NodePort Svc with externalTrafficPolicy=LOCAL => FORWARD + if (src_ip == NODE_IP) { + pcn_log(ctx, LOG_TRACE, + "Matched egress rule RESP_NP_LOCAL: redirected pkt as is to " + "FRONTEND port - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + + // Rule: POD_TO_EXT + // A Pod want to reach the external world => SNAT + new_ip = NODE_IP; + new_port = get_free_port(); + operation = OP_XLATE_SRC; + originating_rule = RULE_POD_TO_EXT; + pcn_log(ctx, LOG_TRACE, + "Matched egress rule POD_TO_EXT: chosen SNAT - (src: %I:%P, dst: " + "%I:%P)", + src_ip, src_port, dst_ip, dst_port); + } else { // outside -> inside + // Check ingress rules + + struct nt_k npr_key = {.port = dst_port, .proto = proto}; + struct nt_v *npr_value = npr_table.lookup(&npr_key); + + // Incoming packet doesn't match any NodePort rule => stack + if (npr_value == NULL) { + pcn_log(ctx, LOG_TRACE, + "No ingress rule match: sent to stack - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return RX_OK; + } + + // Rule: REQ_NP_LOCAL + // Request for a NodePort Svc with externalTrafficPolicy=LOCAL => FORWARD + if (npr_value->external_traffic_policy == ETP_LOCAL) { + pcn_log(ctx, LOG_TRACE, + "Matched ingress rule REQ_NP_LOCAL: redirected pkt as is to " + "BACKEND port - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + return pcn_pkt_redirect(ctx, md, BACKEND_PORT); + } + + // Rule: REQ_NP_CLUSTER + // Request for a NodePort Svc with externalTrafficPolicy=CLUSTER => SNAT + new_ip = INTERNAL_SRC_IP; + new_port = get_free_port(); + operation = OP_XLATE_SRC; + originating_rule = RULE_NODEPORT_CLUSTER; + + pcn_log( + ctx, LOG_TRACE, + "Matched egress rule REQ_NP_CLUSTER: chosen SNAT - (src: %I:%P, dst: " + "%I:%P)", + src_ip, src_port, dst_ip, dst_port); + } + + // No session table exist for the packet but a rule matched, so both sessions + // tables must be updated. In the following, the forward table is intended + // to be the table that handles the sessions in the same direction of the + // actual packet; similarly, the reverse table is intended to be + // the table that handles the sessions in the opposite direction of the + // actual packet. Example: + // packet coming from the BACKEND port + // - forward table = egress session table + // - reverse table = ingress session table + + // Regardless the type of NAT, the forward table key always match the original + // packet quintuple data. Moreover, the forward table value always stores + // the new ip, the new port and the same kind of operation that will be + // applied on the actual packet. The originating rule is determined during + // the rules scan + struct st_k forward_key = {.src_ip = src_ip, + .dst_ip = dst_ip, + .src_port = src_port, + .dst_port = dst_port, + .proto = proto}; + struct st_v forward_value = {.new_ip = new_ip, + .new_port = new_port, + .operation = operation, + .originating_rule = originating_rule}; + + // The reverse table key and value depend on the kind of operation that will + // be applied on the actual packet + struct st_k reverse_key = {0, 0, 0, 0, 0}; + struct st_v reverse_value = {0, 0}; + + if (operation == OP_XLATE_SRC) { + reverse_key.src_ip = dst_ip; + reverse_key.dst_ip = new_ip; + // During the NAT operation, the ICMP ID will be replaced with the value + // of "new port": this means that for the reverse traffic, the session will + // be identified by a packet having both "source port" and "destination port" + // equal to "new port" + reverse_key.src_port = (proto == IPPROTO_ICMP) ? new_port : dst_port; + reverse_key.dst_port = new_port; + + reverse_value.new_ip = src_ip; + reverse_value.new_port = src_port; + reverse_value.operation = OP_XLATE_DST; + } else { + reverse_key.src_ip = new_ip; + reverse_key.dst_ip = src_ip; + reverse_key.src_port = new_port; + // During the NAT operation, the ICMP ID will be replaced with the value + // of "new port": this means that for the reverse traffic, the session will + // be identified by a packet having both "source port" and "destination port" + // equal to "new port" + reverse_key.dst_port = (proto == IPPROTO_ICMP) ? new_port : src_port; + + reverse_value.new_ip = dst_ip; + reverse_value.new_port = dst_port; + reverse_value.operation = OP_XLATE_SRC; + } + // The protocol type in the reverse table key and the originating rule type + // in the reverse table value do not depend on the operation applied on the + // actual packet + reverse_key.proto = proto; + reverse_value.originating_rule = originating_rule; + + if (md->in_port == BACKEND_PORT) { + egress_session_table.update(&forward_key, &forward_value); + ingress_session_table.update(&reverse_key, &reverse_value); + } else { + ingress_session_table.update(&forward_key, &forward_value); + egress_session_table.update(&reverse_key, &reverse_value); + } + + pcn_log(ctx, LOG_TRACE, "Created new session - (src: %I:%P, dst: %I:%P)", + src_ip, src_port, dst_ip, dst_port); + +APPLY_NAT:; + // Save old values in order to update checksums after the NAT is applied + uint32_t old_ip; + uint16_t old_port; + if (operation == OP_XLATE_SRC) { + old_ip = src_ip; + old_port = src_port; + ip->saddr = new_ip; + *src_port_ptr = new_port; + pcn_log(ctx, LOG_TRACE, "Applied SNAT - (src: %I:%P, new_src: %I:%P)", + old_ip, old_port, new_ip, new_port); + } else { + old_ip = dst_ip; + old_port = dst_port; + ip->daddr = new_ip; + *dst_port_ptr = new_port; + pcn_log(ctx, LOG_TRACE, "Applied DNAT - (dst: %I:%P, new_dst: %I:%P)", + old_ip, old_port, new_ip, new_port); + } + + // Update checksums + if (proto != IPPROTO_ICMP) { // only for UDP and TCP + pcn_l4_csum_replace(ctx, l4_csum_offset, old_ip, new_ip, IS_PSEUDO | 4); + } + pcn_l4_csum_replace(ctx, l4_csum_offset, old_port, new_port, 2); + pcn_l3_csum_replace(ctx, IP_CSUM_OFFSET, old_ip, new_ip, 4); + + // Session tables have been updated (if needed) + // The packet has been modified (if needed) + // Nothing more to do, forward the packet + return pcn_pkt_redirect( + ctx, md, md->in_port == BACKEND_PORT ? FRONTEND_PORT : BACKEND_PORT); + +ARP:; + struct arp_hdr *arp = (void *)(eth + 1); + if ((void *)(arp + 1) > data_end) { + pcn_log(ctx, LOG_TRACE, + "Dropped ARP pkt - (in_port: %d) (reason: inconsistent_size)", + md->in_port); + return RX_DROP; + } + + if (md->in_port == BACKEND_PORT) { // inside -> outside + // If a packet coming from the backend port + // - is an ARP request => it is sent to the frontend port + // - otherwise it is dropped + if (arp->ar_op == bpf_htons(ARPOP_REQUEST)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP request pkt from BACKEND port: redirected to " + "FRONTEND port - (in_port: %d) (out_port: %d)", + md->in_port, FRONTEND_PORT); + return pcn_pkt_redirect(ctx, md, FRONTEND_PORT); + } + pcn_log(ctx, LOG_TRACE, + "Dropped non-request ARP pkt coming from BACKEND port - (in_port: " + "%d) (arp_opcode: %d)", + md->in_port, bpf_htons(arp->ar_op)); + } else { // outside -> inside + // If a packet coming from the frontend port + // - is an ARP request => it is sent to the stack + // - is an ARP reply => a copy is sent to the slowpath and the original one + // to the stack + // - otherwise it is dropped + if (arp->ar_op == bpf_ntohs(ARPOP_REQUEST)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP request pkt from FRONTEND port: sent to the stack " + "- (in_port: %d)", + md->in_port); + return RX_OK; + } else if (arp->ar_op == bpf_htons(ARPOP_REPLY)) { + pcn_log(ctx, LOG_TRACE, + "Received ARP reply pkt from FRONTEND port: sent a copy to " + "slow path and a copy to the stack - (in_port: %d)", + md->in_port); + // a copy is sent to the backend port through the slowpath and a copy is + // sent to the stack + pcn_pkt_controller(ctx, md, REASON_ARP_REPLY); + return RX_OK; + } + pcn_log(ctx, LOG_TRACE, + "Dropped non-reply ARP pkt coming from FRONTEND port - (in_port: " + "%d) (arp_opcode: %d)", + md->in_port, bpf_htons(arp->ar_op)); + } + return RX_DROP; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp b/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp new file mode 100644 index 000000000..c0e7eaa99 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/NodeportRule.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include "NodeportRule.h" + +#include "K8sdispatcher.h" +#include "Utils.h" + +NodeportRule::NodeportRule(K8sdispatcher &parent, + const NodeportRuleJsonObject &conf) + : NodeportRuleBase(parent), + parent_{parent}, + nodeportPort_{conf.nodeportPortIsSet() + ? conf.getNodeportPort() + : throw std::runtime_error{"NodePort rule port" + "must be provided"}}, + proto_{ + conf.protoIsSet() + ? conf.getProto() + : throw std:: + runtime_error{"NodePort rule protocol must be provided"}}, + externalTrafficPolicy_{ + conf.externalTrafficPolicyIsSet() + ? conf.getExternalTrafficPolicy() + : NodeportRuleExternalTrafficPolicyEnum::CLUSTER}, + ruleName_{conf.getRuleName()} { + logger()->info("Creating NodeportRule instance"); + if (this->proto_ == L4ProtoEnum::ICMP) { + throw std::runtime_error{"NodePort rule protocol cannot be ICMP"}; + } +} + +NodeportRule::~NodeportRule() { + logger()->info("Destroying NodeportRule instance"); +} + +uint16_t NodeportRule::getNodeportPort() { + return this->nodeportPort_; +} + +L4ProtoEnum NodeportRule::getProto() { + return this->proto_; +} + +NodeportRuleExternalTrafficPolicyEnum NodeportRule::getExternalTrafficPolicy() { + return this->externalTrafficPolicy_; +} + +void NodeportRule::setExternalTrafficPolicy( + const NodeportRuleExternalTrafficPolicyEnum &value) { + logger()->trace( + "Received a request to update NodePort rule external traffic policy"); + try { + logger()->trace("Retrieving NodePort rules kernel map"); + auto npr_table = this->parent_.get_hash_table( + K8sdispatcher::EBPF_NPR_TABLE_MAP); + logger()->trace("Retrieved NodePort rules kernel map"); + + struct nt_k npr_key { + .port = htons(this->nodeportPort_), + .proto = utils::L4ProtoEnum_to_int(this->proto_), + }; + struct nt_v npr_value { + .external_traffic_policy = + utils::NodeportRuleExternalTrafficPolicyEnum_to_int(value) + }; + + logger()->trace( + "Updating NodePort rule external traffic policy in NodePort rules " + "kernel map"); + npr_table.set(npr_key, npr_value); + this->externalTrafficPolicy_ = value; + logger()->trace( + "Updated NodePort rule external traffic policy in NodePort rules " + "kernel map"); + } catch (std::exception &ex) { + logger()->error("Failed to update NodePort rule in kernel map: {}", + ex.what()); + throw std::runtime_error("Failed to update NodePort rule in kernel map"); + } + logger()->trace("Updated NodePort rule external traffic policy"); +} + +std::string NodeportRule::getRuleName() { + return this->ruleName_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/NodeportRule.h b/src/services/pcn-k8sdispatcher/src/NodeportRule.h new file mode 100644 index 000000000..4233c213a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/NodeportRule.h @@ -0,0 +1,59 @@ +/* +* Copyright 2022 The Polycube Authors +* +* 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. + */ + +#pragma once + +#include "../base/NodeportRuleBase.h" + +class K8sdispatcher; + +using namespace polycube::service::model; + +class NodeportRule : public NodeportRuleBase { + public: + NodeportRule(K8sdispatcher &parent, const NodeportRuleJsonObject &conf); + virtual ~NodeportRule(); + + /// + /// NodePort rule nodeport port number + /// + uint16_t getNodeportPort() override; + + /// + /// NodePort rule L4 protocol + /// + L4ProtoEnum getProto() override; + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() override; + void setExternalTrafficPolicy(const NodeportRuleExternalTrafficPolicyEnum &value) override; + + /// + /// An optional name for the NodePort rule + /// + std::string getRuleName() override; + + private: + K8sdispatcher& parent_; + + uint16_t nodeportPort_; + L4ProtoEnum proto_; + + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy_; + std::string ruleName_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/Ports.cpp b/src/services/pcn-k8sdispatcher/src/Ports.cpp new file mode 100644 index 000000000..6d7e402f9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Ports.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include "Ports.h" +#include "K8sdispatcher.h" +#include "Utils.h" + + +Ports::Ports(polycube::service::Cube &parent, + std::shared_ptr port, + const PortsJsonObject &conf) + : PortsBase(parent, port), type_{conf.getType()} { + logger()->info("Creating Ports instance"); + auto ipIsSet = conf.ipIsSet(); + if (this->type_ == PortsTypeEnum::FRONTEND) { + if (!ipIsSet) { + throw std::runtime_error( + "The IP address is mandatory for a FRONTEND port"); + } + + auto ip = conf.getIp(); + if (!utils::is_valid_ipv4_str(ip)) { + throw std::runtime_error{"Invalid IPv4 address"}; + } + this->ip_ = ip; + } else { + if (ipIsSet) { + throw std::runtime_error( + "The IP address in not allowed for a BACKEND port"); + } + } +} + +Ports::~Ports() { + logger()->info("Destroying Ports instance"); +} + +PortsTypeEnum Ports::getType() { + return this->type_; +} + +std::string Ports::getIp() { + return this->ip_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/Ports.h b/src/services/pcn-k8sdispatcher/src/Ports.h new file mode 100644 index 000000000..5383a7cd2 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Ports.h @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#pragma once + +#include "../base/PortsBase.h" + +class K8sdispatcher; + +using namespace polycube::service::model; + +class Ports : public PortsBase { + public: + Ports(polycube::service::Cube &parent, + std::shared_ptr port, + const PortsJsonObject &conf); + virtual ~Ports(); + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + PortsTypeEnum getType() override; + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + std::string getIp() override; + + private: + PortsTypeEnum type_; + std::string ip_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/SessionRule.cpp b/src/services/pcn-k8sdispatcher/src/SessionRule.cpp new file mode 100644 index 000000000..4d011a5ec --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/SessionRule.cpp @@ -0,0 +1,106 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include "SessionRule.h" +#include "K8sdispatcher.h" +#include "Utils.h" + +SessionRule::SessionRule(K8sdispatcher &parent, + const SessionRuleJsonObject &conf) + : SessionRule{parent, + conf.getDirection(), + conf.getSrcIp(), + conf.getDstIp(), + conf.getSrcPort(), + conf.getDstPort(), + conf.getProto(), + conf.getNewIp(), + conf.getNewPort(), + conf.getOperation(), + conf.getOriginatingRule()} {} + +SessionRule::SessionRule(K8sdispatcher &parent, + SessionRuleDirectionEnum direction, std::string srcIp, + std::string dstIp, uint16_t srcPort, uint16_t dstPort, + L4ProtoEnum proto, std::string newIp, uint16_t newPort, + SessionRuleOperationEnum operation, + SessionRuleOriginatingRuleEnum originatingRule) + : SessionRuleBase(parent), + direction_{direction}, + srcIp_{srcIp}, + dstIp_{dstIp}, + srcPort_{srcPort}, + dstPort_{dstPort}, + proto_{proto}, + newIp_{newIp}, + newPort_{newPort}, + operation_{operation}, + originatingRule_{originatingRule} { + logger()->info("Creating SessionRule instance"); + if (!utils::is_valid_ipv4_str(srcIp)) { + throw std::runtime_error{"Invalid source IPv4 address"}; + } + if (!utils::is_valid_ipv4_str(dstIp)) { + throw std::runtime_error{"Invalid destination IPv4 address"}; + } + if (!utils::is_valid_ipv4_str(newIp)) { + throw std::runtime_error{"Invalid new IPv4 address"}; + } +} + +SessionRule::~SessionRule() { + logger()->info("Destroying SessionRule instance"); +} + +SessionRuleDirectionEnum SessionRule::getDirection() { + return this->direction_; +} + +std::string SessionRule::getSrcIp() { + return this->srcIp_; +} + +std::string SessionRule::getDstIp() { + return this->dstIp_; +} + +uint16_t SessionRule::getSrcPort() { + return this->srcPort_; +} + +uint16_t SessionRule::getDstPort() { + return this->dstPort_; +} + +L4ProtoEnum SessionRule::getProto() { + return this->proto_; +} + +std::string SessionRule::getNewIp() { + return this->newIp_; +} + +uint16_t SessionRule::getNewPort() { + return this->newPort_; +} + +SessionRuleOperationEnum SessionRule::getOperation() { + return this->operation_; +} + +SessionRuleOriginatingRuleEnum SessionRule::getOriginatingRule() { + return this->originatingRule_; +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/SessionRule.h b/src/services/pcn-k8sdispatcher/src/SessionRule.h new file mode 100644 index 000000000..f25b44fed --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/SessionRule.h @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#pragma once + +#include "../base/SessionRuleBase.h" + +struct st_k { + uint32_t src_ip; + uint32_t dst_ip; + uint16_t src_port; + uint16_t dst_port; + uint8_t proto; +} __attribute__((packed)); +struct st_v { + uint32_t new_ip; + uint16_t new_port; + uint8_t operation; + uint8_t originating_rule; +} __attribute__((packed)); + +class K8sdispatcher; + +using namespace polycube::service::model; + +class SessionRule : public SessionRuleBase { + public: + SessionRule(K8sdispatcher &parent, const SessionRuleJsonObject &conf); + SessionRule(K8sdispatcher &parent, SessionRuleDirectionEnum direction, + std::string srcIp, std::string dstIp, uint16_t srcPort, + uint16_t dstPort, L4ProtoEnum proto, std::string newIp, + uint16_t newPort, SessionRuleOperationEnum operation, + SessionRuleOriginatingRuleEnum originatingRule); + virtual ~SessionRule(); + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + SessionRuleDirectionEnum getDirection() override; + + /// + /// Session entry source IP address + /// + std::string getSrcIp() override; + + /// + /// Session entry destination IP address + /// + std::string getDstIp() override; + + /// + /// Session entry source L4 port number + /// + uint16_t getSrcPort() override; + + /// + /// Session entry destination L4 port number + /// + uint16_t getDstPort() override; + + /// + /// Session entry L4 protocol + /// + L4ProtoEnum getProto() override; + + /// + /// Translated IP address + /// + std::string getNewIp() override; + + /// + /// Translated L4 port number + /// + uint16_t getNewPort() override; + + /// + /// Operation applied on the original packet + /// + SessionRuleOperationEnum getOperation() override; + + /// + /// Rule originating the session entry + /// + SessionRuleOriginatingRuleEnum getOriginatingRule() override; + + private: + SessionRuleDirectionEnum direction_; + std::string srcIp_; + std::string dstIp_; + uint16_t srcPort_; + uint16_t dstPort_; + L4ProtoEnum proto_; + + std::string newIp_; + uint16_t newPort_; + SessionRuleOperationEnum operation_; + SessionRuleOriginatingRuleEnum originatingRule_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/Utils.cpp b/src/services/pcn-k8sdispatcher/src/Utils.cpp new file mode 100644 index 000000000..ad0ef2a60 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Utils.cpp @@ -0,0 +1,129 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#include "Utils.h" + +#include "base/K8sdispatcherBase.h" + +uint8_t utils::L4ProtoEnum_to_int(L4ProtoEnum proto) { + switch (proto) { + case L4ProtoEnum::TCP: + return 6; + case L4ProtoEnum::UDP: + return 17; + case L4ProtoEnum::ICMP: + return 1; + default: + throw std::runtime_error("Bad proto"); + } +} + +L4ProtoEnum utils::int_to_L4ProtoEnum(uint8_t proto) { + switch (proto) { + case 6: + return L4ProtoEnum::TCP; + case 17: + return L4ProtoEnum::UDP; + case 1: + return L4ProtoEnum::ICMP; + default: + throw std::runtime_error("Bad proto number"); + } +} + +uint8_t utils::SessionRuleOperationEnum_to_int( + SessionRuleOperationEnum operation) { + switch (operation) { + case SessionRuleOperationEnum::XLATE_SRC: + return 0; + case SessionRuleOperationEnum::XLATE_DST: + return 1; + default: + throw std::runtime_error("Bad operation"); + } +} + +SessionRuleOperationEnum utils::int_to_SessionRuleOperationEnum( + const uint8_t operation) { + switch (operation) { + case 0: + return SessionRuleOperationEnum::XLATE_SRC; + case 1: + return SessionRuleOperationEnum::XLATE_DST; + default: + throw std::runtime_error("Bad operation number"); + } +} + +uint8_t utils::SessionRuleOriginatingRuleEnum_to_int( + SessionRuleOriginatingRuleEnum originatingRule) { + switch (originatingRule) { + case SessionRuleOriginatingRuleEnum::POD_TO_EXT: + return 0; + case SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER: + return 1; + default: + throw std::runtime_error("Bad originating rule"); + } +} + +SessionRuleOriginatingRuleEnum utils::int_to_SessionRuleOriginatingRuleEnum( + const uint8_t originatingRule) { + switch (originatingRule) { + case 0: + return SessionRuleOriginatingRuleEnum::POD_TO_EXT; + case 1: + return SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER; + default: + throw std::runtime_error("Bad originating rule number"); + } +} + +uint8_t utils::NodeportRuleExternalTrafficPolicyEnum_to_int( + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy) { + switch (externalTrafficPolicy) { + case NodeportRuleExternalTrafficPolicyEnum::LOCAL: + return 0; + case NodeportRuleExternalTrafficPolicyEnum::CLUSTER: + return 1; + default: + throw std::runtime_error("Bad external traffic policy"); + } +} + +NodeportRuleExternalTrafficPolicyEnum +utils::int_to_NodeportRuleExternalTrafficPolicyEnum( + const uint8_t externalTrafficPolicy) { + switch (externalTrafficPolicy) { + case 0: + return NodeportRuleExternalTrafficPolicyEnum::LOCAL; + case 1: + return NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + default: + throw std::runtime_error("Bad external traffic policy number"); + } +} + +bool utils::is_valid_ipv4_str(std::string const &ip) { + try { + // if the provided IPv4 address is not valid, the following call + // will throw and exception + polycube::service::utils::ip_string_to_nbo_uint(ip); + return true; + } catch (...) { + return false; + } +} \ No newline at end of file diff --git a/src/services/pcn-k8sdispatcher/src/Utils.h b/src/services/pcn-k8sdispatcher/src/Utils.h new file mode 100644 index 000000000..7fc180cc0 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/Utils.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Polycube Authors + * + * 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. + */ + +#ifndef POLYCUBE_UTILS_H +#define POLYCUBE_UTILS_H + +#include "base/NodeportRuleBase.h" +#include "base/SessionRuleBase.h" + +namespace utils { +// conversion functions +uint8_t L4ProtoEnum_to_int(L4ProtoEnum proto); +L4ProtoEnum int_to_L4ProtoEnum(const uint8_t proto); + +uint8_t SessionRuleOperationEnum_to_int(SessionRuleOperationEnum operation); +SessionRuleOperationEnum int_to_SessionRuleOperationEnum( + const uint8_t operation); + +uint8_t SessionRuleOriginatingRuleEnum_to_int( + SessionRuleOriginatingRuleEnum originatingRule); +SessionRuleOriginatingRuleEnum int_to_SessionRuleOriginatingRuleEnum( + const uint8_t originatingRule); + +uint8_t NodeportRuleExternalTrafficPolicyEnum_to_int( + NodeportRuleExternalTrafficPolicyEnum externalTrafficPolicy); +NodeportRuleExternalTrafficPolicyEnum +int_to_NodeportRuleExternalTrafficPolicyEnum( + const uint8_t externalTrafficPolicy); + +// helper functions +bool is_valid_ipv4_str(std::string const &ip); + +} // namespace utils + +#endif // POLYCUBE_UTILS_H diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp new file mode 100644 index 000000000..be5e216c4 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.cpp @@ -0,0 +1,1271 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherApi.h" +#include "K8sdispatcherApiImpl.h" + +using namespace polycube::service::model; +using namespace polycube::service::api::K8sdispatcherApiImpl; + +#ifdef __cplusplus +extern "C" { +#endif + +Response create_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + create_k8sdispatcher_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + create_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + create_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + create_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response create_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + create_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kCreated, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + delete_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_nodeport_rule_list_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + delete_k8sdispatcher_ports_by_id(unique_name, unique_portsName); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response delete_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + delete_k8sdispatcher_ports_list_by_id(unique_name); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_by_id(unique_name); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_internal_src_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_internal_src_ip_by_id(unique_name); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + + + try { + + auto x = read_k8sdispatcher_list_by_id(); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_range_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_nodeport_range_by_id(unique_name); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = NodeportRuleJsonObject::NodeportRuleExternalTrafficPolicyEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_nodeport_rule_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_nodeport_rule_rule_name_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_nodeport_rule_rule_name_by_id(unique_name, unique_nodeportPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_ip_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_ports_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_ports_type_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + + auto x = read_k8sdispatcher_ports_type_by_id(unique_name, unique_portsName); + nlohmann::json response_body; + response_body = PortsJsonObject::PortsTypeEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x.toJson(); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + + try { + + auto x = read_k8sdispatcher_session_rule_list_by_id(unique_name); + nlohmann::json response_body; + for (auto &i : x) { + response_body += i.toJson(); + } + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_new_ip_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_new_ip_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_new_port_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_new_port_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = x; + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_operation_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_operation_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = SessionRuleJsonObject::SessionRuleOperationEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response read_k8sdispatcher_session_rule_originating_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys ) { + // Getting the path params + std::string unique_name { name }; + std::string unique_direction; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "direction")) { + unique_direction = std::string { keys[i].value.string }; + break; + } + } + auto unique_direction_ = SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(unique_direction); + + std::string unique_srcIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-ip")) { + unique_srcIp = std::string { keys[i].value.string }; + break; + } + } + + std::string unique_dstIp; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-ip")) { + unique_dstIp = std::string { keys[i].value.string }; + break; + } + } + + uint16_t unique_srcPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "src-port")) { + unique_srcPort = keys[i].value.uint16; + break; + } + } + + uint16_t unique_dstPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "dst-port")) { + unique_dstPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = SessionRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + + auto x = read_k8sdispatcher_session_rule_originating_rule_by_id(unique_name, unique_direction_, unique_srcIp, unique_dstIp, unique_srcPort, unique_dstPort, unique_proto_); + nlohmann::json response_body; + response_body = SessionRuleJsonObject::SessionRuleOriginatingRuleEnum_to_string(x); + return { kOk, ::strdup(response_body.dump().c_str()) }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + replace_k8sdispatcher_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + replace_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + replace_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + replace_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response replace_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + replace_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + K8sdispatcherJsonObject unique_value { request_body }; + + unique_value.setName(unique_name); + update_k8sdispatcher_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + K8sdispatcherJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_list_by_id(unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_range_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // The conversion is done automatically by the json library + std::string unique_value = request_body; + update_k8sdispatcher_nodeport_range_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + NodeportRuleJsonObject unique_value { request_body }; + + unique_value.setNodeportPort(unique_nodeportPort); + unique_value.setProto(unique_proto_); + update_k8sdispatcher_nodeport_rule_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + uint16_t unique_nodeportPort; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "nodeport-port")) { + unique_nodeportPort = keys[i].value.uint16; + break; + } + } + + std::string unique_proto; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "proto")) { + unique_proto = std::string { keys[i].value.string }; + break; + } + } + auto unique_proto_ = NodeportRuleJsonObject::string_to_L4ProtoEnum(unique_proto); + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + NodeportRuleExternalTrafficPolicyEnum unique_value_ = NodeportRuleJsonObject::string_to_NodeportRuleExternalTrafficPolicyEnum(request_body); + update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(unique_name, unique_nodeportPort, unique_proto_, unique_value_); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_nodeport_rule_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + NodeportRuleJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_nodeport_rule_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_ports_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + std::string unique_portsName; + for (size_t i = 0; i < num_keys; ++i) { + if (!strcmp(keys[i].name, "ports_name")) { + unique_portsName = std::string { keys[i].value.string }; + break; + } + } + + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + PortsJsonObject unique_value { request_body }; + + unique_value.setName(unique_portsName); + update_k8sdispatcher_ports_by_id(unique_name, unique_portsName, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + +Response update_k8sdispatcher_ports_list_by_id_handler( + const char *name, const Key *keys, + size_t num_keys , + const char *value) { + // Getting the path params + std::string unique_name { name }; + // Getting the body param + std::vector unique_value; + + try { + auto request_body = nlohmann::json::parse(std::string { value }); + // Getting the body param + std::vector unique_value; + for (auto &j : request_body) { + PortsJsonObject a { j }; + unique_value.push_back(a); + } + update_k8sdispatcher_ports_list_by_id(unique_name, unique_value); + return { kOk, nullptr }; + } catch(const std::exception &e) { + return { kGenericError, ::strdup(e.what()) }; + } +} + + +Response k8sdispatcher_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + + nlohmann::json val = read_k8sdispatcher_list_by_id_get_list(); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_nodeport_rule_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_nodeport_rule_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_ports_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_ports_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +Response k8sdispatcher_session_rule_list_by_id_help( + const char *name, const Key *keys, size_t num_keys) { + // Getting the path params + std::string unique_name { name }; + nlohmann::json val = read_k8sdispatcher_session_rule_list_by_id_get_list(unique_name); + + return { kOk, ::strdup(val.dump().c_str()) }; +} + +#ifdef __cplusplus +} +#endif + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h new file mode 100644 index 000000000..90287533f --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApi.h @@ -0,0 +1,86 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherApi.h +* +*/ + +#pragma once + +#define POLYCUBE_SERVICE_NAME "k8sdispatcher" + + +#include "polycube/services/response.h" +#include "polycube/services/shared_lib_elements.h" + +#include "K8sdispatcherJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include "SessionRuleJsonObject.h" +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +Response create_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response create_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response delete_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response delete_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_internal_src_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_range_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_nodeport_rule_rule_name_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_ports_type_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_new_ip_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_new_port_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_operation_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response read_k8sdispatcher_session_rule_originating_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys); +Response replace_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response replace_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_range_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_nodeport_rule_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_ports_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); +Response update_k8sdispatcher_ports_list_by_id_handler(const char *name, const Key *keys, size_t num_keys, const char *value); + +Response k8sdispatcher_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_nodeport_rule_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_ports_list_by_id_help(const char *name, const Key *keys, size_t num_keys); +Response k8sdispatcher_session_rule_list_by_id_help(const char *name, const Key *keys, size_t num_keys); + + +#ifdef __cplusplus +} +#endif + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp new file mode 100644 index 000000000..a4bf15212 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.cpp @@ -0,0 +1,855 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherApiImpl.h" + +namespace polycube { +namespace service { +namespace api { + +using namespace polycube::service::model; + +namespace K8sdispatcherApiImpl { +namespace { +std::unordered_map> cubes; +std::mutex cubes_mutex; + +std::shared_ptr get_cube(const std::string &name) { + std::lock_guard guard(cubes_mutex); + auto iter = cubes.find(name); + if (iter == cubes.end()) { + throw std::runtime_error("Cube " + name + " does not exist"); + } + + return iter->second; +} + +} + +void create_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &jsonObject) { + { + // check if name is valid before creating it + std::lock_guard guard(cubes_mutex); + if (cubes.count(name) != 0) { + throw std::runtime_error("There is already a cube with name " + name); + } + } + auto ptr = std::make_shared(name, jsonObject); + std::unordered_map>::iterator iter; + bool inserted; + + std::lock_guard guard(cubes_mutex); + std::tie(iter, inserted) = cubes.emplace(name, std::move(ptr)); + + if (!inserted) { + throw std::runtime_error("There is already a cube with name " + name); + } +} + +void replace_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &bridge){ + throw std::runtime_error("Method not supported!"); +} + +void delete_k8sdispatcher_by_id(const std::string &name) { + std::lock_guard guard(cubes_mutex); + if (cubes.count(name) == 0) { + throw std::runtime_error("Cube " + name + " does not exist"); + } + cubes.erase(name); +} + +std::vector read_k8sdispatcher_list_by_id() { + std::vector jsonObject_vect; + for(auto &i : cubes) { + auto m = get_cube(i.first); + jsonObject_vect.push_back(m->toJsonObject()); + } + return jsonObject_vect; +} + +std::vector> read_k8sdispatcher_list_by_id_get_list() { + std::vector> r; + for (auto &x : cubes) { + nlohmann::fifo_map m; + m["name"] = x.first; + r.push_back(std::move(m)); + } + return r; +} + +/** +* @brief Create nodeport-rule by ID +* +* Create operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->addNodeportRule(nodeportPort, proto, value); +} + +/** +* @brief Create nodeport-rule by ID +* +* Create operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->addNodeportRuleList(value); +} + +/** +* @brief Create ports by ID +* +* Create operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->addPorts(portsName, value); +} + +/** +* @brief Create ports by ID +* +* Create operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +create_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->addPortsList(value); +} + +/** +* @brief Delete nodeport-rule by ID +* +* Delete operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* +*/ +void +delete_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->delNodeportRule(nodeportPort, proto); +} + +/** +* @brief Delete nodeport-rule by ID +* +* Delete operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->delNodeportRuleList(); +} + +/** +* @brief Delete ports by ID +* +* Delete operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->delPorts(portsName); +} + +/** +* @brief Delete ports by ID +* +* Delete operation of resource: ports* +* +* @param[in] name ID of name +* +* Responses: +* +*/ +void +delete_k8sdispatcher_ports_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + k8sdispatcher->delPortsList(); +} + +/** +* @brief Read k8sdispatcher by ID +* +* Read operation of resource: k8sdispatcher* +* +* @param[in] name ID of name +* +* Responses: +* K8sdispatcherJsonObject +*/ +K8sdispatcherJsonObject +read_k8sdispatcher_by_id(const std::string &name) { + return get_cube(name)->toJsonObject(); + +} + +/** +* @brief Read internal-src-ip by ID +* +* Read operation of resource: internal-src-ip* +* +* @param[in] name ID of name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_internal_src_ip_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getInternalSrcIp(); + +} + +/** +* @brief Read nodeport-range by ID +* +* Read operation of resource: nodeport-range* +* +* @param[in] name ID of name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_nodeport_range_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getNodeportRange(); + +} + +/** +* @brief Read nodeport-rule by ID +* +* Read operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* NodeportRuleJsonObject +*/ +NodeportRuleJsonObject +read_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getNodeportRule(nodeportPort, proto)->toJsonObject(); + +} + +/** +* @brief Read external-traffic-policy by ID +* +* Read operation of resource: external-traffic-policy* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* NodeportRuleExternalTrafficPolicyEnum +*/ +NodeportRuleExternalTrafficPolicyEnum +read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + return nodeportRule->getExternalTrafficPolicy(); + +} + +/** +* @brief Read nodeport-rule by ID +* +* Read operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&nodeportRule = k8sdispatcher->getNodeportRuleList(); + std::vector m; + for(auto &i : nodeportRule) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read rule-name by ID +* +* Read operation of resource: rule-name* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_nodeport_rule_rule_name_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + return nodeportRule->getRuleName(); + +} + +/** +* @brief Read ports by ID +* +* Read operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* PortsJsonObject +*/ +PortsJsonObject +read_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getPorts(portsName)->toJsonObject(); + +} + +/** +* @brief Read ip by ID +* +* Read operation of resource: ip* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_ports_ip_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + return ports->getIp(); + +} + +/** +* @brief Read ports by ID +* +* Read operation of resource: ports* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_ports_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&ports = k8sdispatcher->getPortsList(); + std::vector m; + for(auto &i : ports) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read type by ID +* +* Read operation of resource: type* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* +* Responses: +* PortsTypeEnum +*/ +PortsTypeEnum +read_k8sdispatcher_ports_type_by_id(const std::string &name, const std::string &portsName) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + return ports->getType(); + +} + +/** +* @brief Read session-rule by ID +* +* Read operation of resource: session-rule* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleJsonObject +*/ +SessionRuleJsonObject +read_k8sdispatcher_session_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto)->toJsonObject(); + +} + +/** +* @brief Read session-rule by ID +* +* Read operation of resource: session-rule* +* +* @param[in] name ID of name +* +* Responses: +* std::vector +*/ +std::vector +read_k8sdispatcher_session_rule_list_by_id(const std::string &name) { + auto k8sdispatcher = get_cube(name); + auto &&sessionRule = k8sdispatcher->getSessionRuleList(); + std::vector m; + for(auto &i : sessionRule) + m.push_back(i->toJsonObject()); + return m; +} + +/** +* @brief Read new-ip by ID +* +* Read operation of resource: new-ip* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* std::string +*/ +std::string +read_k8sdispatcher_session_rule_new_ip_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getNewIp(); + +} + +/** +* @brief Read new-port by ID +* +* Read operation of resource: new-port* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* uint16_t +*/ +uint16_t +read_k8sdispatcher_session_rule_new_port_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getNewPort(); + +} + +/** +* @brief Read operation by ID +* +* Read operation of resource: operation* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleOperationEnum +*/ +SessionRuleOperationEnum +read_k8sdispatcher_session_rule_operation_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getOperation(); + +} + +/** +* @brief Read originating-rule by ID +* +* Read operation of resource: originating-rule* +* +* @param[in] name ID of name +* @param[in] direction ID of direction +* @param[in] srcIp ID of src-ip +* @param[in] dstIp ID of dst-ip +* @param[in] srcPort ID of src-port +* @param[in] dstPort ID of dst-port +* @param[in] proto ID of proto +* +* Responses: +* SessionRuleOriginatingRuleEnum +*/ +SessionRuleOriginatingRuleEnum +read_k8sdispatcher_session_rule_originating_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) { + auto k8sdispatcher = get_cube(name); + auto sessionRule = k8sdispatcher->getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + return sessionRule->getOriginatingRule(); + +} + +/** +* @brief Replace nodeport-rule by ID +* +* Replace operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->replaceNodeportRule(nodeportPort, proto, value); +} + +/** +* @brief Replace nodeport-rule by ID +* +* Replace operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->replaceNodeportRuleList(value); +} + +/** +* @brief Replace ports by ID +* +* Replace operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->replacePorts(portsName, value); +} + +/** +* @brief Replace ports by ID +* +* Replace operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +replace_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + +/** +* @brief Update k8sdispatcher by ID +* +* Update operation of resource: k8sdispatcher* +* +* @param[in] name ID of name +* @param[in] value k8sdispatcherbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->update(value); +} + +/** +* @brief Update k8sdispatcher by ID +* +* Update operation of resource: k8sdispatcher* +* +* @param[in] value k8sdispatcherbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_list_by_id(const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + +/** +* @brief Update nodeport-range by ID +* +* Update operation of resource: nodeport-range* +* +* @param[in] name ID of name +* @param[in] value Port range used for NodePort Services +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_range_by_id(const std::string &name, const std::string &value) { + auto k8sdispatcher = get_cube(name); + + return k8sdispatcher->setNodeportRange(value); +} + +/** +* @brief Update nodeport-rule by ID +* +* Update operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + + return nodeportRule->update(value); +} + +/** +* @brief Update external-traffic-policy by ID +* +* Update operation of resource: external-traffic-policy* +* +* @param[in] name ID of name +* @param[in] nodeportPort ID of nodeport-port +* @param[in] proto ID of proto +* @param[in] value The external traffic policy of the Kubernetes NodePort Service +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleExternalTrafficPolicyEnum &value) { + auto k8sdispatcher = get_cube(name); + auto nodeportRule = k8sdispatcher->getNodeportRule(nodeportPort, proto); + + return nodeportRule->setExternalTrafficPolicy(value); +} + +/** +* @brief Update nodeport-rule by ID +* +* Update operation of resource: nodeport-rule* +* +* @param[in] name ID of name +* @param[in] value nodeport-rulebody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value) { + auto k8sdispatcher = get_cube(name); + return k8sdispatcher->updateNodeportRuleList(value); +} + +/** +* @brief Update ports by ID +* +* Update operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] portsName ID of ports_name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value) { + auto k8sdispatcher = get_cube(name); + auto ports = k8sdispatcher->getPorts(portsName); + + return ports->update(value); +} + +/** +* @brief Update ports by ID +* +* Update operation of resource: ports* +* +* @param[in] name ID of name +* @param[in] value portsbody object +* +* Responses: +* +*/ +void +update_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value) { + throw std::runtime_error("Method not supported"); +} + + + +/* + * help related + */ + +std::vector> read_k8sdispatcher_nodeport_rule_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&nodeportRule = k8sdispatcher->getNodeportRuleList(); + for(auto &i : nodeportRule) { + nlohmann::fifo_map keys; + + keys["nodeportPort"] = std::to_string(i->getNodeportPort()); + keys["proto"] = NodeportRuleJsonObject::L4ProtoEnum_to_string(i->getProto()); + + r.push_back(keys); + } + return r; +} + +std::vector> read_k8sdispatcher_ports_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&ports = k8sdispatcher->getPortsList(); + for(auto &i : ports) { + nlohmann::fifo_map keys; + + keys["name"] = i->getName(); + + r.push_back(keys); + } + return r; +} + +std::vector> read_k8sdispatcher_session_rule_list_by_id_get_list(const std::string &name) { + std::vector> r; + auto &&k8sdispatcher = get_cube(name); + + auto &&sessionRule = k8sdispatcher->getSessionRuleList(); + for(auto &i : sessionRule) { + nlohmann::fifo_map keys; + + keys["direction"] = SessionRuleJsonObject::SessionRuleDirectionEnum_to_string(i->getDirection()); + keys["srcIp"] = i->getSrcIp(); + keys["dstIp"] = i->getDstIp(); + keys["srcPort"] = std::to_string(i->getSrcPort()); + keys["dstPort"] = std::to_string(i->getDstPort()); + keys["proto"] = SessionRuleJsonObject::L4ProtoEnum_to_string(i->getProto()); + + r.push_back(keys); + } + return r; +} + + +} + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h new file mode 100644 index 000000000..15333cfc5 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/api/K8sdispatcherApiImpl.h @@ -0,0 +1,90 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherApiImpl.h +* +* +*/ + +#pragma once + + +#include +#include +#include +#include "../K8sdispatcher.h" + +#include "K8sdispatcherJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include "SessionRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace api { + +using namespace polycube::service::model; + +namespace K8sdispatcherApiImpl { + void create_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void create_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void create_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void create_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void create_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + void delete_k8sdispatcher_by_id(const std::string &name); + void delete_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + void delete_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name); + void delete_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName); + void delete_k8sdispatcher_ports_list_by_id(const std::string &name); + K8sdispatcherJsonObject read_k8sdispatcher_by_id(const std::string &name); + std::string read_k8sdispatcher_internal_src_ip_by_id(const std::string &name); + std::vector read_k8sdispatcher_list_by_id(); + std::string read_k8sdispatcher_nodeport_range_by_id(const std::string &name); + NodeportRuleJsonObject read_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + NodeportRuleExternalTrafficPolicyEnum read_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + std::vector read_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name); + std::string read_k8sdispatcher_nodeport_rule_rule_name_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto); + PortsJsonObject read_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName); + std::string read_k8sdispatcher_ports_ip_by_id(const std::string &name, const std::string &portsName); + std::vector read_k8sdispatcher_ports_list_by_id(const std::string &name); + PortsTypeEnum read_k8sdispatcher_ports_type_by_id(const std::string &name, const std::string &portsName); + SessionRuleJsonObject read_k8sdispatcher_session_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + std::vector read_k8sdispatcher_session_rule_list_by_id(const std::string &name); + std::string read_k8sdispatcher_session_rule_new_ip_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + uint16_t read_k8sdispatcher_session_rule_new_port_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + SessionRuleOperationEnum read_k8sdispatcher_session_rule_operation_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + SessionRuleOriginatingRuleEnum read_k8sdispatcher_session_rule_originating_rule_by_id(const std::string &name, const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto); + void replace_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void replace_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void replace_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void replace_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void replace_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + void update_k8sdispatcher_by_id(const std::string &name, const K8sdispatcherJsonObject &value); + void update_k8sdispatcher_list_by_id(const std::vector &value); + void update_k8sdispatcher_nodeport_range_by_id(const std::string &name, const std::string &value); + void update_k8sdispatcher_nodeport_rule_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &value); + void update_k8sdispatcher_nodeport_rule_external_traffic_policy_by_id(const std::string &name, const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleExternalTrafficPolicyEnum &value); + void update_k8sdispatcher_nodeport_rule_list_by_id(const std::string &name, const std::vector &value); + void update_k8sdispatcher_ports_by_id(const std::string &name, const std::string &portsName, const PortsJsonObject &value); + void update_k8sdispatcher_ports_list_by_id(const std::string &name, const std::vector &value); + + /* help related */ + std::vector> read_k8sdispatcher_list_by_id_get_list(); + std::vector> read_k8sdispatcher_nodeport_rule_list_by_id_get_list(const std::string &name); + std::vector> read_k8sdispatcher_ports_list_by_id_get_list(const std::string &name); + std::vector> read_k8sdispatcher_session_rule_list_by_id_get_list(const std::string &name); + +} +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp new file mode 100644 index 000000000..8eb540dbe --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.cpp @@ -0,0 +1,171 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "K8sdispatcherBase.h" + +K8sdispatcherBase::K8sdispatcherBase(const std::string name) { + logger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [K8sdispatcher] [%n] [%l] %v"); +} + + + +K8sdispatcherBase::~K8sdispatcherBase() {} + +void K8sdispatcherBase::update(const K8sdispatcherJsonObject &conf) { + set_conf(conf.getBase()); + + if (conf.portsIsSet()) { + for (auto &i : conf.getPorts()) { + auto name = i.getName(); + auto m = getPorts(name); + m->update(i); + } + } + if (conf.nodeportRangeIsSet()) { + setNodeportRange(conf.getNodeportRange()); + } + if (conf.sessionRuleIsSet()) { + for (auto &i : conf.getSessionRule()) { + auto direction = i.getDirection(); + auto srcIp = i.getSrcIp(); + auto dstIp = i.getDstIp(); + auto srcPort = i.getSrcPort(); + auto dstPort = i.getDstPort(); + auto proto = i.getProto(); + auto m = getSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + m->update(i); + } + } + if (conf.nodeportRuleIsSet()) { + for (auto &i : conf.getNodeportRule()) { + auto nodeportPort = i.getNodeportPort(); + auto proto = i.getProto(); + auto m = getNodeportRule(nodeportPort, proto); + m->update(i); + } + } +} + +K8sdispatcherJsonObject K8sdispatcherBase::toJsonObject() { + K8sdispatcherJsonObject conf; + conf.setBase(to_json()); + + conf.setName(getName()); + for (auto &i : getPortsList()) { + conf.addPorts(i->toJsonObject()); + } + conf.setInternalSrcIp(getInternalSrcIp()); + conf.setNodeportRange(getNodeportRange()); + for(auto &i : getSessionRuleList()) { + conf.addSessionRule(i->toJsonObject()); + } + for(auto &i : getNodeportRuleList()) { + conf.addNodeportRule(i->toJsonObject()); + } + + return conf; +} +void K8sdispatcherBase::addPortsList(const std::vector &conf) { + for (auto &i : conf) { + std::string name_ = i.getName(); + addPorts(name_, i); + } +} + +void K8sdispatcherBase::replacePorts(const std::string &name, const PortsJsonObject &conf) { + delPorts(name); + std::string name_ = conf.getName(); + addPorts(name_, conf); +} + +void K8sdispatcherBase::delPortsList() { + auto elements = getPortsList(); + for (auto &i : elements) { + std::string name_ = i->getName(); + delPorts(name_); + } +} + +void K8sdispatcherBase::addPorts(const std::string &name, const PortsJsonObject &conf) { + add_port(name, conf); +} + +void K8sdispatcherBase::delPorts(const std::string &name) { + remove_port(name); +} + +std::shared_ptr K8sdispatcherBase::getPorts(const std::string &name) { + return get_port(name); +} + +std::vector> K8sdispatcherBase::getPortsList() { + return get_ports(); +} +void K8sdispatcherBase::addSessionRuleList(const std::vector &conf) { + for (auto &i : conf) { + SessionRuleDirectionEnum direction_ = i.getDirection(); + std::string srcIp_ = i.getSrcIp(); + std::string dstIp_ = i.getDstIp(); + uint16_t srcPort_ = i.getSrcPort(); + uint16_t dstPort_ = i.getDstPort(); + L4ProtoEnum proto_ = i.getProto(); + addSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_, i); + } +} + +void K8sdispatcherBase::replaceSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) { + delSessionRule(direction, srcIp, dstIp, srcPort, dstPort, proto); + SessionRuleDirectionEnum direction_ = conf.getDirection(); + std::string srcIp_ = conf.getSrcIp(); + std::string dstIp_ = conf.getDstIp(); + uint16_t srcPort_ = conf.getSrcPort(); + uint16_t dstPort_ = conf.getDstPort(); + L4ProtoEnum proto_ = conf.getProto(); + addSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_, conf); +} + +void K8sdispatcherBase::delSessionRuleList() { + auto elements = getSessionRuleList(); + for (auto &i : elements) { + SessionRuleDirectionEnum direction_ = i->getDirection(); + std::string srcIp_ = i->getSrcIp(); + std::string dstIp_ = i->getDstIp(); + uint16_t srcPort_ = i->getSrcPort(); + uint16_t dstPort_ = i->getDstPort(); + L4ProtoEnum proto_ = i->getProto(); + delSessionRule(direction_, srcIp_, dstIp_, srcPort_, dstPort_, proto_); + } +} +void K8sdispatcherBase::addNodeportRuleList(const std::vector &conf) { + for (auto &i : conf) { + uint16_t nodeportPort_ = i.getNodeportPort(); + L4ProtoEnum proto_ = i.getProto(); + addNodeportRule(nodeportPort_, proto_, i); + } +} + +void K8sdispatcherBase::replaceNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf) { + delNodeportRule(nodeportPort, proto); + uint16_t nodeportPort_ = conf.getNodeportPort(); + L4ProtoEnum proto_ = conf.getProto(); + addNodeportRule(nodeportPort_, proto_, conf); +} + +void K8sdispatcherBase::delNodeportRuleList() { + auto elements = getNodeportRuleList(); + for (auto &i : elements) { + uint16_t nodeportPort_ = i->getNodeportPort(); + L4ProtoEnum proto_ = i->getProto(); + delNodeportRule(nodeportPort_, proto_); + } +} + + diff --git a/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h new file mode 100644 index 000000000..375b025e1 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/K8sdispatcherBase.h @@ -0,0 +1,91 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherBase.h +* +* +*/ + +#pragma once + +#include "../serializer/K8sdispatcherJsonObject.h" + +#include "../NodeportRule.h" +#include "../Ports.h" +#include "../SessionRule.h" + +#include "polycube/services/cube.h" +#include "polycube/services/port.h" + + + +#include "polycube/services/utils.h" +#include "polycube/services/fifo_map.hpp" + +#include + +using namespace polycube::service::model; + + +class K8sdispatcherBase: public virtual polycube::service::Cube { + public: + K8sdispatcherBase(const std::string name); + + virtual ~K8sdispatcherBase(); + virtual void update(const K8sdispatcherJsonObject &conf); + virtual K8sdispatcherJsonObject toJsonObject(); + + /// + /// Entry of the ports table + /// + virtual std::shared_ptr getPorts(const std::string &name); + virtual std::vector> getPortsList(); + virtual void addPorts(const std::string &name, const PortsJsonObject &conf); + virtual void addPortsList(const std::vector &conf); + virtual void replacePorts(const std::string &name, const PortsJsonObject &conf); + virtual void delPorts(const std::string &name); + virtual void delPortsList(); + + /// + /// Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy + /// + virtual std::string getInternalSrcIp() = 0; + + /// + /// Port range used for NodePort Services + /// + virtual std::string getNodeportRange() = 0; + virtual void setNodeportRange(const std::string &value) = 0; + + /// + /// Session entry related to a specific traffic direction + /// + virtual std::shared_ptr getSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto) = 0; + virtual std::vector> getSessionRuleList() = 0; + virtual void addSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf) = 0; + virtual void addSessionRuleList(const std::vector &conf); + virtual void replaceSessionRule(const SessionRuleDirectionEnum &direction, const std::string &srcIp, const std::string &dstIp, const uint16_t &srcPort, const uint16_t &dstPort, const L4ProtoEnum &proto, const SessionRuleJsonObject &conf); + virtual void delSessionRule(const SessionRuleDirectionEnum &direction,const std::string &srcIp,const std::string &dstIp,const uint16_t &srcPort,const uint16_t &dstPort,const L4ProtoEnum &proto) = 0; + virtual void delSessionRuleList(); + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + virtual std::shared_ptr getNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto) = 0; + virtual std::vector> getNodeportRuleList() = 0; + virtual void addNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf) = 0; + virtual void addNodeportRuleList(const std::vector &conf); + virtual void updateNodeportRuleList(const std::vector &conf) = 0; + virtual void replaceNodeportRule(const uint16_t &nodeportPort, const L4ProtoEnum &proto, const NodeportRuleJsonObject &conf); + virtual void replaceNodeportRuleList(const std::vector &conf) = 0; + virtual void delNodeportRule(const uint16_t &nodeportPort,const L4ProtoEnum &proto) = 0; + virtual void delNodeportRuleList(); +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp new file mode 100644 index 000000000..97be76fea --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.cpp @@ -0,0 +1,42 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "NodeportRuleBase.h" +#include "../K8sdispatcher.h" + + +NodeportRuleBase::NodeportRuleBase(K8sdispatcher &parent) + : parent_(parent) {} + +NodeportRuleBase::~NodeportRuleBase() {} + +void NodeportRuleBase::update(const NodeportRuleJsonObject &conf) { + + if (conf.externalTrafficPolicyIsSet()) { + setExternalTrafficPolicy(conf.getExternalTrafficPolicy()); + } +} + +NodeportRuleJsonObject NodeportRuleBase::toJsonObject() { + NodeportRuleJsonObject conf; + + conf.setNodeportPort(getNodeportPort()); + conf.setProto(getProto()); + conf.setExternalTrafficPolicy(getExternalTrafficPolicy()); + conf.setRuleName(getRuleName()); + + return conf; +} + +std::shared_ptr NodeportRuleBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h new file mode 100644 index 000000000..e819bf274 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/NodeportRuleBase.h @@ -0,0 +1,65 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* NodeportRuleBase.h +* +* +*/ + +#pragma once + +#include "../serializer/NodeportRuleJsonObject.h" + + + + + + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; + +class NodeportRuleBase { + public: + + NodeportRuleBase(K8sdispatcher &parent); + + virtual ~NodeportRuleBase(); + virtual void update(const NodeportRuleJsonObject &conf); + virtual NodeportRuleJsonObject toJsonObject(); + + /// + /// NodePort rule nodeport port number + /// + virtual uint16_t getNodeportPort() = 0; + + /// + /// NodePort rule L4 protocol + /// + virtual L4ProtoEnum getProto() = 0; + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + virtual NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() = 0; + virtual void setExternalTrafficPolicy(const NodeportRuleExternalTrafficPolicyEnum &value) = 0; + + /// + /// An optional name for the NodePort rule + /// + virtual std::string getRuleName() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp b/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp new file mode 100644 index 000000000..d55efb130 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/PortsBase.cpp @@ -0,0 +1,41 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "PortsBase.h" +#include "../K8sdispatcher.h" + +PortsBase::PortsBase(polycube::service::Cube &parent, + std::shared_ptr port) + : Port(port), parent_(dynamic_cast(parent)) {} + + +PortsBase::~PortsBase() {} + +void PortsBase::update(const PortsJsonObject &conf) { + set_conf(conf.getBase()); + +} + +PortsJsonObject PortsBase::toJsonObject() { + PortsJsonObject conf; + conf.setBase(to_json()); + + conf.setName(getName()); + conf.setType(getType()); + conf.setIp(getIp()); + + return conf; +} + +std::shared_ptr PortsBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/PortsBase.h b/src/services/pcn-k8sdispatcher/src/base/PortsBase.h new file mode 100644 index 000000000..33cf3163a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/PortsBase.h @@ -0,0 +1,59 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* PortsBase.h +* +* +*/ + +#pragma once + +#include "../serializer/PortsJsonObject.h" + + + + +#include "polycube/services/cube.h" +#include "polycube/services/port.h" + +#include "polycube/services/utils.h" +#include "polycube/services/fifo_map.hpp" + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; +class Ports; + +class PortsBase: public polycube::service::Port { + public: + PortsBase(polycube::service::Cube &parent, + std::shared_ptr port); + + virtual ~PortsBase(); + virtual void update(const PortsJsonObject &conf); + virtual PortsJsonObject toJsonObject(); + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + virtual PortsTypeEnum getType() = 0; + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + virtual std::string getIp() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp new file mode 100644 index 000000000..2b5a8a3f4 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.cpp @@ -0,0 +1,45 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + +#include "SessionRuleBase.h" +#include "../K8sdispatcher.h" + + +SessionRuleBase::SessionRuleBase(K8sdispatcher &parent) + : parent_(parent) {} + +SessionRuleBase::~SessionRuleBase() {} + +void SessionRuleBase::update(const SessionRuleJsonObject &conf) { + +} + +SessionRuleJsonObject SessionRuleBase::toJsonObject() { + SessionRuleJsonObject conf; + + conf.setDirection(getDirection()); + conf.setSrcIp(getSrcIp()); + conf.setDstIp(getDstIp()); + conf.setSrcPort(getSrcPort()); + conf.setDstPort(getDstPort()); + conf.setProto(getProto()); + conf.setNewIp(getNewIp()); + conf.setNewPort(getNewPort()); + conf.setOperation(getOperation()); + conf.setOriginatingRule(getOriginatingRule()); + + return conf; +} + +std::shared_ptr SessionRuleBase::logger() { + return parent_.logger(); +} + diff --git a/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h new file mode 100644 index 000000000..90e2ab09a --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/base/SessionRuleBase.h @@ -0,0 +1,94 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* SessionRuleBase.h +* +* +*/ + +#pragma once + +#include "../serializer/SessionRuleJsonObject.h" + + + + + + +#include + +using namespace polycube::service::model; + +class K8sdispatcher; + +class SessionRuleBase { + public: + + SessionRuleBase(K8sdispatcher &parent); + + virtual ~SessionRuleBase(); + virtual void update(const SessionRuleJsonObject &conf); + virtual SessionRuleJsonObject toJsonObject(); + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + virtual SessionRuleDirectionEnum getDirection() = 0; + + /// + /// Session entry source IP address + /// + virtual std::string getSrcIp() = 0; + + /// + /// Session entry destination IP address + /// + virtual std::string getDstIp() = 0; + + /// + /// Session entry source L4 port number + /// + virtual uint16_t getSrcPort() = 0; + + /// + /// Session entry destination L4 port number + /// + virtual uint16_t getDstPort() = 0; + + /// + /// Session entry L4 protocol + /// + virtual L4ProtoEnum getProto() = 0; + + /// + /// Translated IP address + /// + virtual std::string getNewIp() = 0; + + /// + /// Translated L4 port number + /// + virtual uint16_t getNewPort() = 0; + + /// + /// Operation applied on the original packet + /// + virtual SessionRuleOperationEnum getOperation() = 0; + + /// + /// Rule originating the session entry + /// + virtual SessionRuleOriginatingRuleEnum getOriginatingRule() = 0; + + std::shared_ptr logger(); + protected: + K8sdispatcher &parent_; +}; diff --git a/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp new file mode 100644 index 000000000..2296417c0 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.cpp @@ -0,0 +1,69 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +#include "JsonObjectBase.h" + +namespace polycube { +namespace service { +namespace model { + +JsonObjectBase::JsonObjectBase(const nlohmann::json &base) : base_(base) {} + +bool JsonObjectBase::iequals(const std::string &a, const std::string &b) { + if(a.size() != b.size()) + return false; + for (unsigned int i = 0; i < a.size(); i++){ + if(tolower(a[i]) != tolower(b[i])) + return false; + } + return true; +} + +std::string JsonObjectBase::toJson(const std::string& value) { + return value; +} + +std::string JsonObjectBase::toJson(const std::time_t& value) { + char buf[sizeof "2011-10-08T07:07:09Z"]; + strftime(buf, sizeof buf, "%FT%TZ", gmtime(&value)); + return buf; +} + +int32_t JsonObjectBase::toJson(int32_t value) { + return value; +} + +int64_t JsonObjectBase::toJson(int64_t value) { + return value; +} + +double JsonObjectBase::toJson(double value) { + return value; +} + +bool JsonObjectBase::toJson(bool value) { + return value; +} + +nlohmann::json JsonObjectBase::toJson(const JsonObjectBase &content) { + return content.toJson(); +} + +const nlohmann::json &JsonObjectBase::getBase() const { + return base_; +} + +void JsonObjectBase::setBase(const nlohmann::json &base) { + base_ = base; +} + +} +} +} diff --git a/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h new file mode 100644 index 000000000..bda8934b9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/JsonObjectBase.h @@ -0,0 +1,55 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* JsonObjectBase.h +* +* This is the base class for all model classes +*/ + +#pragma once + + +#include "polycube/services/json.hpp" +#include "polycube/services/fifo_map.hpp" +#include +#include + +namespace polycube { +namespace service { +namespace model { + +class JsonObjectBase { + public: + JsonObjectBase() = default; + JsonObjectBase(const nlohmann::json &base); + virtual ~JsonObjectBase() = default; + + virtual nlohmann::json toJson() const = 0; + + static bool iequals(const std::string &a, const std::string &b); + static std::string toJson(const std::string& value); + static std::string toJson(const std::time_t& value); + static int32_t toJson(int32_t value); + static int64_t toJson(int64_t value); + static double toJson(double value); + static bool toJson(bool value); + static nlohmann::json toJson(const JsonObjectBase &content); + + const nlohmann::json &getBase() const; + void setBase(const nlohmann::json &base); + + private: + nlohmann::json base_; +}; + +} +} +} diff --git a/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp new file mode 100644 index 000000000..9ce528e74 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.cpp @@ -0,0 +1,239 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "K8sdispatcherJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +K8sdispatcherJsonObject::K8sdispatcherJsonObject() { + m_nameIsSet = false; + m_portsIsSet = false; + m_internalSrcIpIsSet = false; + m_nodeportRange = "30000-32767"; + m_nodeportRangeIsSet = true; + m_sessionRuleIsSet = false; + m_nodeportRuleIsSet = false; +} + +K8sdispatcherJsonObject::K8sdispatcherJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nameIsSet = false; + m_portsIsSet = false; + m_internalSrcIpIsSet = false; + m_nodeportRangeIsSet = false; + m_sessionRuleIsSet = false; + m_nodeportRuleIsSet = false; + + + if (val.count("name")) { + setName(val.at("name").get()); + } + + if (val.count("ports")) { + for (auto& item : val["ports"]) { + PortsJsonObject newItem{ item }; + m_ports.push_back(newItem); + } + + m_portsIsSet = true; + } + + if (val.count("internal-src-ip")) { + setInternalSrcIp(val.at("internal-src-ip").get()); + } + + if (val.count("nodeport-range")) { + setNodeportRange(val.at("nodeport-range").get()); + } + + if (val.count("session-rule")) { + for (auto& item : val["session-rule"]) { + SessionRuleJsonObject newItem{ item }; + m_sessionRule.push_back(newItem); + } + + m_sessionRuleIsSet = true; + } + + if (val.count("nodeport-rule")) { + for (auto& item : val["nodeport-rule"]) { + NodeportRuleJsonObject newItem{ item }; + m_nodeportRule.push_back(newItem); + } + + m_nodeportRuleIsSet = true; + } +} + +nlohmann::json K8sdispatcherJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nameIsSet) { + val["name"] = m_name; + } + + { + nlohmann::json jsonArray; + for (auto& item : m_ports) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["ports"] = jsonArray; + } + } + + if (m_internalSrcIpIsSet) { + val["internal-src-ip"] = m_internalSrcIp; + } + + if (m_nodeportRangeIsSet) { + val["nodeport-range"] = m_nodeportRange; + } + + { + nlohmann::json jsonArray; + for (auto& item : m_sessionRule) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["session-rule"] = jsonArray; + } + } + + { + nlohmann::json jsonArray; + for (auto& item : m_nodeportRule) { + jsonArray.push_back(JsonObjectBase::toJson(item)); + } + + if (jsonArray.size() > 0) { + val["nodeport-rule"] = jsonArray; + } + } + + return val; +} + +std::string K8sdispatcherJsonObject::getName() const { + return m_name; +} + +void K8sdispatcherJsonObject::setName(std::string value) { + m_name = value; + m_nameIsSet = true; +} + +bool K8sdispatcherJsonObject::nameIsSet() const { + return m_nameIsSet; +} + + + +const std::vector& K8sdispatcherJsonObject::getPorts() const{ + return m_ports; +} + +void K8sdispatcherJsonObject::addPorts(PortsJsonObject value) { + m_ports.push_back(value); + m_portsIsSet = true; +} + + +bool K8sdispatcherJsonObject::portsIsSet() const { + return m_portsIsSet; +} + +void K8sdispatcherJsonObject::unsetPorts() { + m_portsIsSet = false; +} + +std::string K8sdispatcherJsonObject::getInternalSrcIp() const { + return m_internalSrcIp; +} + +void K8sdispatcherJsonObject::setInternalSrcIp(std::string value) { + m_internalSrcIp = value; + m_internalSrcIpIsSet = true; +} + +bool K8sdispatcherJsonObject::internalSrcIpIsSet() const { + return m_internalSrcIpIsSet; +} + + + +std::string K8sdispatcherJsonObject::getNodeportRange() const { + return m_nodeportRange; +} + +void K8sdispatcherJsonObject::setNodeportRange(std::string value) { + m_nodeportRange = value; + m_nodeportRangeIsSet = true; +} + +bool K8sdispatcherJsonObject::nodeportRangeIsSet() const { + return m_nodeportRangeIsSet; +} + +void K8sdispatcherJsonObject::unsetNodeportRange() { + m_nodeportRangeIsSet = false; +} + +const std::vector& K8sdispatcherJsonObject::getSessionRule() const{ + return m_sessionRule; +} + +void K8sdispatcherJsonObject::addSessionRule(SessionRuleJsonObject value) { + m_sessionRule.push_back(value); + m_sessionRuleIsSet = true; +} + + +bool K8sdispatcherJsonObject::sessionRuleIsSet() const { + return m_sessionRuleIsSet; +} + +void K8sdispatcherJsonObject::unsetSessionRule() { + m_sessionRuleIsSet = false; +} + +const std::vector& K8sdispatcherJsonObject::getNodeportRule() const{ + return m_nodeportRule; +} + +void K8sdispatcherJsonObject::addNodeportRule(NodeportRuleJsonObject value) { + m_nodeportRule.push_back(value); + m_nodeportRuleIsSet = true; +} + + +bool K8sdispatcherJsonObject::nodeportRuleIsSet() const { + return m_nodeportRuleIsSet; +} + +void K8sdispatcherJsonObject::unsetNodeportRule() { + m_nodeportRuleIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h new file mode 100644 index 000000000..d0f80cef9 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/K8sdispatcherJsonObject.h @@ -0,0 +1,108 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* K8sdispatcherJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + +#include "SessionRuleJsonObject.h" +#include "NodeportRuleJsonObject.h" +#include "PortsJsonObject.h" +#include +#include "polycube/services/cube.h" + +namespace polycube { +namespace service { +namespace model { + + +/// +/// +/// +class K8sdispatcherJsonObject : public JsonObjectBase { +public: + K8sdispatcherJsonObject(); + K8sdispatcherJsonObject(const nlohmann::json &json); + ~K8sdispatcherJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Name of the k8sdispatcher service + /// + std::string getName() const; + void setName(std::string value); + bool nameIsSet() const; + + /// + /// Entry of the ports table + /// + const std::vector& getPorts() const; + void addPorts(PortsJsonObject value); + bool portsIsSet() const; + void unsetPorts(); + + /// + /// Internal source IP address used for natting incoming packets directed to Kubernetes Services with a CLUSTER external traffic policy + /// + std::string getInternalSrcIp() const; + void setInternalSrcIp(std::string value); + bool internalSrcIpIsSet() const; + + /// + /// Port range used for NodePort Services + /// + std::string getNodeportRange() const; + void setNodeportRange(std::string value); + bool nodeportRangeIsSet() const; + void unsetNodeportRange(); + + /// + /// Session entry related to a specific traffic direction + /// + const std::vector& getSessionRule() const; + void addSessionRule(SessionRuleJsonObject value); + bool sessionRuleIsSet() const; + void unsetSessionRule(); + + /// + /// NodePort rule associated with a Kubernetes NodePort Service + /// + const std::vector& getNodeportRule() const; + void addNodeportRule(NodeportRuleJsonObject value); + bool nodeportRuleIsSet() const; + void unsetNodeportRule(); + +private: + std::string m_name; + bool m_nameIsSet; + std::vector m_ports; + bool m_portsIsSet; + std::string m_internalSrcIp; + bool m_internalSrcIpIsSet; + std::string m_nodeportRange; + bool m_nodeportRangeIsSet; + std::vector m_sessionRule; + bool m_sessionRuleIsSet; + std::vector m_nodeportRule; + bool m_nodeportRuleIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp new file mode 100644 index 000000000..43894f2dd --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.cpp @@ -0,0 +1,186 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "NodeportRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +NodeportRuleJsonObject::NodeportRuleJsonObject() { + m_nodeportPortIsSet = false; + m_protoIsSet = false; + m_externalTrafficPolicy = NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + m_externalTrafficPolicyIsSet = true; + m_ruleNameIsSet = false; +} + +NodeportRuleJsonObject::NodeportRuleJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nodeportPortIsSet = false; + m_protoIsSet = false; + m_externalTrafficPolicyIsSet = false; + m_ruleNameIsSet = false; + + + if (val.count("nodeport-port")) { + setNodeportPort(val.at("nodeport-port").get()); + } + + if (val.count("proto")) { + setProto(string_to_L4ProtoEnum(val.at("proto").get())); + } + + if (val.count("external-traffic-policy")) { + setExternalTrafficPolicy(string_to_NodeportRuleExternalTrafficPolicyEnum(val.at("external-traffic-policy").get())); + } + + if (val.count("rule-name")) { + setRuleName(val.at("rule-name").get()); + } +} + +nlohmann::json NodeportRuleJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nodeportPortIsSet) { + val["nodeport-port"] = m_nodeportPort; + } + + if (m_protoIsSet) { + val["proto"] = L4ProtoEnum_to_string(m_proto); + } + + if (m_externalTrafficPolicyIsSet) { + val["external-traffic-policy"] = NodeportRuleExternalTrafficPolicyEnum_to_string(m_externalTrafficPolicy); + } + + if (m_ruleNameIsSet) { + val["rule-name"] = m_ruleName; + } + + return val; +} + +uint16_t NodeportRuleJsonObject::getNodeportPort() const { + return m_nodeportPort; +} + +void NodeportRuleJsonObject::setNodeportPort(uint16_t value) { + m_nodeportPort = value; + m_nodeportPortIsSet = true; +} + +bool NodeportRuleJsonObject::nodeportPortIsSet() const { + return m_nodeportPortIsSet; +} + + + +L4ProtoEnum NodeportRuleJsonObject::getProto() const { + return m_proto; +} + +void NodeportRuleJsonObject::setProto(L4ProtoEnum value) { + m_proto = value; + m_protoIsSet = true; +} + +bool NodeportRuleJsonObject::protoIsSet() const { + return m_protoIsSet; +} + + + +std::string NodeportRuleJsonObject::L4ProtoEnum_to_string(const L4ProtoEnum &value){ + switch(value) { + case L4ProtoEnum::TCP: + return std::string("tcp"); + case L4ProtoEnum::UDP: + return std::string("udp"); + case L4ProtoEnum::ICMP: + return std::string("icmp"); + default: + throw std::runtime_error("Bad NodeportRule proto"); + } +} + +L4ProtoEnum NodeportRuleJsonObject::string_to_L4ProtoEnum(const std::string &str){ + if (JsonObjectBase::iequals("tcp", str)) + return L4ProtoEnum::TCP; + if (JsonObjectBase::iequals("udp", str)) + return L4ProtoEnum::UDP; + if (JsonObjectBase::iequals("icmp", str)) + return L4ProtoEnum::ICMP; + throw std::runtime_error("NodeportRule proto is invalid"); +} +NodeportRuleExternalTrafficPolicyEnum NodeportRuleJsonObject::getExternalTrafficPolicy() const { + return m_externalTrafficPolicy; +} + +void NodeportRuleJsonObject::setExternalTrafficPolicy(NodeportRuleExternalTrafficPolicyEnum value) { + m_externalTrafficPolicy = value; + m_externalTrafficPolicyIsSet = true; +} + +bool NodeportRuleJsonObject::externalTrafficPolicyIsSet() const { + return m_externalTrafficPolicyIsSet; +} + +void NodeportRuleJsonObject::unsetExternalTrafficPolicy() { + m_externalTrafficPolicyIsSet = false; +} + +std::string NodeportRuleJsonObject::NodeportRuleExternalTrafficPolicyEnum_to_string(const NodeportRuleExternalTrafficPolicyEnum &value){ + switch(value) { + case NodeportRuleExternalTrafficPolicyEnum::LOCAL: + return std::string("local"); + case NodeportRuleExternalTrafficPolicyEnum::CLUSTER: + return std::string("cluster"); + default: + throw std::runtime_error("Bad NodeportRule externalTrafficPolicy"); + } +} + +NodeportRuleExternalTrafficPolicyEnum NodeportRuleJsonObject::string_to_NodeportRuleExternalTrafficPolicyEnum(const std::string &str){ + if (JsonObjectBase::iequals("local", str)) + return NodeportRuleExternalTrafficPolicyEnum::LOCAL; + if (JsonObjectBase::iequals("cluster", str)) + return NodeportRuleExternalTrafficPolicyEnum::CLUSTER; + throw std::runtime_error("NodeportRule externalTrafficPolicy is invalid"); +} +std::string NodeportRuleJsonObject::getRuleName() const { + return m_ruleName; +} + +void NodeportRuleJsonObject::setRuleName(std::string value) { + m_ruleName = value; + m_ruleNameIsSet = true; +} + +bool NodeportRuleJsonObject::ruleNameIsSet() const { + return m_ruleNameIsSet; +} + +void NodeportRuleJsonObject::unsetRuleName() { + m_ruleNameIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h new file mode 100644 index 000000000..9f3838c2d --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/NodeportRuleJsonObject.h @@ -0,0 +1,96 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* NodeportRuleJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + + +namespace polycube { +namespace service { +namespace model { + +#ifndef L4PROTOENUM +#define L4PROTOENUM +enum class L4ProtoEnum { + TCP, UDP, ICMP +}; +#endif +enum class NodeportRuleExternalTrafficPolicyEnum { + LOCAL, CLUSTER +}; + +/// +/// +/// +class NodeportRuleJsonObject : public JsonObjectBase { +public: + NodeportRuleJsonObject(); + NodeportRuleJsonObject(const nlohmann::json &json); + ~NodeportRuleJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// NodePort rule nodeport port number + /// + uint16_t getNodeportPort() const; + void setNodeportPort(uint16_t value); + bool nodeportPortIsSet() const; + + /// + /// NodePort rule L4 protocol + /// + L4ProtoEnum getProto() const; + void setProto(L4ProtoEnum value); + bool protoIsSet() const; + static std::string L4ProtoEnum_to_string(const L4ProtoEnum &value); + static L4ProtoEnum string_to_L4ProtoEnum(const std::string &str); + + /// + /// The external traffic policy of the Kubernetes NodePort Service + /// + NodeportRuleExternalTrafficPolicyEnum getExternalTrafficPolicy() const; + void setExternalTrafficPolicy(NodeportRuleExternalTrafficPolicyEnum value); + bool externalTrafficPolicyIsSet() const; + void unsetExternalTrafficPolicy(); + static std::string NodeportRuleExternalTrafficPolicyEnum_to_string(const NodeportRuleExternalTrafficPolicyEnum &value); + static NodeportRuleExternalTrafficPolicyEnum string_to_NodeportRuleExternalTrafficPolicyEnum(const std::string &str); + + /// + /// An optional name for the NodePort rule + /// + std::string getRuleName() const; + void setRuleName(std::string value); + bool ruleNameIsSet() const; + void unsetRuleName(); + +private: + uint16_t m_nodeportPort; + bool m_nodeportPortIsSet; + L4ProtoEnum m_proto; + bool m_protoIsSet; + NodeportRuleExternalTrafficPolicyEnum m_externalTrafficPolicy; + bool m_externalTrafficPolicyIsSet; + std::string m_ruleName; + bool m_ruleNameIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp new file mode 100644 index 000000000..e9bd6a543 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.cpp @@ -0,0 +1,136 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "PortsJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +PortsJsonObject::PortsJsonObject() { + m_nameIsSet = false; + m_typeIsSet = false; + m_ipIsSet = false; +} + +PortsJsonObject::PortsJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_nameIsSet = false; + m_typeIsSet = false; + m_ipIsSet = false; + + + if (val.count("name")) { + setName(val.at("name").get()); + } + + if (val.count("type")) { + setType(string_to_PortsTypeEnum(val.at("type").get())); + } + + if (val.count("ip")) { + setIp(val.at("ip").get()); + } +} + +nlohmann::json PortsJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_nameIsSet) { + val["name"] = m_name; + } + + if (m_typeIsSet) { + val["type"] = PortsTypeEnum_to_string(m_type); + } + + if (m_ipIsSet) { + val["ip"] = m_ip; + } + + return val; +} + +std::string PortsJsonObject::getName() const { + return m_name; +} + +void PortsJsonObject::setName(std::string value) { + m_name = value; + m_nameIsSet = true; +} + +bool PortsJsonObject::nameIsSet() const { + return m_nameIsSet; +} + + + +PortsTypeEnum PortsJsonObject::getType() const { + return m_type; +} + +void PortsJsonObject::setType(PortsTypeEnum value) { + m_type = value; + m_typeIsSet = true; +} + +bool PortsJsonObject::typeIsSet() const { + return m_typeIsSet; +} + + + +std::string PortsJsonObject::PortsTypeEnum_to_string(const PortsTypeEnum &value){ + switch(value) { + case PortsTypeEnum::BACKEND: + return std::string("backend"); + case PortsTypeEnum::FRONTEND: + return std::string("frontend"); + default: + throw std::runtime_error("Bad Ports type"); + } +} + +PortsTypeEnum PortsJsonObject::string_to_PortsTypeEnum(const std::string &str){ + if (JsonObjectBase::iequals("backend", str)) + return PortsTypeEnum::BACKEND; + if (JsonObjectBase::iequals("frontend", str)) + return PortsTypeEnum::FRONTEND; + throw std::runtime_error("Ports type is invalid"); +} +std::string PortsJsonObject::getIp() const { + return m_ip; +} + +void PortsJsonObject::setIp(std::string value) { + m_ip = value; + m_ipIsSet = true; +} + +bool PortsJsonObject::ipIsSet() const { + return m_ipIsSet; +} + +void PortsJsonObject::unsetIp() { + m_ipIsSet = false; +} + + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h new file mode 100644 index 000000000..f15b2aab1 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/PortsJsonObject.h @@ -0,0 +1,79 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* PortsJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + +#include + +namespace polycube { +namespace service { +namespace model { + +enum class PortsTypeEnum { + BACKEND, FRONTEND +}; + +/// +/// +/// +class PortsJsonObject : public JsonObjectBase { +public: + PortsJsonObject(); + PortsJsonObject(const nlohmann::json &json); + ~PortsJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Port Name + /// + std::string getName() const; + void setName(std::string value); + bool nameIsSet() const; + + /// + /// Type of the K8s Dispatcher cube port (e.g. BACKEND or FRONTEND) + /// + PortsTypeEnum getType() const; + void setType(PortsTypeEnum value); + bool typeIsSet() const; + static std::string PortsTypeEnum_to_string(const PortsTypeEnum &value); + static PortsTypeEnum string_to_PortsTypeEnum(const std::string &str); + + /// + /// IP address of the node interface (only for FRONTEND port) + /// + std::string getIp() const; + void setIp(std::string value); + bool ipIsSet() const; + void unsetIp(); + +private: + std::string m_name; + bool m_nameIsSet; + PortsTypeEnum m_type; + bool m_typeIsSet; + std::string m_ip; + bool m_ipIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp new file mode 100644 index 000000000..225afbc0c --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.cpp @@ -0,0 +1,375 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + + + +#include "SessionRuleJsonObject.h" +#include + +namespace polycube { +namespace service { +namespace model { + +SessionRuleJsonObject::SessionRuleJsonObject() { + m_directionIsSet = false; + m_srcIpIsSet = false; + m_dstIpIsSet = false; + m_srcPortIsSet = false; + m_dstPortIsSet = false; + m_protoIsSet = false; + m_newIpIsSet = false; + m_newPortIsSet = false; + m_operationIsSet = false; + m_originatingRuleIsSet = false; +} + +SessionRuleJsonObject::SessionRuleJsonObject(const nlohmann::json &val) : + JsonObjectBase(val) { + m_directionIsSet = false; + m_srcIpIsSet = false; + m_dstIpIsSet = false; + m_srcPortIsSet = false; + m_dstPortIsSet = false; + m_protoIsSet = false; + m_newIpIsSet = false; + m_newPortIsSet = false; + m_operationIsSet = false; + m_originatingRuleIsSet = false; + + + if (val.count("direction")) { + setDirection(string_to_SessionRuleDirectionEnum(val.at("direction").get())); + } + + if (val.count("src-ip")) { + setSrcIp(val.at("src-ip").get()); + } + + if (val.count("dst-ip")) { + setDstIp(val.at("dst-ip").get()); + } + + if (val.count("src-port")) { + setSrcPort(val.at("src-port").get()); + } + + if (val.count("dst-port")) { + setDstPort(val.at("dst-port").get()); + } + + if (val.count("proto")) { + setProto(string_to_L4ProtoEnum(val.at("proto").get())); + } + + if (val.count("new-ip")) { + setNewIp(val.at("new-ip").get()); + } + + if (val.count("new-port")) { + setNewPort(val.at("new-port").get()); + } + + if (val.count("operation")) { + setOperation(string_to_SessionRuleOperationEnum(val.at("operation").get())); + } + + if (val.count("originating-rule")) { + setOriginatingRule(string_to_SessionRuleOriginatingRuleEnum(val.at("originating-rule").get())); + } +} + +nlohmann::json SessionRuleJsonObject::toJson() const { + nlohmann::json val = nlohmann::json::object(); + if (!getBase().is_null()) { + val.update(getBase()); + } + + if (m_directionIsSet) { + val["direction"] = SessionRuleDirectionEnum_to_string(m_direction); + } + + if (m_srcIpIsSet) { + val["src-ip"] = m_srcIp; + } + + if (m_dstIpIsSet) { + val["dst-ip"] = m_dstIp; + } + + if (m_srcPortIsSet) { + val["src-port"] = m_srcPort; + } + + if (m_dstPortIsSet) { + val["dst-port"] = m_dstPort; + } + + if (m_protoIsSet) { + val["proto"] = L4ProtoEnum_to_string(m_proto); + } + + if (m_newIpIsSet) { + val["new-ip"] = m_newIp; + } + + if (m_newPortIsSet) { + val["new-port"] = m_newPort; + } + + if (m_operationIsSet) { + val["operation"] = SessionRuleOperationEnum_to_string(m_operation); + } + + if (m_originatingRuleIsSet) { + val["originating-rule"] = SessionRuleOriginatingRuleEnum_to_string(m_originatingRule); + } + + return val; +} + +SessionRuleDirectionEnum SessionRuleJsonObject::getDirection() const { + return m_direction; +} + +void SessionRuleJsonObject::setDirection(SessionRuleDirectionEnum value) { + m_direction = value; + m_directionIsSet = true; +} + +bool SessionRuleJsonObject::directionIsSet() const { + return m_directionIsSet; +} + + + +std::string SessionRuleJsonObject::SessionRuleDirectionEnum_to_string(const SessionRuleDirectionEnum &value){ + switch(value) { + case SessionRuleDirectionEnum::INGRESS: + return std::string("ingress"); + case SessionRuleDirectionEnum::EGRESS: + return std::string("egress"); + default: + throw std::runtime_error("Bad SessionRule direction"); + } +} + +SessionRuleDirectionEnum SessionRuleJsonObject::string_to_SessionRuleDirectionEnum(const std::string &str){ + if (JsonObjectBase::iequals("ingress", str)) + return SessionRuleDirectionEnum::INGRESS; + if (JsonObjectBase::iequals("egress", str)) + return SessionRuleDirectionEnum::EGRESS; + throw std::runtime_error("SessionRule direction is invalid"); +} +std::string SessionRuleJsonObject::getSrcIp() const { + return m_srcIp; +} + +void SessionRuleJsonObject::setSrcIp(std::string value) { + m_srcIp = value; + m_srcIpIsSet = true; +} + +bool SessionRuleJsonObject::srcIpIsSet() const { + return m_srcIpIsSet; +} + + + +std::string SessionRuleJsonObject::getDstIp() const { + return m_dstIp; +} + +void SessionRuleJsonObject::setDstIp(std::string value) { + m_dstIp = value; + m_dstIpIsSet = true; +} + +bool SessionRuleJsonObject::dstIpIsSet() const { + return m_dstIpIsSet; +} + + + +uint16_t SessionRuleJsonObject::getSrcPort() const { + return m_srcPort; +} + +void SessionRuleJsonObject::setSrcPort(uint16_t value) { + m_srcPort = value; + m_srcPortIsSet = true; +} + +bool SessionRuleJsonObject::srcPortIsSet() const { + return m_srcPortIsSet; +} + + + +uint16_t SessionRuleJsonObject::getDstPort() const { + return m_dstPort; +} + +void SessionRuleJsonObject::setDstPort(uint16_t value) { + m_dstPort = value; + m_dstPortIsSet = true; +} + +bool SessionRuleJsonObject::dstPortIsSet() const { + return m_dstPortIsSet; +} + + + +L4ProtoEnum SessionRuleJsonObject::getProto() const { + return m_proto; +} + +void SessionRuleJsonObject::setProto(L4ProtoEnum value) { + m_proto = value; + m_protoIsSet = true; +} + +bool SessionRuleJsonObject::protoIsSet() const { + return m_protoIsSet; +} + + + +std::string SessionRuleJsonObject::L4ProtoEnum_to_string(const L4ProtoEnum &value){ + switch(value) { + case L4ProtoEnum::TCP: + return std::string("tcp"); + case L4ProtoEnum::UDP: + return std::string("udp"); + case L4ProtoEnum::ICMP: + return std::string("icmp"); + default: + throw std::runtime_error("Bad SessionRule proto"); + } +} + +L4ProtoEnum SessionRuleJsonObject::string_to_L4ProtoEnum(const std::string &str){ + if (JsonObjectBase::iequals("tcp", str)) + return L4ProtoEnum::TCP; + if (JsonObjectBase::iequals("udp", str)) + return L4ProtoEnum::UDP; + if (JsonObjectBase::iequals("icmp", str)) + return L4ProtoEnum::ICMP; + throw std::runtime_error("SessionRule proto is invalid"); +} +std::string SessionRuleJsonObject::getNewIp() const { + return m_newIp; +} + +void SessionRuleJsonObject::setNewIp(std::string value) { + m_newIp = value; + m_newIpIsSet = true; +} + +bool SessionRuleJsonObject::newIpIsSet() const { + return m_newIpIsSet; +} + +void SessionRuleJsonObject::unsetNewIp() { + m_newIpIsSet = false; +} + +uint16_t SessionRuleJsonObject::getNewPort() const { + return m_newPort; +} + +void SessionRuleJsonObject::setNewPort(uint16_t value) { + m_newPort = value; + m_newPortIsSet = true; +} + +bool SessionRuleJsonObject::newPortIsSet() const { + return m_newPortIsSet; +} + +void SessionRuleJsonObject::unsetNewPort() { + m_newPortIsSet = false; +} + +SessionRuleOperationEnum SessionRuleJsonObject::getOperation() const { + return m_operation; +} + +void SessionRuleJsonObject::setOperation(SessionRuleOperationEnum value) { + m_operation = value; + m_operationIsSet = true; +} + +bool SessionRuleJsonObject::operationIsSet() const { + return m_operationIsSet; +} + +void SessionRuleJsonObject::unsetOperation() { + m_operationIsSet = false; +} + +std::string SessionRuleJsonObject::SessionRuleOperationEnum_to_string(const SessionRuleOperationEnum &value){ + switch(value) { + case SessionRuleOperationEnum::XLATE_SRC: + return std::string("xlate_src"); + case SessionRuleOperationEnum::XLATE_DST: + return std::string("xlate_dst"); + default: + throw std::runtime_error("Bad SessionRule operation"); + } +} + +SessionRuleOperationEnum SessionRuleJsonObject::string_to_SessionRuleOperationEnum(const std::string &str){ + if (JsonObjectBase::iequals("xlate_src", str)) + return SessionRuleOperationEnum::XLATE_SRC; + if (JsonObjectBase::iequals("xlate_dst", str)) + return SessionRuleOperationEnum::XLATE_DST; + throw std::runtime_error("SessionRule operation is invalid"); +} +SessionRuleOriginatingRuleEnum SessionRuleJsonObject::getOriginatingRule() const { + return m_originatingRule; +} + +void SessionRuleJsonObject::setOriginatingRule(SessionRuleOriginatingRuleEnum value) { + m_originatingRule = value; + m_originatingRuleIsSet = true; +} + +bool SessionRuleJsonObject::originatingRuleIsSet() const { + return m_originatingRuleIsSet; +} + +void SessionRuleJsonObject::unsetOriginatingRule() { + m_originatingRuleIsSet = false; +} + +std::string SessionRuleJsonObject::SessionRuleOriginatingRuleEnum_to_string(const SessionRuleOriginatingRuleEnum &value){ + switch(value) { + case SessionRuleOriginatingRuleEnum::POD_TO_EXT: + return std::string("pod_to_ext"); + case SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER: + return std::string("nodeport_cluster"); + default: + throw std::runtime_error("Bad SessionRule originatingRule"); + } +} + +SessionRuleOriginatingRuleEnum SessionRuleJsonObject::string_to_SessionRuleOriginatingRuleEnum(const std::string &str){ + if (JsonObjectBase::iequals("pod_to_ext", str)) + return SessionRuleOriginatingRuleEnum::POD_TO_EXT; + if (JsonObjectBase::iequals("nodeport_cluster", str)) + return SessionRuleOriginatingRuleEnum::NODEPORT_CLUSTER; + throw std::runtime_error("SessionRule originatingRule is invalid"); +} + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h new file mode 100644 index 000000000..3452f2591 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/src/serializer/SessionRuleJsonObject.h @@ -0,0 +1,162 @@ +/** +* k8sdispatcher API generated from k8sdispatcher.yang +* +* NOTE: This file is auto generated by polycube-codegen +* https://github.com/polycube-network/polycube-codegen +*/ + + +/* Do not edit this file manually */ + +/* +* SessionRuleJsonObject.h +* +* +*/ + +#pragma once + + +#include "JsonObjectBase.h" + + +namespace polycube { +namespace service { +namespace model { + +enum class SessionRuleDirectionEnum { + INGRESS, EGRESS +}; +#ifndef L4PROTOENUM +#define L4PROTOENUM +enum class L4ProtoEnum { + TCP, UDP, ICMP +}; +#endif +enum class SessionRuleOperationEnum { + XLATE_SRC, XLATE_DST +}; +enum class SessionRuleOriginatingRuleEnum { + POD_TO_EXT, NODEPORT_CLUSTER +}; + +/// +/// +/// +class SessionRuleJsonObject : public JsonObjectBase { +public: + SessionRuleJsonObject(); + SessionRuleJsonObject(const nlohmann::json &json); + ~SessionRuleJsonObject() final = default; + nlohmann::json toJson() const final; + + + /// + /// Session entry direction (e.g. INGRESS or EGRESS) + /// + SessionRuleDirectionEnum getDirection() const; + void setDirection(SessionRuleDirectionEnum value); + bool directionIsSet() const; + static std::string SessionRuleDirectionEnum_to_string(const SessionRuleDirectionEnum &value); + static SessionRuleDirectionEnum string_to_SessionRuleDirectionEnum(const std::string &str); + + /// + /// Session entry source IP address + /// + std::string getSrcIp() const; + void setSrcIp(std::string value); + bool srcIpIsSet() const; + + /// + /// Session entry destination IP address + /// + std::string getDstIp() const; + void setDstIp(std::string value); + bool dstIpIsSet() const; + + /// + /// Session entry source L4 port number + /// + uint16_t getSrcPort() const; + void setSrcPort(uint16_t value); + bool srcPortIsSet() const; + + /// + /// Session entry destination L4 port number + /// + uint16_t getDstPort() const; + void setDstPort(uint16_t value); + bool dstPortIsSet() const; + + /// + /// Session entry L4 protocol + /// + L4ProtoEnum getProto() const; + void setProto(L4ProtoEnum value); + bool protoIsSet() const; + static std::string L4ProtoEnum_to_string(const L4ProtoEnum &value); + static L4ProtoEnum string_to_L4ProtoEnum(const std::string &str); + + /// + /// Translated IP address + /// + std::string getNewIp() const; + void setNewIp(std::string value); + bool newIpIsSet() const; + void unsetNewIp(); + + /// + /// Translated L4 port number + /// + uint16_t getNewPort() const; + void setNewPort(uint16_t value); + bool newPortIsSet() const; + void unsetNewPort(); + + /// + /// Operation applied on the original packet + /// + SessionRuleOperationEnum getOperation() const; + void setOperation(SessionRuleOperationEnum value); + bool operationIsSet() const; + void unsetOperation(); + static std::string SessionRuleOperationEnum_to_string(const SessionRuleOperationEnum &value); + static SessionRuleOperationEnum string_to_SessionRuleOperationEnum(const std::string &str); + + /// + /// Rule originating the session entry + /// + SessionRuleOriginatingRuleEnum getOriginatingRule() const; + void setOriginatingRule(SessionRuleOriginatingRuleEnum value); + bool originatingRuleIsSet() const; + void unsetOriginatingRule(); + static std::string SessionRuleOriginatingRuleEnum_to_string(const SessionRuleOriginatingRuleEnum &value); + static SessionRuleOriginatingRuleEnum string_to_SessionRuleOriginatingRuleEnum(const std::string &str); + +private: + SessionRuleDirectionEnum m_direction; + bool m_directionIsSet; + std::string m_srcIp; + bool m_srcIpIsSet; + std::string m_dstIp; + bool m_dstIpIsSet; + uint16_t m_srcPort; + bool m_srcPortIsSet; + uint16_t m_dstPort; + bool m_dstPortIsSet; + L4ProtoEnum m_proto; + bool m_protoIsSet; + std::string m_newIp; + bool m_newIpIsSet; + uint16_t m_newPort; + bool m_newPortIsSet; + SessionRuleOperationEnum m_operation; + bool m_operationIsSet; + SessionRuleOriginatingRuleEnum m_originatingRule; + bool m_originatingRuleIsSet; +}; + +} +} +} + diff --git a/src/services/pcn-k8sdispatcher/test/test.sh b/src/services/pcn-k8sdispatcher/test/test.sh new file mode 100755 index 000000000..be221f807 --- /dev/null +++ b/src/services/pcn-k8sdispatcher/test/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +function cleanup { + set +e + polycubectl k8sdispatcher del k0 +} +trap cleanup EXIT + +set -x +set -e + +polycubectl k8sdispatcher add k0 internal-src-ip=3.3.1.1 loglevel=TRACE +polycubectl k0 show +polycubectl k0 ports show +polycubectl k0 nodeport-rule show +polycubectl k0 session-rule show +polycubectl k0 ports add to_frontend type=FRONTEND ip=10.10.10.10 +polycubectl k0 show ports to_frontend +polycubectl k0 ports add to_backend type=BACKEND +polycubectl k0 show ports to_backend +polycubectl k0 nodeport-rule add 32000 TCP external-traffic-policy=LOCAL rule-name=my-rule +polycubectl k0 show nodeport-rule 32000 TCP +polycubectl k0 set nodeport-range=32000-32500 +polycubectl k0 show nodeport-range