You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by pe...@apache.org on 2025/06/24 22:19:59 UTC

(cloudstack) 01/02: FR01: Netris Integration (#1)

This is an automated email from the ASF dual-hosted git repository.

pearl11594 pushed a commit to branch support-acls-for-ipv6
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 604137624db49e852c812728bac838068417b4a0
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Tue May 20 06:49:35 2025 -0300

    FR01: Netris Integration (#1)
    
    * Add Netris module and Add netris provider
    
    * Fix
    
    * Add Netris Provider to the zone creation wizard
    
    * add steps to zone wizard for adding netris controller and public traffic
    
    * cleanup
    
    * Add missing config key
    
    * Add routed mode offerings for Netris VPC (#3)
    
    * Add routed mode offerings for Netris VPC
    
    * update vpc offering name
    
    * generalize the offering creation method for network providers
    
    * log
    
    * remove debug log
    
    * fix failing build
    
    * Add dependency and Netris API client (#4)
    
    * Add dependency and first approach to Netris API client
    
    * Fix authentication and create Netris API client, in progress sites listing
    
    * Fix get sites
    
    * Support for listing VPCs (#5)
    
    * List tenants
    
    * Delegate API classes creation to the SDK and simply invoke the desired API class through CloudStack (#7)
    
    * Delegate API classes creation to the SDK and simply invoke the desired API class through CloudStack
    
    * Pass default auth scheme for now
    
    * Support adding netris provider to CloudStack and Netris VPC Creation (#6)
    
    * Support adding netris provider to CloudStack
    
    * revert marvin change
    
    * add license and perform session check when provider is added
    
    * add license and remove unused import
    
    * fix build failure - uunused imports
    
    * address comments
    
    * fix provider name
    
    * add Netris network element
    
    * add license
    
    * Add netris management APIs and netris service provider
    
    * add license
    
    * revert change
    
    * remove other network elements from Netris element
    
    * fix api name in doc generator
    
    * remove logs
    
    * move session alive check to CheckHealthCommand exec
    
    * Fix zone creation wizard to configure netris provider
    
    * Upgrade GSON version - from PR 8756
    
    * Add additional parametes to the add Netris provider API
    
    * add netris as a host
    
    * add additional params to the resoponse and update UI
    
    * Rename site to site_name
    
    * Create Netris VPC (#8)
    
    * Delegate API classes creation to the SDK and simply invoke the desired API class through CloudStack (#7)
    
    * Delegate API classes creation to the SDK and simply invoke the desired API class through CloudStack
    
    * Pass default auth scheme for now
    
    * Drop for_nsx and for_tungten columns in favour of checking the provider on the ntwserviceprovider map table
    
    * Remove missing setForTungsten occurrence
    
    * Remove forNsx from VPC offerings
    
    * Create Netris VPC
    
    * Fix VPC offerings listing and remove unused dao
    
    * Create VPC fixes
    
    * Upgrade GSON version - from PR 8756
    
    * Fix VPC creation response by using the latest SDK code
    
    * Fix unit test
    
    * Remove unused import
    
    * Fix NSX unit tests after refactoring
    
    * Add Netris key to the VLAN Details table (#10)
    
    * Add Netris key to the VLAN Details table
    
    * update for_<provider> column to be generic
    
    * Fix VPC and add IPAM allocation for the VPC CIDR (#9)
    
    * Fix VPC and add IPAM allocation for the VPC CIDR
    
    * Remove VPC logic
    
    * Use zoneId accountId and domainId on resources creation
    
    * Fix naming
    
    * Fix VR public nic issue
    
    * Fix Netris Public IP for VPC source NAT allocation
    
    * Add Netris VPC Subnets and vNets (#11)
    
    * Add Netris VPC Subnets and vNets
    
    * fix compilation errors
    
    * Add netris subnet
    
    * refactor naming convention to differentiate between VPC tiers and Isolated networks
    
    * revert marvin change
    
    * fix constructor - build failure
    
    * Add support to filter netris offerings, delete netris provider when zone is being deleted
    
    * Fix build
    
    * Fix VPC creation
    
    * Fix vnet creation
    
    * unnecesary log
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    ---------
    
    Co-authored-by: Pearl Dsilva <pe...@gmail.com>
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    * Fix unit tests
    
    * Add support to delete VNets and Subnets (#13)
    
    * Add support to delete VNets and Subnets
    
    * Add support to delete vnet resources
    
    * Add support to delete vnet resources
    
    * extract code to method
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    * Add missing suffix return (#14)
    
    * Set up Netris Public range on new zone addition (#15)
    
    * Set up Netris Public range on new zone addition
    
    * Add dependency to calculate subnet containing a start and end IP
    
    * Remove unused import
    
    * Move dependency to the netris module
    
    * Rename Netris IP range
    
    * Refactor logic
    
    * Revert "Refactor logic"
    
    This reverts commit 7ec36a81320444c37e7bb914dd895060b663411b.
    
    * Fix setup range after adding Netris Provider
    
    * Fix VXLAN range adding on zone creation
    
    * Pass VXLAN ID during creation of Netris vNets (#16)
    
    * add zone params to accepts management vnet
    
    * Release vxlan associated to the netris broadcast domain type
    
    * handle update network broadcast uri
    
    * Update Subnet purpose for Netris Public Traffic (#17)
    
    * Update Subnet purpose for Netris Public Traffic
    
    * search for existing subnet of common purpose type
    
    * Fix VR Public IP address (#20)
    
    * Fix VR Public IP address
    
    * Do not set the Public IP range on Netris side that is not part of the Netris IP Public Pool
    
    * Leave only systemvms tag for the first element
    
    * Fix NSX compatibility
    
    * Pass network gateway instead of network CIDR for Netris vNet creation (#21)
    
    * Run moodifyvxlan script if broadcast domain type is Netris (#18)
    
    * Add support to create Netris VPC / Network offerings (#22)
    
    * Add support to create Netris VPC / Network offerings
    
    * fix support services for netris provider type
    
    * Phase4 - Add support for Source NAT, Static NAT and Port Forwarding (#19)
    
    * Run moodifyvxlan script if broadcast domain type is Netris
    
    * Add Netris NAT offerings
    
    * Add support to add Source nat rules for Natted offering
    
    * fix api params while creating Netris source NAT rule
    
    * Add support to add and delete source nat rule on netris
    
    * Add support to create /32 NAT subnet
    
    * Add support to add and delete Static NAT rules in Netris (#23)
    
    * Add support to add and delete Static NAT rules in Netris
    
    * fix static nat creation on netris & removal of subnet on deletion of static nat rule
    
    * remove nat subnet after deltion of the static nat rule
    
    * add check to see if subnet already exists and add license header
    
    * Add port forwarding rules as DNAT rules in Netris (#24)
    
    * Add port forwarding rules as DNAT rules in Netris
    
    * Fixes
    
    * Allow removing DNAT rules
    
    * Fixes
    
    * Fix subnet search
    
    * Fix update SNAT only for SNAT rules
    
    * Address comments
    
    * Fix
    
    * Fix netris pom xml
    
    * Fix SNAT rule creation
    
    * Fix IP and port placements (#27)
    
    * Fix IP and port placements
    
    * fix dnat to IP for PF rules
    
    * change dnatport
    
    ---------
    
    Co-authored-by: Nicolas Vazquez <ni...@gmail.com>
    
    * List only Netris Public IPs for NAT operations (#26)
    
    * List only Netris Public IPs for NAT operations
    
    * rename getter and change type
    
    * fix failing unit tests
    
    * list all IPs if forProvider is not passed
    
    * fix list public IPs for external providers with additional IP range
    
    * filter provider Ips in a zone with external provider setup
    
    * Prevent acquiring IP that is not from the external provider range
    
    * formating
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    * Support to pass provider when creating public ip range and create IPAM on Netris (#28)
    
    * UI: support to pass provider when creating public ip range
    
    * prevent adding public ip range for a provider that isnt supported in zone
    
    * Create public range on Netris when created on CloudStack
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    * Revert UI filtration for public IPs (#29)
    
    * Fix issue with pagination of public addresses listed after filtering for external providers
    
    * Revert UI filteration for public IPs for external network provider enabled zones
    
    * Fix unit tests (#30)
    
    * Add Netris Tag parameter to the Network provider and fix zone creation wizard (#33)
    
    * Add Netris Tag parameter to the Network provider
    
    * remove unused import
    
    * Fix public IP ranges creation on zone creation (#34)
    
    * use single quotes
    
    ---------
    
    Co-authored-by: Nicolas Vazquez <ni...@gmail.com>
    
    * Fix SourceAddress for SNAt to VPC cidr (#35)
    
    * Fix VPC/network offering service list for external network providers in Routed mode (#32)
    
    * Fix network offering service list for external network providers in Routed mode
    
    * filter out unsupported services based on network mode
    
    * fix supported services list for vpc offering for external providers in Routed mode
    
    * Add support to add and delete and update static routes on Netris (#37)
    
    * Add support to add static routes in Netris
    
    * support to delete static routes on netris
    
    * add defensive check for nextHop
    
    * Add support to update static routes
    
    * add state
    
    * pass empty list for switched to avoid timeout
    
    * Netris: search static route by name and next hop if exists
    
    ---------
    
    Co-authored-by: Wei Zhou <we...@apache.org>
    
    * Netris FR1b: Support Remote Access VPN and Site-to-Site VPN in VPC VR (#41)
    
    * Static Routes: support nexthop
    
    * Update api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java
    
    Co-authored-by: Pearl Dsilva <pe...@gmail.com>
    
    * PR#10064 VR: apply iptables rules when add/remove static routes
    
    * PR#10065 UI: fix cannot open 'Edit tags' modal for static routes
    
    * PR#10066 Static Routes: fix check on wrong global configuration
    
    * PR#10067 VR: fix site-2-site VPN if split connections is enabled
    
    * PR#10081 server: do not allocate nic on public network for NSX VPC VR
    
    * PR#10082 UI: create VPC network offering with conserve mode
    
    * PR#10083 VR: allow outgoing traffic from RAS/VPN clients
    
    * PR#10086 server: fix typo removeaccessvpn in VirtualRouterElement
    
    * server: Add check on Public IP for remote access VPN
    
    * Revert "PR#10083 VR: allow outgoing traffic from RAS/VPN clients"
    
    This reverts commit 2f9b9f428947cac91de322fbdf4a980902a1c0a0.
    
    * VPC: fetch same used IP for domain router if VR is not Source NAT
    
    * VR: pass has_public_network to VR and configure RA/S2S VPN left peers
    
    * Revert "PR#10081 server: do not allocate nic on public network for NSX VPC VR"
    
    This reverts commit 809e269ed6b361d9df1fcef6537762c5612863e0.
    
    * VPC: fetch same used IP for domain router if VR is not Source NAT (v2)
    
    * VR: fix /etc/hosts and nameservers in dnsmasq.conf if VPC VR is not guest gateway
    
    prior to this PR
    ```
    root@r-1167-VM:~# cat /etc/hosts
    127.0.0.1       localhost
    127.0.1.1       r-1167-VM
    ::1     localhost ip6-localhost ip6-loopback
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.21.1.33     dummy-vpc-vpn-001
    172.21.1.1      r-1167-VM data-server
    
    root@r-1167-VM:~# cat /etc/dnsmasq.d/cloud.conf
    dhcp-hostsfile=/etc/dhcphosts.txt
    listen-address=127.0.0.1,172.21.1.234
    dhcp-range=set:interface-eth1-0,172.21.1.234,static
    dhcp-option=tag:interface-eth1-0,15,cs2cloud.internal
    dhcp-option=tag:interface-eth1-0,6,172.21.1.1,10.0.32.1,8.8.8.8
    dhcp-option=tag:interface-eth1-0,3,172.21.1.1
    dhcp-option=eth1,26,1500
    dhcp-option=tag:interface-eth1-0,1,255.255.255.0
    ```
    
    the lines should be
    ```
    172.21.1.234  r-1167-VM data-server
    
    dhcp-option=tag:interface-eth1-0,6,10.0.32.1,8.8.8.8
    ```
    
    * server: Enable static NAT for Domain router if it is not Source NAT
    
    * server: Enable static NAT for Domain router on UI
    
    * server: assign Public IP to VPC VR and enable static nat if VR is not Source NAT
    
    * server: configure dns1 if VR is not Source NAT
    
    * server: remove check on Firewall service when list network service providers
    
    * UI: remove dot from message.enabled.vpn
    
    * systemvm: add default route via first guest gateway if VR does not have public IP/interface
    
    * VR: add fw_dhcpserver for shared network
    
    * VR: pass has_public_network to VR and configure RA/S2S VPN left peers (v2)
    
    * UI: fix request error when create a VPC tier in a non-Netris/NSX env
    
    * systemvm: add default route via first guest gateway (v2)
    
    * VR: configure iptables rules for S2S vpn on first guest interface
    
    * VR: allow FORWARD to guest interfaces if VR is not Public
    
    * VR: configure remote access vpn on first guest interface if not public
    
    * VR: fix error 789 in RA VPN client when both RA and S2S are configured
    
    * server: Apply Static Route for RA/S2S VPN in VPC VR
    
    * VR: do not set mark for Public interface when VR is not really public
    
    * VPN: do not disable static nat if it is used by a RA/S2S VPN
    
    * server: skip check on network conserve mode if disable/enable RA VPN on Router IP
    
    * server: set forRouter to false when release a IP
    
    * VR: diable IP spoofing protection on default guest network
    
    * VR: fix iptables rules only when only S2S vpn is enabled
    
    * UI: show 'VPN Connections' section
    
    * VPC: new methods to configure/reconfigure Static NAT for VPC VR
    
    * API: set Type in ip address response to DomainRouter if it is used by VR
    
    * server: do not allow IP release if it is used by RA or S2S VPN gateway
    
    * VR: check if interface is added
    
    * VR: add default route only when ip is associated to first guest interface
    
    * VR: fix ipsec conf for l2tp and s2s vpn
    
    * server: save placeholder IP for VPC VR to fix the new VR IP when vpc tier is auto-shutdown
    
    * server: get non-placeholder NIC for VPC VR
    
    * VR: wait 15 seconds after starting password server
    
    * server: fix unable to configure static nat due to 'invalid virtual machine id'
    
    * UI: fix link of router in info card
    
    * VPC: apply static route for VPC VPN if needed (refactoring)
    
    * server: fix VR IP of first VPC tier is the VM gateway
    
    * server: update or remove all existing static routes when shutdown a network
    
    * server: update ipaddress after disabling static nat to fix vpc deletion issue
    
    * servr: disable remote access VPN as part of VPC dstroy
    
    * server: apply static routes when implement a vpc tier
    
    * server: apply static routes even if next hop is null
    
    * server: fix Cannot invoke "com.cloud.vm.NicProfile.getRequestedIPv4()" because "requested" is null
    
    * Netris: Update Vpn provider to VpcVirtualRouter
    
    * Netris: Add Vpn service to network offerings and networks
    
    * server: fix CIDR of VPN ip range
    
    * server: set isVrGuestGateway by SoureNat/Gateway service with Provider.VPCVirtualRouter
    
    * VR: password server takes 10-15 seconds to start if VR IP is not configured in /etc/hosts
    
    * Netris: add back routesPutBody.setStateStatus
    
    * engine/schema: remove SQL changes in schema-41910to42000.sql
    
    ---------
    
    Co-authored-by: Pearl Dsilva <pe...@gmail.com>
    
    * Add support for Gateway service for Netris VPC and network offerings (#39)
    
    * Add support for Gateway service for Netris VPC and network offerings
    
    * Restore UserData service
    
    * add gateway only to vpc service
    
    * Add support for gateway service for external network providers for networks in routed mode
    
    * add support for gateway svc
    
    * Revert "add support for gateway svc"
    
    This reverts commit 06645cd1c6d08a81ede5d1431497ea3f2efdc5dc.
    
    * Fix VPC offering creation
    
    * Fix VR public NIC after Gateway service is set to Netris
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    * Netris VPN: Fix s2s vpn status update and isolated network implementation (#42)
    
    * server: fix NPE when deploy vm on isolated network
    
    * vpn: fix s2s vpn status is not updated
    
    Prior to this fix
    ```
    java.lang.IllegalArgumentException: Class com.cloud.agent.api.CheckS2SVpnConnectionsAnswer declares multiple JSON fields named 'details'; conflict is caused by fields com.cloud.agent.api.CheckS2SVpnConnectionsAnswer#details and com.cloud.agent.api.Answer#details
            at com.cloud.agent.transport.ResponseTest.testCheckS2SVpnConnectionsAnswer(ResponseTest.java:42)
    ```
    
    * test: fix test_01_vpn_usage as now it is only possible to create VPN on Source NAT if it uses VR
    
    * VR: fix unable to create remote access VPN on regular isolated network
    
    the error is
    ```
      File "/opt/cloud/bin/configure.py", line 1242, in process
        self.remoteaccessvpn_iptables(self.dbag['public_interface'], public_ip, self.dbag[public_ip])
                                      ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
    KeyError: 'public_interface'
    ```
    
    * Release NAT IP subnet when VPC is removed or IP is released (#44)
    
    * Release NAT IP subnet when VPC is removed or IP is released
    
    * add license
    
    * Add support to add IPv6 Public IP range as IPAM Allocation / Subnet on Netris (#36)
    
    * Add support to add IPv6 Public IP range as IPAM Allocation / Subnet on Netris
    
    * Add ipam alloc and subnet for the ipv6 subnet associated to the vpc tier network
    
    * remove commented code
    
    * Phase5 - Support for ACLs in Netris (#31)
    
    * Add support for Netris ACLs
    
    * acl support
    
    * Make acl api call to netris to create the rule
    
    * refactor add acl rule to populate the right fields
    
    * support icmp type acl rule
    
    * acl rule creation - move netrisnetworkRule
    
    * Update ACL naming on Netris
    
    * Add support for Deletion of netris acls
    
    * Add support to delete and re-order ACL rules
    
    * support creation of default acl rules and replacing acl rules
    
    * fix NSXNetworkRule
    
    * Add global routing flag on subnet creation (#45)
    
    * Support change snat ip (#46)
    
    * Support updating VPC Source NAT IP
    
    * Optimize code
    
    * Update source NAT IP
    
    * Fix naming convention for NAT subnets to follow other resources (#47)
    
    * Fix naming convention for NAT subnets to follow other resources
    
    * Use vpc ID for nat subnets
    
    * Use new nat subnet name for deletion of static nat rule
    
    * fix naming convevntion for nat subnet
    
    * Keep Vpn service to default VPC offering with Natted mode only (#50)
    
    * Add Vpn service to default VPC offering with Routed mode
    
    * Revert change on VPC offering and fix VPN service only for Netris NAT mode
    
    * Validate if given CIDR belongs to a bigger allocation in Netris before creating the zone-level allocation (#48)
    
    * Validate if given CIDR belongs to a bigger allocation in Netris before creating
    
    * rename method
    
    * Phase5 - Support for LB - create, delete and Update operations (#49)
    
    * Add support for Netris ACLs
    
    * acl support
    
    * Make acl api call to netris to create the rule
    
    * refactor add acl rule to populate the right fields
    
    * support icmp type acl rule
    
    * acl rule creation - move netrisnetworkRule
    
    * Update ACL naming on Netris
    
    * Add support for Deletion of netris acls
    
    * Add support to delete and re-order ACL rules
    
    * support creation of default acl rules and replacing acl rules
    
    * fix NSXNetworkRule
    
    * Fix naming convention for NAT subnets to follow other resources
    
    * Use vpc ID for nat subnets
    
    * Phase5 - Support for LB - create, delete and Update operations
    
    * Use new nat subnet name for deletion of static nat rule
    
    * add support to add netris lb rule
    
    * support deletion of LB rule on Netris
    
    * add checks when editing unsupported fields of LB rule for Netris and hide columns on the UI
    
    * fix test failure
    
    * fix imports
    
    * add license
    
    * address comments
    
    * Enable Autoscaling on Netris for CPU and memory (#51)
    
    * Enable Autoscaling on Netris for CPU and memory
    
    * Fix monitor autoscale group and cleanup
    
    * Rename autoscaling group method
    
    * Integrate Autoscaling by allowing to update LB rules
    
    * Refactor according to the SDK changes
    
    * Fix the test failures noticed on #44 (#52)
    
    * Increase code coverage (#54)
    
    * Increase code coverage
    
    * More unit tests
    
    * Remove credentials and mock api client
    
    * NetrisResource tests
    
    * Fix unit test
    
    * Add support to add and remove ACL rules when CIDR list is passed when creating LB rules (#53)
    
    * Add support to add and remove ACL rules when CIDR list is passed when creating LB rules
    
    * add deny all rule
    
    * delete the deny rule as well
    
    * Fix build (#57)
    
    * Prevent Index Out of Bounds exception when naming IPAM subnets (#58)
    
    * Prevent Index Out of Bounds exception when naming IPAM subnets
    
    * fix linter
    
    * Delete netris IPv6 subnet (#59)
    
    * Netris VPN: add static route when update a non-existent static route (#60)
    
    * Fix VPC tier creation failure - prevent creating IPv6 IPAM allocation if it already exists (#61)
    
    * Update netris VPC and tier name (#56)
    
    * Update netris VPC and tier name
    
    * add support to update vpc tier name
    
    * add license
    
    * support editing names of dual stack VPCs
    
    * VR/server: configure default gateway and RA/S2S VPN on the IP/interface with minimum network_id (#43)
    
    * server: fix NPE when deploy vm on isolated network
    
    * vpn: fix s2s vpn status is not updated
    
    Prior to this fix
    ```
    java.lang.IllegalArgumentException: Class com.cloud.agent.api.CheckS2SVpnConnectionsAnswer declares multiple JSON fields named 'details'; conflict is caused by fields com.cloud.agent.api.CheckS2SVpnConnectionsAnswer#details and com.cloud.agent.api.Answer#details
            at com.cloud.agent.transport.ResponseTest.testCheckS2SVpnConnectionsAnswer(ResponseTest.java:42)
    ```
    
    * test: fix test_01_vpn_usage as now it is only possible to create VPN on Source NAT if it uses VR
    
    * VR: fix unable to create remote access VPN on regular isolated network
    
    the error is
    ```
      File "/opt/cloud/bin/configure.py", line 1242, in process
        self.remoteaccessvpn_iptables(self.dbag['public_interface'], public_ip, self.dbag[public_ip])
                                      ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
    KeyError: 'public_interface'
    ```
    
    * VR/server: configure default gateway and RA/S2S VPN on the IP/interface with minimum network_id
    
    * Don't add deny rule if no CIDR list is passed (#62)
    
    * Hide the Stickiness Configure button for Netris Load Balancers (#72)
    
    * Update IPAM subnet purpose to nat before NAT operations if its different (#71)
    
    * Netris VPN: create vpc gateway with specified IP (#63)
    
    * Netris: fix UnsupportedOperationException when create VPC offering with NATTED mode (#75)
    
    fixes
    ```
    2025-03-21T10:42:55,039 ERROR [c.c.a.ApiServer] (qtp1513608173-21:[ctx-f9c7f002, ctx-bcfe846d]) (logid:e12e798f) unhandled exception executing api command: [Ljava.lang.String;@3a1416cd java.lang.UnsupportedOperationException
            at java.base/java.util.AbstractList.add(AbstractList.java:153)
            at java.base/java.util.AbstractList.add(AbstractList.java:111)
            at org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd.getServiceProviderMapForExternalProvider(CreateVPCOfferingCmd.java:248)
    ```
    
    * [UI] Zone wizard creation improvements - rename hostname to url and remove port for Netris Provider (#77)
    
    * [UI] Zone wizard creation improvements - rename hostname to url and remove port for Netris Provider
    
    * Fix schema column for url instead of hostname
    
    * Fix Static NAT rules naming (#83)
    
    * Netris: create VPN gateway with specified public IP on UI (#82)
    
    * Netris vpn: apply static routes when start or delete a VPN connection (#85)
    
    * Netris VPN: apply static routes when start S2S VPN
    
    * Netris: list static routes and revoke the routes which are not needed
    
    * Netris: use route name (x.x.x.0/x) instead of prefix (x.x.x.0) and get clean cidr list
    
    * Netris VPN: fix NPE when list static routes
    
    * Update plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java
    
    * Delete IPv6 allocation after tier removal on VPC with dual stack offering (#86)
    
    * Netris pass v6 gateway (#87)
    
    * pass v6 gateway to netris
    
    * pass v6 gateway to netris
    
    * refactor to address comments
    
    * remove imports
    
    * [VR] Fix IPv6 NIC IP on the VR (#89)
    
    * Add support to edit ACL rules (#74)
    
    * Add support to edit ACL rules
    
    * add support to update acl rules
    
    * remove test file
    
    * VR: advertise SLAAC prefix only if VR is gateway (#91)
    
    * Make reorder ACL items invoke Netris controller (#90)
    
    * VR: fix radvd misconfiguration for non-netris env (#92)
    
    * [VR] Fix object comparisson to string comparisson on python (#93)
    
    * Fix unit tests for ACL (#94)
    
    * Use the previously assigned vNet for Netris Network when it transitions to Implemented state after gc (#88)
    
    * Use the previously assigned vNet for Netris Network when it transitions to Implemented state after gc
    
    * Fix unit tests
    
    ---------
    
    Co-authored-by: nvazquez <ni...@gmail.com>
    
    ---------
    
    Co-authored-by: Pearl Dsilva <pe...@gmail.com>
    Co-authored-by: Wei Zhou <we...@apache.org>
---
 .../cloud/configuration/ConfigurationService.java  |   16 +
 api/src/main/java/com/cloud/event/EventTypes.java  |    2 +
 api/src/main/java/com/cloud/network/IpAddress.java |    1 +
 api/src/main/java/com/cloud/network/Network.java   |    1 +
 api/src/main/java/com/cloud/network/Networks.java  |    3 +-
 .../com/cloud/network/SDNProviderNetworkRule.java  |  139 +-
 .../com/cloud/network/Site2SiteVpnConnection.java  |    2 +-
 .../com/cloud/network/element/NetworkElement.java  |    7 +
 .../element/PortForwardingServiceProvider.java     |   28 +
 .../com/cloud/network/element/VpcProvider.java     |    2 +
 .../java/com/cloud/network/guru/NetworkGuru.java   |    4 +
 .../com/cloud/network/netris/NetrisLbBackend.java  |   26 +-
 .../cloud/network/netris/NetrisNetworkRule.java    |  108 ++
 .../com/cloud/network/netris/NetrisProvider.java   |   15 +-
 .../com/cloud/network/netris/NetrisService.java    |   67 +
 .../java/com/cloud/network/vpc/StaticRoute.java    |    5 +-
 .../com/cloud/network/vpc/StaticRouteProfile.java  |   22 +-
 .../java/com/cloud/network/vpc/VpcOffering.java    |    4 +-
 .../cloud/network/vpc/VpcProvisioningService.java  |    2 +-
 .../java/com/cloud/network/vpc/VpcService.java     |    2 +-
 .../java/com/cloud/offering/NetworkOffering.java   |    6 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |   11 +
 .../admin/network/CreateNetworkOfferingCmd.java    |   56 +-
 .../command/admin/vlan/CreateVlanIpRangeCmd.java   |   13 +-
 .../command/admin/vpc/CreateVPCOfferingCmd.java    |   56 +-
 .../user/address/ListPublicIpAddressesCmd.java     |    7 +
 .../api/command/user/vpc/CreateStaticRouteCmd.java |   51 +-
 .../api/command/user/vpn/CreateVpnGatewayCmd.java  |   13 +-
 .../cloudstack/api/response/AsyncJobResponse.java  |    9 -
 .../response/ClusterDrsPlanMigrationResponse.java  |   12 +-
 .../cloudstack/api/response/IPAddressResponse.java |    6 +
 .../cloudstack/api/response/NetworkResponse.java   |   16 -
 .../api/response/StaticRouteResponse.java          |   24 +-
 .../cloudstack/api/response/SystemVmResponse.java  |    8 -
 .../api/response/VlanIpRangeResponse.java          |   10 +-
 .../cloudstack/api/response/ZoneResponse.java      |   13 +
 client/pom.xml                                     |    5 +
 .../agent/api/CheckS2SVpnConnectionsAnswer.java    |    1 -
 .../cloud/agent/api/SetupGuestNetworkCommand.java  |    9 +
 .../facade/SetGuestNetworkConfigItem.java          |    1 +
 .../virtualnetwork/model/GuestNetwork.java         |    9 +
 .../cloud/agent/transport/ArrayTypeAdaptor.java    |   12 +-
 .../com/cloud/agent/transport/ResponseTest.java    |   46 +
 deps/install-non-oss.sh                            |    3 +
 .../service/NetworkOrchestrationService.java       |    6 +
 .../cloud/configuration/ConfigurationManager.java  |    5 +-
 .../main/java/com/cloud/network/addr/PublicIp.java |    4 +
 .../java/com/cloud/network/rules/RulesManager.java |    2 +
 .../com/cloud/network/vpc/NetworkACLManager.java   |    2 +-
 .../java/com/cloud/network/vpc/VpcManager.java     |   17 +
 .../engine/orchestration/NetworkOrchestrator.java  |   74 +-
 .../src/main/java/com/cloud/dc/dao/VlanDao.java    |    2 +
 .../main/java/com/cloud/dc/dao/VlanDaoImpl.java    |   27 +
 .../com/cloud/network/dao/IPAddressDaoImpl.java    |    1 +
 .../java/com/cloud/network/dao/IPAddressVO.java    |   12 +
 ...teVpnGatewayDao.java => NetrisProviderDao.java} |    5 +-
 ...ewayDaoImpl.java => NetrisProviderDaoImpl.java} |   41 +-
 .../com/cloud/network/dao/RemoteAccessVpnDao.java  |    2 +
 .../cloud/network/dao/RemoteAccessVpnDaoImpl.java  |    7 +
 .../cloud/network/dao/Site2SiteVpnGatewayDao.java  |    2 +
 .../network/dao/Site2SiteVpnGatewayDaoImpl.java    |    8 +
 .../cloud/network/element/NetrisProviderVO.java    |  265 +++
 .../java/com/cloud/network/vpc/StaticRouteVO.java  |   35 +-
 .../java/com/cloud/network/vpc/VpcOfferingVO.java  |   11 -
 .../network/vpc/dao/VpcOfferingServiceMapDao.java  |    5 +
 .../vpc/dao/VpcOfferingServiceMapDaoImpl.java      |   19 +
 .../network/vpc/dao/VpcServiceMapDaoImpl.java      |   11 +-
 .../com/cloud/offerings/NetworkOfferingVO.java     |   24 -
 .../src/main/java/com/cloud/vm/dao/NicDao.java     |    4 +
 .../src/main/java/com/cloud/vm/dao/NicDaoImpl.java |   20 +
 .../spring-engine-schema-core-daos-context.xml     |    1 +
 .../resources/META-INF/db/schema-41910to42000.sql  |   37 +
 .../db/views/cloud.network_offering_view.sql       |    2 -
 .../META-INF/db/views/cloud.vpc_offering_view.sql  |    1 -
 .../networkservice/BaremetalDhcpElement.java       |    6 +
 .../networkservice/BaremetalPxeElement.java        |    6 +
 .../networkservice/BaremetalUserdataElement.java   |    6 +
 .../hypervisor/kvm/resource/BridgeVifDriver.java   |   13 +-
 .../network/element/CiscoNexusVSMElement.java      |    7 +-
 .../cluster/KubernetesClusterManagerImpl.java      |    1 -
 .../cloudstack/metrics/MetricsServiceImpl.java     |    2 +-
 .../cloudstack/response/HostMetricsResponse.java   |   10 -
 .../cloud/network/element/BigSwitchBcfElement.java |    6 +
 .../cloud/network/element/BrocadeVcsElement.java   |    6 +
 .../cloud/network/element/CiscoVnmcElement.java    |    5 +
 .../cloudstack/network/element/DnsNotifier.java    |    6 +
 .../element/ElasticLoadBalancerElement.java        |    6 +
 .../cloudstack/element/GloboDnsElement.java        |    6 +
 .../element/InternalLoadBalancerElement.java       |    6 +
 .../contrail/management/ContrailElementImpl.java   |    5 +
 .../contrail/management/ContrailManagerImpl.java   |    6 +-
 .../management/ContrailVpcElementImpl.java         |    5 +
 plugins/network-elements/netris/pom.xml            |   44 +
 .../apache/cloudstack/StartupNetrisCommand.java    |   11 +-
 .../api/AddOrUpdateNetrisStaticRouteCommand.java   |   48 +
 .../agent/api/CreateNetrisACLCommand.java          |  120 ++
 .../agent/api/CreateNetrisVnetCommand.java         |   85 +
 .../agent/api/CreateNetrisVpcCommand.java          |   16 +-
 .../agent/api/CreateOrUpdateNetrisACLCommand.java  |  120 ++
 ...reateOrUpdateNetrisLoadBalancerRuleCommand.java |   90 +
 .../agent/api/CreateOrUpdateNetrisNatCommand.java  |  149 ++
 .../agent/api/DeleteNetrisACLCommand.java          |   46 +-
 .../api/DeleteNetrisLoadBalancerRuleCommand.java   |   52 +
 .../agent/api/DeleteNetrisNatRuleCommand.java      |   72 +
 .../agent/api/DeleteNetrisStaticRouteCommand.java  |   10 +-
 .../agent/api/DeleteNetrisVnetCommand.java         |   51 +
 .../agent/api/DeleteNetrisVpcCommand.java          |   15 +-
 .../agent/api/ListNetrisStaticRoutesAnswer.java    |   28 +-
 .../agent/api/ListNetrisStaticRoutesCommand.java   |   10 +-
 .../apache/cloudstack/agent/api/NetrisAnswer.java  |   15 +-
 .../apache/cloudstack/agent/api/NetrisCommand.java |   71 +
 .../cloudstack/agent/api/ReleaseNatIpCommand.java  |   15 +-
 .../agent/api/SetupNetrisPublicRangeCommand.java   |   31 +-
 .../agent/api/UpdateNetrisVnetCommand.java         |   51 +
 .../agent/api/UpdateNetrisVpcCommand.java          |   17 +-
 .../api/command/AddNetrisProviderCmd.java          |  123 ++
 .../api/command/DeleteNetrisProviderCmd.java}      |   45 +-
 .../api/command/ListNetrisProvidersCmd.java        |   73 +
 .../api/response/NetrisProviderResponse.java       |  135 ++
 .../cloudstack/resource/NetrisPortGroup.java       |   12 +-
 .../apache/cloudstack/resource/NetrisResource.java |  441 +++++
 .../resource/NetrisResourceObjectUtils.java        |  103 +
 .../apache/cloudstack/service/NetrisApiClient.java |  100 +
 .../cloudstack/service/NetrisApiClientImpl.java    | 2004 ++++++++++++++++++++
 .../apache/cloudstack/service/NetrisElement.java}  |  875 ++++-----
 .../service/NetrisGuestNetworkGuru.java}           |  186 +-
 .../cloudstack/service/NetrisProviderService.java  |   26 +-
 .../service/NetrisProviderServiceImpl.java         |  217 +++
 .../service/NetrisPublicNetworkGuru.java}          |   64 +-
 .../cloudstack/service/NetrisServiceImpl.java      |  532 ++++++
 .../core/spring-netris-core-managers-context.xml   |   31 +
 .../META-INF/cloudstack/netris/module.properties   |   21 +
 .../cloudstack/netris/spring-netris-context.xml    |   41 +
 .../resource/NetrisResourceObjectUtilsTest.java    |  109 ++
 .../cloudstack/resource/NetrisResourceTest.java    |  141 ++
 .../service/NetrisApiClientImplTest.java           |  103 +
 .../cloudstack/service/NetrisElementTest.java      |  141 ++
 .../service/NetrisGuestNetworkGuruTest.java        |  230 +++
 .../service/NetrisPublicNetworkGuruTest.java       |  225 +++
 .../cloudstack/service/NetrisServiceImplTest.java  |   18 +-
 .../cloud/network/element/NetscalerElement.java    |    5 +
 .../cloud/network/element/NiciraNvpElement.java    |    5 +
 .../api/command/DeleteNsxControllerCmd.java        |    3 +-
 .../apache/cloudstack/resource/NsxNetworkRule.java |  353 +---
 .../apache/cloudstack/service/NsxApiClient.java    |   13 +-
 .../org/apache/cloudstack/service/NsxElement.java  |  148 +-
 .../cloudstack/service/NsxGuestNetworkGuru.java    |    9 -
 .../cloudstack/service/NsxPublicNetworkGuru.java   |   18 +-
 .../apache/cloudstack/service/NsxServiceImpl.java  |   17 +-
 .../cloudstack/service/NsxApiClientTest.java       |    6 +-
 .../apache/cloudstack/service/NsxElementTest.java  |   42 +-
 .../service/NsxPublicNetworkGuruTest.java          |   10 +-
 .../network/opendaylight/OpendaylightElement.java  |    6 +
 .../java/com/cloud/network/element/OvsElement.java |    6 +
 .../element/PaloAltoExternalFirewallElement.java   |    6 +
 .../cloudstack/network/element/SspElement.java     |    6 +
 .../command/ConfigTungstenFabricServiceCmd.java    |    1 -
 .../network/tungsten/service/TungstenElement.java  |    6 +
 plugins/pom.xml                                    |    2 +
 pom.xml                                            |    2 +-
 .../main/java/com/cloud/api/ApiResponseHelper.java |   72 +-
 .../cloud/api/query/dao/DataCenterJoinDaoImpl.java |   23 +-
 .../api/query/dao/VpcOfferingJoinDaoImpl.java      |    9 +-
 .../cloud/api/query/vo/NetworkOfferingJoinVO.java  |   20 -
 .../com/cloud/api/query/vo/VpcOfferingJoinVO.java  |    8 -
 .../cloud/api/response/ApiResponseSerializer.java  |    4 +-
 .../configuration/ConfigurationManagerImpl.java    |   94 +-
 .../deploy/DeploymentPlanningManagerImpl.java      |    2 +-
 .../com/cloud/network/IpAddressManagerImpl.java    |   88 +
 .../java/com/cloud/network/NetworkModelImpl.java   |    9 +-
 .../java/com/cloud/network/NetworkServiceImpl.java |   58 +-
 .../com/cloud/network/SDNProviderOpObject.java     |    8 +-
 .../com/cloud/network/as/AutoScaleManagerImpl.java |   14 +-
 .../network/element/ConfigDriveNetworkElement.java |    6 +
 .../network/element/SecurityGroupElement.java      |    6 +
 .../network/element/VirtualRouterElement.java      |    8 +-
 .../network/element/VpcVirtualRouterElement.java   |    5 +
 .../network/guru/ExternalGuestNetworkGuru.java     |    5 +-
 .../com/cloud/network/guru/GuestNetworkGuru.java   |   18 +-
 .../com/cloud/network/guru/PublicNetworkGuru.java  |   14 +-
 .../network/lb/LoadBalancingRulesManagerImpl.java  |   15 +
 .../cloud/network/router/CommandSetupHelper.java   |   11 +-
 .../cloud/network/router/NetworkHelperImpl.java    |   10 +-
 .../cloud/network/router/NicProfileHelperImpl.java |    4 +-
 .../router/VirtualNetworkApplianceManagerImpl.java |    8 +
 .../VpcVirtualNetworkApplianceManagerImpl.java     |   20 +-
 .../com/cloud/network/rules/RulesManagerImpl.java  |   35 +-
 .../cloud/network/vpc/NetworkACLManagerImpl.java   |   11 +-
 .../cloud/network/vpc/NetworkACLServiceImpl.java   |   11 +-
 .../java/com/cloud/network/vpc/VpcManagerImpl.java |  561 +++++-
 .../network/vpn/RemoteAccessVpnManagerImpl.java    |   72 +-
 .../cloud/network/vpn/Site2SiteVpnManagerImpl.java |   76 +-
 .../com/cloud/server/ConfigurationServerImpl.java  |   71 +-
 .../com/cloud/server/ManagementServerImpl.java     |   32 +-
 .../deployment/RouterDeploymentDefinition.java     |   19 +-
 .../RouterDeploymentDefinitionBuilder.java         |    4 +
 .../deployment/VpcRouterDeploymentDefinition.java  |    6 +-
 .../ConfigurationManagerImplTest.java              |    4 +
 .../com/cloud/network/NetworkModelImplTest.java    |    8 +-
 .../cloud/network/as/AutoScaleManagerImplTest.java |   16 +-
 .../cloud/network/lb/UpdateLoadBalancerTest.java   |    7 +-
 .../com/cloud/server/ManagementServerImplTest.java |   10 +
 .../cloud/vpc/MockConfigurationManagerImpl.java    |    4 +-
 .../java/com/cloud/vpc/NetworkACLServiceTest.java  |    6 +
 .../vpc/dao/MockVpcOfferingServiceMapDaoImpl.java  |   11 +
 .../deployment/RouterDeploymentDefinitionTest.java |    3 +
 .../VpcRouterDeploymentDefinitionTest.java         |    3 +
 .../networkoffering/CreateNetworkOfferingTest.java |   20 +-
 .../cloudstack/service/NetrisServiceMockTest.java  |  134 ++
 .../src/test/resources/createNetworkOffering.xml   |    2 +
 systemvm/debian/opt/cloud/bin/configure.py         |   42 +-
 systemvm/debian/opt/cloud/bin/cs/CsAddress.py      |  110 +-
 systemvm/debian/opt/cloud/bin/cs/CsConfig.py       |    3 +
 systemvm/debian/opt/cloud/bin/cs/CsDatabag.py      |    7 +
 systemvm/debian/opt/cloud/bin/cs/CsDhcp.py         |    4 +-
 systemvm/debian/opt/cloud/bin/cs/CsGuestNetwork.py |   10 +-
 systemvm/debian/opt/cloud/bin/cs/CsRoute.py        |   21 +
 .../debian/opt/cloud/bin/cs/CsVpcGuestNetwork.py   |   15 +-
 systemvm/debian/opt/cloud/bin/passwd_server_ip.py  |   13 +-
 test/integration/smoke/test_usage.py               |   12 +-
 tools/apidoc/gen_toc.py                            |    4 +
 ui/public/locales/en.json                          |   32 +-
 ui/src/components/CheckBoxSelectPair.vue           |    8 +-
 ui/src/components/view/InfoCard.vue                |    2 +-
 ui/src/config/section/infra/phynetworks.js         |    2 +-
 ui/src/config/section/network.js                   |    3 +-
 ui/src/views/AutogenView.vue                       |    4 -
 ui/src/views/infra/network/IpRangesTabPublic.vue   |   21 +
 ui/src/views/infra/network/ServiceProvidersTab.vue |   45 +-
 ui/src/views/infra/network/TrafficTypesTab.vue     |    2 +-
 ui/src/views/infra/zone/IpAddressRangeForm.vue     |   20 +-
 ui/src/views/infra/zone/PhysicalNetworksTab.vue    |    2 +-
 ui/src/views/infra/zone/ZoneWizard.vue             |    2 +-
 ui/src/views/infra/zone/ZoneWizardLaunchZone.vue   |   73 +-
 .../infra/zone/ZoneWizardNetworkSetupStep.vue      |   90 +-
 .../zone/ZoneWizardPhysicalNetworkSetupStep.vue    |    1 +
 ui/src/views/network/IpAddressesTab.vue            |   22 +-
 ui/src/views/network/LoadBalancing.vue             |   41 +-
 ui/src/views/network/PublicIpResource.vue          |   31 +-
 ui/src/views/network/StaticRoutesTab.vue           |  131 +-
 ui/src/views/network/VpcTab.vue                    |  133 +-
 ui/src/views/network/VpcTiersTab.vue               |    6 +-
 ui/src/views/network/VpnDetails.vue                |  164 +-
 ui/src/views/offering/AddNetworkOffering.vue       |  156 +-
 ui/src/views/offering/AddVpcOffering.vue           |  100 +-
 .../main/java/com/cloud/utils/net/NetUtils.java    |   34 +
 .../test/java/com/cloud/utils/LogUtilsTest.java    |    2 +-
 .../java/com/cloud/utils/net/NetUtilsTest.java     |   19 +
 248 files changed, 10404 insertions(+), 2088 deletions(-)

diff --git a/api/src/main/java/com/cloud/configuration/ConfigurationService.java b/api/src/main/java/com/cloud/configuration/ConfigurationService.java
index 97d4b42974b..5a4dcb3f569 100644
--- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java
+++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java
@@ -17,7 +17,11 @@
 package com.cloud.configuration;
 
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
+import com.cloud.network.Network;
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
 import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
 import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
@@ -372,4 +376,16 @@ public interface ConfigurationService {
     List<? extends PortableIp> listPortableIps(long id);
 
     Boolean isAccountAllowedToCreateOfferingsWithTags(IsAccountAllowedToCreateOfferingsWithTagsCmd cmd);
+
+    public static final Map<String, String> ProviderDetailKeyMap = Map.of(
+            Network.Provider.Nsx.getName(), ApiConstants.NSX_DETAIL_KEY,
+            Network.Provider.Netris.getName(), ApiConstants.NETRIS_DETAIL_KEY
+    );
+
+    public static boolean IsIpRangeForProvider(Network.Provider provider) {
+        if (Objects.isNull(provider)) {
+            return false;
+        }
+        return ProviderDetailKeyMap.containsKey(provider.getName());
+    }
 }
diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java
index 5e5309965c1..6215afa4513 100644
--- a/api/src/main/java/com/cloud/event/EventTypes.java
+++ b/api/src/main/java/com/cloud/event/EventTypes.java
@@ -495,6 +495,8 @@ public class EventTypes {
 
     public static final String EVENT_ZONE_VLAN_ASSIGN = "ZONE.VLAN.ASSIGN";
     public static final String EVENT_ZONE_VLAN_RELEASE = "ZONE.VLAN.RELEASE";
+    public static final String EVENT_ZONE_VXLAN_ASSIGN = "ZONE.VXLAN.ASSIGN";
+    public static final String EVENT_ZONE_VXLAN_RELEASE = "ZONE.VXLAN.RELEASE";
 
     // Projects
     public static final String EVENT_PROJECT_CREATE = "PROJECT.CREATE";
diff --git a/api/src/main/java/com/cloud/network/IpAddress.java b/api/src/main/java/com/cloud/network/IpAddress.java
index ae1af450577..70d652b54e9 100644
--- a/api/src/main/java/com/cloud/network/IpAddress.java
+++ b/api/src/main/java/com/cloud/network/IpAddress.java
@@ -99,4 +99,5 @@ public interface IpAddress extends ControlledEntity, Identity, InternalIdentity,
 
     boolean isForSystemVms();
 
+    boolean isForRouter();
 }
diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java
index d3bc5005cb7..dc94932e31f 100644
--- a/api/src/main/java/com/cloud/network/Network.java
+++ b/api/src/main/java/com/cloud/network/Network.java
@@ -206,6 +206,7 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
         public static final Provider Tungsten = new Provider("Tungsten", false);
 
         public static final Provider Nsx = new Provider("Nsx", false);
+        public static final Provider Netris = new Provider("Netris", false);
 
         private final String name;
         private final boolean isExternal;
diff --git a/api/src/main/java/com/cloud/network/Networks.java b/api/src/main/java/com/cloud/network/Networks.java
index dfa0ddb84ca..9f06a044111 100644
--- a/api/src/main/java/com/cloud/network/Networks.java
+++ b/api/src/main/java/com/cloud/network/Networks.java
@@ -129,7 +129,8 @@ public class Networks {
         UnDecided(null, null),
         OpenDaylight("opendaylight", String.class),
         TUNGSTEN("tf", String.class),
-        NSX("nsx", String.class);
+        NSX("nsx", String.class),
+        Netris("netris", String.class);
 
         private final String scheme;
         private final Class<?> type;
diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java b/api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java
similarity index 74%
copy from plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java
copy to api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java
index c11141db9d4..a22db4287dc 100644
--- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java
+++ b/api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java
@@ -14,41 +14,33 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package org.apache.cloudstack.resource;
-
-import com.cloud.network.Network;
+package com.cloud.network;
 
 import java.util.List;
 
-public class NsxNetworkRule {
-
-    public enum NsxRuleAction {
-        ALLOW, DROP
-    }
-
-    private long domainId;
-    private long accountId;
-    private long zoneId;
-    private Long networkResourceId;
-    private String networkResourceName;
-    private boolean isVpcResource;
-    private long vmId;
-    private long ruleId;
-    private String publicIp;
-    private String vmIp;
-    private String publicPort;
-    private String privatePort;
-    private String protocol;
-    private String algorithm;
-    private List<NsxLoadBalancerMember> memberList;
-    private NsxRuleAction aclAction;
-    private List<String> sourceCidrList;
-    private List<String> destinationCidrList;
-    private Integer icmpCode;
-
-    private Integer icmpType;
-    private String trafficType;
-    private Network.Service service;
+public class SDNProviderNetworkRule {
+
+    protected long domainId;
+    protected long accountId;
+    protected long zoneId;
+    protected Long networkResourceId;
+    protected String networkResourceName;
+    protected boolean isVpcResource;
+    protected long vmId;
+    protected long ruleId;
+    protected String publicIp;
+    protected String vmIp;
+    protected String publicPort;
+    protected String privatePort;
+    protected String protocol;
+    protected String algorithm;
+    protected List<String> sourceCidrList;
+    protected List<String> destinationCidrList;
+    protected Integer icmpCode;
+
+    protected Integer icmpType;
+    protected String trafficType;
+    protected Network.Service service;
 
     public long getDomainId() {
         return domainId;
@@ -162,22 +154,6 @@ public class NsxNetworkRule {
         return algorithm;
     }
 
-    public List<NsxLoadBalancerMember> getMemberList() {
-        return memberList;
-    }
-
-    public void setMemberList(List<NsxLoadBalancerMember> memberList) {
-        this.memberList = memberList;
-    }
-
-    public NsxRuleAction getAclAction() {
-        return aclAction;
-    }
-
-    public void setAclAction(NsxRuleAction aclAction) {
-        this.aclAction = aclAction;
-    }
-
     public Network.Service getService() {
         return service;
     }
@@ -226,30 +202,28 @@ public class NsxNetworkRule {
         this.trafficType = trafficType;
     }
 
-    public static final class Builder {
-        private long domainId;
-        private long accountId;
-        private long zoneId;
-        private Long networkResourceId;
-        private String networkResourceName;
-        private boolean isVpcResource;
-        private long vmId;
-
-        private long ruleId;
-        private String publicIp;
-        private String vmIp;
-        private String publicPort;
-        private String privatePort;
-        private String protocol;
-        private String algorithm;
-        private List<NsxLoadBalancerMember> memberList;
-        private NsxRuleAction aclAction;
-        private List<String> sourceCidrList;
-        private List<String> destinationidrList;
-        private String trafficType;
-        private Integer icmpType;
-        private Integer icmpCode;
-        private Network.Service service;
+    public static class Builder {
+        public long domainId;
+        public long accountId;
+        public long zoneId;
+        public Long networkResourceId;
+        public String networkResourceName;
+        public boolean isVpcResource;
+        public long vmId;
+
+        public long ruleId;
+        public String publicIp;
+        public String vmIp;
+        public String publicPort;
+        public String privatePort;
+        public String protocol;
+        public String algorithm;
+        public List<String> sourceCidrList;
+        public List<String> destinationCidrList;
+        public String trafficType;
+        public Integer icmpType;
+        public Integer icmpCode;
+        public Network.Service service;
 
         public Builder() {
             // Default constructor
@@ -326,17 +300,6 @@ public class NsxNetworkRule {
             return this;
         }
 
-        public Builder setMemberList(List<NsxLoadBalancerMember> memberList) {
-            this.memberList = memberList;
-            return this;
-        }
-
-
-        public Builder setAclAction(NsxRuleAction aclAction) {
-            this.aclAction = aclAction;
-            return this;
-        }
-
         public Builder setTrafficType(String trafficType) {
             this.trafficType = trafficType;
             return this;
@@ -358,7 +321,7 @@ public class NsxNetworkRule {
         }
 
         public Builder setDestinationCidrList(List<String> destinationCidrList) {
-            this.destinationidrList = destinationCidrList;
+            this.destinationCidrList = destinationCidrList;
             return this;
         }
 
@@ -367,8 +330,8 @@ public class NsxNetworkRule {
             return this;
         }
 
-        public NsxNetworkRule build() {
-            NsxNetworkRule rule = new NsxNetworkRule();
+        public SDNProviderNetworkRule build() {
+            SDNProviderNetworkRule rule = new SDNProviderNetworkRule();
             rule.setDomainId(this.domainId);
             rule.setAccountId(this.accountId);
             rule.setZoneId(this.zoneId);
@@ -383,12 +346,10 @@ public class NsxNetworkRule {
             rule.setProtocol(this.protocol);
             rule.setRuleId(this.ruleId);
             rule.setAlgorithm(this.algorithm);
-            rule.setMemberList(this.memberList);
-            rule.setAclAction(this.aclAction);
             rule.setIcmpType(this.icmpType);
             rule.setIcmpCode(this.icmpCode);
             rule.setSourceCidrList(this.sourceCidrList);
-            rule.setDestinationCidrList(this.destinationidrList);
+            rule.setDestinationCidrList(this.destinationCidrList);
             rule.setTrafficType(this.trafficType);
             rule.setService(service);
             return rule;
diff --git a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java
index 994df875f7d..51036abe060 100644
--- a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java
+++ b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java
@@ -24,7 +24,7 @@ import org.apache.cloudstack.api.InternalIdentity;
 
 public interface Site2SiteVpnConnection extends ControlledEntity, InternalIdentity, Displayable {
     enum State {
-        Pending, Connecting, Connected, Disconnected, Error,
+        Pending, Connecting, Connected, Disconnected, Error, Removed
     }
 
     @Override
diff --git a/api/src/main/java/com/cloud/network/element/NetworkElement.java b/api/src/main/java/com/cloud/network/element/NetworkElement.java
index fa67575edd3..00c530ce114 100644
--- a/api/src/main/java/com/cloud/network/element/NetworkElement.java
+++ b/api/src/main/java/com/cloud/network/element/NetworkElement.java
@@ -23,6 +23,7 @@ import com.cloud.deploy.DeployDestination;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.Network.Capability;
 import com.cloud.network.Network.Provider;
@@ -87,6 +88,12 @@ public interface NetworkElement extends Adapter {
     boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException,
         ResourceUnavailableException;
 
+    /**
+     * Release IP from the network provider if reserved
+     * @param ipAddress
+     */
+    boolean releaseIp(IpAddress ipAddress);
+
     /**
      * The network is being shutdown.
      * @param network
diff --git a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java b/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
index e99bc2fd416..8dcc8b6d0a4 100644
--- a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
+++ b/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
@@ -17,12 +17,40 @@
 package com.cloud.network.element;
 
 import java.util.List;
+import java.util.Objects;
 
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.network.Network;
+import com.cloud.network.rules.FirewallRule;
 import com.cloud.network.rules.PortForwardingRule;
+import com.cloud.network.vpc.NetworkACLItem;
 
 public interface PortForwardingServiceProvider extends NetworkElement, IpDeployingRequester {
+
+    static String getPublicPortRange(PortForwardingRule rule) {
+        return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ?
+                String.valueOf(rule.getSourcePortStart()) :
+                String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd()));
+    }
+
+    static String getPrivatePFPortRange(PortForwardingRule rule) {
+        return rule.getDestinationPortStart() == rule.getDestinationPortEnd() ?
+                String.valueOf(rule.getDestinationPortStart()) :
+                String.valueOf(rule.getDestinationPortStart()).concat("-").concat(String.valueOf(rule.getDestinationPortEnd()));
+    }
+
+    static String getPrivatePortRange(FirewallRule rule) {
+        return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ?
+                String.valueOf(rule.getSourcePortStart()) :
+                String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd()));
+    }
+
+    static String getPrivatePortRangeForACLRule(NetworkACLItem rule) {
+        return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ?
+                String.valueOf(rule.getSourcePortStart()) :
+                String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd()));
+    }
+
     /**
      * Apply rules
      * @param network
diff --git a/api/src/main/java/com/cloud/network/element/VpcProvider.java b/api/src/main/java/com/cloud/network/element/VpcProvider.java
index 6debd1fbc2d..47e70d6597d 100644
--- a/api/src/main/java/com/cloud/network/element/VpcProvider.java
+++ b/api/src/main/java/com/cloud/network/element/VpcProvider.java
@@ -55,4 +55,6 @@ public interface VpcProvider extends NetworkElement {
     boolean applyACLItemsToPrivateGw(PrivateGateway gateway, List<? extends NetworkACLItem> rules) throws ResourceUnavailableException;
 
     boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address);
+
+    boolean updateVpc(Vpc vpc, String previousVpcName);
 }
diff --git a/api/src/main/java/com/cloud/network/guru/NetworkGuru.java b/api/src/main/java/com/cloud/network/guru/NetworkGuru.java
index 7b81c75ed84..ced664e54a9 100644
--- a/api/src/main/java/com/cloud/network/guru/NetworkGuru.java
+++ b/api/src/main/java/com/cloud/network/guru/NetworkGuru.java
@@ -215,4 +215,8 @@ public interface NetworkGuru extends Adapter {
     default boolean isSlaacV6Only() {
         return true;
     }
+
+    default boolean update(Network network, String prevNetworkName) {
+        return true;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java
similarity index 64%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java
index d3fef252f50..afc21f7f511 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java
@@ -14,10 +14,28 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package com.cloud.network.netris;
 
-import com.cloud.utils.db.GenericDao;
+public class NetrisLbBackend {
+    private long vmId;
+    private String vmIp;
+    private int port;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    public NetrisLbBackend(long vmId, String vmIp, int port) {
+        this.vmId = vmId;
+        this.vmIp = vmIp;
+        this.port = port;
+    }
+
+    public long getVmId() {
+        return vmId;
+    }
+
+    public String getVmIp() {
+        return vmIp;
+    }
+
+    public int getPort() {
+        return port;
+    }
 }
diff --git a/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java
new file mode 100644
index 00000000000..211517ead49
--- /dev/null
+++ b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java
@@ -0,0 +1,108 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.network.netris;
+
+import com.cloud.network.SDNProviderNetworkRule;
+
+
+import java.util.List;
+
+public class NetrisNetworkRule {
+    public enum NetrisRuleAction {
+        PERMIT, DENY
+    }
+
+    private SDNProviderNetworkRule baseRule;
+    private NetrisRuleAction aclAction;
+    private List<NetrisLbBackend> lbBackends;
+    private String lbRuleName;
+    private String lbCidrList;
+    private String reason;
+
+    public NetrisNetworkRule(Builder builder) {
+        this.baseRule = builder.baseRule;
+        this.aclAction = builder.aclAction;
+        this.lbBackends = builder.lbBackends;
+        this.reason = builder.reason;
+        this.lbCidrList = builder.lbCidrList;
+        this.lbRuleName = builder.lbRuleName;
+    }
+
+    public NetrisRuleAction getAclAction() {
+        return aclAction;
+    }
+
+    public List<NetrisLbBackend> getLbBackends() {
+        return lbBackends;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public String getLbCidrList() {return lbCidrList; }
+
+    public String getLbRuleName() { return lbRuleName; }
+
+    public SDNProviderNetworkRule getBaseRule() {
+        return baseRule;
+    }
+
+    // Builder class extending the parent builder
+    public static class Builder {
+        private SDNProviderNetworkRule baseRule;
+        private NetrisRuleAction aclAction;
+        private List<NetrisLbBackend> lbBackends;
+        private String reason;
+        private String lbCidrList;
+        private String lbRuleName;
+
+        public Builder baseRule(SDNProviderNetworkRule baseRule) {
+            this.baseRule = baseRule;
+            return this;
+        }
+
+        public Builder aclAction(NetrisRuleAction aclAction) {
+            this.aclAction = aclAction;
+            return this;
+        }
+
+        public Builder lbBackends(List<NetrisLbBackend> lbBackends) {
+            this.lbBackends = lbBackends;
+            return this;
+        }
+
+        public Builder reason(String reason) {
+            this.reason = reason;
+            return this;
+        }
+
+        public Builder lbCidrList(String lbCidrList) {
+            this.lbCidrList = lbCidrList;
+            return this;
+        }
+
+        public Builder lbRuleName(String lbRuleName) {
+            this.lbRuleName = lbRuleName;
+            return this;
+        }
+
+        public NetrisNetworkRule build() {
+            return new NetrisNetworkRule(this);
+        }
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/api/src/main/java/com/cloud/network/netris/NetrisProvider.java
similarity index 68%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to api/src/main/java/com/cloud/network/netris/NetrisProvider.java
index d3fef252f50..fccf2930e97 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/api/src/main/java/com/cloud/network/netris/NetrisProvider.java
@@ -14,10 +14,17 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package com.cloud.network.netris;
 
-import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public interface NetrisProvider extends InternalIdentity, Identity {
+    long getZoneId();
+    String getName();
+    String getUrl();
+    String getUsername();
+    String getSiteName();
+    String getTenantName();
+    String getNetrisTag();
 }
diff --git a/api/src/main/java/com/cloud/network/netris/NetrisService.java b/api/src/main/java/com/cloud/network/netris/NetrisService.java
new file mode 100644
index 00000000000..09d6b753980
--- /dev/null
+++ b/api/src/main/java/com/cloud/network/netris/NetrisService.java
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.network.netris;
+
+import com.cloud.network.IpAddress;
+import com.cloud.network.Network;
+import com.cloud.network.SDNProviderNetworkRule;
+import com.cloud.network.vpc.StaticRoute;
+import com.cloud.network.vpc.Vpc;
+
+import java.util.List;
+
+public interface NetrisService {
+    boolean createIPAMAllocationsForZoneLevelPublicRanges(long zoneId);
+
+    boolean createVpcResource(long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled, String cidr, boolean isVpcNetwork);
+
+    boolean updateVpcResource(long zoneId, long accountId, long domainId, Long vpcId, String vpcName, String previousVpcName);
+
+    boolean deleteVpcResource(long zoneId, long accountId, long domainId, Vpc vpc);
+
+    boolean createVnetResource(Long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr, Boolean globalRouting);
+
+    boolean updateVnetResource(Long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String prevNetworkName);
+
+    boolean deleteVnetResource(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr);
+
+    boolean createSnatRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, long networkId, boolean isForVpc, String vpcCidr, String sourceNatIp);
+
+    boolean createPortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule);
+
+    boolean deletePortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule);
+
+    boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address);
+
+    boolean createStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String vpcCidr, String staticNatIp, String vmIp, long vmId);
+
+    boolean deleteStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String staticNatIp, long vmId);
+
+    boolean addFirewallRules(Network network, List<NetrisNetworkRule> firewallRules);
+    boolean deleteFirewallRules(Network network, List<NetrisNetworkRule> firewallRules);
+
+    boolean addOrUpdateStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId, boolean updateRoute);
+
+    boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId);
+
+    List<StaticRoute> listStaticRoutes(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId);
+
+    boolean releaseNatIp(long zoneId, String publicIp);
+
+    boolean createOrUpdateLbRule(NetrisNetworkRule rule);
+    boolean deleteLbRule(NetrisNetworkRule rule);
+}
diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java
index 5707ca14024..739fca328b8 100644
--- a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java
+++ b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java
@@ -25,6 +25,7 @@ public interface StaticRoute extends ControlledEntity, Identity, InternalIdentit
         Staged, // route been created but has never got through network rule conflict detection.  Routes in this state can not be sent to VPC virtual router.
         Add,    // Add means the route has been created and has gone through network rule conflict detection.
         Active, // Route has been sent to the VPC router and reported to be active.
+        Update,
         Revoke,  // Revoke means this route has been revoked. If this route has been sent to the VPC router, the route will be deleted from database.
         Deleting // rule has been revoked and is scheduled for deletion
     }
@@ -32,7 +33,9 @@ public interface StaticRoute extends ControlledEntity, Identity, InternalIdentit
     /**
      * @return
      */
-    long getVpcGatewayId();
+    Long getVpcGatewayId();
+
+    String getNextHop();
 
     /**
      * @return
diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java
index cb4849f1f7b..c8fc073911f 100644
--- a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java
+++ b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java
@@ -23,7 +23,8 @@ public class StaticRouteProfile implements StaticRoute {
     private String targetCidr;
     private long accountId;
     private long domainId;
-    private long gatewayId;
+    private Long gatewayId;
+    private String nextHop;
     private StaticRoute.State state;
     private long vpcId;
     String vlanTag;
@@ -46,6 +47,18 @@ public class StaticRouteProfile implements StaticRoute {
         ipAddress = gateway.getIp4Address();
     }
 
+    public StaticRouteProfile(StaticRoute staticRoute) {
+        id = staticRoute.getId();
+        uuid = staticRoute.getUuid();
+        targetCidr = staticRoute.getCidr();
+        accountId = staticRoute.getAccountId();
+        domainId = staticRoute.getDomainId();
+        gatewayId = staticRoute.getVpcGatewayId();
+        state = staticRoute.getState();
+        vpcId = staticRoute.getVpcId();
+        gateway = staticRoute.getNextHop();
+    }
+
     @Override
     public long getAccountId() {
         return accountId;
@@ -57,10 +70,15 @@ public class StaticRouteProfile implements StaticRoute {
     }
 
     @Override
-    public long getVpcGatewayId() {
+    public Long getVpcGatewayId() {
         return gatewayId;
     }
 
+    @Override
+    public String getNextHop() {
+        return nextHop;
+    }
+
     @Override
     public String getCidr() {
         return targetCidr;
diff --git a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
index 38263f59667..17f49bb3652 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
@@ -32,6 +32,8 @@ public interface VpcOffering extends InternalIdentity, Identity {
     public static final String redundantVPCOfferingName = "Redundant VPC offering";
     public static final String DEFAULT_VPC_NAT_NSX_OFFERING_NAME = "VPC offering with NSX - NAT Mode";
     public static final String DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME = "VPC offering with NSX - Route Mode";
+    public static final String DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME = "VPC offering with Netris - Route Mode";
+    public static final String DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME = "VPC offering with Netris - NAT Mode";
 
     /**
      *
@@ -56,8 +58,6 @@ public interface VpcOffering extends InternalIdentity, Identity {
      */
     boolean isDefault();
 
-    boolean isForNsx();
-
     NetworkOffering.NetworkMode getNetworkMode();
 
     /**
diff --git a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
index 10f1ddcc12d..97b95339ecf 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
@@ -37,7 +37,7 @@ public interface VpcProvisioningService {
     VpcOffering createVpcOffering(String name, String displayText, List<String> supportedServices,
                                   Map<String, List<String>> serviceProviders,
                                   Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol,
-                                  Long serviceOfferingId, Boolean forNsx, NetworkOffering.NetworkMode networkMode,
+                                  Long serviceOfferingId, String externalProvider, NetworkOffering.NetworkMode networkMode,
                                   List<Long> domainIds, List<Long> zoneIds, VpcOffering.State state,
                                   NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber);
 
diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java
index af2a9847a62..4477f10d1a4 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcService.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java
@@ -238,7 +238,7 @@ public interface VpcService {
      * @param cidr
      * @return
      */
-    StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException;
+    StaticRoute createStaticRoute(Long gatewayId, Long vpcId, String nextHop, String cidr) throws NetworkRuleConflictException;
 
     /**
      * Lists static routes based on parameters passed to the call
diff --git a/api/src/main/java/com/cloud/offering/NetworkOffering.java b/api/src/main/java/com/cloud/offering/NetworkOffering.java
index 7011aea679e..5000a4f8c62 100644
--- a/api/src/main/java/com/cloud/offering/NetworkOffering.java
+++ b/api/src/main/java/com/cloud/offering/NetworkOffering.java
@@ -64,6 +64,8 @@ public interface NetworkOffering extends InfrastructureEntity, InternalIdentity,
     public static final String DEFAULT_NAT_NSX_OFFERING_FOR_VPC = "DefaultNATNSXNetworkOfferingForVpc";
     public static final String DEFAULT_NAT_NSX_OFFERING_FOR_VPC_WITH_ILB = "DefaultNATNSXNetworkOfferingForVpcWithInternalLB";
     public static final String DEFAULT_ROUTED_NSX_OFFERING_FOR_VPC = "DefaultRoutedNSXNetworkOfferingForVpc";
+    public static final String DEFAULT_ROUTED_NETRIS_OFFERING_FOR_VPC = "DefaultRoutedNetrisNetworkOfferingForVpc";
+    public static final String DEFAULT_NAT_NETRIS_OFFERING_FOR_VPC = "DefaultNATNetrisNetworkOfferingForVpc";
     public static final String DEFAULT_NAT_NSX_OFFERING = "DefaultNATNSXNetworkOffering";
     public static final String DEFAULT_ROUTED_NSX_OFFERING = "DefaultRoutedNSXNetworkOffering";
     public final static String QuickCloudNoServices = "QuickCloudNoServices";
@@ -102,10 +104,6 @@ public interface NetworkOffering extends InfrastructureEntity, InternalIdentity,
 
     boolean isForVpc();
 
-    boolean isForTungsten();
-
-    boolean isForNsx();
-
     NetworkMode getNetworkMode();
 
     TrafficType getTrafficType();
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index bb16b0ff90d..1c85e6a8c03 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -210,6 +210,7 @@ public class ApiConstants {
     public static final String FORMAT = "format";
     public static final String FOR_VIRTUAL_NETWORK = "forvirtualnetwork";
     public static final String FOR_SYSTEM_VMS = "forsystemvms";
+    public static final String FOR_PROVIDER = "forprovider";
     public static final String FULL_PATH = "fullpath";
     public static final String GATEWAY = "gateway";
     public static final String IP6_GATEWAY = "ip6gateway";
@@ -253,6 +254,7 @@ public class ApiConstants {
     public static final String PREVIOUS_OWNER_ID = "previousownerid";
     public static final String PREVIOUS_OWNER_NAME = "previousownername";
     public static final String NEXT_ACL_RULE_ID = "nextaclruleid";
+    public static final String NEXT_HOP = "nexthop";
     public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash";
     public static final String IMAGE_PATH = "imagepath";
     public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported";
@@ -325,6 +327,8 @@ public class ApiConstants {
     public static final String MIN_CPU_NUMBER = "mincpunumber";
     public static final String MIN_MEMORY = "minmemory";
     public static final String MIGRATION_TYPE = "migrationtype";
+    public static final String MIGRATION_JOB_ID = "migrationjobid";
+    public static final String MIGRATION_JOB_STATUS = "migrationjobstatus";
     public static final String MIGRATIONS = "migrations";
     public static final String MEMORY = "memory";
     public static final String MODE = "mode";
@@ -448,6 +452,7 @@ public class ApiConstants {
     public static final String SIGNATURE = "signature";
     public static final String SIGNATURE_VERSION = "signatureversion";
     public static final String SINCE = "since";
+    public static final String SITE_NAME = "sitename";
     public static final String SIZE = "size";
     public static final String SIZEGB = "sizegb";
     public static final String SNAPSHOT = "snapshot";
@@ -490,6 +495,7 @@ public class ApiConstants {
     public static final String TIMEOUT = "timeout";
     public static final String TIMEZONE = "timezone";
     public static final String TIMEZONEOFFSET = "timezoneoffset";
+    public static final String TENANT_NAME = "tenantname";
     public static final String TOTAL = "total";
     public static final String TOTAL_SUBNETS = "totalsubnets";
     public static final String TYPE = "type";
@@ -858,6 +864,8 @@ public class ApiConstants {
     public static final String NETWORK = "network";
     public static final String VPC_ID = "vpcid";
     public static final String VPC_NAME = "vpcname";
+    public static final String VPC_GATEWAY_ID = "vpcgatewayid";
+    public static final String VPC_GATEWAY_IP = "vpcgatewayip";
     public static final String GATEWAY_ID = "gatewayid";
     public static final String CAN_USE_FOR_DEPLOY = "canusefordeploy";
     public static final String RESOURCE_IDS = "resourceids";
@@ -1143,6 +1151,9 @@ public class ApiConstants {
     public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid";
     public static final String HAS_RULES = "hasrules";
     public static final String NSX_DETAIL_KEY = "forNsx";
+    public static final String NETRIS_DETAIL_KEY = "forNetris";
+    public static final String NETRIS_TAG = "netristag";
+    public static final String NETRIS_VXLAN_ID = "netrisvxlanid";
     public static final String DISK_PATH = "diskpath";
     public static final String IMPORT_SOURCE = "importsource";
     public static final String TEMP_PATH = "temppath";
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java
index af3db374a7c..603e34d600b 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.api.command.admin.network;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -140,12 +141,19 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
             description = "true if network offering is meant to be used for VPC, false otherwise.")
     private Boolean forVpc;
 
+    @Deprecated
     @Parameter(name = ApiConstants.FOR_NSX,
             type = CommandType.BOOLEAN,
             description = "true if network offering is meant to be used for NSX, false otherwise.",
             since = "4.20.0")
     private Boolean forNsx;
 
+    @Parameter(name = ApiConstants.PROVIDER,
+            type = CommandType.STRING,
+            description = "Name of the provider providing the service",
+            since = "4.20.0")
+    private String provider;
+
     @Parameter(name = ApiConstants.NSX_SUPPORT_LB,
             type = CommandType.BOOLEAN,
             description = "true if network offering for NSX network offering supports Load balancer service.",
@@ -257,18 +265,38 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
         return serviceOfferingId;
     }
 
+    public boolean isExternalNetworkProvider() {
+        return Arrays.asList("NSX", "Netris").stream()
+                .anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
+    }
+
+    public boolean isForNsx() {
+        return provider != null && provider.equalsIgnoreCase("NSX");
+    }
+
+    public boolean isForNetris() {
+        return provider != null && provider.equalsIgnoreCase("Netris");
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
     public List<String> getSupportedServices() {
-        if (!isForNsx()) {
+        if (!isExternalNetworkProvider()) {
             return supportedServices == null ? new ArrayList<String>() : supportedServices;
         } else {
             List<String> services = new ArrayList<>(List.of(
                     Dhcp.getName(),
                     Dns.getName(),
-                    StaticNat.getName(),
-                    SourceNat.getName(),
-                    PortForwarding.getName(),
                     UserData.getName()
             ));
+            if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
+                services.addAll(Arrays.asList(
+                        StaticNat.getName(),
+                        SourceNat.getName(),
+                        PortForwarding.getName()));
+            }
             if (getNsxSupportsLbService()) {
                 services.add(Lb.getName());
             }
@@ -308,10 +336,6 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
         return forVpc;
     }
 
-    public boolean isForNsx() {
-        return BooleanUtils.isTrue(forNsx);
-    }
-
     public String getNetworkMode() {
         return networkMode;
     }
@@ -345,7 +369,7 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
 
     public Map<String, List<String>> getServiceProviders() {
         Map<String, List<String>> serviceProviderMap = new HashMap<>();
-        if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isForNsx()) {
+        if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) {
             Collection servicesCollection = serviceProviderList.values();
             Iterator iter = servicesCollection.iterator();
             while (iter.hasNext()) {
@@ -361,17 +385,16 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
                 providerList.add(provider);
                 serviceProviderMap.put(service, providerList);
             }
-        } else if (Boolean.TRUE.equals(forNsx)) {
-            getServiceProviderMapForNsx(serviceProviderMap);
+        } else if (isExternalNetworkProvider()) {
+            getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName());
         }
         return serviceProviderMap;
     }
 
-    private void getServiceProviderMapForNsx(Map<String, List<String>> serviceProviderMap) {
+    private void getServiceProviderMapForExternalProvider(Map<String, List<String>> serviceProviderMap, String provider) {
         String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() :
                 VirtualRouterProvider.Type.VirtualRouter.name();
-        List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "SecurityGroup", "Connectivity",
-                "Gateway", "BaremetalPxeService"));
+        List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "Gateway", "SecurityGroup", "Connectivity", "BaremetalPxeService"));
         List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
         List<String> allServices = Service.listAllServices().stream().map(Service::getName).collect(Collectors.toList());
         if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) {
@@ -384,8 +407,9 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
                 continue;
             if (routerSupported.contains(service))
                 serviceProviderMap.put(service, List.of(routerProvider));
-            else
-                serviceProviderMap.put(service, List.of(Network.Provider.Nsx.getName()));
+            else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || NetworkACL.getName().equalsIgnoreCase(service)) {
+                    serviceProviderMap.put(service, List.of(provider));
+                }
             if (!getNsxSupportsLbService()) {
                 serviceProviderMap.remove(Lb.getName());
             }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java
index c0ba99a8233..87a4bd07a68 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java
@@ -16,6 +16,8 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.vlan;
 
+import com.cloud.configuration.ConfigurationService;
+import com.cloud.network.Network;
 import com.cloud.utils.net.NetUtils;
 
 import org.apache.cloudstack.api.APICommand;
@@ -39,7 +41,6 @@ import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.user.Account;
 
-import java.util.Objects;
 
 @APICommand(name = "createVlanIpRange", description = "Creates a VLAN IP range.", responseObject = VlanIpRangeResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -114,8 +115,8 @@ public class CreateVlanIpRangeCmd extends BaseCmd {
     @Parameter(name = ApiConstants.FOR_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "true if IP range is set to system vms, false if not")
     private Boolean forSystemVms;
 
-    @Parameter(name = ApiConstants.FOR_NSX, type = CommandType.BOOLEAN, description = "true if the IP range is used for NSX resource", since = "4.20.0")
-    private boolean forNsx;
+    @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Provider name for which the IP range is reserved for", since = "4.20.0")
+    private String provider;
 
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
@@ -157,12 +158,12 @@ public class CreateVlanIpRangeCmd extends BaseCmd {
         return startIp;
     }
 
-    public boolean isForNsx() {
-        return !Objects.isNull(forNsx) && forNsx;
+    public Network.Provider getProvider() {
+        return Network.Provider.getProvider(provider);
     }
 
     public String getVlan() {
-        if ((vlan == null || vlan.isEmpty()) && !isForNsx()) {
+        if ((vlan == null || vlan.isEmpty()) && !ConfigurationService.IsIpRangeForProvider(getProvider())) {
             vlan = "untagged";
         }
         return vlan;
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java
index 73b4f5df196..7ebe67a476b 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java
@@ -17,6 +17,7 @@
 package org.apache.cloudstack.api.command.admin.vpc;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -25,10 +26,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.network.Network;
 import com.cloud.network.VirtualRouterProvider;
+import com.cloud.offering.NetworkOffering;
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
 import org.apache.commons.collections.CollectionUtils;
@@ -57,6 +60,7 @@ import static com.cloud.network.Network.Service.SourceNat;
 import static com.cloud.network.Network.Service.PortForwarding;
 import static com.cloud.network.Network.Service.NetworkACL;
 import static com.cloud.network.Network.Service.UserData;
+import static com.cloud.network.Network.Service.Gateway;
 
 @APICommand(name = "createVPCOffering", description = "Creates VPC offering", responseObject = VpcOfferingResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -112,12 +116,19 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
             since = "4.13")
     private List<Long> zoneIds;
 
+    @Deprecated
     @Parameter(name = ApiConstants.FOR_NSX,
             type = CommandType.BOOLEAN,
             description = "true if network offering is meant to be used for NSX, false otherwise.",
             since = "4.20.0")
     private Boolean forNsx;
 
+    @Parameter(name = ApiConstants.PROVIDER,
+            type = CommandType.STRING,
+            description = "Name of the provider providing the service",
+            since = "4.20.0")
+    private String provider;
+
     @Parameter(name = ApiConstants.NSX_SUPPORT_LB,
             type = CommandType.BOOLEAN,
             description = "true if network offering for NSX VPC offering supports Load balancer service.",
@@ -158,20 +169,31 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
         return StringUtils.isEmpty(displayText) ? vpcOfferingName : displayText;
     }
 
+    public boolean isExternalNetworkProvider() {
+        return Arrays.asList("NSX", "Netris").stream()
+                .anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
+    }
+
     public List<String> getSupportedServices() {
-        if (!isForNsx() && CollectionUtils.isEmpty(supportedServices)) {
+        if (!isExternalNetworkProvider() && CollectionUtils.isEmpty(supportedServices)) {
             throw new InvalidParameterValueException("Supported services needs to be provided");
         }
-        if (isForNsx()) {
+        if (isExternalNetworkProvider()) {
             supportedServices = new ArrayList<>(List.of(
                     Dhcp.getName(),
                     Dns.getName(),
-                    StaticNat.getName(),
-                    SourceNat.getName(),
                     NetworkACL.getName(),
-                    PortForwarding.getName(),
                     UserData.getName()
                     ));
+            if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
+                supportedServices.addAll(Arrays.asList(
+                        StaticNat.getName(),
+                        SourceNat.getName(),
+                        PortForwarding.getName()));
+            }
+            if (NetworkOffering.NetworkMode.ROUTED.name().equalsIgnoreCase(getNetworkMode())) {
+                supportedServices.add(Gateway.getName());
+            }
             if (getNsxSupportsLbService()) {
                 supportedServices.add(Lb.getName());
             }
@@ -179,8 +201,8 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
         return supportedServices;
     }
 
-    public boolean isForNsx() {
-        return BooleanUtils.isTrue(forNsx);
+    public String getProvider() {
+        return provider;
     }
 
     public String getNetworkMode() {
@@ -193,7 +215,7 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
 
     public Map<String, List<String>> getServiceProviders() {
         Map<String, List<String>> serviceProviderMap = new HashMap<>();
-        if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isForNsx()) {
+        if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) {
             Collection<? extends Map<String, String>> servicesCollection = serviceProviderList.values();
             Iterator<? extends Map<String, String>> iter = servicesCollection.iterator();
             while (iter.hasNext()) {
@@ -213,16 +235,18 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
                 providerList.add(provider);
                 serviceProviderMap.put(service, providerList);
             }
-        } else if (Boolean.TRUE.equals(forNsx)) {
-            getServiceProviderMapForNsx(serviceProviderMap);
+        } else if (isExternalNetworkProvider()) {
+            getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName());
         }
 
         return serviceProviderMap;
     }
 
-    private void getServiceProviderMapForNsx(Map<String, List<String>> serviceProviderMap) {
-        List<String> unsupportedServices = List.of("Vpn", "BaremetalPxeService", "SecurityGroup", "Connectivity",
-                "Gateway", "Firewall");
+    private void getServiceProviderMapForExternalProvider(Map<String, List<String>> serviceProviderMap, String provider) {
+        List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "BaremetalPxeService", "SecurityGroup", "Connectivity", "Firewall"));
+        if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
+            unsupportedServices.add("Gateway");
+        }
         List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
         List<String> allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList());
         for (String service : allServices) {
@@ -230,8 +254,10 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
                 continue;
             if (routerSupported.contains(service))
                 serviceProviderMap.put(service, List.of(VirtualRouterProvider.Type.VPCVirtualRouter.name()));
-            else
-                serviceProviderMap.put(service, List.of(Network.Provider.Nsx.getName()));
+            else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) ||
+                    Stream.of(NetworkACL.getName(), Gateway.getName()).anyMatch(s -> s.equalsIgnoreCase(service))) {
+                serviceProviderMap.put(service, List.of(provider));
+            }
         }
         if (!getNsxSupportsLbService()) {
             serviceProviderMap.remove(Lb.getName());
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java
index 5760ca3ba1c..a9d651ae5fe 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java
@@ -108,6 +108,9 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC
     @Parameter(name = ApiConstants.FOR_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "true if range is dedicated for system VMs", since = "4.20.0")
     private Boolean forSystemVMs;
 
+    @Parameter(name = ApiConstants.FOR_PROVIDER, type = CommandType.BOOLEAN, description = "true if range is dedicated for external network provider", since = "4.20.0")
+    private Boolean forProvider;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -183,6 +186,10 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC
         return BooleanUtils.isTrue(forSystemVMs);
     }
 
+    public boolean isForProvider() {
+        return BooleanUtils.isTrue(forProvider);
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java
index b28c02cb800..41779ccdeb1 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java
@@ -27,6 +27,7 @@ import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.PrivateGatewayResponse;
 import org.apache.cloudstack.api.response.StaticRouteResponse;
+import org.apache.cloudstack.api.response.VpcResponse;
 import org.apache.cloudstack.context.CallContext;
 
 import com.cloud.event.EventTypes;
@@ -45,20 +46,38 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd {
     @Parameter(name = ApiConstants.GATEWAY_ID,
                type = CommandType.UUID,
                entityType = PrivateGatewayResponse.class,
-               required = true,
-               description = "the gateway id we are creating static route for")
+               description = "the gateway id we are creating static route for. Mutually exclusive with the nexthop parameter")
     private Long gatewayId;
 
+    @Parameter(name = ApiConstants.VPC_ID,
+            type = CommandType.UUID,
+            entityType = VpcResponse.class,
+            description = "the vpc id for which the static route is created. This is required for nexthop parameter")
+    private Long vpcId;
+
+    @Parameter(name = ApiConstants.NEXT_HOP,
+            type = CommandType.STRING,
+            description = "the next hop of static route. Mutually exclusive with the gatewayid parameter")
+    private String nextHop;
+
     @Parameter(name = ApiConstants.CIDR, required = true, type = CommandType.STRING, description = "static route cidr")
     private String cidr;
 
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
-    public long getGatewayId() {
+    public Long getGatewayId() {
         return gatewayId;
     }
 
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public String getNextHop() {
+        return nextHop;
+    }
+
     public String getCidr() {
         return cidr;
     }
@@ -69,7 +88,7 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd {
     @Override
     public void create() throws ResourceAllocationException {
         try {
-            StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getCidr());
+            StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getVpcId(), getNextHop(), getCidr());
             setEntityId(result.getId());
             setEntityUuid(result.getUuid());
         } catch (NetworkRuleConflictException ex) {
@@ -114,11 +133,8 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd {
 
     @Override
     public long getEntityOwnerId() {
-        VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId);
-        if (gateway == null) {
-            throw new InvalidParameterValueException("Invalid gateway id is specified");
-        }
-        return _entityMgr.findById(Vpc.class, gateway.getVpcId()).getAccountId();
+        Long vpcId = getSyncObjId();
+        return _entityMgr.findById(Vpc.class, vpcId).getAccountId();
     }
 
     @Override
@@ -128,11 +144,20 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd {
 
     @Override
     public Long getSyncObjId() {
-        VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId);
-        if (gateway == null) {
-            throw new InvalidParameterValueException("Invalid id is specified for the gateway");
+        if (gatewayId != null) {
+            VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId);
+            if (gateway == null) {
+                throw new InvalidParameterValueException("Invalid id is specified for the gateway");
+            }
+            return gateway.getVpcId();
+        } else if (vpcId != null) {
+            Vpc vpc = _entityMgr.findById(Vpc.class, vpcId);
+            if (vpc == null) {
+                throw new InvalidParameterValueException("Invalid vpc id is specified");
+            }
+            return vpc.getId();
         }
-        return gateway.getVpcId();
+        throw new InvalidParameterValueException("One of vpcId or gatewayId must be specified");
     }
 
     @Override
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java
index 6f31176c4ff..f7230e6f8e8 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java
@@ -28,6 +28,7 @@ import org.apache.cloudstack.api.BaseAsyncCmd;
 import org.apache.cloudstack.api.BaseAsyncCreateCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.IPAddressResponse;
 import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse;
 import org.apache.cloudstack.api.response.VpcResponse;
 import org.apache.cloudstack.context.CallContext;
@@ -44,9 +45,15 @@ public class CreateVpnGatewayCmd extends BaseAsyncCreateCmd {
                type = CommandType.UUID,
                entityType = VpcResponse.class,
                required = true,
-               description = "public ip address id of the vpn gateway")
+               description = "id of the vpc")
     private Long vpcId;
 
+    @Parameter(name = ApiConstants.IP_ADDRESS_ID,
+            type = CommandType.UUID,
+            entityType = IPAddressResponse.class,
+            description = "the public IP address ID for which VPN gateway is being enabled. By default the source NAT IP or router IP will be used.")
+    private Long ipAddressId;
+
     @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the vpn to the end user or not", since = "4.4", authorized = {RoleType.Admin})
     private Boolean display;
 
@@ -58,6 +65,10 @@ public class CreateVpnGatewayCmd extends BaseAsyncCreateCmd {
         return vpcId;
     }
 
+    public Long getIpAddressId() {
+        return ipAddressId;
+    }
+
     @Deprecated
     public Boolean getDisplay() {
         return display;
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
index 3eeaaef2afa..e1db5aa6824 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java
@@ -55,10 +55,6 @@ public class AsyncJobResponse extends BaseResponse {
     @Param(description = "the async command executed")
     private String cmd;
 
-    @SerializedName("jobstatus")
-    @Param(description = "the current job status-should be 0 for PENDING")
-    private Integer jobStatus;
-
     @SerializedName("jobprocstatus")
     @Param(description = "the progress information of the PENDING job")
     private Integer jobProcStatus;
@@ -119,11 +115,6 @@ public class AsyncJobResponse extends BaseResponse {
         this.cmd = cmd;
     }
 
-    @Override
-    public void setJobStatus(Integer jobStatus) {
-        this.jobStatus = jobStatus;
-    }
-
     public void setJobProcStatus(Integer jobProcStatus) {
         this.jobProcStatus = jobProcStatus;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java
index 4114c228e26..f399c1260f7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java
@@ -50,13 +50,13 @@ public class ClusterDrsPlanMigrationResponse extends BaseResponse {
     @Param(description = "Destination host for VM migration")
     String destHostName;
 
-    @SerializedName(ApiConstants.JOB_ID)
+    @SerializedName(ApiConstants.MIGRATION_JOB_ID)
     @Param(description = "id of VM migration async job")
-    private Long jobId;
+    private Long migrationJobId;
 
-    @SerializedName(ApiConstants.JOB_STATUS)
+    @SerializedName(ApiConstants.MIGRATION_JOB_STATUS)
     @Param(description = "Job status of VM migration async job")
-    private JobInfo.Status jobStatus;
+    private JobInfo.Status migrationJobStatus;
 
 
     public ClusterDrsPlanMigrationResponse(String vmId, String vmName, String srcHostId, String srcHostName,
@@ -68,8 +68,8 @@ public class ClusterDrsPlanMigrationResponse extends BaseResponse {
         this.srcHostName = srcHostName;
         this.destHostId = destHostId;
         this.destHostName = destHostName;
-        this.jobId = jobId;
-        this.jobStatus = jobStatus;
+        this.migrationJobId = jobId;
+        this.migrationJobStatus = jobStatus;
         this.setObjectName(ApiConstants.MIGRATIONS);
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java
index 0018edc8638..0f339d5d5b1 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java
@@ -175,6 +175,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co
     @Param(description="true if range is dedicated for System VMs")
     private boolean forSystemVms;
 
+    @SerializedName(ApiConstants.FOR_PROVIDER)
+    @Param(description="true if range is dedicated for external network providers")
+    private boolean forProvider;
+
     public void setIpAddress(String ipAddress) {
         this.ipAddress = ipAddress;
     }
@@ -332,4 +336,6 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co
     public void setForSystemVms(boolean forSystemVms) {
         this.forSystemVms = forSystemVms;
     }
+
+    public void setForProvider(boolean forProvider) { this.forProvider = forProvider; }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
index a80317c83cd..e7ad244e1da 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
@@ -208,14 +208,6 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
     @Param(description = "Name of the VPC to which this network belongs", since = "4.15")
     private String vpcName;
 
-    @SerializedName(ApiConstants.ASSOCIATED_NETWORK_ID)
-    @Param(description = "the ID of the Network associated with this network")
-    private String associatedNetworkId;
-
-    @SerializedName(ApiConstants.ASSOCIATED_NETWORK)
-    @Param(description = "the name of the Network associated with this network")
-    private String associatedNetworkName;
-
     @SerializedName(ApiConstants.TUNGSTEN_VIRTUAL_ROUTER_UUID)
     @Param(description = "Tungsten-Fabric virtual router the network belongs to")
     private String tungstenVirtualRouterUuid;
@@ -612,14 +604,6 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
         this.vpcName = vpcName;
     }
 
-    public void setAssociatedNetworkId(String associatedNetworkId) {
-        this.associatedNetworkId = associatedNetworkId;
-    }
-
-    public void setAssociatedNetworkName(String associatedNetworkName) {
-        this.associatedNetworkName = associatedNetworkName;
-    }
-
     @Override
     public void setResourceIconResponse(ResourceIconResponse icon) {
         this.icon = icon;
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java
index 51f8a130383..9d0f90459a7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java
@@ -42,9 +42,17 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit
     @Param(description = "VPC the static route belongs to")
     private String vpcId;
 
-    @SerializedName(ApiConstants.GATEWAY_ID)
+    @SerializedName(ApiConstants.VPC_GATEWAY_ID)
     @Param(description = "VPC gateway the route is created for")
-    private String gatewayId;
+    private String vpcGatewayId;
+
+    @SerializedName(ApiConstants.VPC_GATEWAY_IP)
+    @Param(description = "IP of VPC gateway the route is created for")
+    private String vpcGatewayIp;
+
+    @SerializedName(ApiConstants.NEXT_HOP)
+    @Param(description = "Next hop of the static route")
+    private String nextHop;
 
     @SerializedName(ApiConstants.CIDR)
     @Param(description = "static route CIDR")
@@ -95,8 +103,16 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit
         this.vpcId = vpcId;
     }
 
-    public void setGatewayId(String gatewayId) {
-        this.gatewayId = gatewayId;
+    public void setVpcGatewayId(String vpcGatewayId) {
+        this.vpcGatewayId = vpcGatewayId;
+    }
+
+    public void setVpcGatewayIp(String vpcGatewayIp) {
+        this.vpcGatewayIp = vpcGatewayIp;
+    }
+
+    public void setNextHop(String nextHop) {
+        this.nextHop = nextHop;
     }
 
     public void setCidr(String cidr) {
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
index 31a8b731491..c98235b2347 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java
@@ -38,14 +38,6 @@ public class SystemVmResponse extends BaseResponseWithAnnotations {
     @Param(description = "the system VM type")
     private String systemVmType;
 
-    @SerializedName("jobid")
-    @Param(description = "the job ID associated with the system VM. This is only displayed if the router listed is part of a currently running asynchronous job.")
-    private String jobId;
-
-    @SerializedName("jobstatus")
-    @Param(description = "the job status associated with the system VM.  This is only displayed if the router listed is part of a currently running asynchronous job.")
-    private Integer jobStatus;
-
     @SerializedName("zoneid")
     @Param(description = "the Zone ID for the system VM")
     private String zoneId;
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java
index 1492c23e882..2ee748f0c59 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java
@@ -127,9 +127,9 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit
     @Param(description = "indicates whether VLAN IP range is dedicated to system vms or not")
     private Boolean forSystemVms;
 
-    @SerializedName(ApiConstants.FOR_NSX)
-    @Param(description = "indicates whether IP range is dedicated to NSX resources or not")
-    private Boolean forNsx;
+    @SerializedName(ApiConstants.PROVIDER)
+    @Param(description = "indicates whether IP range is dedicated to NSX resources or not", since = "4.20.0")
+    private String provider;
 
     public void setId(String id) {
         this.id = id;
@@ -249,7 +249,7 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit
         this.ip6Cidr = ip6Cidr;
     }
 
-    public void setForNsx(Boolean forNsx) {
-        this.forNsx = forNsx;
+    public void setProvider(String provider) {
+        this.provider = provider;
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
index 143dfad0eaf..475c8b9eef2 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
@@ -145,10 +145,15 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
     @Param(description = "the type of the zone - core or edge", since = "4.18.0")
     String type;
 
+    @Deprecated(since = "4.20")
     @SerializedName(ApiConstants.NSX_ENABLED)
     @Param(description = "true, if zone is NSX enabled", since = "4.20.0")
     private boolean nsxEnabled = false;
 
+    @SerializedName(ApiConstants.PROVIDER)
+    @Param(description = "External network provider if any", since = "4.20.0")
+    private String provider = null;
+
     @SerializedName(ApiConstants.MULTI_ARCH)
     @Param(description = "true, if zone contains clusters and hosts from different CPU architectures", since = "4.20")
     private boolean multiArch;
@@ -368,6 +373,14 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
         return nsxEnabled;
     }
 
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+
     @Override
     public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) {
         this.resourceIconResponse = resourceIconResponse;
diff --git a/client/pom.xml b/client/pom.xml
index eddf49c040f..4cdb93d89e7 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -1097,6 +1097,11 @@
                     <artifactId>cloud-plugin-network-nsx</artifactId>
                     <version>${project.version}</version>
                 </dependency>
+                <dependency>
+                    <groupId>org.apache.cloudstack</groupId>
+                    <artifactId>cloud-plugin-network-netris</artifactId>
+                    <version>${project.version}</version>
+                </dependency>
                 <dependency>
                     <groupId>org.apache.cloudstack</groupId>
                     <artifactId>cloud-plugin-network-tungsten</artifactId>
diff --git a/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java b/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java
index b299c602dde..351702a048c 100644
--- a/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java
+++ b/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java
@@ -25,7 +25,6 @@ import java.util.Map;
 public class CheckS2SVpnConnectionsAnswer extends Answer {
     Map<String, Boolean> ipToConnected;
     Map<String, String> ipToDetail;
-    String details;
 
     protected CheckS2SVpnConnectionsAnswer() {
         ipToConnected = new HashMap<String, Boolean>();
diff --git a/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java
index 06583f2d0d3..fef0ceed6b8 100644
--- a/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java
@@ -36,6 +36,7 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand {
     String routerIpv6Gateway = null;
     String routerIpv6Cidr = null;
     boolean isVrGuestGateway = false;
+    long networkId;
 
     public NicTO getNic() {
         return nic;
@@ -123,4 +124,12 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand {
     public void setVrGuestGateway(boolean vrGuestGateway) {
         isVrGuestGateway = vrGuestGateway;
     }
+
+    public long getNetworkId() {
+        return networkId;
+    }
+
+    public void setNetworkId(long networkId) {
+        this.networkId = networkId;
+    }
 }
diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java
index 1a6824ceb7f..57c96ec4bc9 100644
--- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java
+++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java
@@ -76,6 +76,7 @@ public class SetGuestNetworkConfigItem extends AbstractConfigItemFacade {
         guestNetwork.setRouterIp6Gateway(command.getRouterIpv6Gateway());
         guestNetwork.setRouterIp6Cidr(command.getRouterIpv6Cidr());
         guestNetwork.setVrGuestGateway(command.isVrGuestGateway());
+        guestNetwork.setNetworkId(command.getNetworkId());
 
         return generateConfigItems(guestNetwork);
     }
diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java
index a416b4bc5e4..6bf36d62bf9 100644
--- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java
+++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java
@@ -38,6 +38,7 @@ public class GuestNetwork extends ConfigBase {
     private String routerIp6Gateway;
     private String routerIp6Cidr;
     private boolean isVrGuestGateway;
+    long networkId;
 
     private Integer mtu;
 
@@ -211,4 +212,12 @@ public class GuestNetwork extends ConfigBase {
     public void setVrGuestGateway(boolean vrGuestGateway) {
         isVrGuestGateway = vrGuestGateway;
     }
+
+    public long getNetworkId() {
+        return networkId;
+    }
+
+    public void setNetworkId(long networkId) {
+        this.networkId = networkId;
+    }
 }
diff --git a/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java b/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java
index 7229f0903b7..eabcc82040d 100644
--- a/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java
+++ b/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java
@@ -75,13 +75,17 @@ public class ArrayTypeAdaptor<T> implements JsonDeserializer<T[]>, JsonSerialize
             try {
                 clazz = Class.forName(name);
             } catch (ClassNotFoundException e) {
-                throw new CloudRuntimeException("can't find " + name);
+                throw new JsonParseException("can't find " + name);
             }
             T cmd = (T)_gson.fromJson(entry.getValue(), clazz);
             cmds.add(cmd);
         }
-        Class<?> type = ((Class<?>)typeOfT).getComponentType();
-        T[] ts = (T[])Array.newInstance(type, cmds.size());
-        return cmds.toArray(ts);
+        try {
+            Class<?> type =  Class.forName(typeOfT.getTypeName().replace("[]", ""));
+            T[] ts = (T[])Array.newInstance(type, cmds.size());
+            return cmds.toArray(ts);
+        } catch (ClassNotFoundException e) {
+            throw new CloudRuntimeException("can't find " + typeOfT.getTypeName());
+        }
     }
 }
diff --git a/core/src/test/java/com/cloud/agent/transport/ResponseTest.java b/core/src/test/java/com/cloud/agent/transport/ResponseTest.java
new file mode 100644
index 00000000000..06869b42eb9
--- /dev/null
+++ b/core/src/test/java/com/cloud/agent/transport/ResponseTest.java
@@ -0,0 +1,46 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package com.cloud.agent.transport;
+
+import junit.framework.TestCase;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.junit.Assert;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckS2SVpnConnectionsAnswer;
+
+import com.cloud.agent.transport.Request.Version;
+
+public class ResponseTest extends TestCase {
+    protected Logger logger = LogManager.getLogger(getClass());
+
+    public void testCheckS2SVpnConnectionsAnswer() {
+        logger.info("Testing CheckS2SVpnConnectionsAnswer");
+        String content = "[{\"com.cloud.agent.api.CheckS2SVpnConnectionsAnswer\":{\"ipToConnected\":{\"10.0.53.13\":true}," +
+                "\"ipToDetail\":{\"10.0.53.13\":\"IPsec SA found;Site-to-site VPN have connected\"}," +
+                "\"details\":\"10.0.53.13:0:IPsec SA found;Site-to-site VPN have connected\\u0026\\n\"," +
+                "\"result\":true,\"contextMap\":{},\"wait\":0,\"bypassHostMaintenance\":false}}]";
+        Response response = new Response(Version.v2, 1L, 2L, 3L, 1L, (short)1, content);
+        Answer answer = response.getAnswer();
+        Assert.assertTrue(answer instanceof CheckS2SVpnConnectionsAnswer);
+    }
+
+}
diff --git a/deps/install-non-oss.sh b/deps/install-non-oss.sh
index 56cfc2cba9c..e40946ce312 100755
--- a/deps/install-non-oss.sh
+++ b/deps/install-non-oss.sh
@@ -85,4 +85,7 @@ mvn install:install-file -Dfile=juniper-contrail-api-1.0-SNAPSHOT.jar -DgroupId=
 # From https://github.com/radu-todirica/tungsten-api/raw/master/net/juniper/tungsten/juniper-tungsten-api/2.0/juniper-tungsten-api-2.0.jar
 mvn install:install-file -Dfile=juniper-tungsten-api-2.0.jar -DgroupId=net.juniper.tungsten -DartifactId=juniper-tungsten-api -Dversion=2.0 -Dpackaging=jar
 
+# Netris Integration
+mvn install:install-file -Dfile=netris-java-sdk-1.0.0.jar -DgroupId=io.netris -DartifactId=netris-java-sdk -Dversion=1.0.0 -Dpackaging=jar
+
 exit 0
diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
index d8e97f0277b..adce5f2f8b4 100644
--- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
+++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
@@ -82,6 +82,9 @@ public interface NetworkOrchestrationService {
     ConfigKey<Integer> NetworkLockTimeout = new ConfigKey<Integer>(Integer.class, NetworkLockTimeoutCK, "Network", "600",
         "Lock wait timeout (seconds) while implementing network", true, Scope.Global, null);
 
+    ConfigKey<String> DeniedRoutes = new ConfigKey<String>(String.class, "denied.routes", "Network", "",
+            "Routes that are denied, can not be used for Static Routes creation for the VPC Private Gateway", true, ConfigKey.Scope.Zone, null);
+
     ConfigKey<String> GuestDomainSuffix = new ConfigKey<String>(String.class, GuestDomainSuffixCK, "Network", "cloud.internal",
         "Default domain name for vms inside virtualized networks fronted by router", true, ConfigKey.Scope.Zone, null);
 
@@ -109,6 +112,9 @@ public interface NetworkOrchestrationService {
     static final ConfigKey<Boolean> NSX_ENABLED = new ConfigKey<>(Boolean.class, "nsx.plugin.enable", "Advanced", "false",
             "Indicates whether to enable the NSX plugin", false, ConfigKey.Scope.Zone, null);
 
+    ConfigKey<Boolean> NETRIS_ENABLED = new ConfigKey<>(Boolean.class, "netris.plugin.enable", "Advanced", "false",
+            "Indicates whether to enable the Netris plugin", false, ConfigKey.Scope.Zone, null);
+
     List<? extends Network> setupNetwork(Account owner, NetworkOffering offering, DeploymentPlan plan, String name, String displayText, boolean isDefault)
         throws ConcurrentOperationException;
 
diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
index 01fd54430d6..3191d014c66 100644
--- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
+++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
@@ -214,6 +214,7 @@ public interface ConfigurationManager {
      * @param forVpc
      * @param forTungsten
      * @param forNsx
+     * @param forNetris
      * @param domainIds
      * @param zoneIds
      * @return network offering object
@@ -223,11 +224,11 @@ public interface ConfigurationManager {
                                             Integer networkRate, Map<Service, Set<Provider>> serviceProviderMap, boolean isDefault, Network.GuestType type, boolean systemOnly, Long serviceOfferingId,
                                             boolean conserveMode, Map<Service, Map<Capability, String>> serviceCapabilityMap, boolean specifyIpRanges, boolean isPersistent,
                                             Map<NetworkOffering.Detail, String> details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc,
-                                            Boolean forTungsten, boolean forNsx, NetworkOffering.NetworkMode networkMode, List<Long> domainIds, List<Long> zoneIds, boolean enableOffering, final NetUtils.InternetProtocol internetProtocol,
+                                            Boolean forTungsten, boolean forNsx, boolean forNetris, NetworkOffering.NetworkMode networkMode, List<Long> domainIds, List<Long> zoneIds, boolean enableOffering, final NetUtils.InternetProtocol internetProtocol,
                                             NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber);
 
     Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetworkId, boolean forVirtualNetwork, boolean forSystemVms, Long podId, String startIP, String endIP,
-        String vlanGateway, String vlanNetmask, String vlanId, boolean bypassVlanOverlapCheck, Domain domain, Account vlanOwner, String startIPv6, String endIPv6, String vlanIp6Gateway, String vlanIp6Cidr, boolean forNsx)
+        String vlanGateway, String vlanNetmask, String vlanId, boolean bypassVlanOverlapCheck, Domain domain, Account vlanOwner, String startIPv6, String endIPv6, String vlanIp6Gateway, String vlanIp6Cidr, Provider provider)
         throws InsufficientCapacityException, ConcurrentOperationException, InvalidParameterValueException;
 
     void createDefaultSystemNetworks(long zoneId) throws ConcurrentOperationException;
diff --git a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java
index d69a72a02c5..7132094ce11 100644
--- a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java
+++ b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java
@@ -275,5 +275,9 @@ public class PublicIp implements PublicIpAddress {
         return false;
     }
 
+    @Override
+    public boolean isForRouter() {
+        return _addr.isForRouter();
+    }
 
 }
diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java
index c77874329fc..7bab2a26fb4 100644
--- a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java
+++ b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java
@@ -53,6 +53,8 @@ public interface RulesManager extends RulesService {
 
     boolean disableStaticNat(long ipAddressId, Account caller, long callerUserId, boolean releaseIpIfElastic) throws ResourceUnavailableException;
 
+    boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke);
+
     /**
      * @param networkId
      * @param continueOnError
diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java
index de69b894183..b192cd8d71c 100644
--- a/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java
+++ b/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java
@@ -93,5 +93,5 @@ public interface NetworkACLManager {
 
     boolean applyACLToPrivateGw(PrivateGateway gateway) throws ResourceUnavailableException;
 
-    boolean reorderAclRules(VpcVO vpc, List<? extends Network> networks, List<? extends NetworkACLItem> networkACLItems);
+    boolean reorderAclRules(VpcVO vpc, List<? extends Network> networks, List<? extends NetworkACLItem> networkACLItems, Network.Provider networkProvider);
 }
diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
index a340f49c13f..9018c0d17a3 100644
--- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
+++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
@@ -35,6 +35,7 @@ import com.cloud.network.Network.Provider;
 import com.cloud.network.Network.Service;
 import com.cloud.network.PhysicalNetwork;
 import com.cloud.network.addr.PublicIp;
+import com.cloud.network.dao.IPAddressVO;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.user.Account;
 
@@ -174,4 +175,20 @@ public interface VpcManager {
      * @return
      */
     boolean isSrcNatIpRequired(long vpcOfferingId);
+
+    boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId);
+
+    List<StaticRouteVO> getVpcStaticRoutes(Long vpcId);
+
+    List<StaticRouteProfile> getVpcStaticRoutes(List<? extends StaticRoute> routes);
+
+    boolean isProviderSupportServiceInVpc(long vpcId, Service service, Provider provider);
+
+    IPAddressVO getIpAddressForVpcVr(Vpc vpc, IPAddressVO ipAddress, boolean allocateIpIfNeeded);
+
+    boolean configStaticNatForVpcVr(Vpc vpc, IPAddressVO ipAddress);
+
+    void reconfigStaticNatForVpcVr(Long vpcId);
+
+    boolean applyStaticRouteForVpcVpnIfNeeded(Long vpcId, boolean updateAllVpn) throws ResourceUnavailableException;
 }
diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
index e1b798d16d6..30b63053927 100644
--- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
+++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
@@ -43,6 +43,8 @@ import com.cloud.bgp.BGPService;
 import com.cloud.dc.VlanDetailsVO;
 import com.cloud.dc.dao.ASNumberDao;
 import com.cloud.dc.dao.VlanDetailsDao;
+import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
+import com.cloud.network.dao.NetrisProviderDao;
 import com.cloud.network.dao.NsxProviderDao;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.annotation.AnnotationService;
@@ -355,9 +357,13 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
     @Inject
     private NsxProviderDao nsxProviderDao;
     @Inject
+    private NetrisProviderDao netrisProviderDao;
+    @Inject
     private ASNumberDao asNumberDao;
     @Inject
     private BGPService bgpService;
+    @Inject
+    private Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao;
 
     @Override
     public List<NetworkGuru> getNetworkGurus() {
@@ -558,27 +564,27 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.QuickCloudNoServices) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.QuickCloudNoServices, "Offering for QuickCloud with no services", TrafficType.Guest, null, true,
                             Availability.Optional, null, new HashMap<Network.Service, Set<Network.Provider>>(), true, Network.GuestType.Shared, false, null, true, null, true,
-                            false, null, false, null, true, false, false, false, null, null, null, true, null, null, false);
+                            false, null, false, null, true, false, false, false, false,null, null, null, true, null, null, false);
                 }
 
                 //#2 - SG enabled network offering
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOfferingWithSGService) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOfferingWithSGService, "Offering for Shared Security group enabled networks",
                             TrafficType.Guest, null, true, Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true,
-                            null, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false);
+                            null, true, false, null, false, null, true, false, false, false, false,null, null, null, true, null, null, false);
                 }
 
                 //#3 - shared network offering with no SG service
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOffering) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOffering, "Offering for Shared networks", TrafficType.Guest, null, true,
                             Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, null, true, false, null, false,
-                            null, true, false, false, false, null,null, null, true, null, null, false);
+                            null, true, false, false, false, false,null,null, null, true, null, null, false);
                 }
 
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DEFAULT_TUNGSTEN_SHARED_NETWORK_OFFERING_WITH_SGSERVICE) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DEFAULT_TUNGSTEN_SHARED_NETWORK_OFFERING_WITH_SGSERVICE, "Offering for Tungsten Shared Security group enabled networks",
                             TrafficType.Guest, null, true, Availability.Optional, null, defaultTungstenSharedSGEnabledNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true,
-                            null, true, false, null, false, null, true, false, true, false, null, null,null, true, null, null, false);
+                            null, true, false, null, false, null, true, false, true, false, false,null, null,null, true, null, null, false);
                     offering.setState(NetworkOffering.State.Enabled);
                     _networkOfferingDao.update(offering.getId(), offering);
                 }
@@ -588,14 +594,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingWithSourceNatService,
                             "Offering for Isolated networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Required, null,
                             defaultIsolatedSourceNatEnabledNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null,
-                            true, false, false, false, null, null,null, true, null, null, false);
+                            true, false, false, false, false,null, null,null, true, null, null, false);
                 }
 
                 //#5 - default vpc offering with LB service
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks,
                             "Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null,
-                            defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false);
+                            defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, false,null, null, null,true, null, null, false);
                 }
 
                 //#6 - default vpc offering with no LB service
@@ -604,14 +610,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                     defaultVPCOffProviders.remove(Service.Lb);
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksNoLB,
                             "Offering for Isolated VPC networks with Source Nat service enabled and LB service disabled", TrafficType.Guest, null, false, Availability.Optional,
-                            null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false);
+                            null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, false,null, null, null,true, null, null, false);
                 }
 
                 //#7 - isolated offering with source nat disabled
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOffering) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOffering, "Offering for Isolated networks with no Source Nat service",
                             TrafficType.Guest, null, true, Availability.Optional, null, defaultIsolatedNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null,
-                            true, null, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false);
+                            true, null, true, false, null, false, null, true, false, false, false, false,null, null, null, true, null, null, false);
                 }
 
                 //#8 - network offering with internal lb service
@@ -633,7 +639,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB,
                             "Offering for Isolated VPC networks with Internal Lb support", TrafficType.Guest, null, false, Availability.Optional, null, internalLbOffProviders,
-                            true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null, true, null, null, false);
+                            true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, false,null, null, null, true, null, null, false);
                     offering.setInternalLb(true);
                     offering.setPublicLb(false);
                     _networkOfferingDao.update(offering.getId(), offering);
@@ -664,7 +670,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                 if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedEIPandELBNetworkOffering) == null) {
                     offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedEIPandELBNetworkOffering,
                             "Offering for Shared networks with Elastic IP and Elastic LB capabilities", TrafficType.Guest, null, true, Availability.Optional, null,
-                            netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false);
+                            netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, false, false,false, null, null, null, true, null, null, false);
                     offering.setDedicatedLB(false);
                     _networkOfferingDao.update(offering.getId(), offering);
                 }
@@ -821,6 +827,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                         if (domainId != null && aclType == ACLType.Domain) {
                             _networksDao.addDomainToNetwork(id, domainId, subdomainAccess == null || subdomainAccess);
                         }
+                        String ipv6Cidr = network.getIp6Cidr();
+                        String ipv6Gateway = network.getIp6Gateway();
+                        if (StringUtils.isNoneBlank(ipv6Cidr, ipv6Gateway)) {
+                            ipv6Service.assignIpv6SubnetToNetwork(ipv6Cidr, networkPersisted.getId());
+                        }
                     }
                 });
                 guru.setup(network, relatedFile);
@@ -1055,7 +1066,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
         return Transaction.execute(new TransactionCallback<NicVO>() {
             @Override
             public NicVO doInTransaction(TransactionStatus status) {
-                NicVO vo = _nicDao.findByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId);
+                NicVO vo = _nicDao.findNonPlaceHolderByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId);
                 if (vo == null) {
                     applyProfileToNic(nic, profile, deviceId);
                     vo = _nicDao.persist(nic);
@@ -1082,10 +1093,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                 return null;
             }
 
-            if (isNicAllocatedForNsxPublicNetworkOnVR(network, profile, vm)) {
+            if (isNicAllocatedForProviderPublicNetworkOnVR(network, profile, vm, Provider.Nsx)) {
                 String guruName = "NsxPublicNetworkGuru";
                 NetworkGuru nsxGuru = AdapterBase.getAdapterByName(networkGurus, guruName);
                 nsxGuru.allocate(network, profile, vm);
+            } else if (isNicAllocatedForProviderPublicNetworkOnVR(network, profile, vm, Provider.Netris)) {
+                String guruName = "NetrisPublicNetworkGuru";
+                NetworkGuru netrisGuru = AdapterBase.getAdapterByName(networkGurus, guruName);
+                netrisGuru.allocate(network, profile, vm);
             }
 
             if (isDefaultNic != null) {
@@ -1148,7 +1163,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
         return new Pair<NicProfile, Integer>(vmNic, Integer.valueOf(deviceId));
     }
 
-    private boolean isNicAllocatedForNsxPublicNetworkOnVR(Network network, NicProfile requested, VirtualMachineProfile vm) {
+    private boolean isNicAllocatedForProviderPublicNetworkOnVR(Network network, NicProfile requested, VirtualMachineProfile vm, Provider provider) {
         if (ObjectUtils.anyNull(network, requested, vm)) {
             return false;
         }
@@ -1158,7 +1173,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
             return false;
         }
         long dataCenterId = vm.getVirtualMachine().getDataCenterId();
-        if (nsxProviderDao.findByZoneId(dataCenterId) == null) {
+        if (Provider.Nsx == provider && nsxProviderDao.findByZoneId(dataCenterId) == null) {
+            return false;
+        } else if (Provider.Netris == provider && netrisProviderDao.findByZoneId(dataCenterId) == null) {
             return false;
         }
 
@@ -1168,14 +1185,16 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
         if (CollectionUtils.isEmpty(ips)) {
             return false;
         }
+
         ips = ips.stream().filter(x -> !x.getAddress().addr().equals(requested.getIPv4Address())).collect(Collectors.toList());
         IPAddressVO ip = ips.get(0);
-        VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(ip.getVlanId(), ApiConstants.NSX_DETAIL_KEY);
+        String detailKey = Provider.Nsx == provider ? ApiConstants.NSX_DETAIL_KEY : ApiConstants.NETRIS_DETAIL_KEY;
+        VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(ip.getVlanId(), detailKey);
         if (vlanDetail == null) {
             return false;
         }
-        boolean isForNsx = vlanDetail.getValue().equalsIgnoreCase("true");
-        return isForNsx && !ip.isForSystemVms();
+        boolean isForProvider = vlanDetail.getValue().equalsIgnoreCase("true");
+        return isForProvider && !ip.isForSystemVms();
     }
 
     private void setMtuDetailsInVRNic(final Pair<NetworkVO, VpcVO> networks, Network network, NicVO vo) {
@@ -1696,6 +1715,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                     }
                 }
             }
+            if (network.getVpcId() != null) {
+                _vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId());
+                try {
+                    _vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true);
+                } catch (ResourceUnavailableException e) {
+                    logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage());
+                }
+            }
         } finally {
             for (final NetworkElement element : networkElements) {
                 if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
@@ -1941,6 +1968,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                                 ip.setOneToOneNat(false);
                                 ip.setAssociatedWithVmId(null);
                                 ip.setVmIp(null);
+                                ip.setForRouter(false);
                                 _ipAddressDao.update(ip.getId(), ip);
                             }
                         }
@@ -3283,6 +3311,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
                 }
             }
         }
+        if (network.getVpcId() != null) {
+            _vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId());
+            try {
+                _vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true);
+            } catch (ResourceUnavailableException e) {
+                logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage());
+            }
+        }
         return success;
     }
 
@@ -4863,9 +4899,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout,
+        return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes,
                 GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
                 PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled,
-                TUNGSTEN_ENABLED, NSX_ENABLED };
+                TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED };
     }
 }
diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java
index 84f38f05441..a6c267bb189 100644
--- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java
+++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java
@@ -64,4 +64,6 @@ public interface VlanDao extends GenericDao<VlanVO, Long> {
     List<VlanVO> listIpv6RangeByZoneIdAndVlanId(long zoneId, String vlanId);
 
     List<VlanVO> listIpv6SupportingVlansByZone(long zoneId);
+
+    List<VlanVO> listVlansForExternalNetworkProvider(long zoneId, String detailKey);
 }
diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java
index 461a9a13b10..d9fad3cad12 100644
--- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.dc.VlanDetailsVO;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Component;
@@ -67,6 +68,8 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
     protected SearchBuilder<VlanVO> ZoneVlanIp6Search;
     protected SearchBuilder<VlanVO> ZoneIp6Search;
     protected SearchBuilder<VlanVO> ZoneVlansSearch;
+    protected SearchBuilder<VlanVO> ProviderVlanSearch;
+    protected SearchBuilder<VlanDetailsVO> VlanDetailsProviderSearch;
 
     protected SearchBuilder<AccountVlanMapVO> AccountVlanMapSearch;
     protected SearchBuilder<DomainVlanMapVO> DomainVlanMapSearch;
@@ -79,6 +82,8 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
     protected DomainVlanMapDao _domainVlanMapDao;
     @Inject
     protected IPAddressDao _ipAddressDao;
+    @Inject
+    protected VlanDetailsDao vlanDetailsDao;
 
     @Override
     public VlanVO findByZoneAndVlanId(long zoneId, String vlanId) {
@@ -277,6 +282,19 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
         ZoneVlansSearch.and("zoneId", ZoneVlansSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
         ZoneVlansSearch.and("vlan", ZoneVlansSearch.entity().getVlanTag(), SearchCriteria.Op.IN);
         ZoneVlansSearch.done();
+
+        ProviderVlanSearch = createSearchBuilder();
+        ProviderVlanSearch.and("removed", ProviderVlanSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
+        ProviderVlanSearch.and("dataCenterId", ProviderVlanSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
+        VlanDetailsProviderSearch = vlanDetailsDao.createSearchBuilder();
+        VlanDetailsProviderSearch.and("name", VlanDetailsProviderSearch.entity().getName(), SearchCriteria.Op.EQ);
+        VlanDetailsProviderSearch.and("value", VlanDetailsProviderSearch.entity().getValue(), SearchCriteria.Op.EQ);
+        ProviderVlanSearch.join("VlanDetailsProviderSearch", VlanDetailsProviderSearch, ProviderVlanSearch.entity().getId(),
+                VlanDetailsProviderSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER);
+
+        VlanDetailsProviderSearch.done();
+        ProviderVlanSearch.done();
+
         return result;
     }
 
@@ -434,4 +452,13 @@ public class VlanDaoImpl extends GenericDaoBase<VlanVO, Long> implements VlanDao
         return listBy(sc);
     }
 
+    @Override
+    public List<VlanVO> listVlansForExternalNetworkProvider(long zoneId, String detailKey) {
+        SearchCriteria<VlanVO> sc = ProviderVlanSearch.create();
+        sc.setParameters("dataCenterId", zoneId);
+        sc.setJoinParameters("VlanDetailsProviderSearch", "name", detailKey);
+        sc.setJoinParameters("VlanDetailsProviderSearch", "value", "true");
+        return search(sc, null);
+    }
+
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java
index aa143838c34..2b5277f1560 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java
@@ -197,6 +197,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen
         address.setSourceNat(false);
         address.setOneToOneNat(false);
         address.setAssociatedWithVmId(null);
+        address.setForRouter(false);
         address.setState(State.Free);
         address.setAssociatedWithNetworkId(null);
         address.setVpcId(null);
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java
index 4c7569a55b9..6be7d24e367 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java
@@ -116,6 +116,9 @@ public class IPAddressVO implements IpAddress {
     @Column(name = "forsystemvms")
     private boolean forSystemVms = false;
 
+    @Column(name = "for_router")
+    private boolean forRouter = false;
+
     @Column(name= GenericDao.REMOVED_COLUMN)
     private Date removed;
 
@@ -385,4 +388,13 @@ public class IPAddressVO implements IpAddress {
     public boolean isForSystemVms() {
         return forSystemVms;
     }
+
+    @Override
+    public boolean isForRouter() {
+        return forRouter;
+    }
+
+    public void setForRouter(boolean forRouter) {
+        this.forRouter = forRouter;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java
similarity index 83%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java
index d3fef252f50..fe21f72e4db 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java
@@ -16,8 +16,9 @@
 // under the License.
 package com.cloud.network.dao;
 
+import com.cloud.network.element.NetrisProviderVO;
 import com.cloud.utils.db.GenericDao;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public interface NetrisProviderDao extends GenericDao<NetrisProviderVO, Long> {
+    NetrisProviderVO findByZoneId(long zoneId);
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.java
similarity index 51%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java
copy to engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.java
index d1fde963217..86ea04f1db0 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.java
@@ -16,32 +16,37 @@
 // under the License.
 package com.cloud.network.dao;
 
-import javax.inject.Inject;
-
-import org.springframework.stereotype.Component;
-
+import com.cloud.network.element.NetrisProviderVO;
+import com.cloud.utils.db.DB;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import org.springframework.stereotype.Component;
 
 @Component
-public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase<Site2SiteVpnGatewayVO, Long> implements Site2SiteVpnGatewayDao {
-    @Inject
-    protected IPAddressDao _addrDao;
-
-
-    private final SearchBuilder<Site2SiteVpnGatewayVO> AllFieldsSearch;
-
-    protected Site2SiteVpnGatewayDaoImpl() {
-        AllFieldsSearch = createSearchBuilder();
-        AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), SearchCriteria.Op.EQ);
-        AllFieldsSearch.done();
+@DB()
+public class NetrisProviderDaoImpl extends GenericDaoBase<NetrisProviderVO, Long> implements NetrisProviderDao {
+
+    final SearchBuilder<NetrisProviderVO> allFieldsSearch;
+
+    public NetrisProviderDaoImpl() {
+        super();
+        allFieldsSearch = createSearchBuilder();
+        allFieldsSearch.and("id", allFieldsSearch.entity().getId(),
+                SearchCriteria.Op.EQ);
+        allFieldsSearch.and("uuid", allFieldsSearch.entity().getUuid(),
+                SearchCriteria.Op.EQ);
+        allFieldsSearch.and("hostname", allFieldsSearch.entity().getUrl(),
+                SearchCriteria.Op.EQ);
+        allFieldsSearch.and("zone_id", allFieldsSearch.entity().getZoneId(),
+                SearchCriteria.Op.EQ);
+        allFieldsSearch.done();
     }
 
     @Override
-    public Site2SiteVpnGatewayVO findByVpcId(long vpcId) {
-        SearchCriteria<Site2SiteVpnGatewayVO> sc = AllFieldsSearch.create();
-        sc.setParameters("vpcId", vpcId);
+    public NetrisProviderVO findByZoneId(long zoneId) {
+        SearchCriteria<NetrisProviderVO> sc = allFieldsSearch.create();
+        sc.setParameters("zone_id", zoneId);
         return findOneBy(sc);
     }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java
index 8113c06c866..f4120a138ac 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java
@@ -33,4 +33,6 @@ public interface RemoteAccessVpnDao extends GenericDao<RemoteAccessVpnVO, Long>
     List<RemoteAccessVpnVO> findByAccount(Long accountId);
 
     List<RemoteAccessVpnVO> listByNetworkId(Long networkId);
+
+    List<RemoteAccessVpnVO> listByVpcId(Long vpcId);
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java
index 484aa6f6631..ccbc60a5562 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java
@@ -85,4 +85,11 @@ public class RemoteAccessVpnDaoImpl extends GenericDaoBase<RemoteAccessVpnVO, Lo
         sc.setParameters("networkId", networkId);
         return listBy(sc);
     }
+
+    @Override
+    public List<RemoteAccessVpnVO> listByVpcId(Long vpcId) {
+        SearchCriteria<RemoteAccessVpnVO> sc = AllFieldsSearch.create();
+        sc.setParameters("vpcId", vpcId);
+        return listBy(sc);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
index d3fef252f50..3475003c269 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
@@ -20,4 +20,6 @@ import com.cloud.utils.db.GenericDao;
 
 public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
     Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+
+    Site2SiteVpnGatewayVO findByPublicIpAddress(long ipAddressId);
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java
index d1fde963217..0aeefe90c29 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java
@@ -35,6 +35,7 @@ public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase<Site2SiteVpnGatew
     protected Site2SiteVpnGatewayDaoImpl() {
         AllFieldsSearch = createSearchBuilder();
         AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("ipAddressId", AllFieldsSearch.entity().getAddrId(), SearchCriteria.Op.EQ);
         AllFieldsSearch.done();
     }
 
@@ -44,4 +45,11 @@ public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase<Site2SiteVpnGatew
         sc.setParameters("vpcId", vpcId);
         return findOneBy(sc);
     }
+
+    @Override
+    public Site2SiteVpnGatewayVO findByPublicIpAddress(long ipAddressId) {
+        SearchCriteria<Site2SiteVpnGatewayVO> sc = AllFieldsSearch.create();
+        sc.setParameters("ipAddressId", ipAddressId);
+        return findOneBy(sc);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java
new file mode 100644
index 00000000000..113678f7b01
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java
@@ -0,0 +1,265 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.network.element;
+
+import com.cloud.network.netris.NetrisProvider;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "netris_providers")
+public class NetrisProviderVO implements NetrisProvider {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    long id;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "name")
+    private String name;
+
+    @Column(name = "zone_id")
+    private long zoneId;
+
+    @Column(name = "host_id")
+    private long hostId;
+
+    @Column(name = "url")
+    private String url;
+
+    @Column(name = "username")
+    private String username;
+
+    @Column(name = "password")
+    private String password;
+
+    @Column(name = "site_name")
+    private String siteName;
+
+    @Column(name = "tenant_name")
+    private String tenantName;
+
+    @Column(name = "netris_tag")
+    private String netrisTag;
+
+    @Column(name = "created")
+    private Date created;
+
+    @Column(name = "removed")
+    private Date removed;
+
+    public NetrisProviderVO() {
+        this.uuid = UUID.randomUUID().toString();
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    @Override
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public long getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(long zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public long getHostId() {
+        return hostId;
+    }
+
+    public void setHostId(long hostId) {
+        this.hostId = hostId;
+    }
+
+    @Override
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getSiteName() {
+        return siteName;
+    }
+
+    public void setSiteName(String siteName) {
+        this.siteName = siteName;
+    }
+
+    public String getTenantName() {
+        return tenantName;
+    }
+
+    public void setTenantName(String tenantName) {
+        this.tenantName = tenantName;
+    }
+
+    public String getNetrisTag() {
+        return netrisTag;
+    }
+
+    public void setNetrisTag(String netrisTag) {
+        this.netrisTag = netrisTag;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(Date removed) {
+        this.removed = removed;
+    }
+
+    public static final class Builder {
+        private long zoneId;
+        private long hostId;
+        private String name;
+        private String url;
+        private String username;
+        private String password;
+        private String siteName;
+        private String tenantName;
+        private String netrisTag;
+
+        public Builder() {
+            // Default constructor
+        }
+
+        public Builder setZoneId(long zoneId) {
+            this.zoneId = zoneId;
+            return this;
+        }
+
+        public Builder setHostId(long hostId) {
+            this.hostId = hostId;
+            return this;
+        }
+
+        public Builder setName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder setUrl(String url) {
+            this.url = url;
+            return this;
+        }
+
+        public Builder setUsername(String username) {
+            this.username = username;
+            return this;
+        }
+
+        public Builder setPassword(String password) {
+            this.password = password;
+            return this;
+        }
+
+        public Builder setSiteName(String siteName) {
+            this.siteName = siteName;
+            return this;
+        }
+
+        public Builder setTenantName(String tenantName) {
+            this.tenantName = tenantName;
+            return this;
+        }
+
+        public Builder setNetrisTag(String netrisTag) {
+            this.netrisTag = netrisTag;
+            return this;
+        }
+
+        public NetrisProviderVO build() {
+            NetrisProviderVO provider = new NetrisProviderVO();
+            provider.setZoneId(this.zoneId);
+            provider.setHostId(this.hostId);
+            provider.setUuid(UUID.randomUUID().toString());
+            provider.setName(this.name);
+            provider.setUrl(this.url);
+            provider.setUsername(this.username);
+            provider.setPassword(this.password);
+            provider.setSiteName(this.siteName);
+            provider.setTenantName(this.tenantName);
+            provider.setNetrisTag(this.netrisTag);
+            provider.setCreated(new Date());
+            return provider;
+        }
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java
index 2246bd6eed2..632d96819cd 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java
@@ -27,6 +27,7 @@ import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
+import javax.persistence.Transient;
 
 import com.cloud.utils.db.GenericDao;
 
@@ -42,7 +43,10 @@ public class StaticRouteVO implements StaticRoute {
     String uuid;
 
     @Column(name = "vpc_gateway_id", updatable = false)
-    long vpcGatewayId;
+    Long vpcGatewayId;
+
+    @Column(name = "next_hop")
+    private String nextHop;
 
     @Column(name = "cidr")
     private String cidr;
@@ -67,6 +71,9 @@ public class StaticRouteVO implements StaticRoute {
         uuid = UUID.randomUUID().toString();
     }
 
+    @Transient
+    boolean forVpn = false;
+
     /**
      * @param vpcGatewayId
      * @param cidr
@@ -74,7 +81,7 @@ public class StaticRouteVO implements StaticRoute {
      * @param accountId TODO
      * @param domainId TODO
      */
-    public StaticRouteVO(long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId) {
+    public StaticRouteVO(Long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId, String nextHop) {
         super();
         this.vpcGatewayId = vpcGatewayId;
         this.cidr = cidr;
@@ -82,14 +89,32 @@ public class StaticRouteVO implements StaticRoute {
         this.vpcId = vpcId;
         this.accountId = accountId;
         this.domainId = domainId;
+        this.nextHop = nextHop;
+        uuid = UUID.randomUUID().toString();
+    }
+
+    public StaticRouteVO(String cidr, Long vpcId, long accountId, long domainId, String nextHop, State state, boolean forVpn) {
+        super();
+        this.cidr = cidr;
+        this.state = state;
+        this.vpcId = vpcId;
+        this.accountId = accountId;
+        this.domainId = domainId;
+        this.nextHop = nextHop;
         uuid = UUID.randomUUID().toString();
+        this.forVpn = forVpn;
     }
 
     @Override
-    public long getVpcGatewayId() {
+    public Long getVpcGatewayId() {
         return vpcGatewayId;
     }
 
+    @Override
+    public String getNextHop() {
+        return nextHop;
+    }
+
     @Override
     public String getCidr() {
         return cidr;
@@ -145,4 +170,8 @@ public class StaticRouteVO implements StaticRoute {
     public String getName() {
         return null;
     }
+
+    public boolean isForVpn() {
+        return forVpn;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
index 41254ba4a8b..7945642c1d6 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
@@ -59,9 +59,6 @@ public class VpcOfferingVO implements VpcOffering {
     @Column(name = "default")
     boolean isDefault = false;
 
-    @Column(name = "for_nsx")
-    boolean forNsx = false;
-
     @Column(name = "network_mode")
     NetworkOffering.NetworkMode networkMode;
 
@@ -158,14 +155,6 @@ public class VpcOfferingVO implements VpcOffering {
         return isDefault;
     }
 
-    public boolean isForNsx() {
-        return forNsx;
-    }
-
-    public void setForNsx(boolean forNsx) {
-        this.forNsx = forNsx;
-    }
-
     public NetworkOffering.NetworkMode getNetworkMode() {
         return networkMode;
     }
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java
index 06cfd25e670..020536e97ec 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java
@@ -18,6 +18,7 @@ package com.cloud.network.vpc.dao;
 
 import java.util.List;
 
+import com.cloud.network.Network;
 import com.cloud.network.Network.Service;
 import com.cloud.network.vpc.VpcOfferingServiceMapVO;
 import com.cloud.utils.db.GenericDao;
@@ -37,4 +38,8 @@ public interface VpcOfferingServiceMapDao extends GenericDao<VpcOfferingServiceM
 
     VpcOfferingServiceMapVO findByServiceProviderAndOfferingId(String service, String provider, long vpcOfferingId);
 
+    boolean isProviderForVpcOffering(Network.Provider provider, long vpcOfferingId);
+
+    List<VpcOfferingServiceMapVO> listProvidersForServiceForVpcOffering(long vpcOfferingId, Service service);
+
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java
index c7400f6edfd..dcb1becf9e8 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java
@@ -19,6 +19,7 @@ package com.cloud.network.vpc.dao;
 import java.util.List;
 
 
+import com.cloud.network.Network;
 import org.springframework.stereotype.Component;
 
 import com.cloud.network.Network.Service;
@@ -110,4 +111,22 @@ public class VpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOfferingServ
 
         return findOneBy(sc);
     }
+
+    @Override
+    public boolean isProviderForVpcOffering(Network.Provider provider, long vpcOfferingId) {
+        SearchCriteria<VpcOfferingServiceMapVO> sc = AllFieldsSearch.create();
+        sc.setParameters("vpcOffId", vpcOfferingId);
+        sc.setParameters("provider", provider.getName());
+        return findOneBy(sc) != null;
+    }
+
+    @Override
+    public List<VpcOfferingServiceMapVO> listProvidersForServiceForVpcOffering(long vpcOfferingId, Service service) {
+        SearchCriteria<VpcOfferingServiceMapVO> sc = AllFieldsSearch.create();
+
+        sc.setParameters("vpcOffId", vpcOfferingId);
+        sc.setParameters("service", service.getName());
+
+        return customSearch(sc, null);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java
index 753c45fcc78..a5c4c83ff0f 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java
@@ -68,8 +68,15 @@ public class VpcServiceMapDaoImpl extends GenericDaoBase<VpcServiceMapVO, Long>
 
     @Override
     public boolean canProviderSupportServiceInVpc(long vpcId, Service service, Provider provider) {
-        // TODO Auto-generated method stub
-        return false;
+        SearchCriteria<VpcServiceMapVO> sc = AllFieldsSearch.create();
+        sc.setParameters("vpcId", vpcId);
+        sc.setParameters("service", service.getName());
+        sc.setParameters("provider", provider.getName());
+        if (findOneBy(sc) != null) {
+            return true;
+        } else {
+            return false;
+        }
     }
 
     @Override
diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java
index 0bf110757d7..cf766551421 100644
--- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java
+++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java
@@ -133,12 +133,6 @@ public class NetworkOfferingVO implements NetworkOffering {
     @Column(name = "for_vpc")
     boolean forVpc;
 
-    @Column(name = "for_tungsten")
-    boolean forTungsten = false;
-
-    @Column(name = "for_nsx")
-    boolean forNsx = false;
-
     @Column(name = "network_mode")
     NetworkMode networkMode;
 
@@ -199,24 +193,6 @@ public class NetworkOfferingVO implements NetworkOffering {
         this.forVpc = isForVpc;
     }
 
-    @Override
-    public boolean isForTungsten() {
-        return forTungsten;
-    }
-
-    public void setForTungsten(boolean forTungsten) {
-        this.forTungsten = forTungsten;
-    }
-
-    @Override
-    public boolean isForNsx() {
-        return forNsx;
-    }
-
-    public void setForNsx(boolean forNsx) {
-        this.forNsx = forNsx;
-    }
-
     @Override
     public NetworkMode getNetworkMode() {
         return networkMode;
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
index d34b03c4cb0..70a2558e2d4 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java
@@ -46,8 +46,12 @@ public interface NicDao extends GenericDao<NicVO, Long> {
 
     NicVO findByNetworkIdAndTypeIncludingRemoved(long networkId, VirtualMachine.Type vmType);
 
+    NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType);
+
     NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId);
 
+    NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId);
+
     NicVO findByNetworkIdAndMacAddress(long networkId, String mac);
 
     NicVO findDefaultNicForVM(long instanceId);
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
index 7d1af1982ae..3618785c1b8 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java
@@ -69,6 +69,7 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao {
         AllFieldsSearch.and("secondaryip", AllFieldsSearch.entity().getSecondaryIp(), Op.EQ);
         AllFieldsSearch.and("nicid", AllFieldsSearch.entity().getId(), Op.EQ);
         AllFieldsSearch.and("strategy", AllFieldsSearch.entity().getReservationStrategy(), Op.EQ);
+        AllFieldsSearch.and("strategyNEQ", AllFieldsSearch.entity().getReservationStrategy(), Op.NEQ);
         AllFieldsSearch.and("reserverName",AllFieldsSearch.entity().getReserver(),Op.EQ);
         AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.EQ);
         AllFieldsSearch.and("deviceid", AllFieldsSearch.entity().getDeviceId(), Op.EQ);
@@ -195,6 +196,15 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao {
         return findByNetworkIdAndTypeInternal(networkId, vmType, true);
     }
 
+    @Override
+    public NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType) {
+        SearchCriteria<NicVO> sc = AllFieldsSearch.create();
+        sc.setParameters("network", networkId);
+        sc.setParameters("vmType", vmType);
+        sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString());
+        return findOneBy(sc);
+    }
+
     @Override
     public NicVO findByNetworkIdTypeAndGateway(long networkId, VirtualMachine.Type vmType, String gateway) {
         SearchCriteria<NicVO> sc = AllFieldsSearch.create();
@@ -222,6 +232,16 @@ public class NicDaoImpl extends GenericDaoBase<NicVO, Long> implements NicDao {
         return findOneBy(sc);
     }
 
+    @Override
+    public NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId) {
+        SearchCriteria<NicVO> sc = AllFieldsSearch.create();
+        sc.setParameters("address", ip4Address);
+        sc.setParameters("network", networkId);
+        sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString());
+        return findOneBy(sc);
+    }
+
+
     @Override
     public NicVO findByNetworkIdAndMacAddress(long networkId, String mac) {
         SearchCriteria<NicVO> sc = AllFieldsSearch.create();
diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
index 171685ce413..9a0bd416aca 100644
--- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
+++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml
@@ -137,6 +137,7 @@
   <bean id="objectInDataStoreDaoImpl" class="org.apache.cloudstack.storage.db.ObjectInDataStoreDaoImpl" />
   <bean id="ovsProviderDaoImpl" class="com.cloud.network.dao.OvsProviderDaoImpl" />
   <bean id="nsxControllerDaoImpl" class="com.cloud.network.dao.NsxProviderDaoImpl" />
+  <bean id="netrisProviderDaoImpl" class="com.cloud.network.dao.NetrisProviderDaoImpl" />
   <bean id="tungstenControllerDaoImpl" class="com.cloud.network.dao.TungstenProviderDaoImpl"/>
   <bean id="physicalNetworkDaoImpl" class="com.cloud.network.dao.PhysicalNetworkDaoImpl" />
   <bean id="physicalNetworkIsolationMethodDaoImpl" class="com.cloud.network.dao.PhysicalNetworkIsolationMethodDaoImpl" />
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
index c36b71c2f25..133a8b582b9 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
@@ -425,3 +425,40 @@ INSERT IGNORE INTO `cloud`.`guest_os_hypervisor` (uuid, hypervisor_type, hypervi
 
 CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_instance', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for vm" ');
 CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'delete_protection', 'boolean DEFAULT FALSE COMMENT "delete protection for volumes" ');
+
+-- Netris Plugin
+CREATE TABLE `cloud`.`netris_providers` (
+    `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+    `uuid` varchar(40),
+    `zone_id` bigint unsigned NOT NULL COMMENT 'Zone ID',
+    `host_id` bigint unsigned NOT NULL COMMENT 'Host ID',
+    `name` varchar(40),
+    `url` varchar(255) NOT NULL,
+    `username` varchar(255) NOT NULL,
+    `password` varchar(255) NOT NULL,
+    `site_name` varchar(255) NOT NULL,
+    `tenant_name` varchar(255) NOT NULL,
+    `netris_tag` varchar(255) NOT NULL,
+    `created` datetime NOT NULL COMMENT 'created date',
+    `removed` datetime COMMENT 'removed date if not null',
+    PRIMARY KEY (`id`),
+    CONSTRAINT `fk_netris_providers__zone_id` FOREIGN KEY `fk_netris_providers__zone_id` (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE,
+    INDEX `i_netris_providers__zone_id`(`zone_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Drop the Tungsten and NSX columns from the network offerings (replaced by checking the provider on the ntwk_offering_service_map table)
+ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `for_tungsten`;
+ALTER TABLE `cloud`.`network_offerings` DROP COLUMN `for_nsx`;
+
+-- Drop the Tungsten and NSX columns from the VPC offerings (replaced by checking the provider on the vpc_offering_service_map table)
+ALTER TABLE `cloud`.`vpc_offerings` DROP COLUMN `for_nsx`;
+
+-- Add next_hop to the static_routes table
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.static_routes', 'next_hop', 'varchar(50) COMMENT "next hop of the static route" AFTER `vpc_gateway_id`');
+
+-- Add `for_router` to `user_ip_address` table
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user_ip_address', 'for_router', 'tinyint(1) DEFAULT 0 COMMENT "True if the ip address is used by Domain Router to expose services"');
+
+-- Add Netris Autoscaling rules
+INSERT IGNORE INTO `cloud`.`counter` (uuid, provider, source, name, value, created) VALUES (UUID(), 'Netris', 'cpu', 'VM CPU - average percentage', 'vm.cpu.average.percentage', NOW());
+INSERT IGNORE INTO `cloud`.`counter` (uuid, provider, source, name, value, created) VALUES (UUID(), 'Netris', 'memory', 'VM Memory - average percentage', 'vm.memory.average.percentage', NOW());
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql
index b6abaabcd48..4fb12ce0388 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql
@@ -59,8 +59,6 @@ SELECT
     `network_offerings`.`supports_public_access` AS `supports_public_access`,
     `network_offerings`.`supports_vm_autoscaling` AS `supports_vm_autoscaling`,
     `network_offerings`.`for_vpc` AS `for_vpc`,
-    `network_offerings`.`for_tungsten` AS `for_tungsten`,
-    `network_offerings`.`for_nsx` AS `for_nsx`,
     `network_offerings`.`network_mode` AS `network_mode`,
     `network_offerings`.`service_package_id` AS `service_package_id`,
     `network_offerings`.`routing_mode` AS `routing_mode`,
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
index c74d50590de..751d8f91a25 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
@@ -28,7 +28,6 @@ select
     `vpc_offerings`.`display_text` AS `display_text`,
     `vpc_offerings`.`state` AS `state`,
     `vpc_offerings`.`default` AS `default`,
-    `vpc_offerings`.`for_nsx` AS `for_nsx`,
     `vpc_offerings`.`network_mode` AS `network_mode`,
     `vpc_offerings`.`created` AS `created`,
     `vpc_offerings`.`removed` AS `removed`,
diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java
index e39b40cfc68..2e9d6989550 100644
--- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java
+++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java
@@ -34,6 +34,7 @@ import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.host.Host;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.Network.Capability;
 import com.cloud.network.Network.GuestType;
@@ -125,6 +126,11 @@ public class BaremetalDhcpElement extends AdapterBase implements DhcpServiceProv
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         return true;
diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java
index fa708e7be4c..adcb96f6116 100644
--- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java
+++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java
@@ -29,6 +29,7 @@ import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.Network.Capability;
 import com.cloud.network.Network.GuestType;
@@ -161,6 +162,11 @@ public class BaremetalPxeElement extends AdapterBase implements NetworkElement {
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     private void releaseVlan(Network network, VirtualMachineProfile vm) {
         vlanMgr.releaseVlan(network, vm);
     }
diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java
index d2c9731ddd1..2ec05d443aa 100644
--- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java
+++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java
@@ -30,6 +30,7 @@ import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.Network.Capability;
 import com.cloud.network.Network.Provider;
@@ -124,6 +125,11 @@ public class BaremetalUserdataElement extends AdapterBase implements NetworkElem
         return false;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         // TODO Auto-generated method stub
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java
index 26b8de53083..8ed780a9714 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java
@@ -51,6 +51,10 @@ public class BridgeVifDriver extends VifDriverBase {
     private String _controlCidr = NetUtils.getLinkLocalCIDR();
     private Long libvirtVersion;
 
+    private static boolean isVxlanOrNetris(String protocol) {
+        return protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme()) || protocol.equals(Networks.BroadcastDomainType.Netris.scheme());
+    }
+
     @Override
     public void configure(Map<String, Object> params) throws ConfigurationException {
 
@@ -177,7 +181,7 @@ public class BridgeVifDriver extends VifDriverBase {
 
     protected boolean isBroadcastTypeVlanOrVxlan(final NicTO nic) {
         return nic != null && (nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan
-                || nic.getBroadcastType() == Networks.BroadcastDomainType.Vxlan);
+                || nic.getBroadcastType() == Networks.BroadcastDomainType.Vxlan || nic.getBroadcastType() == Networks.BroadcastDomainType.Netris);
     }
 
     protected boolean isValidProtocolAndVnetId(final String vNetId, final String protocol) {
@@ -284,7 +288,7 @@ public class BridgeVifDriver extends VifDriverBase {
 
     private String createVnetBr(String vNetId, String pifKey, String protocol) throws InternalErrorException {
         String nic = _pifs.get(pifKey);
-        if (nic == null || protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) {
+        if (nic == null || isVxlanOrNetris(protocol)) {
             // if not found in bridge map, maybe traffic label refers to pif already?
             File pif = new File("/sys/class/net/" + pifKey);
             if (pif.isDirectory()) {
@@ -292,7 +296,7 @@ public class BridgeVifDriver extends VifDriverBase {
             }
         }
         String brName = "";
-        if (protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) {
+        if (isVxlanOrNetris(protocol)) {
             brName = generateVxnetBrName(nic, vNetId);
         } else {
             brName = generateVnetBrName(nic, vNetId);
@@ -304,7 +308,7 @@ public class BridgeVifDriver extends VifDriverBase {
     private void createVnet(String vnetId, String pif, String brName, String protocol) throws InternalErrorException {
         synchronized (_vnetBridgeMonitor) {
             String script = _modifyVlanPath;
-            if (protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) {
+            if (isVxlanOrNetris(protocol)) {
                 script = _modifyVxlanPath;
             }
             final Script command = new Script(script, _timeout, logger);
@@ -358,6 +362,7 @@ public class BridgeVifDriver extends VifDriverBase {
             }
 
             String scriptPath = null;
+            // TODO: check for netris as well
             if (cmdout != null && cmdout.contains("vxlan")) {
                 scriptPath = _modifyVxlanPath;
             } else {
diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/network/element/CiscoNexusVSMElement.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/network/element/CiscoNexusVSMElement.java
index 2503e0ac7fd..441bdb83854 100644
--- a/plugins/hypervisors/vmware/src/main/java/com/cloud/network/element/CiscoNexusVSMElement.java
+++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/network/element/CiscoNexusVSMElement.java
@@ -24,7 +24,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
-
+import com.cloud.network.IpAddress;
 import com.cloud.api.commands.DeleteCiscoNexusVSMCmd;
 import com.cloud.api.commands.DisableCiscoNexusVSMCmd;
 import com.cloud.api.commands.EnableCiscoNexusVSMCmd;
@@ -106,6 +106,11 @@ public class CiscoNexusVSMElement extends CiscoNexusVSMDeviceManagerImpl impleme
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         return true;
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
index 71be8b1a475..b5afc13dadd 100644
--- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java
@@ -2008,7 +2008,6 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
                         forVpc, true, false, false);
         if (forNsx) {
             defaultKubernetesServiceNetworkOffering.setNetworkMode(NetworkOffering.NetworkMode.NATTED);
-            defaultKubernetesServiceNetworkOffering.setForNsx(true);
         }
         defaultKubernetesServiceNetworkOffering.setSupportsVmAutoScaling(true);
         defaultKubernetesServiceNetworkOffering.setState(NetworkOffering.State.Enabled);
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
index 6025a41d69c..b6cdbccf69c 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java
@@ -712,7 +712,7 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
             metricsResponse.setCpuTotal(hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
             metricsResponse.setCpuUsed(hostResponse.getCpuUsed(), hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
             metricsResponse.setCpuAllocated(hostResponse.getCpuAllocated(), hostResponse.getCpuNumber(), hostResponse.getCpuSpeed());
-            metricsResponse.setLoadAverage(hostResponse.getAverageLoad());
+            metricsResponse.setCpuAverageLoad(hostResponse.getAverageLoad());
             metricsResponse.setMemTotal(hostResponse.getMemoryTotal());
             metricsResponse.setMemAllocated(hostResponse.getMemoryAllocated());
             metricsResponse.setMemUsed(hostResponse.getMemoryUsed());
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
index 8e12d2cbb6d..39d759f333f 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsResponse.java
@@ -52,10 +52,6 @@ public class HostMetricsResponse extends HostResponse {
     @Param(description = "the total cpu allocated in Ghz")
     private String cpuAllocated;
 
-    @SerializedName("cpuloadaverage")
-    @Param(description = "the average cpu load the last minute")
-    private Double loadAverage;
-
     @SerializedName("memorytotalgb")
     @Param(description = "the total memory capacity in GiB")
     private String memTotal;
@@ -132,12 +128,6 @@ public class HostMetricsResponse extends HostResponse {
         }
     }
 
-    public void setLoadAverage(final Double loadAverage) {
-        if (loadAverage != null) {
-            this.loadAverage = loadAverage;
-        }
-    }
-
     public void setCpuAllocated(final String cpuAllocated, final Integer cpuNumber, final Long cpuSpeed) {
         if (cpuAllocated != null && cpuNumber != null && cpuSpeed != null) {
             this.cpuAllocated = String.format("%.2f Ghz", parseCPU(cpuAllocated) * cpuNumber * cpuSpeed / (100.0 * 1000.0));
diff --git a/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java b/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java
index 5fc9480c610..f3624ebeb40 100644
--- a/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java
+++ b/plugins/network-elements/bigswitch/src/main/java/com/cloud/network/element/BigSwitchBcfElement.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.IpAddress;
 import org.springframework.stereotype.Component;
 import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
 import org.apache.commons.net.util.SubnetUtils;
@@ -318,6 +319,11 @@ NetworkACLServiceProvider, FirewallServiceProvider, ResourceStateAdapter {
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         if (!canHandle(network, Service.Connectivity)) {
diff --git a/plugins/network-elements/brocade-vcs/src/main/java/com/cloud/network/element/BrocadeVcsElement.java b/plugins/network-elements/brocade-vcs/src/main/java/com/cloud/network/element/BrocadeVcsElement.java
index daf9c1c4e08..510900cc105 100644
--- a/plugins/network-elements/brocade-vcs/src/main/java/com/cloud/network/element/BrocadeVcsElement.java
+++ b/plugins/network-elements/brocade-vcs/src/main/java/com/cloud/network/element/BrocadeVcsElement.java
@@ -27,6 +27,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.IpAddress;
 import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
 import org.springframework.stereotype.Component;
@@ -194,6 +195,11 @@ public class BrocadeVcsElement extends AdapterBase implements NetworkElement, Re
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         if (!canHandle(network, Service.Connectivity)) {
diff --git a/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java b/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java
index bea5a2c3f25..352eba4674d 100644
--- a/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java
+++ b/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java
@@ -434,6 +434,11 @@ public class CiscoVnmcElement extends AdapterBase implements SourceNatServicePro
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     private boolean cleanupLogicalEdgeFirewall(long vlanId, long hostId) {
         CleanupLogicalEdgeFirewallCommand cmd = new CleanupLogicalEdgeFirewallCommand(vlanId);
         Answer answer = _agentMgr.easySend(hostId, cmd);
diff --git a/plugins/network-elements/dns-notifier/src/main/java/org/apache/cloudstack/network/element/DnsNotifier.java b/plugins/network-elements/dns-notifier/src/main/java/org/apache/cloudstack/network/element/DnsNotifier.java
index 0b96aa35150..04f092648c2 100644
--- a/plugins/network-elements/dns-notifier/src/main/java/org/apache/cloudstack/network/element/DnsNotifier.java
+++ b/plugins/network-elements/dns-notifier/src/main/java/org/apache/cloudstack/network/element/DnsNotifier.java
@@ -26,6 +26,7 @@ import com.cloud.deploy.DeployDestination;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.Network.Capability;
 import com.cloud.network.Network.Provider;
@@ -86,6 +87,11 @@ public class DnsNotifier extends AdapterBase implements NetworkElement {
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         return true;
diff --git a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java
index c1ea7823811..f512a08dffd 100644
--- a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java
+++ b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/element/ElasticLoadBalancerElement.java
@@ -24,6 +24,7 @@ import java.util.Set;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.IpAddress;
 import org.springframework.stereotype.Component;
 
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -136,6 +137,11 @@ public class ElasticLoadBalancerElement extends AdapterBase implements LoadBalan
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         // TODO kill all loadbalancer vms by calling the ElasticLoadBalancerManager
diff --git a/plugins/network-elements/globodns/src/main/java/com/globo/globodns/cloudstack/element/GloboDnsElement.java b/plugins/network-elements/globodns/src/main/java/com/globo/globodns/cloudstack/element/GloboDnsElement.java
index 09830d9a810..c83a913a61c 100644
--- a/plugins/network-elements/globodns/src/main/java/com/globo/globodns/cloudstack/element/GloboDnsElement.java
+++ b/plugins/network-elements/globodns/src/main/java/com/globo/globodns/cloudstack/element/GloboDnsElement.java
@@ -26,6 +26,7 @@ import java.util.Set;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.IpAddress;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.springframework.stereotype.Component;
@@ -175,6 +176,11 @@ public class GloboDnsElement extends AdapterBase implements ResourceStateAdapter
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         return true;
diff --git a/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java b/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java
index 0a9b4a7131a..dc48d70f8a7 100644
--- a/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java
+++ b/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java
@@ -26,6 +26,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import com.cloud.network.IpAddress;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
@@ -221,6 +222,11 @@ public class InternalLoadBalancerElement extends AdapterBase implements LoadBala
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return true;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         List<? extends VirtualRouter> internalLbVms = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM);
diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java
index 44cbc6c305f..fb832bf1d82 100644
--- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java
+++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java
@@ -244,6 +244,11 @@ public class ContrailElementImpl extends AdapterBase
         return true;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return false;
+    }
+
     /**
      * Network disable
      */
diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
index 6da2e216ff8..da08ba2a6c2 100644
--- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
+++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
@@ -217,7 +217,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager
         ConfigurationManager configMgr = (ConfigurationManager) _configService;
         NetworkOfferingVO voffer = configMgr.createNetworkOffering(offeringName, offeringDisplayText,
                 TrafficType.Public, null, true, Availability.Optional, null, serviceProviderMap, true,
-                Network.GuestType.Shared, false, null, false, null, true, false, null, true, null, false, false, false, false, null, null, null, true, null, null, false);
+                Network.GuestType.Shared, false, null, false, null, true, false, null, true, null, false, false, false, false, false,null, null, null, true, null, null, false);
         long id = voffer.getId();
         _networkOfferingDao.update(id, voffer);
         return _networkOfferingDao.findById(id);
@@ -252,7 +252,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager
         ConfigurationManager configMgr = (ConfigurationManager)_configService;
         NetworkOfferingVO voffer =
                 configMgr.createNetworkOffering(offeringName, offeringDisplayText, TrafficType.Guest, null, false, Availability.Optional, null, serviceProviderMap, true,
-                        Network.GuestType.Isolated, false, null, false, null, false, true, null, true, null, false, offeringName.equals(vpcRouterOfferingName), false, false, null,  null, null, true, null, null, false);
+                        Network.GuestType.Isolated, false, null, false, null, false, true, null, true, null, false, offeringName.equals(vpcRouterOfferingName), false, false, false,null,  null, null, true, null, null, false);
         if (offeringName.equals(vpcRouterOfferingName)) {
             voffer.setInternalLb(true);
         }
@@ -293,7 +293,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager
             }
             serviceProviderMap.put(svc, providerSet);
         }
-        vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, false, null, null, null, VpcOffering.State.Enabled, null, false);
+        vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, null, null, VpcOffering.State.Enabled, null, false);
         long id = vpcOffer.getId();
         _vpcOffDao.update(id, (VpcOfferingVO)vpcOffer);
         return _vpcOffDao.findById(id);
diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailVpcElementImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailVpcElementImpl.java
index d5060443c9e..d492e6214ec 100644
--- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailVpcElementImpl.java
+++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailVpcElementImpl.java
@@ -201,4 +201,9 @@ public class ContrailVpcElementImpl extends ContrailElementImpl implements Netwo
     public boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address) {
         return true;
     }
+
+    @Override
+    public boolean updateVpc(Vpc vpc, String previousVpcName) {
+        return true;
+    }
 }
diff --git a/plugins/network-elements/netris/pom.xml b/plugins/network-elements/netris/pom.xml
new file mode 100644
index 00000000000..a50013ee351
--- /dev/null
+++ b/plugins/network-elements/netris/pom.xml
@@ -0,0 +1,44 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>cloud-plugin-network-netris</artifactId>
+    <name>Apache CloudStack Plugin - Netris</name>
+
+    <parent>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloudstack-plugins</artifactId>
+        <version>4.20.0.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>io.netris</groupId>
+            <artifactId>netris-java-sdk</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.seancfoley</groupId>
+            <artifactId>ipaddress</artifactId>
+            <version>5.5.1</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/StartupNetrisCommand.java
similarity index 76%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/StartupNetrisCommand.java
index d3fef252f50..04be93067a6 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/StartupNetrisCommand.java
@@ -14,10 +14,13 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack;
 
-import com.cloud.utils.db.GenericDao;
+import com.cloud.agent.api.StartupCommand;
+import com.cloud.host.Host;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public class StartupNetrisCommand extends StartupCommand {
+    public StartupNetrisCommand() {
+        super(Host.Type.L2Networking);
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java
new file mode 100644
index 00000000000..392016fc238
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/AddOrUpdateNetrisStaticRouteCommand.java
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class AddOrUpdateNetrisStaticRouteCommand extends NetrisCommand {
+    private String prefix;
+    private String nextHop;
+    private Long routeId;
+    private boolean updateRoute;
+
+    public AddOrUpdateNetrisStaticRouteCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String prefix, String nextHop, Long routeId, boolean updateRoute) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.prefix = prefix;
+        this.nextHop = nextHop;
+        this.routeId = routeId;
+        this.updateRoute = updateRoute;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public String getNextHop() {
+        return nextHop;
+    }
+
+    public Long getRouteId() {
+        return routeId;
+    }
+
+    public boolean isUpdateRoute() {
+        return updateRoute;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisACLCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisACLCommand.java
new file mode 100644
index 00000000000..ea39cd7f366
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisACLCommand.java
@@ -0,0 +1,120 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+import org.apache.cloudstack.resource.NetrisPortGroup;
+
+public class CreateNetrisACLCommand extends NetrisCommand {
+    private String vpcName;
+    private Long vpcId;
+    private String action;
+    private String destPrefix;
+    private String sourcePrefix;
+    private Integer destPortStart;
+    private Integer destPortEnd;
+    private Integer icmpType;
+    private String protocol;
+    private NetrisPortGroup portGroup;
+    private String netrisAclName;
+    private String reason;
+
+
+    public CreateNetrisACLCommand(long zoneId, Long accountId, Long domainId, String name, Long id, String vpcName, Long vpcId, boolean isVpc, String action,
+                                  String sourcePrefix, String destPrefix, Integer destPortStart, Integer destPortEnd, String protocol) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.vpcName = vpcName;
+        this.vpcId = vpcId;
+        this.action = action;
+        this.sourcePrefix = sourcePrefix;
+        this.destPrefix = destPrefix;
+        this.destPortStart = destPortStart;
+        this.destPortEnd = destPortEnd;
+        this.protocol = protocol;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public void setVpcName(String vpcName) {
+        this.vpcName = vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    public String getDestPrefix() {
+        return destPrefix;
+    }
+
+    public String getSourcePrefix() {
+        return sourcePrefix;
+    }
+
+    public Integer getDestPortStart() {
+        return destPortStart;
+    }
+
+    public Integer getDestPortEnd() {
+        return destPortEnd;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public NetrisPortGroup getPortGroup() {
+        return portGroup;
+    }
+
+    public void setPortGroup(NetrisPortGroup portGroup) {
+        this.portGroup = portGroup;
+    }
+
+    public Integer getIcmpType() {
+        return icmpType;
+    }
+
+    public void setIcmpType(Integer icmpType) {
+        this.icmpType = icmpType;
+    }
+
+    public String getNetrisAclName() {
+        return netrisAclName;
+    }
+
+    public void setNetrisAclName(String netrisAclName) {
+        this.netrisAclName = netrisAclName;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVnetCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVnetCommand.java
new file mode 100644
index 00000000000..e6de0244e48
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVnetCommand.java
@@ -0,0 +1,85 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class CreateNetrisVnetCommand extends NetrisCommand {
+    private String vpcName;
+    private Long vpcId;
+    private String cidr;
+    private Integer vxlanId;
+    private String gateway;
+    private String netrisTag;
+    private String ipv6Cidr;
+    private Boolean globalRouting;
+
+    public CreateNetrisVnetCommand(Long zoneId, Long accountId, Long domainId, String vpcName, Long vpcId, String vNetName, Long networkId, String cidr, String gateway, boolean isVpc) {
+        super(zoneId, accountId, domainId, vNetName, networkId, isVpc);
+        this.vpcId = vpcId;
+        this.vpcName = vpcName;
+        this.cidr = cidr;
+        this.gateway = gateway;
+    }
+
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public String getCidr() {
+        return cidr;
+    }
+
+    public Integer getVxlanId() {
+        return vxlanId;
+    }
+
+    public void setVxlanId(Integer vxlanId) {
+        this.vxlanId = vxlanId;
+    }
+
+    public String getGateway() {
+        return gateway;
+    }
+
+    public String getNetrisTag() {
+        return netrisTag;
+    }
+
+    public void setNetrisTag(String netrisTag) {
+        this.netrisTag = netrisTag;
+    }
+
+    public String getIpv6Cidr() {
+        return ipv6Cidr;
+    }
+
+    public void setIpv6Cidr(String ipv6Cidr) {
+        this.ipv6Cidr = ipv6Cidr;
+    }
+
+    public Boolean isGlobalRouting() {
+        return globalRouting;
+    }
+
+    public void setGlobalRouting(Boolean globalRouting) {
+        this.globalRouting = globalRouting;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVpcCommand.java
similarity index 65%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVpcCommand.java
index d3fef252f50..cff25f78287 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateNetrisVpcCommand.java
@@ -14,10 +14,18 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
+public class CreateNetrisVpcCommand extends NetrisCommand {
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    private final String cidr;
+
+    public CreateNetrisVpcCommand(long zoneId, long accountId, long domainId, String name, String cidr, long vpcId, boolean isVpc) {
+        super(zoneId, accountId, domainId, name, vpcId, isVpc);
+        this.cidr = cidr;
+    }
+
+    public String getCidr() {
+        return cidr;
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisACLCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisACLCommand.java
new file mode 100644
index 00000000000..00fc5bafb7e
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisACLCommand.java
@@ -0,0 +1,120 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+import org.apache.cloudstack.resource.NetrisPortGroup;
+
+public class CreateOrUpdateNetrisACLCommand extends NetrisCommand {
+    private String vpcName;
+    private Long vpcId;
+    private String action;
+    private String destPrefix;
+    private String sourcePrefix;
+    private Integer destPortStart;
+    private Integer destPortEnd;
+    private Integer icmpType;
+    private String protocol;
+    private NetrisPortGroup portGroup;
+    private String netrisAclName;
+    private String reason;
+
+
+    public CreateOrUpdateNetrisACLCommand(long zoneId, Long accountId, Long domainId, String name, Long id, String vpcName, Long vpcId, boolean isVpc, String action,
+                                          String sourcePrefix, String destPrefix, Integer destPortStart, Integer destPortEnd, String protocol) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.vpcName = vpcName;
+        this.vpcId = vpcId;
+        this.action = action;
+        this.sourcePrefix = sourcePrefix;
+        this.destPrefix = destPrefix;
+        this.destPortStart = destPortStart;
+        this.destPortEnd = destPortEnd;
+        this.protocol = protocol;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public void setVpcName(String vpcName) {
+        this.vpcName = vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+
+    public String getAction() {
+        return action;
+    }
+
+    public String getDestPrefix() {
+        return destPrefix;
+    }
+
+    public String getSourcePrefix() {
+        return sourcePrefix;
+    }
+
+    public Integer getDestPortStart() {
+        return destPortStart;
+    }
+
+    public Integer getDestPortEnd() {
+        return destPortEnd;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public NetrisPortGroup getPortGroup() {
+        return portGroup;
+    }
+
+    public void setPortGroup(NetrisPortGroup portGroup) {
+        this.portGroup = portGroup;
+    }
+
+    public Integer getIcmpType() {
+        return icmpType;
+    }
+
+    public void setIcmpType(Integer icmpType) {
+        this.icmpType = icmpType;
+    }
+
+    public String getNetrisAclName() {
+        return netrisAclName;
+    }
+
+    public void setNetrisAclName(String netrisAclName) {
+        this.netrisAclName = netrisAclName;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java
new file mode 100644
index 00000000000..a2a080b33d9
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisLoadBalancerRuleCommand.java
@@ -0,0 +1,90 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+import com.cloud.network.netris.NetrisLbBackend;
+
+import java.util.List;
+
+public class CreateOrUpdateNetrisLoadBalancerRuleCommand extends NetrisCommand {
+    private final String publicPort;
+    private final String privatePort;
+    private final String algorithm;
+    private final String protocol;
+    List<NetrisLbBackend> lbBackends;
+    private String publicIp;
+    private final Long lbId;
+    private String cidrList;
+    private String ruleName;
+
+    public CreateOrUpdateNetrisLoadBalancerRuleCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc,
+                                                       List<NetrisLbBackend> lbBackends, long lbId, String publicIp, String publicPort,
+                                                       String privatePort, String algorithm, String protocol) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.lbId = lbId;
+        this.publicIp = publicIp;
+        this.publicPort = publicPort;
+        this.privatePort = privatePort;
+        this.algorithm = algorithm;
+        this.protocol = protocol;
+        this.lbBackends = lbBackends;
+    }
+
+    public String getPublicPort() {
+        return publicPort;
+    }
+
+    public String getPrivatePort() {
+        return privatePort;
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public List<NetrisLbBackend> getLbBackends() {
+        return lbBackends;
+    }
+
+    public Long getLbId() {
+        return lbId;
+    }
+
+    public String getPublicIp() {
+        return publicIp;
+    }
+
+    public String getCidrList() {
+        return cidrList;
+    }
+
+    public void setCidrList(String cidrList) {
+        this.cidrList = cidrList;
+    }
+
+    public String getRuleName() {
+        return ruleName;
+    }
+
+    public void setRuleName(String ruleName) {
+        this.ruleName = ruleName;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisNatCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisNatCommand.java
new file mode 100644
index 00000000000..5e0546a8256
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/CreateOrUpdateNetrisNatCommand.java
@@ -0,0 +1,149 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class CreateOrUpdateNetrisNatCommand extends NetrisCommand {
+    private String vpcName;
+    private Long vpcId;
+    private String vpcCidr;
+    private String natRuleName;
+    private String natIp;
+    private String natRuleType;
+
+    // Parameters for DNAT
+    private String protocol;
+    private String sourceAddress;
+    private String sourcePort;
+    private String destinationAddress;
+    private String destinationPort;
+    private String state;
+
+    // Parameters for SNAT (Static NAT)
+    private String vmIp;
+
+
+    public CreateOrUpdateNetrisNatCommand(long zoneId, Long accountId, Long domainId, String vpcName, Long vpcId, String vNetName, Long networkId, boolean isVpc, String vpcCidr) {
+        super(zoneId, accountId, domainId, vNetName, networkId, isVpc);
+        this.vpcName = vpcName;
+        this.vpcId = vpcId;
+        this.vpcCidr = vpcCidr;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public String getNatIp() {
+        return natIp;
+    }
+
+    public void setNatRuleName(String natRuleName) {
+        this.natRuleName = natRuleName;
+    }
+
+    public String getNatRuleName() {
+        return natRuleName;
+    }
+
+    public String getVpcCidr() {
+        return vpcCidr;
+    }
+
+    public void setNatIp(String natIp) {
+        this.natIp = natIp;
+    }
+
+    public String getVmIp() {
+        return vmIp;
+    }
+
+    public void setVmIp(String vmIp) {
+        this.vmIp = vmIp;
+    }
+
+    public String getNatRuleType() {
+        return natRuleType;
+    }
+
+    public void setNatRuleType(String natRuleType) {
+        this.natRuleType = natRuleType;
+    }
+
+    public void setVpcName(String vpcName) {
+        this.vpcName = vpcName;
+    }
+
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+
+    public void setVpcCidr(String vpcCidr) {
+        this.vpcCidr = vpcCidr;
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getSourceAddress() {
+        return sourceAddress;
+    }
+
+    public void setSourceAddress(String sourceAddress) {
+        this.sourceAddress = sourceAddress;
+    }
+
+    public String getSourcePort() {
+        return sourcePort;
+    }
+
+    public void setSourcePort(String sourcePort) {
+        this.sourcePort = sourcePort;
+    }
+
+    public String getDestinationAddress() {
+        return destinationAddress;
+    }
+
+    public void setDestinationAddress(String destinationAddress) {
+        this.destinationAddress = destinationAddress;
+    }
+
+    public String getDestinationPort() {
+        return destinationPort;
+    }
+
+    public void setDestinationPort(String destinationPort) {
+        this.destinationPort = destinationPort;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisACLCommand.java
similarity index 52%
copy from engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisACLCommand.java
index 06cfd25e670..e4a4993a257 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisACLCommand.java
@@ -14,27 +14,33 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.vpc.dao;
+package org.apache.cloudstack.agent.api;
 
 import java.util.List;
 
-import com.cloud.network.Network.Service;
-import com.cloud.network.vpc.VpcOfferingServiceMapVO;
-import com.cloud.utils.db.GenericDao;
-
-public interface VpcOfferingServiceMapDao extends GenericDao<VpcOfferingServiceMapVO, Long> {
-
-    List<VpcOfferingServiceMapVO> listByVpcOffId(long vpcOffId);
-
-    /**
-     * @param networkOfferingId
-     * @param services
-     * @return
-     */
-    boolean areServicesSupportedByVpcOffering(long vpcOfferingId, Service[] services);
-
-    List<String> listServicesForVpcOffering(long vpcOfferingId);
-
-    VpcOfferingServiceMapVO findByServiceProviderAndOfferingId(String service, String provider, long vpcOfferingId);
-
+public class DeleteNetrisACLCommand extends NetrisCommand {
+    Long vpcId;
+    String vpcName;
+    List<String> aclRuleNames;
+    public DeleteNetrisACLCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, Long vpcId, String vpcName) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.vpcId = vpcId;
+        this.vpcName = vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public List<String> getAclRuleNames() {
+        return aclRuleNames;
+    }
+
+    public void setAclRuleNames(List<String> aclRuleNames) {
+        this.aclRuleNames = aclRuleNames;
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java
new file mode 100644
index 00000000000..3ca32c8112c
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisLoadBalancerRuleCommand.java
@@ -0,0 +1,52 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class DeleteNetrisLoadBalancerRuleCommand extends NetrisCommand {
+    private Long lbId;
+    private String ruleName;
+    private String cidrList;
+
+    public DeleteNetrisLoadBalancerRuleCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, Long lbId) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.lbId = lbId;
+    }
+
+    public Long getLbId() {
+        return lbId;
+    }
+
+    public void setLbId(Long lbId) {
+        this.lbId = lbId;
+    }
+
+    public String getRuleName() {
+        return ruleName;
+    }
+
+    public void setRuleName(String ruleName) {
+        this.ruleName = ruleName;
+    }
+
+    public String getCidrList() {
+        return cidrList;
+    }
+
+    public void setCidrList(String cidrList) {
+        this.cidrList = cidrList;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisNatRuleCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisNatRuleCommand.java
new file mode 100644
index 00000000000..b01b2f45192
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisNatRuleCommand.java
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class DeleteNetrisNatRuleCommand extends NetrisCommand {
+
+    private String vpcName;
+    private Long vpcId;
+    private String natRuleType;
+    private String natRuleName;
+    private String natIp;
+
+    public DeleteNetrisNatRuleCommand(long zoneId, Long accountId, Long domainId, String vpcName, Long vpcId, String vNetName, Long networkId, boolean isVpc) {
+        super(zoneId, accountId, domainId, vNetName, networkId, isVpc);
+        this.vpcName = vpcName;
+        this.vpcId = vpcId;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public void setVpcName(String vpcName) {
+        this.vpcName = vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+
+    public String getNatRuleType() {
+        return natRuleType;
+    }
+
+    public void setNatRuleType(String natRuleType) {
+        this.natRuleType = natRuleType;
+    }
+
+    public String getNatRuleName() {
+        return natRuleName;
+    }
+
+    public void setNatRuleName(String natRuleName) {
+        this.natRuleName = natRuleName;
+    }
+
+    public String getNatIp() {
+        return natIp;
+    }
+
+    public void setNatIp(String natIp) {
+        this.natIp = natIp;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java
similarity index 66%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java
index d3fef252f50..b531a049b0f 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisStaticRouteCommand.java
@@ -14,10 +14,10 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
-
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public class DeleteNetrisStaticRouteCommand extends AddOrUpdateNetrisStaticRouteCommand {
+    public DeleteNetrisStaticRouteCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String prefix, String nextHop, Long routeId) {
+        super(zoneId, accountId, domainId, name, id, isVpc, prefix, nextHop, routeId, false);
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVnetCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVnetCommand.java
new file mode 100644
index 00000000000..240c6c5417c
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVnetCommand.java
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class DeleteNetrisVnetCommand extends NetrisCommand {
+    private String vpcName;
+    private Long vpcId;
+    private final String vNetCidr;
+    private String vNetV6Cidr;
+
+    public DeleteNetrisVnetCommand(long zoneId, long accountId, long domainId, String name, long id, String vpcName, Long vpcId, String vNetCidr, boolean isVpc) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.vpcName = vpcName;
+        this.vpcId = vpcId;
+        this.vNetCidr = vNetCidr;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public String getVNetCidr() {
+        return vNetCidr;
+    }
+
+    public String getvNetV6Cidr() {
+        return vNetV6Cidr;
+    }
+
+    public void setvNetV6Cidr(String vNetV6Cidr) {
+        this.vNetV6Cidr = vNetV6Cidr;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVpcCommand.java
similarity index 65%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVpcCommand.java
index d3fef252f50..d560f0b8cd9 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/DeleteNetrisVpcCommand.java
@@ -14,10 +14,17 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
+public class DeleteNetrisVpcCommand extends NetrisCommand {
+    private final String cidr;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    public DeleteNetrisVpcCommand(long zoneId, long accountId, long domainId, String name, String cidr, long vpcId, boolean isVpc) {
+        super(zoneId, accountId, domainId, name, vpcId, isVpc);
+        this.cidr = cidr;
+    }
+
+    public String getCidr() {
+        return cidr;
+    }
 }
diff --git a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesAnswer.java
similarity index 61%
copy from api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesAnswer.java
index e99bc2fd416..421dc3359b6 100644
--- a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesAnswer.java
@@ -14,21 +14,23 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.element;
+package org.apache.cloudstack.agent.api;
+
+import com.cloud.agent.api.Command;
+import com.cloud.network.vpc.StaticRoute;
 
 import java.util.List;
 
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.Network;
-import com.cloud.network.rules.PortForwardingRule;
+public class ListNetrisStaticRoutesAnswer extends NetrisAnswer {
+
+    List<StaticRoute> staticRoutes;
+
+    public ListNetrisStaticRoutesAnswer(final Command command, final List<StaticRoute> staticRoutes) {
+        super(command, true, "OK");
+        this.staticRoutes = staticRoutes;
+    }
 
-public interface PortForwardingServiceProvider extends NetworkElement, IpDeployingRequester {
-    /**
-     * Apply rules
-     * @param network
-     * @param rules
-     * @return
-     * @throws ResourceUnavailableException
-     */
-    boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException;
+    public List<StaticRoute> getStaticRoutes() {
+        return staticRoutes;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesCommand.java
similarity index 66%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesCommand.java
index d3fef252f50..e116a2386cb 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ListNetrisStaticRoutesCommand.java
@@ -14,10 +14,10 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
-
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public class ListNetrisStaticRoutesCommand extends AddOrUpdateNetrisStaticRouteCommand {
+    public ListNetrisStaticRoutesCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String prefix, String nextHop, Long routeId) {
+        super(zoneId, accountId, domainId, name, id, isVpc, prefix, nextHop, routeId, false);
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisAnswer.java
similarity index 66%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisAnswer.java
index d3fef252f50..197500a3cce 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisAnswer.java
@@ -14,10 +14,17 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.Command;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+public class NetrisAnswer extends Answer {
+    public NetrisAnswer(final Command command, final boolean success, final String details) {
+        super(command, success, details);
+    }
+
+    public NetrisAnswer(final Command command, final Exception e) {
+        super(command, e);
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java
new file mode 100644
index 00000000000..ceb33ea24ed
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/NetrisCommand.java
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+import com.cloud.agent.api.Command;
+
+public class NetrisCommand extends Command {
+    private final long zoneId;
+    private final Long accountId;
+    private final Long domainId;
+    private String name;
+    private final Long id;
+    private final boolean isVpc;
+
+    public NetrisCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc) {
+        this.zoneId = zoneId;
+        this.accountId = accountId;
+        this.domainId = domainId;
+        this.name = name;
+        this.id = id;
+        this.isVpc = isVpc;
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public long getZoneId() {
+        return zoneId;
+    }
+
+    public Long getAccountId() {
+        return accountId;
+    }
+
+    public Long getDomainId() {
+        return domainId;
+    }
+
+    public boolean isVpc() {
+        return isVpc;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ReleaseNatIpCommand.java
similarity index 66%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ReleaseNatIpCommand.java
index d3fef252f50..ac3a4faba4d 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/ReleaseNatIpCommand.java
@@ -14,10 +14,17 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
+public class ReleaseNatIpCommand extends NetrisCommand {
+    private String natIp;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    public ReleaseNatIpCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc, String natIp) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+        this.natIp = natIp;
+    }
+
+    public String getNatIp() {
+        return natIp;
+    }
 }
diff --git a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java
similarity index 59%
copy from api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java
index e99bc2fd416..5cbdd96e76a 100644
--- a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/SetupNetrisPublicRangeCommand.java
@@ -14,21 +14,24 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.element;
+package org.apache.cloudstack.agent.api;
 
-import java.util.List;
+public class SetupNetrisPublicRangeCommand extends NetrisCommand {
 
-import com.cloud.exception.ResourceUnavailableException;
-import com.cloud.network.Network;
-import com.cloud.network.rules.PortForwardingRule;
+    private final String superCidr;
+    private final String exactCidr;
 
-public interface PortForwardingServiceProvider extends NetworkElement, IpDeployingRequester {
-    /**
-     * Apply rules
-     * @param network
-     * @param rules
-     * @return
-     * @throws ResourceUnavailableException
-     */
-    boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException;
+    public SetupNetrisPublicRangeCommand(long zoneId, String superCidr, String exactCidr) {
+        super(zoneId, null, null, null, zoneId, false);
+        this.superCidr = superCidr;
+        this.exactCidr = exactCidr;
+    }
+
+    public String getSuperCidr() {
+        return superCidr;
+    }
+
+    public String getExactCidr() {
+        return exactCidr;
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVnetCommand.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVnetCommand.java
new file mode 100644
index 00000000000..99a266e15ad
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVnetCommand.java
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.api;
+
+public class UpdateNetrisVnetCommand extends NetrisCommand{
+    private String prevNetworkName;
+    private String vpcName;
+    private Long vpcId;
+
+    public UpdateNetrisVnetCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+    }
+
+    public String getPrevNetworkName() {
+        return prevNetworkName;
+    }
+
+    public void setPrevNetworkName(String prevNetworkName) {
+        this.prevNetworkName = prevNetworkName;
+    }
+
+    public String getVpcName() {
+        return vpcName;
+    }
+
+    public void setVpcName(String vpcName) {
+        this.vpcName = vpcName;
+    }
+
+    public Long getVpcId() {
+        return vpcId;
+    }
+
+    public void setVpcId(Long vpcId) {
+        this.vpcId = vpcId;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVpcCommand.java
similarity index 60%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVpcCommand.java
index d3fef252f50..764d9155a02 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/agent/api/UpdateNetrisVpcCommand.java
@@ -14,10 +14,19 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.agent.api;
 
-import com.cloud.utils.db.GenericDao;
+public class UpdateNetrisVpcCommand extends NetrisCommand {
+    private String previousVpcName;
+    public UpdateNetrisVpcCommand(long zoneId, Long accountId, Long domainId, String name, Long id, boolean isVpc) {
+        super(zoneId, accountId, domainId, name, id, isVpc);
+    }
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    public String getPreviousVpcName() {
+        return previousVpcName;
+    }
+
+    public void setPreviousVpcName(String previousVpcName) {
+        this.previousVpcName = previousVpcName;
+    }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/AddNetrisProviderCmd.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/AddNetrisProviderCmd.java
new file mode 100644
index 00000000000..a7ac3843020
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/AddNetrisProviderCmd.java
@@ -0,0 +1,123 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command;
+
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.network.netris.NetrisProvider;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.NetrisProviderResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.service.NetrisProviderService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+
+@APICommand(name = AddNetrisProviderCmd.APINAME, description = "Add Netris Provider to CloudStack",
+        responseObject = NetrisProviderResponse.class, requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false, since = "4.20")
+public class AddNetrisProviderCmd extends BaseCmd {
+    public static final String APINAME = "addNetrisProvider";
+    public static final Logger LOGGER = LoggerFactory.getLogger(AddNetrisProviderCmd.class.getName());
+
+    @Inject
+    NetrisProviderService netrisProviderService;
+
+    @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true,
+            description = "the ID of zone")
+    private Long zoneId;
+
+    @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Netris provider name")
+    private String name;
+
+    @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "Netris provider URL")
+    private String url;
+
+    @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Username to log into Netris")
+    private String username;
+
+    @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, required = true, description = "Password to login into Netris")
+    private String password;
+
+    @Parameter(name = ApiConstants.SITE_NAME, type = CommandType.STRING, required = true, description = "Netris Site name")
+    private String siteName;
+
+    @Parameter(name = ApiConstants.TENANT_NAME, type = CommandType.STRING, required = true, description = "Password to login into Netris")
+    private String tenantName;
+
+    @Parameter(name = ApiConstants.NETRIS_TAG, type = CommandType.STRING, required = true, description = "Netris tag for vNets")
+    private String netrisTag;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getSiteName() {
+        return siteName;
+    }
+
+    public String getTenantName() {
+        return tenantName;
+    }
+
+    public String getNetrisTag() {
+        return netrisTag;
+    }
+
+    @Override
+    public void execute() throws ServerApiException, ConcurrentOperationException {
+        NetrisProvider provider = netrisProviderService.addProvider(this);
+        NetrisProviderResponse response = netrisProviderService.createNetrisProviderResponse(provider);
+        if (response == null)
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add Netris provider");
+        else {
+            response.setResponseName(getCommandName());
+            setResponseObject(response);
+        }
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        return CallContext.current().getCallingAccount().getId();
+    }
+}
diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/api/command/DeleteNsxControllerCmd.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/DeleteNetrisProviderCmd.java
similarity index 70%
copy from plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/api/command/DeleteNsxControllerCmd.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/DeleteNetrisProviderCmd.java
index 5a3e5586400..27e29071c45 100644
--- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/api/command/DeleteNsxControllerCmd.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/DeleteNetrisProviderCmd.java
@@ -16,62 +16,59 @@
 // under the License.
 package org.apache.cloudstack.api.command;
 
-import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.api.APICommand;
-import org.apache.cloudstack.api.Parameter;
-import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.ApiErrorCode;
-import org.apache.cloudstack.api.response.NsxControllerResponse;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.NetrisProviderResponse;
 import org.apache.cloudstack.api.response.SuccessResponse;
-import org.apache.cloudstack.service.NsxProviderService;
+import org.apache.cloudstack.service.NetrisProviderService;
 
 import javax.inject.Inject;
 
-import static org.apache.cloudstack.api.command.DeleteNsxControllerCmd.APINAME;
+import static org.apache.cloudstack.api.command.DeleteNetrisProviderCmd.APINAME;
 
-@APICommand(name = APINAME, description = "delete NSX Controller to CloudStack",
-        responseObject = NsxControllerResponse.class, requestHasSensitiveInfo = false,
-        responseHasSensitiveInfo = false, since = "4.19.0")
-public class DeleteNsxControllerCmd extends BaseCmd {
-    public static final String APINAME = "deleteNsxController";
+@APICommand(name = APINAME, description = "delete Netris Provider to CloudStack",
+        responseObject = NetrisProviderResponse.class, requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false, since = "4.20.0")
+public class DeleteNetrisProviderCmd extends BaseCmd {
+    public static final String APINAME = "deleteNetrisProvider";
 
     @Inject
-    protected NsxProviderService nsxProviderService;
-/////////////////////////////////////////////////////
+    private NetrisProviderService netrisProviderService;
+
+    /////////////////////////////////////////////////////
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
-
-    @Parameter(name = ApiConstants.NSX_CONTROLLER_ID, type = CommandType.UUID, entityType = NsxControllerResponse.class,
-            required = true, description = "NSX Controller ID")
-    private Long nsxControllerId;
+    @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = NetrisProviderResponse.class,
+            required = true, description = "Netris Provider ID")
+    private Long id;
 
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
-    public Long getNsxControllerId() {
-        return nsxControllerId;
-    }
+    public Long getId() { return id; }
 
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
 
-
     @Override
     public void execute() throws ServerApiException, ConcurrentOperationException {
         try {
-            boolean deleted = nsxProviderService.deleteNsxController(getNsxControllerId());
+            boolean deleted = netrisProviderService.deleteNetrisProvider(getId());
             if (deleted) {
                 SuccessResponse response = new SuccessResponse(getCommandName());
                 response.setResponseName(getCommandName());
                 setResponseObject(response);
             } else {
-                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove NSX Controller from Zone");
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to Netris provider from zone");
             }
         } catch (InvalidParameterValueException e) {
             throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/ListNetrisProvidersCmd.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/ListNetrisProvidersCmd.java
new file mode 100644
index 00000000000..ed3d64e867a
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/command/ListNetrisProvidersCmd.java
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command;
+
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.utils.StringUtils;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.NetrisProviderResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.service.NetrisProviderService;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(name = ListNetrisProvidersCmd.APINAME, description = "list all Netris providers added to CloudStack",
+        responseObject = NetrisProviderResponse.class, requestHasSensitiveInfo = false,
+        responseHasSensitiveInfo = false, since = "4.20.0")
+public class ListNetrisProvidersCmd extends BaseListCmd {
+    public static final String APINAME = "listNetrisProviders";
+
+    @Inject
+    NetrisProviderService netrisProviderService;
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+    ///
+    @Parameter(name = ApiConstants.ZONE_ID, description = "Netris provider added to the specific zone",
+            type = CommandType.UUID, entityType = ZoneResponse.class)
+    Long zoneId;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getZoneId() {
+        return zoneId;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() throws ServerApiException, ConcurrentOperationException {
+        List<BaseResponse> baseResponseList = netrisProviderService.listNetrisProviders(zoneId);
+        List<BaseResponse> pagingList = StringUtils.applyPagination(baseResponseList, this.getStartIndex(), this.getPageSizeVal());
+        ListResponse<BaseResponse> listResponse = new ListResponse<>();
+        listResponse.setResponses(pagingList);
+        listResponse.setResponseName(getCommandName());
+        setResponseObject(listResponse);
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/response/NetrisProviderResponse.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/response/NetrisProviderResponse.java
new file mode 100644
index 00000000000..d130c3a7372
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/api/response/NetrisProviderResponse.java
@@ -0,0 +1,135 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.response;
+
+import com.cloud.network.netris.NetrisProvider;
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+
+@EntityReference(value = {NetrisProvider.class})
+public class NetrisProviderResponse extends BaseResponse {
+    @SerializedName(ApiConstants.NAME)
+    @Param(description = "Netris Provider name")
+    private String name;
+
+    @SerializedName(ApiConstants.UUID)
+    @Param(description = "Netris provider uuid")
+    private String uuid;
+
+    @SerializedName(ApiConstants.ZONE_ID)
+    @Param(description = "Zone ID to which the Netris Provider is associated with")
+    private String zoneId;
+
+    @SerializedName(ApiConstants.ZONE_NAME)
+    @Param(description = "Zone name to which the Netris Provider is associated with")
+    private String zoneName;
+
+    @SerializedName(ApiConstants.HOST_NAME)
+    @Param(description = "Netris Provider hostname or IP address")
+    private String hostname;
+
+    @SerializedName(ApiConstants.PORT)
+    @Param(description = "Netris Provider port")
+    private String port;
+
+    @SerializedName(ApiConstants.SITE_NAME)
+    @Param(description = "Netris Provider site")
+    private String siteName;
+
+    @SerializedName(ApiConstants.TENANT_NAME)
+    @Param(description = "Netris Admin tenant name")
+    private String tenantName;
+
+    @SerializedName(ApiConstants.NETRIS_TAG)
+    @Param(description = "Netris Tag for vNets")
+    private String netrisTag;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public String getZoneId() {
+        return zoneId;
+    }
+
+    public void setZoneId(String zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public String getZoneName() {
+        return zoneName;
+    }
+
+    public void setZoneName(String zoneName) {
+        this.zoneName = zoneName;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getSiteName() {
+        return siteName;
+    }
+
+    public void setSiteName(String siteName) {
+        this.siteName = siteName;
+    }
+
+    public String getTenantName() {
+        return tenantName;
+    }
+
+    public void setTenantName(String tenantName) {
+        this.tenantName = tenantName;
+    }
+
+    public String getNetrisTag() {
+        return netrisTag;
+    }
+
+    public void setNetrisTag(String netrisTag) {
+        this.netrisTag = netrisTag;
+    }
+}
diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisPortGroup.java
similarity index 78%
copy from engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisPortGroup.java
index d3fef252f50..13c4c90cf3f 100644
--- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisPortGroup.java
@@ -14,10 +14,14 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.network.dao;
+package org.apache.cloudstack.resource;
 
-import com.cloud.utils.db.GenericDao;
+public class NetrisPortGroup {
+    private String ports;
 
-public interface Site2SiteVpnGatewayDao extends GenericDao<Site2SiteVpnGatewayVO, Long> {
-    Site2SiteVpnGatewayVO findByVpcId(long vpcId);
+    public NetrisPortGroup(String ports) {
+        this.ports = ports;
+    }
+
+    public String getPorts() { return ports; }
 }
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java
new file mode 100644
index 00000000000..4aef6d2d80a
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResource.java
@@ -0,0 +1,441 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.resource;
+
+import com.cloud.agent.IAgentControl;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CheckHealthAnswer;
+import com.cloud.agent.api.CheckHealthCommand;
+import com.cloud.agent.api.Command;
+import com.cloud.agent.api.PingCommand;
+import com.cloud.agent.api.ReadyAnswer;
+import com.cloud.agent.api.ReadyCommand;
+import com.cloud.agent.api.StartupCommand;
+import com.cloud.host.Host;
+import com.cloud.network.vpc.StaticRoute;
+import com.cloud.resource.ServerResource;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisACLCommand;
+import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.ListNetrisStaticRoutesAnswer;
+import org.apache.cloudstack.agent.api.ListNetrisStaticRoutesCommand;
+import org.apache.cloudstack.agent.api.NetrisAnswer;
+import org.apache.cloudstack.StartupNetrisCommand;
+import org.apache.cloudstack.agent.api.ReleaseNatIpCommand;
+import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVpcCommand;
+import org.apache.cloudstack.service.NetrisApiClient;
+import org.apache.cloudstack.service.NetrisApiClientImpl;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.naming.ConfigurationException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NetrisResource implements ServerResource {
+    protected Logger logger = LogManager.getLogger(getClass());
+
+    private String name;
+    protected String endpointUrl;
+    protected String username;
+    protected String password;
+    protected String guid;
+    protected String zoneId;
+    protected String siteName;
+    protected String adminTenantName;
+
+    protected NetrisApiClient netrisApiClient;
+
+    @Override
+    public Host.Type getType() {
+        return Host.Type.Routing;
+    }
+
+    @Override
+    public StartupCommand[] initialize() {
+        StartupNetrisCommand sc = new StartupNetrisCommand();
+        sc.setGuid(guid);
+        sc.setName(name);
+        sc.setDataCenter(zoneId);
+        sc.setPod("");
+        sc.setPrivateIpAddress("");
+        sc.setStorageIpAddress("");
+        sc.setVersion("");
+        return new StartupCommand[] {sc};
+    }
+
+    @Override
+    public PingCommand getCurrentStatus(long id) {
+        return null;
+    }
+
+    @Override
+    public Answer executeRequest(Command cmd) {
+        if (cmd instanceof ReadyCommand) {
+            return executeRequest((ReadyCommand) cmd);
+        } else if (cmd instanceof CheckHealthCommand) {
+            return executeRequest((CheckHealthCommand) cmd);
+        } else if (cmd instanceof CreateNetrisVpcCommand) {
+            return executeRequest((CreateNetrisVpcCommand) cmd);
+        } else if (cmd instanceof UpdateNetrisVpcCommand) {
+          return executeRequest((UpdateNetrisVpcCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisVpcCommand) {
+            return executeRequest((DeleteNetrisVpcCommand) cmd);
+        } else if (cmd instanceof CreateNetrisVnetCommand) {
+            return executeRequest((CreateNetrisVnetCommand) cmd);
+        } else if (cmd instanceof UpdateNetrisVnetCommand) {
+          return executeRequest((UpdateNetrisVnetCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisVnetCommand) {
+          return executeRequest((DeleteNetrisVnetCommand) cmd);
+        } else if (cmd instanceof SetupNetrisPublicRangeCommand) {
+            return executeRequest((SetupNetrisPublicRangeCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisNatRuleCommand) {
+            return executeRequest((DeleteNetrisNatRuleCommand) cmd);
+        } else if (cmd instanceof CreateOrUpdateNetrisNatCommand) {
+          return executeRequest((CreateOrUpdateNetrisNatCommand) cmd);
+        } else if (cmd instanceof CreateOrUpdateNetrisACLCommand) {
+            return executeRequest((CreateOrUpdateNetrisACLCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisACLCommand) {
+            return executeRequest((DeleteNetrisACLCommand) cmd);
+        } else if (cmd instanceof ListNetrisStaticRoutesCommand) {
+            return executeRequest((ListNetrisStaticRoutesCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisStaticRouteCommand) {
+            return executeRequest((DeleteNetrisStaticRouteCommand) cmd);
+        } else if (cmd instanceof AddOrUpdateNetrisStaticRouteCommand) {
+            return executeRequest((AddOrUpdateNetrisStaticRouteCommand) cmd);
+        } else if (cmd instanceof ReleaseNatIpCommand) {
+          return executeRequest((ReleaseNatIpCommand) cmd);
+        } else if (cmd instanceof CreateOrUpdateNetrisLoadBalancerRuleCommand) {
+            return executeRequest((CreateOrUpdateNetrisLoadBalancerRuleCommand) cmd);
+        } else if (cmd instanceof DeleteNetrisLoadBalancerRuleCommand) {
+          return executeRequest((DeleteNetrisLoadBalancerRuleCommand) cmd);
+        } else {
+            return Answer.createUnsupportedCommandAnswer(cmd);
+        }
+    }
+
+    @Override
+    public void disconnected() {
+        // Do nothing
+    }
+
+    @Override
+    public IAgentControl getAgentControl() {
+        return null;
+    }
+
+    @Override
+    public void setAgentControl(IAgentControl agentControl) {
+        // Do nothing
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public void setConfigParams(Map<String, Object> params) {
+        // Do nothing
+    }
+
+    @Override
+    public Map<String, Object> getConfigParams() {
+        return new HashMap<>();
+    }
+
+    @Override
+    public int getRunLevel() {
+        return 0;
+    }
+
+    @Override
+    public void setRunLevel(int level) {
+
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        endpointUrl = (String) params.get("url");
+        if (endpointUrl == null) {
+            throw new ConfigurationException("Missing Netris provider URL from params: " + params);
+        }
+
+        username = (String) params.get("username");
+        if (username == null) {
+            throw new ConfigurationException("Missing Netris username from params: " + params);
+        }
+
+        password = (String) params.get("password");
+        if (password == null) {
+            throw new ConfigurationException("Missing Netris password from params: " + params);
+        }
+
+        this.name = (String) params.get("name");
+        if (this.name == null) {
+            throw new ConfigurationException("Unable to find name");
+        }
+
+        guid = (String) params.get("guid");
+        if (guid == null) {
+            throw new ConfigurationException("Unable to find the guid");
+        }
+
+        zoneId = (String) params.get("zoneId");
+        if (zoneId == null) {
+            throw new ConfigurationException("Unable to find zone");
+        }
+
+        siteName = (String) params.get("siteName");
+        if (siteName == null) {
+            throw new ConfigurationException("Unable to find the Netris site name");
+        }
+
+        adminTenantName = (String) params.get("tenantName");
+        if (adminTenantName == null) {
+            throw new ConfigurationException("Unable to find the Netris admin tenant name");
+        }
+
+        netrisApiClient = new NetrisApiClientImpl(endpointUrl, username, password, siteName, adminTenantName);
+        return netrisApiClient.isSessionAlive();
+    }
+
+    private Answer executeRequest(ReadyCommand cmd) {
+        return new ReadyAnswer(cmd);
+    }
+
+    private Answer executeRequest(CheckHealthCommand cmd) {
+        return new CheckHealthAnswer(cmd, netrisApiClient.isSessionAlive());
+    }
+
+    private Answer executeRequest(CreateNetrisVpcCommand cmd) {
+        try {
+            boolean result = netrisApiClient.createVpc(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Netris VPC %s creation failed", cmd.getName()));
+            }
+            return new NetrisAnswer(cmd, true, "OK");
+        } catch (CloudRuntimeException e) {
+            String msg = String.format("Error creating Netris VPC: %s", e.getMessage());
+            logger.error(msg, e);
+            return new NetrisAnswer(cmd, new CloudRuntimeException(msg));
+        }
+    }
+
+    private Answer executeRequest(UpdateNetrisVpcCommand cmd) {
+        try {
+            boolean result = netrisApiClient.updateVpc(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Netris VPC %s creation failed", cmd.getName()));
+            }
+            return new NetrisAnswer(cmd, true, "OK");
+        } catch (CloudRuntimeException e) {
+            String msg = String.format("Error creating Netris VPC: %s", e.getMessage());
+            logger.error(msg, e);
+            return new NetrisAnswer(cmd, new CloudRuntimeException(msg));
+        }
+    }
+
+    private Answer executeRequest(CreateNetrisVnetCommand cmd) {
+        try {
+            String vpcName = cmd.getVpcName();
+            boolean result = netrisApiClient.createVnet(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Netris VPC %s creation failed", vpcName));
+            }
+            return new NetrisAnswer(cmd, true, "OK");
+        } catch (CloudRuntimeException e) {
+            String msg = String.format("Error creating Netris VPC: %s", e.getMessage());
+            logger.error(msg, e);
+            return new NetrisAnswer(cmd, new CloudRuntimeException(msg));
+        }
+    }
+
+    private Answer executeRequest(UpdateNetrisVnetCommand cmd) {
+        try {
+            String networkName = cmd.getName();
+            String prevNetworkName = cmd.getPrevNetworkName();
+            boolean result = netrisApiClient.updateVnet(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Failed to update network name from %s to %s", prevNetworkName, networkName));
+            }
+            return new NetrisAnswer(cmd, true, "OK");
+        } catch (CloudRuntimeException e) {
+            String msg = String.format("Error updating Netris vNet: %s", e.getMessage());
+            logger.error(msg, e);
+            return new NetrisAnswer(cmd, new CloudRuntimeException(msg));
+        }
+    }
+
+    private Answer executeRequest(DeleteNetrisVnetCommand cmd) {
+        try {
+            String networkName = cmd.getName();
+            boolean result = netrisApiClient.deleteVnet(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Netris vNet: %s deletion failed", networkName));
+            }
+            return new NetrisAnswer(cmd, true, "OK");
+        } catch (CloudRuntimeException e) {
+            String msg = String.format("Error deleting Netris vNet: %s", e.getMessage());
+            logger.error(msg, e);
+            return new NetrisAnswer(cmd, new CloudRuntimeException(msg));
+        }
+    }
+
+    private Answer executeRequest(DeleteNetrisVpcCommand cmd) {
+        boolean result = netrisApiClient.deleteVpc(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Netris VPC %s deletion failed", cmd.getName()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(SetupNetrisPublicRangeCommand cmd) {
+        boolean result = netrisApiClient.setupZoneLevelPublicRange(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, "Netris Setup for Public Range failed");
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(CreateOrUpdateNetrisNatCommand cmd) {
+        String natRuleType = cmd.getNatRuleType();
+        if ("SNAT".equals(natRuleType)) {
+            boolean result = netrisApiClient.createOrUpdateSNATRule(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Failed to create SNAT rule on Netris for network %s", cmd.getName()));
+            }
+        } else if ("DNAT".equals(natRuleType)) {
+            boolean result = netrisApiClient.createOrUpdateDNATRule(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Failed to create DNAT rule on Netris for network %s", cmd.getName()));
+            }
+        } else if ("STATICNAT".equals(natRuleType)) {
+            boolean result = netrisApiClient.createStaticNatRule(cmd);
+            if (!result) {
+                return new NetrisAnswer(cmd, false, String.format("Failed to create SNAT rule on Netris for network %s", cmd.getName()));
+            }
+        }
+
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(DeleteNetrisNatRuleCommand cmd) {
+        boolean result = netrisApiClient.deleteNatRule(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Netris NAT rule: %s deletion failed", cmd.getNatRuleName()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(CreateOrUpdateNetrisACLCommand cmd) {
+        boolean result = netrisApiClient.addOrUpdateAclRule(cmd, false);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Creation of Netris ACL rule: %s failed", cmd.getNetrisAclName()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(DeleteNetrisACLCommand cmd) {
+        boolean result = netrisApiClient.deleteAclRule(cmd, false);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to delete Netris ACLs: %s", String.join("'", cmd.getAclRuleNames())));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(ListNetrisStaticRoutesCommand cmd) {
+        List<StaticRoute> staticRoutes = netrisApiClient.listStaticRoutes(cmd);
+        return new ListNetrisStaticRoutesAnswer(cmd, staticRoutes);
+    }
+
+    private Answer executeRequest(AddOrUpdateNetrisStaticRouteCommand cmd) {
+        boolean result = netrisApiClient.addOrUpdateStaticRoute(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to add static route for VPC: %s, prefix: %s, nextHop: %s ", cmd.getName(), cmd.getPrefix(), cmd.getNextHop()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(DeleteNetrisStaticRouteCommand cmd) {
+        boolean result = netrisApiClient.deleteStaticRoute(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to add static route for VPC: %s, prefix: %s, nextHop: %s ", cmd.getName(), cmd.getPrefix(), cmd.getNextHop()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(ReleaseNatIpCommand cmd) {
+        boolean result = netrisApiClient.releaseNatIp(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to release NAT IP: %s", cmd.getNatIp()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd) {
+        boolean result = netrisApiClient.createOrUpdateLbRule(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to create Netris LB rule for %s: %s, " +
+                    "for private port: %s and public port: %s", getNetworkType(cmd.isVpc()), cmd.getName(), cmd.getPrivatePort(), cmd.getPublicPort()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private Answer executeRequest(DeleteNetrisLoadBalancerRuleCommand cmd) {
+        boolean result = netrisApiClient.deleteLbRule(cmd);
+        if (!result) {
+            return new NetrisAnswer(cmd, false, String.format("Failed to delete Netris LB rule for %s: %s", getNetworkType(cmd.isVpc()), cmd.getName()));
+        }
+        return new NetrisAnswer(cmd, true, "OK");
+    }
+
+    private String getNetworkType(Boolean isVpc) {
+        if (isVpc) {
+            return "VPC";
+        }
+        return "Network";
+    }
+
+    @Override
+    public boolean start() {
+        return true;
+    }
+
+    @Override
+    public boolean stop() {
+        return true;
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java
new file mode 100644
index 00000000000..164c56cd763
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/resource/NetrisResourceObjectUtils.java
@@ -0,0 +1,103 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.resource;
+
+import org.apache.cloudstack.agent.api.NetrisCommand;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.Objects;
+
+public class NetrisResourceObjectUtils {
+
+    public enum NetrisObjectType {
+        VPC, IPAM_ALLOCATION, IPAM_SUBNET, VNET, SNAT, STATICNAT, DNAT, STATICROUTE, ACL, LB
+    }
+
+    public static String retrieveNetrisResourceObjectName(NetrisCommand cmd, NetrisObjectType netrisObjectType, String... suffixes) {
+        long zoneId = cmd.getZoneId();
+        Long accountId = cmd.getAccountId();
+        Long domainId = cmd.getDomainId();
+        Long objectId = cmd.getId();
+        String objectName = cmd.getName();
+        boolean isVpc = cmd.isVpc();
+        boolean isZoneLevel = accountId == null && domainId == null;
+
+        StringBuilder stringBuilder = new StringBuilder();
+        if (isZoneLevel) {
+            stringBuilder.append(String.format("Z%s", zoneId));
+        } else {
+            stringBuilder.append(String.format("D%s-A%s-Z%s", domainId, accountId, zoneId));
+        }
+        String prefix = isVpc ? "-V" : "-N";
+        switch (netrisObjectType) {
+            case VPC:
+                if (ArrayUtils.isEmpty(suffixes)) {
+                    stringBuilder.append(String.format("%s%s-%s", prefix, objectId, objectName));
+                } else {
+                    stringBuilder.append(String.format("%s%s", prefix, suffixes[0]));
+                    suffixes = new String[0];
+                }
+                break;
+            case IPAM_ALLOCATION:
+                if (!isZoneLevel) {
+                    stringBuilder.append(String.format("%s%s", prefix, objectId));
+                }
+                break;
+            case IPAM_SUBNET:
+                if (!isZoneLevel) {
+                    if (Objects.nonNull(objectId) && Objects.nonNull(objectName) && !isVpc) {
+                        stringBuilder.append(String.format("-N%s", objectId));
+                    } else {
+                        stringBuilder.append(String.format("-V%s-%s", suffixes[0], suffixes[1]));
+                        return stringBuilder.toString();
+                    }
+                }
+                break;
+            case SNAT:
+                stringBuilder.append(String.format("%s%s-%s", prefix, suffixes[0], "SNAT"));
+                suffixes = new String[0];
+                break;
+            case STATICNAT:
+                stringBuilder.append(String.format("%s%s-VM%s-%s", prefix, suffixes[0], suffixes[1], "STATICNAT"));
+                suffixes = new String[0];
+                break;
+            case DNAT:
+                stringBuilder.append(String.format("%s%s-%s", prefix, suffixes[0], "DNAT"));
+                suffixes = ArrayUtils.subarray(suffixes, 1, suffixes.length);
+                break;
+            case STATICROUTE:
+                stringBuilder.append(String.format("%s%s-%s%s", prefix, suffixes[0], "ROUTE", suffixes[1]));
+                suffixes = new String[0];
+                break;
+            case VNET:
+            case ACL:
+               break;
+            case LB:
+                stringBuilder.append(String.format("%s%s", prefix, objectId));
+                break;
+            default:
+                stringBuilder.append(String.format("-%s", objectName));
+                break;
+        }
+        if (ArrayUtils.isNotEmpty(suffixes)) {
+            for (String suffix : suffixes) {
+                stringBuilder.append(String.format("-%s", suffix));
+            }
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java
new file mode 100644
index 00000000000..4261c6dc961
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClient.java
@@ -0,0 +1,100 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.service;
+
+import com.cloud.network.vpc.StaticRoute;
+import io.netris.ApiException;
+import io.netris.model.GetSiteBody;
+import io.netris.model.VPCListing;
+import io.netris.model.response.TenantResponse;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisACLCommand;
+import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.ListNetrisStaticRoutesCommand;
+import org.apache.cloudstack.agent.api.ReleaseNatIpCommand;
+import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVpcCommand;
+
+import java.util.List;
+
+public interface NetrisApiClient {
+    boolean isSessionAlive();
+    List<GetSiteBody> listSites();
+    List<VPCListing> listVPCs();
+    List<TenantResponse> listTenants() throws ApiException;
+
+    /**
+     * Create a VPC on CloudStack creates the following Netris resources:
+     * - Create a Netris VPC with the VPC name
+     * - Create an IPAM Allocation for the created Netris VPC using the Prefix = VPC CIDR
+     */
+    boolean createVpc(CreateNetrisVpcCommand cmd);
+
+    boolean updateVpc(UpdateNetrisVpcCommand cmd);
+
+    /**
+     * Delete a VPC on CloudStack removes the following Netris resources:
+     * - Delete the IPAM Allocation for the VPC using the Prefix = VPC CIDR
+     * - Delete a Netris VPC with the VPC name
+     */
+    boolean deleteVpc(DeleteNetrisVpcCommand cmd);
+
+    /**
+     * Creation of a VPC network tier creates the following Netris resources:
+     * - Creates a Netris IPAM Subnet for the specified network tier's CIDR
+     * - Creates a Netris vNet
+     */
+    boolean createVnet(CreateNetrisVnetCommand cmd);
+
+    boolean updateVnet(UpdateNetrisVnetCommand cmd);
+
+    /**
+     * Deletion of a VPC network tier deletes the following Netris resources:
+     * - Deletes the Netris IPAM Subnet for the specified network tier's CIDR
+     * - Deletes the Netris vNet
+     */
+    boolean deleteVnet(DeleteNetrisVnetCommand cmd);
+
+    /**
+     * Check and create zone level Netris Public range in the following manner:
+     * - Check the IPAM allocation for the zone super CIDR. In case it doesn't exist, create it
+     * - Check the IPAM subnet for NAT purpose for the range start-end. In case it doesn't exist, create it
+     */
+    boolean setupZoneLevelPublicRange(SetupNetrisPublicRangeCommand cmd);
+    boolean createOrUpdateSNATRule(CreateOrUpdateNetrisNatCommand cmd);
+    boolean createOrUpdateDNATRule(CreateOrUpdateNetrisNatCommand cmd);
+    boolean createStaticNatRule(CreateOrUpdateNetrisNatCommand cmd);
+    boolean deleteNatRule(DeleteNetrisNatRuleCommand cmd);
+    boolean addOrUpdateAclRule(CreateOrUpdateNetrisACLCommand cmd, boolean forLb);
+    boolean deleteAclRule(DeleteNetrisACLCommand cmd, boolean forLb);
+    boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd);
+    boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand cmd);
+    List<StaticRoute> listStaticRoutes(ListNetrisStaticRoutesCommand cmd);
+    boolean releaseNatIp(ReleaseNatIpCommand cmd);
+    boolean createOrUpdateLbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd);
+    boolean deleteLbRule(DeleteNetrisLoadBalancerRuleCommand cmd);
+}
diff --git a/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java
new file mode 100644
index 00000000000..d9f7678d964
--- /dev/null
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisApiClientImpl.java
@@ -0,0 +1,2004 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.service;
+
+import com.cloud.domain.Domain;
+import com.cloud.network.netris.NetrisLbBackend;
+import com.cloud.network.netris.NetrisNetworkRule;
+import com.cloud.network.vpc.StaticRoute;
+import com.cloud.network.vpc.StaticRouteVO;
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.NetUtils;
+import inet.ipaddr.IPAddress;
+import inet.ipaddr.IPAddressString;
+import io.netris.ApiClient;
+import io.netris.ApiException;
+import io.netris.ApiResponse;
+import io.netris.api.v1.AclApi;
+import io.netris.api.v1.AuthenticationApi;
+import io.netris.api.v1.RoutesApi;
+import io.netris.api.v1.SitesApi;
+import io.netris.api.v1.TenantsApi;
+import io.netris.api.v2.IpamApi;
+import io.netris.api.v2.L4LoadBalancerApi;
+import io.netris.api.v2.NatApi;
+import io.netris.api.v2.VNetApi;
+import io.netris.api.v2.VpcApi;
+import io.netris.model.AclAddItem;
+import io.netris.model.AclBodyVpc;
+import io.netris.model.AclDeleteItem;
+import io.netris.model.AclEditItem;
+import io.netris.model.AclGetBody;
+import io.netris.model.AclResponseGetOk;
+import io.netris.model.AllocationBody;
+import io.netris.model.AllocationBodyVpc;
+import io.netris.model.FilterBySites;
+import io.netris.model.FilterByVpc;
+import io.netris.model.GetSiteBody;
+
+import io.netris.model.InlineResponse20015;
+import io.netris.model.InlineResponse20016;
+import io.netris.model.InlineResponse2003;
+import io.netris.model.InlineResponse2004;
+import io.netris.model.InlineResponse2004Data;
+import io.netris.model.IpTree;
+import io.netris.model.IpTreeAllocation;
+import io.netris.model.IpTreeAllocationTenant;
+import io.netris.model.IpTreeSubnet;
+import io.netris.model.IpTreeSubnetSites;
+import io.netris.model.L4LBSite;
+import io.netris.model.L4LbAddItem;
+import io.netris.model.L4LbEditItem;
+import io.netris.model.L4LbTenant;
+import io.netris.model.L4LbVpc;
+import io.netris.model.L4LoadBalancerBackendItem;
+import io.netris.model.L4LoadBalancerItem;
+import io.netris.model.L4lbAddOrUpdateItem;
+import io.netris.model.L4lbresBody;
+import io.netris.model.NatBodySiteSite;
+import io.netris.model.NatBodyVpcVpc;
+import io.netris.model.NatGetBody;
+import io.netris.model.NatPostBody;
+import io.netris.model.NatPutBody;
+import io.netris.model.NatResponseGetOk;
+import io.netris.model.ResAddEditBody;
+import io.netris.model.RoutesBody;
+import io.netris.model.RoutesBodyId;
+import io.netris.model.RoutesBodyVpcVpc;
+import io.netris.model.RoutesGetBody;
+import io.netris.model.RoutesPostBody;
+import io.netris.model.RoutesPutBody;
+import io.netris.model.RoutesResponseGetOk;
+import io.netris.model.SitesResponseOK;
+import io.netris.model.SubnetBody;
+import io.netris.model.SubnetResBody;
+import io.netris.model.VPCAdminTenant;
+import io.netris.model.VPCCreate;
+import io.netris.model.VPCListing;
+import io.netris.model.VPCResource;
+import io.netris.model.VPCResourceIpam;
+import io.netris.model.VPCResponseOK;
+import io.netris.model.VPCResponseObjectOK;
+import io.netris.model.VPCResponseResourceOK;
+import io.netris.model.VnetAddBody;
+import io.netris.model.VnetAddBodyDhcp;
+import io.netris.model.VnetAddBodyDhcpOptionSet;
+import io.netris.model.VnetAddBodyGateways;
+import io.netris.model.VnetAddBodyVpc;
+import io.netris.model.VnetEditBody;
+import io.netris.model.VnetEditBodyDhcp;
+import io.netris.model.VnetEditBodyGateways;
+import io.netris.model.VnetResAddBody;
+import io.netris.model.VnetResDeleteBody;
+import io.netris.model.VnetResListBody;
+import io.netris.model.VnetsBody;
+import io.netris.model.VpcEditResponseOK;
+import io.netris.model.VpcVpcIdBody;
+import io.netris.model.response.AuthResponse;
+import io.netris.model.response.L4LbEditResponse;
+import io.netris.model.response.TenantResponse;
+import io.netris.model.response.TenantsResponse;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisACLCommand;
+import org.apache.cloudstack.agent.api.AddOrUpdateNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.CreateOrUpdateNetrisNatCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.CreateNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisACLCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisLoadBalancerRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisNatRuleCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisStaticRouteCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.DeleteNetrisVpcCommand;
+import org.apache.cloudstack.agent.api.ListNetrisStaticRoutesCommand;
+import org.apache.cloudstack.agent.api.NetrisCommand;
+import org.apache.cloudstack.agent.api.ReleaseNatIpCommand;
+import org.apache.cloudstack.agent.api.SetupNetrisPublicRangeCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVnetCommand;
+import org.apache.cloudstack.agent.api.UpdateNetrisVpcCommand;
+import org.apache.cloudstack.resource.NetrisResourceObjectUtils;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+public class NetrisApiClientImpl implements NetrisApiClient {
+
+    private final Logger logger = LogManager.getLogger(getClass());
+    private static final String ANY_IP = "0.0.0.0/0";
+    private static final String[] PROTOCOL_LIST = new String[]{"TCP", "UDP", "ICMP", "ALL"};
+
+    protected ApiClient apiClient;
+
+    protected final int siteId;
+    private final String siteName;
+    protected final int tenantId;
+    private final String tenantName;
+
+    public NetrisApiClientImpl(String endpointBaseUrl, String username, String password, String siteName, String adminTenantName) {
+        try {
+            apiClient = new ApiClient(endpointBaseUrl, username, password, 1L);
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error creating the Netris API Client for %s", endpointBaseUrl), e);
+        }
+        Pair<Integer, String> sitePair = getNetrisSitePair(siteName);
+        Pair<Integer, String> tenantPair = getNetrisTenantPair(adminTenantName);
+        this.siteId = sitePair.first();
+        this.siteName = sitePair.second();
+        this.tenantId = tenantPair.first();
+        this.tenantName = tenantPair.second();
+    }
+
+    private Pair<Integer, String> getNetrisSitePair(String siteName) {
+        List<GetSiteBody> sites = listSites();
+        if (CollectionUtils.isEmpty(sites)) {
+            throw new CloudRuntimeException("There are no Netris sites, please check the Netris endpoint");
+        }
+        List<GetSiteBody> filteredSites = sites.stream().filter(x -> x.getName().equals(siteName)).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(filteredSites)) {
+            throw new CloudRuntimeException(String.format("Cannot find a site matching name %s on Netris, please check the Netris endpoint", siteName));
+        }
+        return new Pair<>(filteredSites.get(0).getId(), siteName);
+    }
+
+    private Pair<Integer, String> getNetrisTenantPair(String adminTenantName) {
+        List<TenantResponse> tenants = listTenants();
+        if (CollectionUtils.isEmpty(tenants)) {
+            throw new CloudRuntimeException("There are no Netris tenants, please check the Netris endpoint");
+        }
+        List<TenantResponse> filteredTenants = tenants.stream().filter(x -> x.getName().equals(adminTenantName)).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(filteredTenants)) {
+            throw new CloudRuntimeException(String.format("Cannot find a site matching name %s on Netris, please check the Netris endpoint", adminTenantName));
+        }
+        return new Pair<>(filteredTenants.get(0).getId().intValue(), adminTenantName);
+    }
+
+    protected void logAndThrowException(String prefix, ApiException e) throws CloudRuntimeException {
+        String msg = String.format("%s: (%s, %s, %s)", prefix, e.getCode(), e.getMessage(), e.getResponseBody());
+        logger.error(msg, e);
+        throw new CloudRuntimeException(msg);
+    }
+
+    @Override
+    public boolean isSessionAlive() {
+        ApiResponse<AuthResponse> response = null;
+        try {
+            AuthenticationApi api = apiClient.getApiStubForMethod(AuthenticationApi.class);
+            response = api.apiAuthGet();
+        } catch (ApiException e) {
+            logAndThrowException("Error checking the Netris API session is alive", e);
+        }
+        return response != null && response.getStatusCode() == 200;
+    }
+
+    @Override
+    public List<GetSiteBody> listSites() {
+        SitesResponseOK response = null;
+        try {
+            SitesApi api = apiClient.getApiStubForMethod(SitesApi.class);
+            response = api.apiSitesGet();
+        } catch (ApiException e) {
+            logAndThrowException("Error listing Netris Sites", e);
+        }
+        return response != null ? response.getData() : null;
+    }
+
+    @Override
+    public List<VPCListing> listVPCs() {
+        VPCResponseOK response = null;
+        try {
+            VpcApi api = apiClient.getApiStubForMethod(VpcApi.class);
+            response = api.apiV2VpcGet();
+        } catch (ApiException e) {
+            logAndThrowException("Error listing Netris VPCs", e);
+        }
+        return response != null ? response.getData() : null;
+    }
+
+    @Override
+    public List<TenantResponse> listTenants() {
+        ApiResponse<TenantsResponse> response = null;
+        try {
+            TenantsApi api = apiClient.getApiStubForMethod(TenantsApi.class);
+            response = api.apiTenantsGet();
+        } catch (ApiException e) {
+            logAndThrowException("Error listing Netris Tenants", e);
+        }
+        return (response != null && response.getData() != null) ? response.getData().getData() : null;
+    }
+
+    private VPCResponseObjectOK createVpcInternal(String vpcName, int adminTenantId, String adminTenantName) {
+        VPCResponseObjectOK response;
+        logger.debug(String.format("Creating Netris VPC %s", vpcName));
+        try {
+            VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class);
+            VPCCreate body = new VPCCreate();
+            body.setName(vpcName);
+            VPCAdminTenant vpcAdminTenant = new VPCAdminTenant();
+            vpcAdminTenant.setId(adminTenantId);
+            vpcAdminTenant.name(adminTenantName);
+            body.setAdminTenant(vpcAdminTenant);
+            response = vpcApi.apiV2VpcPost(body);
+        } catch (ApiException e) {
+            logAndThrowException("Error creating Netris VPC", e);
+            return null;
+        }
+        return response;
+    }
+
+    private VpcEditResponseOK updateVpcInternal(String vpcName, String prevVpcName, int adminTenantId, String adminTenantName) {
+        VpcEditResponseOK response;
+        logger.debug(String.format("Updating Netris VPC name from %s to %s", prevVpcName, vpcName));
+        try {
+            VPCListing vpcResource = getVpcByNameAndTenant(prevVpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", prevVpcName, tenantId);
+                return null;
+            }
+            VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class);
+            VpcVpcIdBody body = new VpcVpcIdBody();
+            body.setName(vpcName);
+            VPCAdminTenant vpcAdminTenant = new VPCAdminTenant();
+            vpcAdminTenant.setId(adminTenantId);
+            vpcAdminTenant.name(adminTenantName);
+            body.setAdminTenant(vpcAdminTenant);
+            response = vpcApi.apiV2VpcVpcIdPut(body, vpcResource.getId());
+        } catch (ApiException e) {
+            logAndThrowException("Error updating Netris VPC", e);
+            return null;
+        }
+        return response;
+    }
+
+    private InlineResponse2004Data createIpamAllocationInternal(String ipamName, String ipamPrefix, VPCListing vpc) {
+        logger.debug(String.format("Creating Netris IPAM Allocation %s for VPC %s", ipamPrefix, vpc.getName()));
+        try {
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            AllocationBody body = new AllocationBody();
+            AllocationBodyVpc allocationBodyVpc = new AllocationBodyVpc();
+            allocationBodyVpc.setId(vpc.getId());
+            allocationBodyVpc.setName(vpc.getName());
+            body.setVpc(allocationBodyVpc);
+            body.setName(ipamName);
+            body.setPrefix(ipamPrefix);
+            IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant();
+            allocationTenant.setId(new BigDecimal(tenantId));
+            allocationTenant.setName(tenantName);
+            body.setTenant(allocationTenant);
+            InlineResponse2004 ipamResponse = ipamApi.apiV2IpamAllocationPost(body);
+            if (ipamResponse == null || !ipamResponse.isIsSuccess()) {
+                String reason = ipamResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris Allocation {} for VPC {} creation failed: {}", ipamPrefix, vpc.getName(), reason);
+                return null;
+            }
+            logger.debug(String.format("Successfully created VPC %s and its IPAM Allocation %s on Netris", vpc.getName(), ipamPrefix));
+            return ipamResponse.getData();
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error creating Netris IPAM Allocation %s for VPC %s", ipamPrefix, vpc.getName()), e);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean createVpc(CreateNetrisVpcCommand cmd) {
+        String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC);
+        VPCResponseObjectOK createdVpc = createVpcInternal(netrisVpcName, tenantId, tenantName);
+        if (createdVpc == null || !createdVpc.isIsSuccess()) {
+            String reason = createdVpc == null ? "Empty response" : "Operation failed on Netris";
+            logger.debug("The Netris VPC {} creation failed: {}", cmd.getName(), reason);
+            return false;
+        }
+
+        String netrisIpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, cmd.getCidr());
+        String vpcCidr = cmd.getCidr();
+        InlineResponse2004Data createdIpamAllocation = createIpamAllocationInternal(netrisIpamAllocationName, vpcCidr, createdVpc.getData());
+        return createdIpamAllocation != null;
+    }
+
+    @Override
+    public boolean updateVpc(UpdateNetrisVpcCommand cmd) {
+        Long domainId = cmd.getDomainId();
+        Long zoneId = cmd.getZoneId();
+        Long accountId = cmd.getAccountId();
+        Long vpcId = cmd.getId();
+        String prevVpcName = cmd.getPreviousVpcName();
+        String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC);
+        String netrisPrevVpcName = String.format("D%s-A%s-Z%s-V%s-%s", domainId, accountId, zoneId, vpcId, prevVpcName);
+        VpcEditResponseOK updatedVpc = updateVpcInternal(netrisVpcName, netrisPrevVpcName, tenantId, tenantName);
+        if (updatedVpc == null || !updatedVpc.isIsSuccess()) {
+            String reason = updatedVpc == null ? "Empty response" : "Operation failed on Netris";
+            logger.debug("The update of Netris VPC {} failed: {}", cmd.getPreviousVpcName(), reason);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean deleteNatRule(DeleteNetrisNatRuleCommand cmd) {
+        try {
+            String suffix = getNetrisVpcNameSuffix(cmd.getVpcId(), cmd.getVpcName(), cmd.getId(), cmd.getName(), cmd.isVpc());
+            String vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix);
+            VPCListing vpcResource = getVpcByNameAndTenant(vpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId);
+                return false;
+            }
+            String natRuleName = cmd.getNatRuleName();
+            NatGetBody existingNatRule = netrisNatRuleExists(natRuleName);
+            boolean ruleExists = Objects.nonNull(existingNatRule);
+            if (ruleExists) {
+                deleteNatRule(natRuleName, existingNatRule.getId(), vpcResource.getName());
+                if (cmd.getNatRuleType().equals("STATICNAT")) {
+                    String natIp = cmd.getNatIp();
+                    String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), natIp);
+                    deleteNatSubnet(netrisSubnetName, vpcResource.getId(), natIp);
+                }
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Error deleting Netris NAT Rule", e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean addOrUpdateAclRule(CreateOrUpdateNetrisACLCommand cmd, boolean forLb) {
+        String aclName = cmd.getNetrisAclName();
+        try {
+            AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class);
+            VPCListing vpcResource;
+            String netrisVpcName;
+            if (forLb) {
+                vpcResource = getSystemVpc();
+                netrisVpcName = vpcResource.getName();
+
+            } else {
+                netrisVpcName = getNetrisVpcName(cmd, cmd.getVpcId(), cmd.getVpcName());
+                vpcResource = getNetrisVpcResource(netrisVpcName);
+                if (Objects.isNull(vpcResource)) {
+                    return false;
+                }
+            }
+            AclBodyVpc vpc = new AclBodyVpc().id(vpcResource.getId());
+            List<String> aclNames = List.of(aclName);
+            Pair<Boolean, List<BigDecimal>> resultAndMatchingAclIds = getMatchingAclIds(aclNames, netrisVpcName);
+            List<BigDecimal> aclIdList = resultAndMatchingAclIds.second();
+            if (!aclIdList.isEmpty()) {
+                logger.debug("Netris ACL rule: {} already exists, updating it...", aclName);
+                AclEditItem aclEditItem = getAclEditItem(cmd, aclName, aclIdList.get(0));
+                aclEditItem.setVpc(vpc);
+                try {
+                    aclApi.apiAclPut(aclEditItem);
+                } catch (ApiException e) {
+                    if (e.getResponseBody().contains("This kind of acl already exists")) {
+                        logger.info("Netris ACL rule: {} already exists and doesn't need to be updated", aclName);
+                        return true;
+                    }
+                    throw new CloudRuntimeException("Error updating Netris ACL rule", e);
+                }
+                return true;
+            }
+            AclAddItem aclAddItem = getAclAddItem(cmd, aclName);
+            aclAddItem.setVpc(vpc);
+            aclApi.apiAclPost(aclAddItem);
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to create Netris ACL: %s", cmd.getNetrisAclName()), e);
+        }
+        return true;
+    }
+
+    AclAddItem getAclAddItem(CreateOrUpdateNetrisACLCommand cmd, String aclName) throws ApiException {
+        AclAddItem aclAddItem = new AclAddItem();
+        aclAddItem.setAction(cmd.getAction());
+        aclAddItem.setComment(String.format("ACL rule: %s. %s", cmd.getNetrisAclName(), cmd.getReason()));
+        aclAddItem.setName(aclName);
+        String protocol = cmd.getProtocol();
+        if ("TCP".equals(protocol)) {
+            aclAddItem.setEstablished(new BigDecimal(1));
+        } else {
+            aclAddItem.setReverse("yes");
+        }
+        if (!Arrays.asList(PROTOCOL_LIST).contains(protocol)) {
+            aclAddItem.setProto("ip");
+            aclAddItem.setSrcPortTo(cmd.getIcmpType());
+            // TODO: set proto number: where should the protocol number be set - API sets the protocol number to Src-from & to and Dest-from & to fields
+        } else if ("ICMP".equals(protocol)) {
+            aclAddItem.setProto("icmp");
+            if (cmd.getIcmpType() != -1) {
+                aclAddItem.setIcmpType(cmd.getIcmpType());
+            }
+        } else {
+            aclAddItem.setProto(protocol.toLowerCase(Locale.ROOT));
+        }
+
+        aclAddItem.setDstPortFrom(cmd.getDestPortStart());
+        aclAddItem.setDstPortTo(cmd.getDestPortEnd());
+        aclAddItem.setDstPrefix(cmd.getDestPrefix());
+        aclAddItem.setSrcPrefix(cmd.getSourcePrefix());
+        aclAddItem.setSrcPortFrom(1);
+        aclAddItem.setSrcPortTo(65535);
+        if (NatPutBody.ProtocolEnum.ICMP.name().equalsIgnoreCase(protocol)) {
+            aclAddItem.setIcmpType(cmd.getIcmpType());
+        }
+
+        return aclAddItem;
+    }
+
+    AclEditItem getAclEditItem(CreateOrUpdateNetrisACLCommand cmd, String aclName, BigDecimal aclId) throws ApiException {
+        AclEditItem aclEditItem = new AclEditItem();
+        aclEditItem.setId(aclId);
+        aclEditItem.setAction(cmd.getAction());
+        aclEditItem.setComment(String.format("ACL rule: %s. %s", cmd.getNetrisAclName(), cmd.getReason()));
+        aclEditItem.setName(aclName);
+        String protocol = cmd.getProtocol();
+        if ("TCP".equals(protocol)) {
+            aclEditItem.setEstablished(new BigDecimal(1));
+        } else {
+            aclEditItem.setReverse("yes");
+        }
+        if (!Arrays.asList(PROTOCOL_LIST).contains(protocol)) {
+            aclEditItem.setProto("ip");
+            aclEditItem.setSrcPortTo(cmd.getIcmpType());
+            // TODO: set proto number: where should the protocol number be set - API sets the protocol number to Src-from & to and Dest-from & to fields
+        } else if ("ICMP".equals(protocol)) {
+            aclEditItem.setProto("icmp");
+            if (cmd.getIcmpType() != -1) {
+                aclEditItem.setIcmpType(cmd.getIcmpType());
+            }
+        } else {
+            aclEditItem.setProto(protocol.toLowerCase(Locale.ROOT));
+        }
+
+        aclEditItem.setDstPortFrom(cmd.getDestPortStart());
+        aclEditItem.setDstPortTo(cmd.getDestPortEnd());
+        aclEditItem.setDstPrefix(cmd.getDestPrefix());
+        aclEditItem.setSrcPrefix(cmd.getSourcePrefix());
+        aclEditItem.setSrcPortFrom(1);
+        aclEditItem.setSrcPortTo(65535);
+        if (NatPutBody.ProtocolEnum.ICMP.name().equalsIgnoreCase(protocol)) {
+            aclEditItem.setIcmpType(cmd.getIcmpType());
+        }
+
+        return aclEditItem;
+    }
+
+    @Override
+    public boolean deleteAclRule(DeleteNetrisACLCommand cmd, boolean forLb) {
+        List<String> aclNames = cmd.getAclRuleNames();
+        try {
+            AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class);
+
+            String vpcName;
+            if (!forLb) {
+                String suffix = getNetrisVpcNameSuffix(cmd.getVpcId(), cmd.getVpcName(), cmd.getId(), cmd.getName(), cmd.isVpc());
+                vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix);
+            } else {
+                VPCListing vpcResource = getSystemVpc();
+                vpcName = vpcResource.getName();
+            }
+            Pair<Boolean, List<BigDecimal>> resultAndMatchingAclIds = getMatchingAclIds(aclNames, vpcName);
+            Boolean result = resultAndMatchingAclIds.first();
+            List<BigDecimal> matchingAclIds = resultAndMatchingAclIds.second();
+            if (!result) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId);
+                return false;
+            }
+            if (matchingAclIds.isEmpty()) {
+                logger.warn("There doesn't seem to be any ACLs on Netris matching {}", aclNames.size() > 1 ? String.join(",", aclNames) : aclNames);
+                return true;
+            }
+            AclDeleteItem aclDeleteItem = new AclDeleteItem();
+            aclDeleteItem.setId(matchingAclIds);
+            aclDeleteItem.setTenantsID(String.valueOf(tenantId));
+            aclApi.apiAclDelete(aclDeleteItem);
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to delete Netris ACLs: %s", String.join(",", cmd.getAclRuleNames())), e);
+        }
+        return true;
+    }
+
+    Pair<Boolean, List<BigDecimal>> getMatchingAclIds(List<String> aclNames, String vpcName) {
+        try {
+            AclApi aclApi = apiClient.getApiStubForMethod(AclApi.class);
+            VPCListing vpcResource = getVpcByNameAndTenant(vpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId);
+                return new Pair<>(false, Collections.emptyList());
+            }
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(vpcResource.getId());
+            FilterBySites siteFilter = new FilterBySites();
+            siteFilter.add(siteId);
+            AclResponseGetOk aclGetResponse = aclApi.apiAclGet(siteFilter, vpcFilter);
+            if (aclGetResponse == null || !aclGetResponse.isIsSuccess()) {
+                logger.warn("No ACLs were found to be present for the specific Netris VPC resource {}." +
+                        " Netris ACLs may have been deleted out of band.", vpcName);
+                return new Pair<>(true, Collections.emptyList());
+            }
+            List<AclGetBody> aclList = aclGetResponse.getData();
+            return new Pair<>(true, aclList.stream()
+                    .filter(acl -> aclNames.contains(acl.getName()))
+                    .map(acl -> BigDecimal.valueOf(acl.getId()))
+                    .collect(Collectors.toList()));
+        } catch (ApiException e) {
+            logAndThrowException("Failed to retrieve Netris ACLs", e);
+        }
+        return new Pair<>(true, Collections.emptyList());
+    }
+
+    public boolean addOrUpdateStaticRoute(AddOrUpdateNetrisStaticRouteCommand cmd) {
+        String prefix = cmd.getPrefix();
+        String nextHop = cmd.getNextHop();
+        Long vpcId = cmd.getId();
+        String vpcName = cmd.getName();
+        boolean updateRoute = cmd.isUpdateRoute();
+        try {
+            String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true);
+            String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix);
+            VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+                return false;
+            }
+
+            String[] suffixes = new String[2];
+            suffixes[0] = vpcId.toString();
+            suffixes[1] = cmd.getRouteId().toString();
+            String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes);
+
+            Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, null, staticRouteId);
+            if (updateRoute) {
+                if (!existingStaticRoute.first()) {
+                    logger.error("The Netris static route {} does not exist for VPC {}, adding it", prefix, netrisVpcName);
+                    return addStaticRouteInternal(vpcResource, netrisVpcName, prefix, nextHop, staticRouteId);
+                }
+                return updateStaticRouteInternal(existingStaticRoute.second().getId(), netrisVpcName, prefix, nextHop, staticRouteId);
+            } else {
+                if (existingStaticRoute.first()) {
+                    String existingNextHop = existingStaticRoute.second().getNextHop();
+                    if (existingNextHop != null && existingNextHop.equals(nextHop)) {
+                        logger.debug("The Netris static route {} already exists for VPC {}", prefix, netrisVpcName);
+                        return true;
+                    } else {
+                        logger.debug("The Netris static route {} already exists but has different next hop {} for VPC {}", prefix, nextHop, netrisVpcName);
+                        return false;
+                    }
+                }
+                return addStaticRouteInternal(vpcResource, netrisVpcName, prefix, nextHop, staticRouteId);
+            }
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Error adding Netris static route", e);
+        }
+    }
+
+    private boolean addStaticRouteInternal(VPCListing vpcResource, String netrisVpcName, String prefix, String nextHop, String staticRouteId) {
+        try {
+            RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class);
+            RoutesPostBody routesPostBody = new RoutesPostBody();
+            routesPostBody.setPrefix(prefix);
+            routesPostBody.setNextHop(nextHop);
+            routesPostBody.setSiteId(new BigDecimal(siteId));
+            routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE);
+
+            RoutesBodyVpcVpc vpcBody = new RoutesBodyVpcVpc();
+            vpcBody.setId(vpcResource.getId());
+            vpcBody.setName(vpcResource.getName());
+            vpcBody.setIsDefault(vpcResource.isIsDefault());
+            vpcBody.setIsSystem(vpcResource.isIsSystem());
+            routesPostBody.setVpc(vpcBody);
+
+            routesPostBody.setDescription(staticRouteId);
+            routesPostBody.setStateStatus(RoutesBody.StateStatusEnum.ACTIVE);
+            routesPostBody.setSwitches(Collections.emptyList());
+
+            InlineResponse2004 routeResponse = routesApi.apiRoutesPost(routesPostBody);
+            if (routeResponse == null || !routeResponse.isIsSuccess()) {
+                String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris static route creation failed for netris VPC - {}: {}", netrisVpcName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+        } catch (ApiException e) {
+            logAndThrowException("Error adding Netris static route", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean updateStaticRouteInternal(Integer id, String netrisVpcName, String prefix, String nextHop, String staticRouteId) {
+        try {
+            RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class);
+            RoutesPutBody routesPutBody = new RoutesPutBody();
+            routesPutBody.setId(id);
+            routesPutBody.setPrefix(prefix);
+            routesPutBody.setNextHop(nextHop);
+            routesPutBody.setSiteId(new BigDecimal(siteId));
+            routesPutBody.setStateStatus(RoutesPutBody.StateStatusEnum.ACTIVE);
+
+            routesPutBody.setDescription(staticRouteId);
+            routesPutBody.setSwitches(Collections.emptyList());
+
+            InlineResponse2003 routeResponse = routesApi.apiRoutesPut(routesPutBody);
+            if (routeResponse == null || !routeResponse.isIsSuccess()) {
+                String reason = routeResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("Failed to update Netris static route for netris VPC - {}: {}", netrisVpcName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+        } catch (ApiException e) {
+            logAndThrowException("Error updating Netris static route", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean deleteStaticRoute(DeleteNetrisStaticRouteCommand cmd) {
+        Long vpcId = cmd.getId();
+        String vpcName = cmd.getName();
+        String prefix = cmd.getPrefix();
+        String nextHop = cmd.getNextHop();
+        try {
+            String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true);
+            String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix);
+            VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+                return false;
+            }
+
+            String[] suffixes = new String[2];
+            suffixes[0] = vpcId.toString();
+            suffixes[1] = cmd.getRouteId().toString();
+            String staticRouteId = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffixes);
+            Pair<Boolean, RoutesGetBody> existingStaticRoute = staticRouteExists(vpcResource.getId(), prefix, nextHop, staticRouteId);
+
+            if (Boolean.FALSE.equals(existingStaticRoute.first())) {
+                logger.debug("The Netris static route {} does not exist for VPC {}", prefix, netrisVpcName);
+                return true;
+            }
+            RoutesGetBody existingRoute = existingStaticRoute.second();
+            RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class);
+            RoutesBodyId id = new RoutesBodyId();
+            id.setId(existingRoute.getId());
+            InlineResponse2003 routeDeleteResponse = routesApi.apiRoutesDelete(id);
+            if (routeDeleteResponse == null || !routeDeleteResponse.isIsSuccess()) {
+                String reason = routeDeleteResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris static route deletion failed for netris VPC - {}: {}", netrisVpcName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+            return true;
+        } catch (ApiException e) {
+            logAndThrowException("Error deleting Netris static route", e);
+        }
+        return false;
+    }
+
+    @Override
+    public List<StaticRoute> listStaticRoutes(ListNetrisStaticRoutesCommand cmd) {
+        Long vpcId = cmd.getId();
+        String vpcName = cmd.getName();
+        String prefix = cmd.getPrefix();
+        String nextHop = cmd.getNextHop();
+        String vpcSuffix = getNetrisVpcNameSuffix(vpcId, vpcName, null, null, true);
+        String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, vpcSuffix);
+        VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+        if (vpcResource == null) {
+            logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+            return new ArrayList<>();
+        }
+
+        List<RoutesGetBody> staticRoutes = listStaticRoutes(vpcResource.getId(), prefix, nextHop);
+        if (CollectionUtils.isEmpty(staticRoutes)) {
+            return new ArrayList<>();
+        }
+        List<StaticRoute> result = new ArrayList<>();
+        for (RoutesGetBody staticRoute : staticRoutes) {
+            // All static routes belong the SYSTEM account, which does not matter
+            result.add(new StaticRouteVO(null, staticRoute.getName(), vpcId, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, staticRoute.getNextHop()));
+        }
+        return result;
+    }
+
+    @Override
+    public boolean releaseNatIp(ReleaseNatIpCommand cmd) {
+        String natIp = cmd.getNatIp() + "/32";
+        try {
+            VPCListing systemVpc = getSystemVpc();
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(systemVpc.getId());
+            SubnetResBody subnetResponse = ipamApi.apiV2IpamSubnetsGet(vpcFilter);
+            if (subnetResponse == null || !subnetResponse.isIsSuccess()) {
+                String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("Failed to retrieve Netris Public NAT IPs due to {}", reason);
+                throw new CloudRuntimeException(reason);
+            }
+            List<IpTreeSubnet> natIps = subnetResponse.getData().stream().filter(ip -> ip.getPrefix().equals(natIp)).collect(Collectors.toList());
+            if (!natIps.isEmpty()) {
+                ipamApi.apiV2IpamTypeIdDelete("subnet", natIps.get(0).getId().intValue());
+            }
+
+        } catch (ApiException e) {
+            logAndThrowException("Failed to release Netris IP", e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean createOrUpdateLbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd) {
+        boolean isVpc = cmd.isVpc();
+        Long networkResourceId = cmd.getId();
+        String networkResourceName = cmd.getName();
+        Long domainId = cmd.getDomainId();
+        Long accountId = cmd.getAccountId();
+        Long zoneId = cmd.getZoneId();
+        Long lbId = cmd.getLbId();
+        String publicIp = cmd.getPublicIp();
+        List<NetrisLbBackend> lbBackends = cmd.getLbBackends();
+
+        try {
+            String resourcePrefix = isVpc ? "V" : "N";
+            String netrisResourceName = String.format("D%s-A%s-Z%s-%s%s-%s", domainId, accountId, zoneId, resourcePrefix, networkResourceId, networkResourceName);
+            VPCListing vpcResource = getNetrisVpcResource(netrisResourceName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisResourceName, tenantId);
+                return false;
+            }
+            createLBSubnet(cmd, publicIp + "/32", vpcResource.getId());
+
+            String suffix = String.format("LB%s", lbId);
+            String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix);
+            Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, vpcResource.getName());
+            Boolean result = resultAndMatchingLbId.first();
+            List<BigDecimal> matchingLbId = resultAndMatchingLbId.second();
+            if (Boolean.FALSE.equals(result)) {
+                logger.warn("Could not find the Netris LB rule with name {}", lbName);
+            }
+            boolean updateRule = !matchingLbId.isEmpty();
+            L4lbAddOrUpdateItem l4lbAddItem = getL4LbRule(cmd, vpcResource, lbName, publicIp, lbBackends, updateRule);
+            L4LoadBalancerApi loadBalancerApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class);
+            boolean success;
+            L4LbEditResponse editResponse = null;
+            ResAddEditBody createResponse = null;
+            if (updateRule) {
+                editResponse = loadBalancerApi.apiV2L4lbIdPut((L4LbEditItem) l4lbAddItem, matchingLbId.get(0).intValue());
+                success = editResponse.isIsSuccess();
+            } else {
+                createResponse = loadBalancerApi.apiV2L4lbPost((L4LbAddItem) l4lbAddItem);
+                success = createResponse.isIsSuccess();
+            }
+            if (ObjectUtils.allNull(editResponse, createResponse) || Boolean.FALSE.equals(success)) {
+                throw new CloudRuntimeException(String.format("Failed to %s Netris LB rule", updateRule ? "update" : "create"));
+            }
+            if (Objects.nonNull(cmd.getCidrList()) && !cmd.getCidrList().isEmpty()) {
+                applyAclRulesForLb(cmd, lbName);
+            }
+        } catch (ApiException e) {
+            logAndThrowException("Failed to create Netris load balancer rule", e);
+        }
+        return true;
+    }
+
+    private void applyAclRulesForLb(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, String lbName) {
+        // Add deny all rule first
+        addOrUpdateAclRule(createNetrisACLRuleCommand(cmd, lbName, "ANY",
+                NetrisNetworkRule.NetrisRuleAction.DENY.name().toLowerCase(Locale.ROOT), 0), true);
+        AtomicInteger cidrIndex = new AtomicInteger(1);
+        for (String cidr : cmd.getCidrList().split(" ")) {
+            try {
+                addOrUpdateAclRule(createNetrisACLRuleCommand(cmd, lbName, cidr,
+                        NetrisNetworkRule.NetrisRuleAction.PERMIT.name().toLowerCase(Locale.ROOT),
+                        cidrIndex.getAndIncrement()), true);
+            } catch (Exception e) {
+                throw new CloudRuntimeException(String.format("Failed to add Netris ACL rule for LB CIDR %s", cidr), e);
+            }
+        }
+    }
+
+    private CreateOrUpdateNetrisACLCommand createNetrisACLRuleCommand(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, String netrisLbName, String cidr, String action, int index) {
+        Long zoneId = cmd.getZoneId();
+        Long accountId = cmd.getAccountId();
+        Long domainId = cmd.getDomainId();
+        String networkName = null;
+        Long networkId = null;
+        String vpcName = null;
+        Long vpcId = null;
+        boolean isVpc = cmd.isVpc();
+        if (isVpc) {
+            vpcId = cmd.getId();
+            vpcName = cmd.getName();
+        } else {
+            networkName = cmd.getName();
+            networkId = cmd.getId();
+        }
+        String destinationPrefix = cmd.getPublicIp() + "/32";
+        String srcPort = cmd.getPublicPort();
+        String dstPort = cmd.getPublicPort();
+        CreateOrUpdateNetrisACLCommand aclCommand = new CreateOrUpdateNetrisACLCommand(zoneId, accountId, domainId, networkName, networkId,
+                vpcName, vpcId, Objects.nonNull(vpcId), action, NetrisServiceImpl.getPrefix(cidr), NetrisServiceImpl.getPrefix(destinationPrefix),
+                Integer.parseInt(srcPort), Integer.parseInt(dstPort), cmd.getProtocol());
+        String aclName;
+        if (isVpc) {
+            aclName =  String.format("V%s-LBACL%s-%s", vpcId, index, cmd.getRuleName());
+        } else {
+            aclName = String.format("N%s-LBACL%s-%s", networkId, index, cmd.getRuleName());
+        }
+        String netrisAclName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.ACL, aclName);
+        aclCommand.setNetrisAclName(netrisAclName);
+        aclCommand.setReason(String.format("ACL Rule for CIDR %s of LB %s ", aclName, netrisLbName));
+        return aclCommand;
+    }
+
+    private L4lbAddOrUpdateItem getL4LbRule(CreateOrUpdateNetrisLoadBalancerRuleCommand cmd, VPCListing vpcResource, String lbName,
+                                            String publicIp, List<NetrisLbBackend> lbBackends, boolean updateRule) {
+        L4lbAddOrUpdateItem l4lbAddItem = updateRule ? new L4LbEditItem() : new L4LbAddItem();
+        try {
+            l4lbAddItem.setName(lbName);
+
+            String protocol = cmd.getProtocol().toUpperCase(Locale.ROOT);
+            if (!Arrays.asList("TCP", "UDP").contains(protocol)) {
+                throw new CloudRuntimeException("Invalid protocol " + protocol);
+            }
+            l4lbAddItem.setProtocol(cmd.getProtocol().toUpperCase(Locale.ROOT));
+            L4LBSite site = new L4LBSite();
+            site.setId(siteId);
+            site.setName(siteName);
+            l4lbAddItem.setSite(site);
+            l4lbAddItem.setSiteID(new BigDecimal(siteId));
+
+            L4LbTenant tenant = new L4LbTenant();
+            tenant.setId(tenantId);
+            tenant.setName(tenantName);
+            l4lbAddItem.setTenant(tenant);
+
+            L4LbVpc vpc = new L4LbVpc();
+            vpc.setId(vpcResource.getId());
+            l4lbAddItem.setVpc(vpc);
+
+            l4lbAddItem.setAutomatic(false);
+            l4lbAddItem.setIpFamily(NetUtils.isIpv4(publicIp) ? L4lbAddOrUpdateItem.IpFamilyEnum.IPv4 : L4lbAddOrUpdateItem.IpFamilyEnum.IPv6);
+            l4lbAddItem.setIp(publicIp);
+            l4lbAddItem.setStatus("enable");
+
+            List<L4LoadBalancerBackendItem> backends = new ArrayList<>();
+            for (NetrisLbBackend backend : lbBackends) {
+                L4LoadBalancerBackendItem backendItem = new L4LoadBalancerBackendItem();
+                backendItem.setIp(backend.getVmIp());
+                backendItem.setPort(backend.getPort());
+                backends.add(backendItem);
+            }
+            l4lbAddItem.setBackend(backends);
+            l4lbAddItem.setPort(Integer.valueOf(cmd.getPublicPort()));
+            l4lbAddItem.setHealthCheck(L4lbAddOrUpdateItem.HealthCheckEnum.NONE);
+        } catch (Exception e) {
+            throw new CloudRuntimeException("Failed to create Netris load balancer rule", e);
+        }
+        return  l4lbAddItem;
+    }
+
+    @Override
+    public boolean deleteLbRule(DeleteNetrisLoadBalancerRuleCommand cmd) {
+        boolean isVpc = cmd.isVpc();
+        String vpcName = null;
+        String networkName = null;
+        Long vpcId = null;
+        Long networkId = null;
+        if (isVpc) {
+            vpcName = cmd.getName();
+            vpcId = cmd.getId();
+        } else {
+            networkName = cmd.getName();
+            networkId = cmd.getId();
+        }
+        Long lbId = cmd.getLbId();
+        String cidrList = cmd.getCidrList();
+        try {
+            String suffix = getNetrisVpcNameSuffix(vpcId, vpcName, networkId, networkName, isVpc);
+            String netrisVpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix);
+            suffix = String.format("LB%s", lbId);
+            String lbName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.LB, suffix);
+            Pair<Boolean, List<BigDecimal>> resultAndMatchingLbId = getMatchingLbRule(lbName, netrisVpcName);
+            Boolean result = resultAndMatchingLbId.first();
+            List<BigDecimal> matchingLbId = resultAndMatchingLbId.second();
+            if (!result) {
+                logger.error("Could not find the Netris LB rule with name {}", lbName);
+                return false;
+            }
+            if (matchingLbId.isEmpty()) {
+                logger.warn("There doesn't seem to be any LB rule on Netris matching {}", lbName);
+                return true;
+            }
+
+            L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class);
+            lbApi.apiV2L4lbIdDelete(matchingLbId.get(0).intValue());
+            if (Objects.nonNull(cidrList)) {
+                deleteAclRulesForLb(cmd);
+            }
+        } catch (ApiException e) {
+            logAndThrowException("Failed to delete Netris load balancer rule", e);
+        }
+        return true;
+    }
+
+    private void deleteAclRulesForLb(DeleteNetrisLoadBalancerRuleCommand cmd) {
+        // delete the deny rule
+        deleteAclRule(deleteNetrisACLCommand(cmd, 0), true);
+        AtomicInteger cidrIndex = new AtomicInteger(1);
+        for (String cidr : cmd.getCidrList().split(" ")) {
+            try {
+                deleteAclRule(deleteNetrisACLCommand(cmd, cidrIndex.getAndIncrement()), true);
+            } catch (Exception e) {
+                throw new CloudRuntimeException(String.format("Failed to delete Netris ACL rule for LB CIDR %s", cidr), e);
+            }
+        }
+    }
+
+    private DeleteNetrisACLCommand deleteNetrisACLCommand(DeleteNetrisLoadBalancerRuleCommand cmd, int index) {
+        Long zoneId = cmd.getZoneId();
+        Long accountId = cmd.getAccountId();
+        Long domainId = cmd.getDomainId();
+        String networkName = null;
+        Long networkId = null;
+        String vpcName = null;
+        Long vpcId = null;
+        boolean isVpc = cmd.isVpc();
+        if (isVpc) {
+            vpcId = cmd.getId();
+            vpcName = cmd.getName();
+        } else {
+            networkName = cmd.getName();
+            networkId = cmd.getId();
+        }
+        DeleteNetrisACLCommand deleteAclCommand = new DeleteNetrisACLCommand(zoneId, accountId, domainId, networkName, networkId, isVpc, vpcId, vpcName);
+        String aclName;
+        if (isVpc) {
+            aclName =  String.format("V%s-LBACL%s-%s", vpcId, index, cmd.getRuleName());
+        } else {
+            aclName = String.format("N%s-LBACL%s-%s", networkId, index, cmd.getRuleName());
+        }
+        String netrisAclName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.ACL, aclName);
+        deleteAclCommand.setAclRuleNames(Collections.singletonList(netrisAclName));
+        return deleteAclCommand;
+    }
+
+    private Pair<Boolean, List<BigDecimal>> getMatchingLbRule(String lbName, String vpcName) {
+        try {
+            VPCListing vpcResource = getVpcByNameAndTenant(vpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId);
+                return new Pair<>(false, Collections.emptyList());
+            }
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(vpcResource.getId());
+            FilterBySites siteFilter = new FilterBySites();
+            siteFilter.add(siteId);
+            L4LoadBalancerApi lbApi = apiClient.getApiStubForMethod(L4LoadBalancerApi.class);
+            L4lbresBody lbGetResponse = lbApi.apiV2L4lbGet(siteFilter, vpcFilter);
+            if (lbGetResponse == null || !lbGetResponse.isIsSuccess()) {
+                logger.warn("No LB rules were found to be present for the specific Netris VPC resource {}." +
+                        " Netris LB rules may have been deleted out of band.", vpcName);
+                return new Pair<>(true, Collections.emptyList());
+            }
+            List<L4LoadBalancerItem> lbList = lbGetResponse.getData();
+            return new Pair<>(true, lbList.stream()
+                    .filter(lb -> lbName.equals(lb.getName()))
+                    .map(acl -> BigDecimal.valueOf(acl.getId()))
+                    .collect(Collectors.toList()));
+        } catch (ApiException e) {
+            logAndThrowException("Failed to retrieve Netris LB rules", e);
+        }
+        return new Pair<>(true, Collections.emptyList());
+    }
+
+    private List<RoutesGetBody> listStaticRoutes(Integer netrisVpcId, String prefix, String nextHop) {
+        try {
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(netrisVpcId);
+            FilterBySites sitesFilter = new FilterBySites();
+            sitesFilter.add(siteId);
+            RoutesApi routesApi = apiClient.getApiStubForMethod(RoutesApi.class);
+            RoutesResponseGetOk routesResponseGetOk = routesApi.apiRoutesGet(sitesFilter, vpcFilter);
+            if (Objects.isNull(routesResponseGetOk) || Boolean.FALSE.equals(routesResponseGetOk.isIsSuccess())) {
+                logger.warn("Failed to retrieve static routes");
+                return null;
+            }
+            List<RoutesGetBody> routesList = routesResponseGetOk.getData();
+            return routesList.stream()
+                    .filter(x -> (Objects.isNull(prefix) || x.getName().equals(prefix)) &&
+                            (Objects.isNull(nextHop) || x.getNextHop().equals(nextHop)))
+                    .collect(Collectors.toList());
+        } catch (ApiException e) {
+            logAndThrowException("Error listing Netris static routes", e);
+        }
+        return null;
+    }
+
+    private Pair<Boolean, RoutesGetBody> staticRouteExists(Integer netrisVpcId, String prefix, String nextHop, String description) {
+        List<RoutesGetBody> staticRoutes = listStaticRoutes(netrisVpcId, prefix, nextHop);
+        if (staticRoutes == null) {
+            return new Pair<>(false, null);
+        }
+        return new Pair<>(!staticRoutes.isEmpty(), staticRoutes.isEmpty() ? null : staticRoutes.get(0));
+    }
+
+    public void deleteNatRule(String natRuleName, Integer snatRuleId, String netrisVpcName) {
+        logger.debug("Deleting NAT rule on Netris: {} for VPC {}", natRuleName, netrisVpcName);
+        try {
+            NatApi natApi = apiClient.getApiStubForMethod(NatApi.class);
+            natApi.apiV2NatIdDelete(snatRuleId);
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to delete NAT rule: %s for VPC: %s", natRuleName, netrisVpcName), e);
+        }
+    }
+
+    private void deleteVpcIpamAllocationInternal(VPCListing vpcResource, String allocationName) {
+        logger.debug("Deleting Netris VPC IPAM Allocation {} for VPC {}", allocationName, vpcResource.getName());
+        try {
+            VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class);
+            VPCResponseResourceOK vpcResourcesResponse = vpcApi.apiV2VpcVpcIdResourcesGet(vpcResource.getId());
+            VPCResourceIpam vpcAllocationResource = getVpcAllocationResource(vpcResourcesResponse, allocationName);
+            if (Objects.isNull(vpcAllocationResource)) {
+                logger.info("No VPC IPAM Allocation found for VPC {}", allocationName);
+                return;
+            }
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            logger.debug("Removing the IPAM allocation {} with ID {}", vpcAllocationResource.getName(), vpcAllocationResource.getId());
+            ipamApi.apiV2IpamTypeIdDelete("allocation", vpcAllocationResource.getId());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error removing IPAM Allocation %s for VPC %s", allocationName, vpcResource.getName()), e);
+        }
+    }
+
+    private VPCResourceIpam getVpcAllocationResource(VPCResponseResourceOK vpcResourcesResponse, String allocationName) {
+        VPCResource resource = vpcResourcesResponse.getData().get(0);
+        List<VPCResourceIpam> vpcAllocations = resource.getAllocation();
+        if (CollectionUtils.isNotEmpty(vpcAllocations)) {
+            vpcAllocations = vpcAllocations.stream().filter(x -> x.getName().equalsIgnoreCase(allocationName)).collect(Collectors.toList());
+            return CollectionUtils.isNotEmpty(vpcAllocations) ? vpcAllocations.get(0) : null;
+        }
+        return null;
+    }
+
+    private VPCListing getVpcByNameAndTenant(String vpcName) {
+        try {
+            List<VPCListing> vpcListings = listVPCs();
+            List<VPCListing> vpcs = vpcListings.stream()
+                    .filter(x -> x.getName().equals(vpcName) && x.getAdminTenant().getId().equals(tenantId))
+                    .collect(Collectors.toList());
+            return vpcs.isEmpty() ? null : vpcs.get(0);
+        } catch (Exception e) {
+            throw new CloudRuntimeException(String.format("Error getting VPC %s information: %s", vpcName, e.getMessage()), e);
+        }
+    }
+
+    private VPCResponseObjectOK deleteVpcInternal(VPCListing vpcResource) {
+        try {
+            VpcApi vpcApi = apiClient.getApiStubForMethod(VpcApi.class);
+            logger.debug("Removing the VPC {} with ID {}", vpcResource.getName(), vpcResource.getId());
+            return vpcApi.apiV2VpcVpcIdDelete(vpcResource.getId());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error deleting VPC %s: %s", vpcResource.getName(), e.getResponseBody()), e);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean deleteVpc(DeleteNetrisVpcCommand cmd) {
+        String suffix = String.valueOf(cmd.getId());
+        String vpcName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC);
+        VPCListing vpcResource = getVpcByNameAndTenant(vpcName);
+        if (vpcResource == null) {
+            logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", vpcName, tenantId);
+            return false;
+        }
+        String snatRuleName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.SNAT, suffix);
+        NatGetBody existingNatRule = netrisNatRuleExists(snatRuleName);
+        boolean ruleExists = Objects.nonNull(existingNatRule);
+        if (ruleExists) {
+            deleteNatRule(snatRuleName, existingNatRule.getId(), vpcResource.getName());
+        }
+
+        String vpcAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, cmd.getCidr());
+        deleteVpcIpamAllocationInternal(vpcResource, vpcAllocationName);
+        VPCResponseObjectOK response = deleteVpcInternal(vpcResource);
+        return response != null && response.isIsSuccess();
+    }
+
+    @Override
+    public boolean createVnet(CreateNetrisVnetCommand cmd) {
+        String vpcName = cmd.getVpcName();
+        Long vpcId = cmd.getVpcId();
+        String networkName = cmd.getName();
+        Long networkId = cmd.getId();
+        String vnetCidr = cmd.getCidr();
+        Integer vxlanId = cmd.getVxlanId();
+        String netrisTag = cmd.getNetrisTag();
+        String netmask = vnetCidr.split("/")[1];
+        String netrisGateway = cmd.getGateway() + "/" + netmask;
+        String netrisV6Cidr = cmd.getIpv6Cidr();
+        boolean isVpc = cmd.isVpc();
+        Boolean isGlobalRouting = cmd.isGlobalRouting();
+
+        try {
+            String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName);
+            VPCListing associatedVpc = getNetrisVpcResource(netrisVpcName);
+            if (associatedVpc == null) {
+                logger.error("Failed to find Netris VPC with name: {}, to create the corresponding vNet for network {}", netrisVpcName, networkName);
+                return false;
+            }
+
+            String vNetName;
+            if (isVpc) {
+                vNetName = String.format("V%s-N%s-%s", vpcId, networkId, networkName);
+            } else {
+                vNetName = String.format("N%s-%s", networkId, networkName);
+            }
+            String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ;
+            String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), vnetCidr) ;
+
+            createIpamSubnetInternal(netrisSubnetName, vnetCidr, SubnetBody.PurposeEnum.COMMON, associatedVpc, isGlobalRouting);
+            if (Objects.nonNull(netrisV6Cidr)) {
+                String netrisV6IpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, netrisV6Cidr);
+                String netrisV6SubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET,  String.valueOf(cmd.getVpcId()), netrisV6Cidr) ;
+                BigDecimal ipamAllocationId = getIpamAllocationIdByPrefixAndVpc(netrisV6Cidr, associatedVpc);
+                if (ipamAllocationId == null) {
+                    InlineResponse2004Data createdIpamAllocation = createIpamAllocationInternal(netrisV6IpamAllocationName, netrisV6Cidr, associatedVpc);
+                    if (Objects.isNull(createdIpamAllocation)) {
+                        throw new CloudRuntimeException(String.format("Failed to create Netris IPAM Allocation %s for VPC %s", netrisV6IpamAllocationName, netrisVpcName));
+                    }
+                }
+                createIpamSubnetInternal(netrisV6SubnetName, netrisV6Cidr, SubnetBody.PurposeEnum.COMMON, associatedVpc, isGlobalRouting);
+            }
+            logger.debug("Successfully created IPAM Subnet {} for network {} on Netris", netrisSubnetName, networkName);
+
+            VnetResAddBody vnetResponse = createVnetInternal(associatedVpc, netrisVnetName, netrisGateway, netrisV6Cidr, vxlanId, netrisTag);
+            if (vnetResponse == null || !vnetResponse.isIsSuccess()) {
+                String reason = vnetResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris vNet creation {} failed: {}", vNetName, reason);
+                return false;
+            }
+        } catch (ApiException e) {
+            throw new CloudRuntimeException(String.format("Failed to create Netris vNet %s", networkName), e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean updateVnet(UpdateNetrisVnetCommand cmd) {
+        String networkName = cmd.getName();
+        Long networkId = cmd.getId();
+        String prevNetworkName = cmd.getPrevNetworkName();
+        String vpcName = cmd.getVpcName();
+        Long vpcId = cmd.getVpcId();
+        boolean isVpc = cmd.isVpc();
+
+        String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName);
+        VPCListing associatedVpc = getNetrisVpcResource(netrisVpcName);
+        if (associatedVpc == null) {
+            logger.error("Failed to find Netris VPC with name: {}, to create the corresponding vNet for network {}", netrisVpcName, networkName);
+            return false;
+        }
+
+        String vNetName;
+        String prevVnetName;
+        if (isVpc) {
+            vNetName = String.format("V%s-N%s-%s", vpcId, networkId, networkName);
+            prevVnetName = String.format("V%s-N%s-%s", vpcId, networkId, prevNetworkName);
+        } else {
+            vNetName = String.format("N%s-%s", networkId, networkName);
+            prevVnetName = String.format("N%s-%s", networkId, prevNetworkName);
+        }
+        String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ;
+        String prevNetrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, prevVnetName) ;
+
+        VnetResAddBody response = updateVnetInternal(associatedVpc, netrisVnetName, prevNetrisVnetName);
+        if (response == null || !response.isIsSuccess()) {
+            String reason = response == null ? "Empty response" : "Operation failed on Netris";
+            logger.debug("Netris vNet: {} update failed: {}", vNetName, reason);
+            return false;
+        }
+        return true;
+    }
+
+
+    private VnetResAddBody updateVnetInternal(VPCListing associatedVpc, String netrisVnetName, String prevVnetName) {
+        logger.debug("Updating Netris vNet name from {} to {} ", netrisVnetName, prevVnetName);
+        try {
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(associatedVpc.getId());
+            FilterBySites siteFilter = new FilterBySites();
+            siteFilter.add(siteId);
+            List<VnetsBody> vnetsList = getVnets(associatedVpc, prevVnetName, siteFilter, vpcFilter);
+            if (CollectionUtils.isEmpty(vnetsList)) {
+                String errorMsg = String.format("Could not find vNet with name: %s", prevVnetName);
+                logger.error(errorMsg);
+                throw new CloudRuntimeException(errorMsg);
+            }
+            VnetsBody vnetsBody = vnetsList.get(0);
+
+            VnetEditBody vnetBody = new VnetEditBody();
+
+            vnetBody.setCustomAnycastMac(vnetBody.getCustomAnycastMac());
+
+            VnetEditBodyGateways gatewayV4 = new VnetEditBodyGateways();
+            gatewayV4.prefix(vnetsBody.getGateways().get(0).getPrefix());
+            gatewayV4.setDhcpEnabled(false);
+            VnetEditBodyDhcp dhcp = new VnetEditBodyDhcp();
+            dhcp.setEnd("");
+            dhcp.setStart("");
+            dhcp.setOptionSet(new VnetAddBodyDhcpOptionSet());
+            gatewayV4.setDhcp(dhcp);
+            List<VnetEditBodyGateways> gatewaysList = new ArrayList<>();
+            gatewaysList.add(gatewayV4);
+
+
+            if (vnetsBody.getGateways().size() > 1 && Objects.nonNull(vnetsBody.getGateways().get(1))) {
+                String netrisV6Gateway = vnetsBody.getGateways().get(1).getPrefix();
+                VnetEditBodyGateways gatewayV6 = new VnetEditBodyGateways();
+                gatewayV6.prefix(netrisV6Gateway);
+                gatewayV6.setDhcpEnabled(false);
+                gatewayV6.setDhcp(dhcp);
+                gatewaysList.add(gatewayV6);
+            }
+
+            vnetBody.setGateways(gatewaysList);
+            vnetBody.setGuestTenants(new ArrayList<>());
+            vnetBody.setL3vpn(false);
+            vnetBody.setName(netrisVnetName);
+            vnetBody.setNativeVlan(0);
+            vnetBody.setVxlanID(vnetsBody.getVxlanID());
+            vnetBody.setPorts(new ArrayList<>());
+
+            IpTreeSubnetSites subnetSites = new IpTreeSubnetSites();
+            subnetSites.setId(new BigDecimal(siteId));
+            subnetSites.setName(siteName);
+            List<IpTreeSubnetSites> subnetSitesList = new ArrayList<>();
+            subnetSitesList.add(subnetSites);
+            vnetBody.setSites(subnetSitesList);
+
+            vnetBody.setState(VnetEditBody.StateEnum.ACTIVE);
+
+            vnetBody.setTags(new ArrayList<>());
+
+            IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant();
+            allocationTenant.setId(new BigDecimal(tenantId));
+            allocationTenant.setName(tenantName);
+            vnetBody.setTenant(allocationTenant);
+
+            vnetBody.setVlan(0);
+            vnetBody.setVlanAware(false);
+            vnetBody.setVlans("");
+
+            VnetAddBodyVpc vpc = new VnetAddBodyVpc();
+            vpc.setName(associatedVpc.getName());
+            vpc.setId(associatedVpc.getId());
+            vnetBody.setVpc(vpc);
+
+            vnetBody.setTags(vnetsBody.getTags());
+
+            VNetApi vnetApi = apiClient.getApiStubForMethod(VNetApi.class);
+            return vnetApi.apiV2VnetIdPut(vnetBody, vnetsBody.getId().intValue());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error creating Netris vNet %s for VPC %s", netrisVnetName, associatedVpc.getName()), e);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean deleteVnet(DeleteNetrisVnetCommand cmd) {
+        String vpcName = cmd.getVpcName();
+        Long vpcId = cmd.getVpcId();
+        String networkName = cmd.getName();
+        Long networkId = cmd.getId();
+        boolean isVpc = cmd.isVpc();
+        String vnetCidr = cmd.getVNetCidr();
+        String vnetV6Cidr = cmd.getvNetV6Cidr();
+        try {
+            String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName);
+            VPCListing associatedVpc = getNetrisVpcResource(netrisVpcName);
+            if (associatedVpc == null) {
+                logger.error("Failed to find Netris VPC with name: {}, to create the corresponding vNet for network {}", netrisVpcName, networkName);
+                return false;
+            }
+
+            String vNetName;
+            if (isVpc) {
+                vNetName = String.format("V%s-N%s-%s", vpcId, networkId, networkName);
+            } else {
+                vNetName = String.format("N%s-%s", networkId, networkName);
+            }
+
+            String netrisVnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VNET, vNetName) ;
+            String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), vnetCidr);
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(associatedVpc.getId());
+            FilterBySites siteFilter = new FilterBySites();
+            siteFilter.add(siteId);
+            deleteVnetInternal(associatedVpc, siteFilter, vpcFilter, netrisVnetName, vNetName);
+
+            logger.debug("Successfully deleted vNet {}", vNetName);
+            deleteSubnetInternal(vpcFilter, netrisVnetName, netrisSubnetName);
+            if (Objects.nonNull(vnetV6Cidr)) {
+                String netrisV6IpamAllocationName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, vnetV6Cidr);
+                String netrisV6SubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(cmd.getVpcId()), vnetV6Cidr);
+                deleteSubnetInternal(vpcFilter, netrisVnetName, netrisV6SubnetName);
+                deleteVpcIpamAllocationInternal(associatedVpc, netrisV6IpamAllocationName);
+            }
+
+        } catch (Exception e) {
+            throw new CloudRuntimeException(String.format("Failed to delete Netris vNet %s", networkName), e);
+        }
+        return true;
+    }
+
+    protected VPCListing getSystemVpc() throws ApiException {
+        List<VPCListing> systemVpcList = listVPCs().stream().filter(VPCListing::isIsSystem).collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(systemVpcList)) {
+            String msg = "Cannot find any system VPC";
+            logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        }
+        return systemVpcList.get(0);
+    }
+
+    private BigDecimal getIpamAllocationIdByPrefixAndVpc(String superCidrPrefix, VPCListing vpc) throws ApiException {
+        IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+        FilterBySites filterBySites = new FilterBySites();
+        filterBySites.add(siteId);
+        FilterByVpc filterByVpc = new FilterByVpc();
+        filterByVpc.add(vpc.getId());
+        IpTree ipamTree = ipamApi.apiV2IpamGet(filterBySites, filterByVpc);
+        List<IpTreeAllocation> superCidrList = ipamTree.getData().stream()
+                .filter(x -> x.getPrefix().equals(superCidrPrefix) || isAllocationPartOfBiggerAllocation(x.getPrefix(), superCidrPrefix))
+                .collect(Collectors.toList());
+        return CollectionUtils.isEmpty(superCidrList) ? null : superCidrList.get(0).getId();
+    }
+
+    private boolean isAllocationPartOfBiggerAllocation(String netrisAllocation, String providedAllocation) {
+        IPAddress biggerAllocation = new IPAddressString(netrisAllocation).getAddress();
+        IPAddress smallerAllocation = new IPAddressString(providedAllocation).getAddress();
+
+        return biggerAllocation.contains(smallerAllocation);
+
+    }
+
+    private IpTreeSubnet getIpamSubnetByAllocationAndPrefixAndPurposeAndVpc(BigDecimal ipamAllocationId, String exactCidr, IpTreeSubnet.PurposeEnum purpose, VPCListing vpc) throws ApiException {
+        IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+        FilterByVpc filterByVpc = new FilterByVpc();
+        filterByVpc.add(vpc.getId());
+        SubnetResBody subnetResBody = ipamApi.apiV2IpamSubnetsGet(filterByVpc);
+        List<IpTreeSubnet> exactSubnetList = subnetResBody.getData().stream()
+                .filter(x -> ipamAllocationId != null ?
+                        x.getAllocationID().equals(ipamAllocationId) && x.getPrefix().equals(exactCidr) && x.getPurpose() == purpose :
+                        x.getPrefix().equals(exactCidr) && x.getPurpose() == purpose)
+                .collect(Collectors.toList());
+        return CollectionUtils.isEmpty(exactSubnetList) ? null : exactSubnetList.get(0);
+    }
+
+    @Override
+    public boolean setupZoneLevelPublicRange(SetupNetrisPublicRangeCommand cmd) {
+        String superCidr = cmd.getSuperCidr();
+        String exactCidr = cmd.getExactCidr();
+        try {
+            VPCListing systemVpc = getSystemVpc();
+            logger.debug("Checking if the Netris Public Super CIDR {} exists", superCidr);
+            BigDecimal ipamAllocationId = getIpamAllocationIdByPrefixAndVpc(superCidr, systemVpc);
+            if (ipamAllocationId == null) {
+                String ipamName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_ALLOCATION, superCidr);
+                InlineResponse2004Data ipamAllocation = createIpamAllocationInternal(ipamName, superCidr, systemVpc);
+                if (ipamAllocation == null) {
+                    String msg = String.format("Could not create the zone level super CIDR %s for the system VPC", superCidr);
+                    logger.error(msg);
+                    throw new CloudRuntimeException(msg);
+                }
+                ipamAllocationId = new BigDecimal(ipamAllocation.getId());
+            }
+            IpTreeSubnet exactSubnet = getIpamSubnetByAllocationAndPrefixAndPurposeAndVpc(ipamAllocationId, exactCidr, IpTreeSubnet.PurposeEnum.COMMON, systemVpc);
+            if (exactSubnet == null) {
+                String ipamSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, exactCidr);
+                createIpamSubnetInternal(ipamSubnetName, exactCidr, SubnetBody.PurposeEnum.COMMON, systemVpc, null);
+            }
+        } catch (ApiException e) {
+            String msg = String.format("Error setting up the Netris Public Range %s on super CIDR %s", exactCidr, superCidr);
+            logAndThrowException(msg, e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean createOrUpdateNatRuleInternal(CreateOrUpdateNetrisNatCommand cmd) {
+        String ruleName = cmd.getNatRuleName();
+        long vpcId = cmd.getVpcId();
+        Long networkId = cmd.getId();
+        String networkName = cmd.getName();
+        String vpcName = cmd.getVpcName();
+        String vpcCidr = cmd.getVpcCidr();
+        boolean isVpc = cmd.isVpc();
+        NatPostBody.ActionEnum action = getNatActionFromRuleType(cmd.getNatRuleType());
+        NatPostBody.ProtocolEnum protocol = getProtocolFromString(cmd.getProtocol());
+        NatPostBody.StateEnum state = getStateFromString(cmd.getState());
+
+        String vNetName = isVpc ?
+                String.format("V%s-N%s-%s", vpcId, networkId, networkName) :
+                String.format("N%s-%s", networkId, networkName);
+        String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName);
+        VPCListing vpcResource = getNetrisVpcResource(netrisVpcName);
+        if (vpcResource == null) {
+            logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+            return false;
+        }
+
+        String targetIpSubnet = null;
+        if (NatPostBody.ActionEnum.SNAT == action) {
+            targetIpSubnet = cmd.getNatIp() + "/32";
+        } else if (NatPostBody.ActionEnum.DNAT == action) {
+            targetIpSubnet = cmd.getDestinationAddress() + "/32";
+        }
+
+        if (StringUtils.isNotBlank(targetIpSubnet) && existsDestinationSubnet(targetIpSubnet)) {
+            logger.debug("Creating subnet with NAT purpose for {}}", targetIpSubnet);
+            createNatSubnet(cmd, targetIpSubnet, vpcResource.getId());
+        }
+
+        NatGetBody existingNatRule = netrisNatRuleExists(ruleName);
+        boolean ruleExists = Objects.nonNull(existingNatRule);
+        if (!ruleExists) {
+            String destinationAddress = action == NatPostBody.ActionEnum.SNAT ? ANY_IP : cmd.getDestinationAddress() + "/32";
+            String destinationPort = cmd.getDestinationPort();
+            String sourceAddress = action == NatPostBody.ActionEnum.SNAT ? vpcCidr : ANY_IP;
+            String sourcePort = "1-65535";
+            String snatToIp = action == NatPostBody.ActionEnum.SNAT ? targetIpSubnet : null;
+            String dnatToIp = action == NatPostBody.ActionEnum.DNAT ? cmd.getSourceAddress() + "/32" : null;
+            String dnatToPort = action == NatPostBody.ActionEnum.DNAT ? cmd.getSourcePort() : null;
+            return createNatRuleInternal(ruleName, action, protocol, state, destinationAddress, destinationPort,
+                    sourceAddress, sourcePort, snatToIp, dnatToIp, dnatToPort, netrisVpcName, networkName, vNetName);
+        } else if (NatPostBody.ActionEnum.SNAT == action) {
+            return updateSnatRuleInternal(ruleName, targetIpSubnet, netrisVpcName, networkName, vNetName, existingNatRule.getId(), vpcCidr);
+        }
+        return true;
+    }
+
+    private NatPostBody.StateEnum getStateFromString(String state) {
+        return NatPostBody.StateEnum.fromValue(state);
+    }
+
+    private NatPostBody.ActionEnum getNatActionFromRuleType(String natRuleType) {
+        return NatPostBody.ActionEnum.fromValue(natRuleType);
+    }
+
+    @Override
+    public boolean createOrUpdateSNATRule(CreateOrUpdateNetrisNatCommand cmd) {
+        return createOrUpdateNatRuleInternal(cmd);
+    }
+
+    private boolean existsDestinationSubnet(String destinationSubnet) {
+        try {
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(getSystemVpc().getId());
+            List<IpTreeSubnet> targetSubnetList = getSubnet(vpcFilter, destinationSubnet);
+            return targetSubnetList != null;
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error checking if subnet %s exists: %s", destinationSubnet, e.getMessage()), e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean createStaticNatRule(CreateOrUpdateNetrisNatCommand cmd) {
+        String staticNatRuleName = cmd.getNatRuleName();
+        String natIP = cmd.getNatIp() + "/32";
+        String vmIp = cmd.getVmIp() + "/32";
+        String vpcName = cmd.getVpcName();
+        String vpcCidr = cmd.getVpcCidr();
+        Long vpcId = cmd.getVpcId();
+        Long networkId = cmd.getId();
+        String networkName = cmd.getName();
+        boolean isVpc = cmd.isVpc();
+
+        try {
+            String netrisVpcName = getNetrisVpcName(cmd, vpcId, vpcName);
+            VPCListing vpcResource = getNetrisVpcResource(netrisVpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+                return false;
+            }
+            // Create a /32 subnet for the DNAT IP
+            createNatSubnet(cmd, natIP, vpcResource.getId());
+            NatApi natApi = apiClient.getApiStubForMethod(NatApi.class);
+            NatPostBody natBody = new NatPostBody();
+            natBody.setAction(NatPostBody.ActionEnum.DNAT);
+            natBody.setDestinationAddress(natIP);
+            natBody.setName(staticNatRuleName);
+            natBody.setProtocol(NatPostBody.ProtocolEnum.ALL);
+            natBody.setState(NatPostBody.StateEnum.ENABLED);
+            natBody.setComment(String.format("Static NAT rule for %s", netrisVpcName));
+
+            NatBodySiteSite site = new NatBodySiteSite();
+            site.setId(siteId);
+            site.setName(siteName);
+            natBody.setSite(site);
+            natBody.setSourceAddress(ANY_IP);
+            natBody.setDnatToIP(vmIp);
+
+            NatBodyVpcVpc vpc = new NatBodyVpcVpc();
+            vpc.setId(vpcResource.getId());
+            vpc.setName(vpcResource.getName());
+            natBody.setVpc(vpc);
+
+            InlineResponse20015 natResponse = natApi.apiV2NatPost(natBody);
+            if (natResponse == null || !natResponse.isIsSuccess()) {
+                String reason = natResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris static NAT (DNAT) rule creation failed for netris VPC - {}: {}", netrisVpcName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to create Static NAT (DNAT) rule for network : %s", Objects.nonNull(vpcName) ? vpcName : networkName), e);
+        }
+        return true;
+    }
+
+    private void createNatSubnet(NetrisCommand cmd, String natIp, Integer netrisVpcId) {
+        try {
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(netrisVpcId);
+            String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET, String.valueOf(((CreateOrUpdateNetrisNatCommand)cmd).getVpcId()), natIp);
+            List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName);
+            if (matchedSubnets.isEmpty()) {
+                VPCListing systemVpc = getSystemVpc();
+                createIpamSubnetInternal(netrisSubnetName, natIp, SubnetBody.PurposeEnum.NAT, systemVpc, null);
+            } else {
+                IpTreeSubnet existingSubnet = matchedSubnets.stream().filter(x -> x.getPrefix().equals(natIp)).collect(Collectors.toList()).get(0);
+                if (existingSubnet.getPurpose() != IpTreeSubnet.PurposeEnum.NAT) {
+                    VPCListing systemVpc = getSystemVpc();
+                    logger.debug("Subnet: {} already exists, but purpose is not NAT, updating its purpose to 'nat'", natIp);
+                    updateIpamSubnetInternal(existingSubnet.getId().intValue(), netrisSubnetName, natIp, SubnetBody.PurposeEnum.NAT, systemVpc, null);
+                }
+            }
+        } catch (ApiException e) {
+            throw new CloudRuntimeException(String.format("Failed to create subnet for %s with NAT purpose", natIp));
+        }
+    }
+
+    private void createLBSubnet(NetrisCommand cmd, String lbIp, Integer netrisVpcId) {
+        try {
+            FilterByVpc vpcFilter = new FilterByVpc();
+            vpcFilter.add(netrisVpcId);
+            String netrisSubnetName = NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd,
+                    NetrisResourceObjectUtils.NetrisObjectType.IPAM_SUBNET,
+                    String.valueOf(cmd.getId()), lbIp);
+            List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName);
+            VPCListing systemVpc = getSystemVpc();
+            if (matchedSubnets.isEmpty()) {
+                createIpamSubnetInternal(netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null);
+            } else if (IpTreeSubnet.PurposeEnum.LOAD_BALANCER != matchedSubnets.get(0).getPurpose()){
+                logger.debug("Updating existing NAT subnet {} to have load balancer purpose", netrisSubnetName);
+                updateIpamSubnetInternal(matchedSubnets.get(0).getId().intValue(), netrisSubnetName, lbIp, SubnetBody.PurposeEnum.LOAD_BALANCER, systemVpc, null);
+            }
+            logger.debug("LB subnet: {} already exists", netrisSubnetName);
+        } catch (ApiException e) {
+            throw new CloudRuntimeException(String.format("Failed to create subnet for %s with LB purpose", lbIp));
+        }
+    }
+
+    private NatPostBody.ProtocolEnum getProtocolFromString(String protocol) {
+        return NatPostBody.ProtocolEnum.fromValue(protocol);
+    }
+
+    private NatPostBody createNatRulePostBody(String ruleName, NatPostBody.ActionEnum action, NatPostBody.ProtocolEnum protocol, NatPostBody.StateEnum state,
+                                              String destinationAddress, String destinationPort,
+                                              String sourceAddress, String sourcePort,
+                                              String dnatToIp, String dnatToPort,
+                                              String netrisVpcName, String snatIP, String comment) {
+        NatPostBody natBody = new NatPostBody();
+        natBody.setAction(action);
+        natBody.setName(ruleName);
+        natBody.setProtocol(protocol);
+        natBody.setState(state);
+        if (StringUtils.isNotBlank(comment)) {
+            natBody.setComment(comment);
+        }
+
+        natBody.setDestinationAddress(destinationAddress);
+        if (StringUtils.isNotBlank(destinationPort)) {
+            natBody.setDestinationPort(destinationPort);
+        }
+
+        if (StringUtils.isNotBlank(sourceAddress)) {
+            natBody.setSourceAddress(sourceAddress);
+        }
+        if (StringUtils.isNotBlank(sourcePort)) {
+            natBody.setSourcePort(sourcePort);
+        }
+
+        NatBodySiteSite site = new NatBodySiteSite();
+        site.setId(siteId);
+        site.setName(siteName);
+        natBody.setSite(site);
+
+        if (StringUtils.isNotBlank(snatIP)) {
+            natBody.setSnatToIP(snatIP);
+        }
+
+        if (StringUtils.isNotBlank(dnatToIp)) {
+            natBody.setDnatToIP(dnatToIp);
+        }
+        if (StringUtils.isNotBlank(dnatToPort)) {
+            natBody.setDnatToPort(Integer.valueOf(dnatToPort));
+        }
+
+        NatBodyVpcVpc vpc = new NatBodyVpcVpc();
+        VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+        if (vpcResource == null) {
+            logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+            return null;
+        }
+        vpc.setId(vpcResource.getId());
+        vpc.setName(vpcResource.getName());
+        natBody.setVpc(vpc);
+        return natBody;
+    }
+
+    @Override
+    public boolean createOrUpdateDNATRule(CreateOrUpdateNetrisNatCommand cmd) {
+        return createOrUpdateNatRuleInternal(cmd);
+    }
+
+    private boolean createNatRuleInternal(String ruleName, NatPostBody.ActionEnum action, NatPostBody.ProtocolEnum protocol, NatPostBody.StateEnum state,
+                                          String destinationAddress, String destinationPort, String sourceAddress, String sourcePort,
+                                          String sNatToIp, String dNatToIp, String dNatToPort,
+                                          String netrisVpcName, String networkName, String vNetName) {
+        try {
+            NatApi natApi = apiClient.getApiStubForMethod(NatApi.class);
+            String comment = String.format("NAT rule for %s with action %s", netrisVpcName, action.name());
+            NatPostBody natBody = createNatRulePostBody(ruleName, action, protocol, state,
+                    destinationAddress, destinationPort, sourceAddress, sourcePort,
+                    dNatToIp, dNatToPort, netrisVpcName, sNatToIp, comment);
+            if (natBody == null) {
+                return false;
+            }
+            InlineResponse20015 natResponse = natApi.apiV2NatPost(natBody);
+            if (natResponse == null || !natResponse.isIsSuccess()) {
+                String reason = natResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris NAT rule {} creation failed for network(vNet) - {}({}): {}", action.name(), networkName, vNetName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to create NAT rule %s for network(vNet): %s(%s)", action.name(), networkName, vNetName), e);
+        }
+        return true;
+    }
+
+    private boolean updateSnatRuleInternal(String snatRuleName, String snatIP, String netrisVpcName, String networkName,
+                                           String vNetName, Integer netisSnatId, String vpcCidr) {
+        try {
+            NatApi natApi = apiClient.getApiStubForMethod(NatApi.class);
+            NatPutBody natBody = new NatPutBody();
+            natBody.setAction(NatPutBody.ActionEnum.SNAT);
+            natBody.setDestinationAddress(ANY_IP);
+            natBody.setName(snatRuleName);
+            natBody.setProtocol(NatPutBody.ProtocolEnum.ALL);
+
+            NatBodySiteSite site = new NatBodySiteSite();
+            site.setId(siteId);
+            site.setName(siteName);
+            natBody.setSite(site);
+            natBody.setSourceAddress(vpcCidr);
+            natBody.setSnatToIP(snatIP);
+
+            NatBodyVpcVpc vpc = new NatBodyVpcVpc();
+            VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+            if (vpcResource == null) {
+                logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+                return false;
+            }
+            vpc.setId(vpcResource.getId());
+            vpc.setName(vpcResource.getName());
+            natBody.setVpc(vpc);
+
+            InlineResponse20016 natUpdateResponse = natApi.apiV2NatIdPut(natBody, netisSnatId);
+            if (natUpdateResponse == null || !natUpdateResponse.isIsSuccess()) {
+                String reason = natUpdateResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("Update of Netris SNAT rule failed for network(vNet) - {}({}): {}", networkName, vNetName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to create SNAT rule for network(vNet): %s(%s)", networkName, vNetName), e);
+        }
+        return true;
+    }
+
+    private List<VnetsBody> getVnets(VPCListing associatedVpc, String netrisVnetName, FilterBySites siteFilter, FilterByVpc vpcFilter) {
+        try {
+            VNetApi vNetApi = apiClient.getApiStubForMethod(VNetApi.class);
+            VnetResListBody vnetList = vNetApi.apiV2VnetGet(siteFilter, vpcFilter);
+            if (vnetList == null || !vnetList.isIsSuccess()) {
+                throw new CloudRuntimeException(String.format("Failed to list vNets for the given VPC: %s and site: %s", associatedVpc.getName(), siteName));
+            }
+            return vnetList.getData().stream().filter(vnet -> vnet.getName().equals(netrisVnetName)).collect(Collectors.toList());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to get vNets: %s", netrisVnetName), e);
+        }
+        return Collections.emptyList();
+    }
+
+    private void deleteVnetInternal(VPCListing associatedVpc, FilterBySites siteFilter, FilterByVpc vpcFilter, String netrisVnetName, String vNetName) {
+        try {
+            VNetApi vNetApi = apiClient.getApiStubForMethod(VNetApi.class);
+            List<VnetsBody> vnetsList = getVnets(associatedVpc, netrisVnetName, siteFilter, vpcFilter);
+            if (CollectionUtils.isEmpty(vnetsList)) {
+                logger.debug("vNet: {} for the given VPC: {} appears to already be deleted on Netris", vNetName, associatedVpc.getName());
+                return;
+            }
+            VnetsBody vnetsBody = vnetsList.get(0);
+
+            VnetResDeleteBody deleteVnetResponse = vNetApi.apiV2VnetIdDelete(vnetsBody.getId().intValue());
+            if (deleteVnetResponse == null || !deleteVnetResponse.isIsSuccess()) {
+                throw new CloudRuntimeException(String.format("Failed to delete vNet: %s", vNetName));
+            }
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to delete vNet: %s", netrisVnetName), e);
+        }
+    }
+
+    private List<IpTreeSubnet> getSubnet(FilterByVpc vpcFilter, String netrisSubnetName) {
+        try {
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            SubnetResBody subnetsResponse = ipamApi.apiV2IpamSubnetsGet(vpcFilter);
+            List<IpTreeSubnet> subnets = subnetsResponse.getData();
+            return subnets.stream().filter(subnet -> subnet.getName().equals(netrisSubnetName)).collect(Collectors.toList());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to get IPAM subnet: %s", netrisSubnetName), e);
+        }
+        return new ArrayList<>();
+    }
+
+    private void deleteSubnetInternal(FilterByVpc vpcFilter, String netrisVnetName, String netrisSubnetName) {
+        try {
+            String logString = "";
+            if (Objects.nonNull(netrisVnetName)) {
+                logString = String.format("for vNet: %s ", netrisVnetName);
+            }
+            logger.debug("Deleting Netris VPC IPAM Subnet {} {}", netrisSubnetName, logString);
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            List<IpTreeSubnet> matchedSubnets = getSubnet(vpcFilter, netrisSubnetName);
+            if (CollectionUtils.isEmpty(matchedSubnets)) {
+                logger.debug("IPAM subnet: {} {} appears to already be deleted on Netris", netrisSubnetName, logString);
+                return;
+            }
+
+            ipamApi.apiV2IpamTypeIdDelete("subnet", matchedSubnets.get(0).getId().intValue());
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Failed to delete subnet: %s", netrisSubnetName), e);
+        }
+    }
+
+    private SubnetBody getIpamSubnetBody(VPCListing vpc, SubnetBody.PurposeEnum purpose, String subnetName, String subnetPrefix, Boolean isGlobalRouting) {
+        SubnetBody subnetBody = new SubnetBody();
+        subnetBody.setName(subnetName);
+
+        AllocationBodyVpc vpcAllocationBody = new AllocationBodyVpc();
+        vpcAllocationBody.setName(vpc.getName());
+        vpcAllocationBody.setId(vpc.getId());
+        subnetBody.setVpc(vpcAllocationBody);
+
+        IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant();
+        allocationTenant.setId(new BigDecimal(tenantId));
+        allocationTenant.setName(tenantName);
+        subnetBody.setTenant(allocationTenant);
+
+        IpTreeSubnetSites subnetSites = new IpTreeSubnetSites();
+        subnetSites.setId(new BigDecimal(siteId));
+        subnetSites.setName(siteName);
+        subnetBody.setSites(List.of(subnetSites));
+
+        subnetBody.setPurpose(purpose);
+        subnetBody.setPrefix(subnetPrefix);
+        if (isGlobalRouting != null) {
+            subnetBody.setGlobalRouting(isGlobalRouting);
+        }
+        return subnetBody;
+    }
+
+    private InlineResponse2004Data createIpamSubnetInternal(String subnetName, String subnetPrefix, SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) {
+        logger.debug("Creating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName());
+        try {
+
+            SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting);
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetPost(subnetBody);
+            if (subnetResponse == null || !subnetResponse.isIsSuccess()) {
+                String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris IPAM Subnet {} creation failed: {}", subnetName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+            return subnetResponse.getData();
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error creating Netris IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e);
+            return null;
+        }
+    }
+
+    private InlineResponse2004Data updateIpamSubnetInternal(Integer netrisSubnetId, String subnetName, String subnetPrefix,
+                                                            SubnetBody.PurposeEnum purpose, VPCListing vpc, Boolean isGlobalRouting) {
+        logger.debug("Updating Netris IPAM Subnet {} for VPC {}", subnetPrefix, vpc.getName());
+        try {
+
+            SubnetBody subnetBody = getIpamSubnetBody(vpc, purpose, subnetName, subnetPrefix, isGlobalRouting);
+            IpamApi ipamApi = apiClient.getApiStubForMethod(IpamApi.class);
+            InlineResponse2004 subnetResponse = ipamApi.apiV2IpamSubnetIdPut(subnetBody, netrisSubnetId);
+            if (subnetResponse == null || !subnetResponse.isIsSuccess()) {
+                String reason = subnetResponse == null ? "Empty response" : "Operation failed on Netris";
+                logger.debug("The Netris IPAM Subnet {} update failed: {}", subnetName, reason);
+                throw new CloudRuntimeException(reason);
+            }
+            return subnetResponse.getData();
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error Updating Netris IPAM Subnet %s for VPC %s", subnetPrefix, vpc.getName()), e);
+            return null;
+        }
+    }
+
+    VnetResAddBody createVnetInternal(VPCListing associatedVpc, String netrisVnetName, String netrisGateway, String netrisV6Cidr, Integer vxlanId, String netrisTag) {
+        logger.debug("Creating Netris VPC vNet {} for CIDR {}", netrisVnetName, netrisGateway);
+        try {
+            VnetAddBody vnetBody = new VnetAddBody();
+
+            vnetBody.setCustomAnycastMac("");
+
+            VnetAddBodyGateways gatewayV4 = new VnetAddBodyGateways();
+            gatewayV4.prefix(netrisGateway);
+            gatewayV4.setDhcpEnabled(false);
+            VnetAddBodyDhcp dhcp = new VnetAddBodyDhcp();
+            dhcp.setEnd("");
+            dhcp.setStart("");
+            dhcp.setOptionSet(new VnetAddBodyDhcpOptionSet());
+            gatewayV4.setDhcp(dhcp);
+            List<VnetAddBodyGateways> gatewaysList = new ArrayList<>();
+            gatewaysList.add(gatewayV4);
+
+            if (Objects.nonNull(netrisV6Cidr)) {
+                VnetAddBodyGateways gatewayV6 = new VnetAddBodyGateways();
+                gatewayV6.prefix(NetUtils.getIpv6Gateway(netrisV6Cidr));
+                gatewayV6.setDhcpEnabled(false);
+                gatewayV6.setDhcp(dhcp);
+                gatewaysList.add(gatewayV6);
+            }
+
+            vnetBody.setGateways(gatewaysList);
+            vnetBody.setGuestTenants(new ArrayList<>());
+            vnetBody.setL3vpn(false);
+            vnetBody.setName(netrisVnetName);
+            vnetBody.setNativeVlan(0);
+            vnetBody.setVxlanID(vxlanId);
+            vnetBody.setPorts(new ArrayList<>());
+
+            IpTreeSubnetSites subnetSites = new IpTreeSubnetSites();
+            subnetSites.setId(new BigDecimal(siteId));
+            subnetSites.setName(siteName);
+            List<IpTreeSubnetSites> subnetSitesList = new ArrayList<>();
+            subnetSitesList.add(subnetSites);
+            vnetBody.setSites(subnetSitesList);
+
+            vnetBody.setState(VnetAddBody.StateEnum.ACTIVE);
+
+            vnetBody.setTags(new ArrayList<>());
+
+            IpTreeAllocationTenant allocationTenant = new IpTreeAllocationTenant();
+            allocationTenant.setId(new BigDecimal(tenantId));
+            allocationTenant.setName(tenantName);
+            vnetBody.setTenant(allocationTenant);
+
+            vnetBody.setVlan(0);
+            vnetBody.setVlanAware(false);
+            vnetBody.setVlans("");
+
+            VnetAddBodyVpc vpc = new VnetAddBodyVpc();
+            vpc.setName(associatedVpc.getName());
+            vpc.setId(associatedVpc.getId());
+            vnetBody.setVpc(vpc);
+
+            vnetBody.setTags(Collections.singletonList(netrisTag));
+
+            VNetApi vnetApi = apiClient.getApiStubForMethod(VNetApi.class);
+            return vnetApi.apiV2VnetPost(vnetBody);
+        } catch (ApiException e) {
+            logAndThrowException(String.format("Error creating Netris vNet %s for VPC %s", netrisVnetName, associatedVpc.getName()), e);
+            return null;
+        }
+    }
+
+
+
+    private String getNetrisVpcNameSuffix(Long vpcId, String vpcName, Long networkId, String networkName, boolean isVpc) {
+        String suffix = null;
+        if (isVpc) {
+            suffix = String.format("%s-%s", vpcId, vpcName);
+        } else {
+            suffix = String.format("%s-%s", networkId, networkName);
+        }
+        return suffix;
+    }
+
+    private NatGetBody netrisNatRuleExists(String netrisNatRule) {
+        try {
+            NatApi natApi = apiClient.getApiStubForMethod(NatApi.class);
+            //NatResponseGetOk response = natApi.apiV2NatGet(null, Arrays.asList(new BigDecimal(vpcId)));
+            NatResponseGetOk response = natApi.apiV2NatGet(null, null);
+            if (Objects.isNull(response) || !response.isIsSuccess()) {
+                throw new CloudRuntimeException("Failed to list Netris NAT rules");
+            }
+            List<NatGetBody> data = response.getData().stream().filter(natData -> natData.getName().equals(netrisNatRule)).collect(Collectors.toList());
+            if (data.isEmpty()) {
+                return null;
+            }
+            return data.get(0);
+
+        } catch (ApiException e) {
+            throw new CloudRuntimeException("Failed to list Netris NAT rules");
+        }
+    }
+
+    private VPCListing getNetrisVpcResource(String netrisVpcName) {
+        VPCListing vpcResource = getVpcByNameAndTenant(netrisVpcName);
+        if (vpcResource == null) {
+            logger.error("Could not find the Netris VPC resource with name {} and tenant ID {}", netrisVpcName, tenantId);
+        }
+        return vpcResource;
+    }
+
+    private String getNetrisVpcName(NetrisCommand cmd, Long vpcId, String vpcName) {
+        String suffix = getNetrisVpcNameSuffix(vpcId, vpcName, cmd.getId(), cmd.getName(), cmd.isVpc());
+        return NetrisResourceObjectUtils.retrieveNetrisResourceObjectName(cmd, NetrisResourceObjectUtils.NetrisObjectType.VPC, suffix);
+    }
+
+    private void deleteNatSubnet(String netrisSubnetName, Integer netrisVpcId, String natIp) {
+        FilterByVpc vpcFilter = new FilterByVpc();
+        vpcFilter.add(netrisVpcId);
+        deleteSubnetInternal(vpcFilter, null, netrisSubnetName);
+    }
+}
diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java
similarity index 55%
copy from plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java
copy to plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java
index 7673e5a6038..0e798ebb6be 100644
--- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java
+++ b/plugins/network-elements/netris/src/main/java/org/apache/cloudstack/service/NetrisElement.java
@@ -34,7 +34,6 @@ import com.cloud.domain.dao.DomainDao;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.ConnectionException;
 import com.cloud.exception.InsufficientCapacityException;
-import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
@@ -42,41 +41,38 @@ import com.cloud.host.Status;
 import com.cloud.network.IpAddress;
 import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
-import com.cloud.network.Networks;
 import com.cloud.network.PhysicalNetworkServiceProvider;
 import com.cloud.network.PublicIpAddress;
-import com.cloud.network.VirtualRouterProvider;
+import com.cloud.network.SDNProviderNetworkRule;
+import com.cloud.network.SDNProviderOpObject;
+import com.cloud.network.as.AutoScaleCounter;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
 import com.cloud.network.dao.LoadBalancerVMMapDao;
 import com.cloud.network.dao.LoadBalancerVMMapVO;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
-import com.cloud.network.dao.PhysicalNetworkDao;
-import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
-import com.cloud.network.dao.PhysicalNetworkVO;
-import com.cloud.network.dao.VirtualRouterProviderDao;
 import com.cloud.network.element.DhcpServiceProvider;
 import com.cloud.network.element.DnsServiceProvider;
-import com.cloud.network.element.FirewallServiceProvider;
 import com.cloud.network.element.IpDeployer;
 import com.cloud.network.element.LoadBalancingServiceProvider;
 import com.cloud.network.element.NetworkACLServiceProvider;
 import com.cloud.network.element.PortForwardingServiceProvider;
 import com.cloud.network.element.StaticNatServiceProvider;
 import com.cloud.network.element.VirtualRouterElement;
-import com.cloud.network.element.VirtualRouterProviderVO;
 import com.cloud.network.element.VpcProvider;
 import com.cloud.network.lb.LoadBalancingRule;
+import com.cloud.network.netris.NetrisLbBackend;
+import com.cloud.network.netris.NetrisService;
 import com.cloud.network.rules.FirewallRule;
 import com.cloud.network.rules.LoadBalancerContainer;
 import com.cloud.network.rules.PortForwardingRule;
 import com.cloud.network.rules.StaticNat;
 import com.cloud.network.vpc.NetworkACLItem;
 import com.cloud.network.vpc.PrivateGateway;
+import com.cloud.network.vpc.StaticRoute;
 import com.cloud.network.vpc.StaticRouteProfile;
 import com.cloud.network.vpc.Vpc;
-import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.offering.NetworkOffering;
@@ -89,8 +85,6 @@ import com.cloud.user.AccountManager;
 import com.cloud.uservm.UserVm;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.AdapterBase;
-import com.cloud.utils.db.QueryBuilder;
-import com.cloud.utils.db.SearchCriteria;
 import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallback;
 import com.cloud.utils.exception.CloudRuntimeException;
@@ -99,21 +93,14 @@ import com.cloud.vm.ReservationContext;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachineProfile;
 import com.cloud.vm.dao.VMInstanceDao;
-import net.sf.ehcache.config.InvalidConfigurationException;
-import org.apache.cloudstack.NsxAnswer;
-import org.apache.cloudstack.StartupNsxCommand;
+import com.google.gson.Gson;
+import org.apache.cloudstack.StartupNetrisCommand;
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.command.admin.internallb.ConfigureInternalLoadBalancerElementCmd;
-import org.apache.cloudstack.api.command.admin.internallb.CreateInternalLoadBalancerElementCmd;
-import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd;
-import org.apache.cloudstack.network.element.InternalLoadBalancerElementService;
-import org.apache.cloudstack.resource.NsxLoadBalancerMember;
-import org.apache.cloudstack.resource.NsxNetworkRule;
-import org.apache.cloudstack.resource.NsxOpObject;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import com.cloud.network.netris.NetrisNetworkRule;
 import org.apache.cloudstack.resourcedetail.FirewallRuleDetailVO;
 import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.springframework.stereotype.Component;
 
 import javax.inject.Inject;
@@ -126,56 +113,54 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.LongFunction;
-import java.util.stream.Collectors;
 
 @Component
-public class NsxElement extends AdapterBase implements  DhcpServiceProvider, DnsServiceProvider, VpcProvider,
+public class NetrisElement extends AdapterBase implements DhcpServiceProvider, DnsServiceProvider, VpcProvider,
         StaticNatServiceProvider, IpDeployer, PortForwardingServiceProvider, NetworkACLServiceProvider,
-        LoadBalancingServiceProvider, FirewallServiceProvider, InternalLoadBalancerElementService, ResourceStateAdapter, Listener {
+        LoadBalancingServiceProvider, ResourceStateAdapter, Listener {
 
-
-    @Inject
-    AccountManager accountMgr;
-    @Inject
-    NsxServiceImpl nsxService;
     @Inject
-    DataCenterDao dataCenterDao;
-    @Inject
-    NetworkDao networkDao;
+    NetworkModel networkModel;
     @Inject
     AgentManager agentManager;
     @Inject
     ResourceManager resourceManager;
     @Inject
-    PhysicalNetworkDao physicalNetworkDao;
+    private NetrisService netrisService;
     @Inject
-    NetworkModel networkModel;
+    private AccountManager accountManager;
     @Inject
-    DomainDao domainDao;
+    private DataCenterDao dataCenterDao;
     @Inject
-    protected VpcOfferingServiceMapDao vpcOfferingServiceMapDao;
+    private NetworkDao networkDao;
     @Inject
-    IPAddressDao ipAddressDao;
+    private DomainDao domainDao;
     @Inject
-    VMInstanceDao vmInstanceDao;
+    private VpcDao vpcDao;
     @Inject
-    VpcDao vpcDao;
+    private FirewallRuleDetailsDao firewallRuleDetailsDao;
     @Inject
-    LoadBalancerVMMapDao lbVmMapDao;
+    private IPAddressDao ipAddressDao;
     @Inject
-    VirtualRouterProviderDao vrProviderDao;
+    private VMInstanceDao vmInstanceDao;
     @Inject
-    PhysicalNetworkServiceProviderDao pNtwkSvcProviderDao;
-    @Inject
-    FirewallRuleDetailsDao firewallRuleDetailsDao;
+    LoadBalancerVMMapDao lbVmMapDao;
 
     protected Logger logger = LogManager.getLogger(getClass());
 
     private final Map<Network.Service, Map<Network.Capability, String>> capabilities = initCapabilities();
 
+    protected static List<AutoScaleCounter> getNetrisAutoScaleCounters() {
+        AutoScaleCounter counter;
+        final List<AutoScaleCounter> counterList = new ArrayList<>();
+        counter = new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.Cpu);
+        counterList.add(counter);
+        counter = new AutoScaleCounter(AutoScaleCounter.AutoScaleCounterType.Memory);
+        counterList.add(counter);
+        return counterList;
+    }
 
-    private static Map<Network.Service, Map<Network.Capability, String>> initCapabilities() {
+    protected static Map<Network.Service, Map<Network.Capability, String>> initCapabilities() {
         Map<Network.Service, Map<Network.Capability, String>> capabilities = new HashMap<>();
 
         Map<Network.Capability, String> dhcpCapabilities = Map.of(Network.Capability.DhcpAccrossMultipleSubnets, "true");
@@ -194,6 +179,10 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         lbCapabilities.put(Network.Capability.SupportedProtocols, "tcp, udp");
         lbCapabilities.put(Network.Capability.SupportedStickinessMethods, VirtualRouterElement.getHAProxyStickinessCapability());
         lbCapabilities.put(Network.Capability.LbSchemes, String.join(",", LoadBalancerContainer.Scheme.Internal.name(), LoadBalancerContainer.Scheme.Public.name()));
+        final Gson gson = new Gson();
+        final String autoScaleCounterList = gson.toJson(getNetrisAutoScaleCounters());
+        lbCapabilities.put(Network.Capability.AutoScaleCounters, autoScaleCounterList);
+        lbCapabilities.put(Network.Capability.VmAutoScaling, "true");
 
         capabilities.put(Network.Service.Lb, lbCapabilities);
         capabilities.put(Network.Service.PortForwarding, null);
@@ -210,9 +199,78 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         Map<Network.Capability, String> sourceNatCapabilities = new HashMap<>();
         sourceNatCapabilities.put(Network.Capability.RedundantRouter, "true");
         sourceNatCapabilities.put(Network.Capability.SupportedSourceNatTypes, "peraccount");
+        capabilities.put(Network.Service.Gateway, null);
         capabilities.put(Network.Service.SourceNat, sourceNatCapabilities);
         return capabilities;
     }
+
+    @Override
+    public boolean processAnswers(long agentId, long seq, Answer[] answers) {
+        return false;
+    }
+
+    @Override
+    public boolean processCommands(long agentId, long seq, Command[] commands) {
+        return false;
+    }
+
+    @Override
+    public AgentControlAnswer processControlCommand(long agentId, AgentControlCommand cmd) {
+        return null;
+    }
+
+    @Override
+    public void processHostAdded(long hostId) {
+        // Do nothing
+    }
+
+    @Override
+    public void processConnect(Host host, StartupCommand cmd, boolean forRebalance) throws ConnectionException {
+        // Do nothing
+    }
+
+    @Override
+    public boolean processDisconnect(long agentId, Status state) {
+        return false;
+    }
+
+    @Override
+    public void processHostAboutToBeRemoved(long hostId) {
+        // Do nothing
+    }
+
+    @Override
+    public void processHostRemoved(long hostId, long clusterId) {
+        // Do nothing
+    }
+
+    @Override
+    public boolean isRecurring() {
+        return false;
+    }
+
+    @Override
+    public int getTimeout() {
+        return 0;
+    }
+
+    @Override
+    public boolean processTimeout(long agentId, long seq) {
+        return false;
+    }
+
+    protected boolean canHandle(Network network, Network.Service service) {
+        logger.debug("Checking if Netris Element can handle service " + service.getName() + " on network "
+                + network.getDisplayText());
+
+        if (!networkModel.isProviderForNetwork(getProvider(), network.getId())) {
+            logger.debug("Netris Element is not a provider for network " + network.getDisplayText());
+            return false;
+        }
+
+        return true;
+    }
+
     @Override
     public boolean addDhcpEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
         return true;
@@ -265,12 +323,18 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
 
     @Override
     public Network.Provider getProvider() {
-        return Network.Provider.Nsx;
+        return Network.Provider.Netris;
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        agentManager.registerForHostEvents(this, true, true, true);
+        resourceManager.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
+        return true;
     }
 
     @Override
     public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
-        // TODO: Check if the network is NSX based (was already implemented as part of the guru.setup()
         return true;
     }
 
@@ -284,6 +348,11 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         return false;
     }
 
+    @Override
+    public boolean releaseIp(IpAddress ipAddress) {
+        return netrisService.releaseNatIp(ipAddress.getDataCenterId(), ipAddress.getAddress().addr());
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
         return canHandle(network, Network.Service.Connectivity);
@@ -291,7 +360,7 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
 
     @Override
     public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
-        Account account = accountMgr.getAccount(network.getAccountId());
+        Account account = accountManager.getAccount(network.getAccountId());
         NetworkVO networkVO = networkDao.findById(network.getId());
         DataCenterVO zone = dataCenterDao.findById(network.getDataCenterId());
         DomainVO domain = domainDao.findById(account.getDomainId());
@@ -300,7 +369,16 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
             logger.error(msg);
             throw new CloudRuntimeException(msg);
         }
-        return nsxService.deleteNetwork(zone.getId(), account.getId(), domain.getId(), networkVO);
+        String vpcName = null;
+        Long vpcId = network.getVpcId();
+        if (Objects.nonNull(vpcId)) {
+            VpcVO vpc = vpcDao.findById(vpcId);
+            if (Objects.nonNull(vpc)) {
+                vpcName = vpc.getName();
+            }
+        }
+        netrisService.deleteVnetResource(zone.getId(), account.getId(), domain.getId(), vpcName, vpcId, networkVO.getName(), network.getId(), network.getCidr());
+        return true;
     }
 
     @Override
@@ -323,23 +401,6 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         return true;
     }
 
-    @Override
-    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
-        agentManager.registerForHostEvents(this, true, true, true);
-        resourceManager.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
-        return true;
-    }
-
-    @Override
-    public boolean start() {
-        return false;
-    }
-
-    @Override
-    public boolean stop() {
-        return false;
-    }
-
     @Override
     public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
         return null;
@@ -347,7 +408,7 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
 
     @Override
     public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map<String, String> details, List<String> hostTags) {
-        if (!(startup[0] instanceof StartupNsxCommand)) {
+        if (!(startup[0] instanceof StartupNetrisCommand)) {
             return null;
         }
         host.setType(Host.Type.L2Networking);
@@ -359,164 +420,263 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         return null;
     }
 
-    private DomainVO getDomainFromAccount(Account account) {
-        DomainVO domain = domainDao.findById(account.getDomainId());
-        if (Objects.isNull(domain)) {
-            String msg = String.format("Unable to find domain with id: %s", account.getDomainId());
-            logger.error(msg);
-            throw new CloudRuntimeException(msg);
-        }
-        return domain;
-    }
-
     @Override
     public boolean implementVpc(Vpc vpc, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
-        DataCenterVO zone = zoneFunction.apply(vpc.getZoneId());
-        Pair<Boolean, Account> isNsxAndAccount = validateVpcConfigurationAndGetAccount(zone, vpc);
-        if (Boolean.FALSE.equals(isNsxAndAccount.first())) {
-            return true;
-        }
-        if (Boolean.TRUE.equals(isNsxAndAccount.first()) && Objects.isNull(isNsxAndAccount.second())) {
-            throw new InvalidParameterValueException(String.format("Failed to find account with id %s", vpc.getAccountId()));
-        }
         return true;
     }
 
     @Override
-    public boolean shutdownVpc(Vpc vpc, ReservationContext context) throws ConcurrentOperationException {
-        DataCenterVO zone = zoneFunction.apply(vpc.getZoneId());
-        Pair<Boolean, Account> isNsxAndAccount = validateVpcConfigurationAndGetAccount(zone, vpc);
-        if (Boolean.FALSE.equals(isNsxAndAccount.first())) {
-            return true;
-        }
-        if (Boolean.TRUE.equals(isNsxAndAccount.first()) && Objects.isNull(isNsxAndAccount.second())) {
-            throw new InvalidParameterValueException(String.format("Failed to find account with id %s", vpc.getAccountId()));
-        }
-        Account account = isNsxAndAccount.second();
-        DomainVO domain = getDomainFromAccount(account);
-        return nsxService.deleteVpcNetwork(vpc.getZoneId(), account.getId(), domain.getId(), vpc.getId(), vpc.getName());
-    }
-
-    private Pair<Boolean, Account> validateVpcConfigurationAndGetAccount(DataCenterVO zone, Vpc vpc) {
-        if (Objects.isNull(zone)) {
-            throw new InvalidParameterValueException(String.format("Failed to find zone with id %s", vpc.getZoneId()));
-        }
-        Account account = null;
-        boolean forNsx = false;
-        List<PhysicalNetworkVO> physicalNetworks = physicalNetworkDao.listByZoneAndTrafficType(zone.getId(), Networks.TrafficType.Guest);
-        if (CollectionUtils.isNullOrEmpty(physicalNetworks)) {
-            String err = String.format("Desired physical network is not present in the zone %s for traffic type %s. ", zone.getName(), Networks.TrafficType.Guest.name());
-            logger.error(err);
-            throw new InvalidConfigurationException(err);
-        }
-        List<PhysicalNetworkVO> filteredPhysicalNetworks = physicalNetworks.stream().filter(x -> x.getIsolationMethods().contains("NSX")).collect(Collectors.toList());
-        if (CollectionUtils.isNullOrEmpty(filteredPhysicalNetworks)) {
-            String err = String.format("No physical network with NSX isolation type for traffic type %s is present in the zone %s.", Networks.TrafficType.Guest.name(), zone.getName());
-            logger.error(err);
-            throw new InvalidConfigurationException(err);
-        }
-        if (filteredPhysicalNetworks.get(0).getIsolationMethods().contains("NSX")) {
-            account = accountMgr.getAccount(vpc.getAccountId());
-            forNsx = true;
-        }
-        return new Pair<>(forNsx, account);
+    public boolean shutdownVpc(Vpc vpc, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
+        long zoneId = vpc.getZoneId();
+        long accountId = vpc.getAccountId();
+        long domainId = vpc.getDomainId();
+        return netrisService.deleteVpcResource(zoneId, accountId, domainId, vpc);
     }
 
     @Override
     public boolean createPrivateGateway(PrivateGateway gateway) throws ConcurrentOperationException, ResourceUnavailableException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean deletePrivateGateway(PrivateGateway privateGateway) throws ConcurrentOperationException, ResourceUnavailableException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean applyStaticRoutes(Vpc vpc, List<StaticRouteProfile> routes) throws ResourceUnavailableException {
-        return false;
+        List<StaticRoute> existingStaticRoutes = netrisService.listStaticRoutes(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, null, null, null);
+        List<String> staticRouteCidrs = new ArrayList<>();
+        for(StaticRouteProfile staticRoute : routes) {
+            if (StaticRoute.State.Add == staticRoute.getState()) {
+                netrisService.addOrUpdateStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId(), false);
+            } else if (StaticRoute.State.Revoke == staticRoute.getState()) {
+                netrisService.deleteStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId());
+            } else if (StaticRoute.State.Update == staticRoute.getState()) {
+                netrisService.addOrUpdateStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), staticRoute.getGateway(), staticRoute.getId(), true);
+            }
+            staticRouteCidrs.add(staticRoute.getCidr());
+        }
+        for (StaticRoute staticRoute : existingStaticRoutes) {
+            if (!staticRouteCidrs.contains(staticRoute.getCidr())) {
+                logger.info("Revoking static route with cidr {} which are not used by VPC {}", staticRoute.getCidr(), vpc);
+                netrisService.deleteStaticRoute(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getName(), vpc.getId(), true, staticRoute.getCidr(), null, staticRoute.getId());
+            }
+        }
+        return true;
     }
 
     @Override
     public boolean applyACLItemsToPrivateGw(PrivateGateway gateway, List<? extends NetworkACLItem> rules) throws ResourceUnavailableException {
-        return false;
+        return true;
     }
 
     @Override
-    public boolean processAnswers(long agentId, long seq, Answer[] answers) {
-        return false;
+    public boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address) {
+        return netrisService.updateVpcSourceNatIp(vpc, address);
     }
 
     @Override
-    public boolean processCommands(long agentId, long seq, Command[] commands) {
-        return false;
+    public boolean updateVpc(Vpc vpc, String previousVpcName) {
+        return netrisService.updateVpcResource(vpc.getZoneId(), vpc.getAccountId(), vpc.getDomainId(), vpc.getId(), vpc.getName(), previousVpcName);
     }
 
     @Override
-    public AgentControlAnswer processControlCommand(long agentId, AgentControlCommand cmd) {
-        return null;
-    }
+    public boolean applyNetworkACLs(Network network, List<? extends NetworkACLItem> rules) throws ResourceUnavailableException {
+        if (!canHandle(network, Network.Service.NetworkACL)) {
+            return false;
+        }
 
-    @Override
-    public void processHostAdded(long hostId) {
-        // Do nothing
+        List<NetrisNetworkRule> nsxDelNetworkRules = new ArrayList<>();
+        boolean success = true;
+        for (NetworkACLItem rule : rules) {
+            String privatePort = PortForwardingServiceProvider.getPrivatePortRangeForACLRule(rule);
+            NetrisNetworkRule networkRule = getNetrisNetworkRuleForAcl(rule, privatePort);
+            if (Arrays.asList(NetworkACLItem.State.Active, NetworkACLItem.State.Add).contains(rule.getState())) {
+                success = success && netrisService.addFirewallRules(network, List.of(networkRule));
+            } else if (NetworkACLItem.State.Revoke == rule.getState()) {
+                nsxDelNetworkRules.add(networkRule);
+            }
+        }
+
+        if (!nsxDelNetworkRules.isEmpty()) {
+            success = netrisService.deleteFirewallRules(network, nsxDelNetworkRules);
+            if (!success) {
+                logger.warn("Not all firewall rules were successfully deleted");
+            }
+        }
+        return success;
     }
 
-    @Override
-    public void processConnect(Host host, StartupCommand cmd, boolean forRebalance) throws ConnectionException {
-        // Do nothing
+    private NetrisNetworkRule getNetrisNetworkRuleForAcl(NetworkACLItem rule, String privatePort) {
+        SDNProviderNetworkRule baseNetworkRule = new SDNProviderNetworkRule.Builder()
+                .setRuleId(rule.getId())
+                .setSourceCidrList(!CollectionUtils.isNullOrEmpty(rule.getSourceCidrList())  ? transformCidrListValues(rule.getSourceCidrList()) : List.of("ANY"))
+                .setTrafficType(rule.getTrafficType().toString())
+                .setProtocol(rule.getProtocol().toUpperCase())
+                .setPublicPort(String.valueOf(rule.getSourcePortStart()))
+                .setPrivatePort(String.valueOf(privatePort))
+                .setIcmpCode(rule.getIcmpCode())
+                .setIcmpType(rule.getIcmpType())
+                .setService(Network.Service.NetworkACL)
+                .build();
+        return new NetrisNetworkRule.Builder()
+                .baseRule(baseNetworkRule)
+                .aclAction(transformActionValue(rule.getAction()))
+                        .reason(rule.getReason())
+                .build();
     }
 
-    @Override
-    public boolean processDisconnect(long agentId, Status state) {
-        return false;
+    protected List<String> transformCidrListValues(List<String> sourceCidrList) {
+        List<String> list = new ArrayList<>();
+        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(sourceCidrList)) {
+            for (String cidr : sourceCidrList) {
+                if (cidr.equals("0.0.0.0/0")) {
+                    list.add("ANY");
+                } else {
+                    list.add(cidr);
+                }
+            }
+        }
+        return list;
     }
 
-    @Override
-    public void processHostAboutToBeRemoved(long hostId) {
-        // Do nothing
+    protected NetrisNetworkRule.NetrisRuleAction transformActionValue(NetworkACLItem.Action action) {
+        if (action == NetworkACLItem.Action.Allow) {
+            return NetrisNetworkRule.NetrisRuleAction.PERMIT;
+        } else if (action == NetworkACLItem.Action.Deny) {
+            return NetrisNetworkRule.NetrisRuleAction.DENY;
+        }
+        String err = String.format("Unsupported action %s", action.toString());
+        logger.error(err);
+        throw new CloudRuntimeException(err);
     }
 
     @Override
-    public void processHostRemoved(long hostId, long clusterId) {
-        // Do nothing
+    public boolean reorderAclRules(Vpc vpc, List<? extends Network> networks, List<? extends NetworkACLItem> networkACLItems) {
+        List<NetrisNetworkRule> aclRulesList = new ArrayList<>();
+        for (NetworkACLItem rule : networkACLItems) {
+            String privatePort = PortForwardingServiceProvider.getPrivatePortRangeForACLRule(rule);
+            aclRulesList.add(getNetrisNetworkRuleForAcl(rule, privatePort));
+        }
+        for (Network network: networks) {
+            netrisService.deleteFirewallRules(network, aclRulesList);
+        }
+        boolean success = true;
+        for (Network network : networks) {
+            for (NetrisNetworkRule aclRule : aclRulesList) {
+                success = success && netrisService.addFirewallRules(network, List.of(aclRule));
+            }
+        }
+        return success;
     }
 
     @Override
-    public boolean isRecurring() {
-        return false;
+    public IpDeployer getIpDeployer(Network network) {
+        return this;
     }
 
     @Override
-    public int getTimeout() {
-        return 0;
+    public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException {
+        if (!canHandle(network, Network.Service.PortForwarding)) {
+            return false;
+        }
+        return applyPFRulesInternal(network, rules);
     }
 
-    @Override
-    public boolean processTimeout(long agentId, long seq) {
-        return false;
+    private boolean addOrRemovePFRuleOnNetris(UserVm vm, PortForwardingRule rule, NetrisNetworkRule networkRule, SDNProviderOpObject netrisObject, boolean create) {
+        logger.debug("{} port forwarding rule on Netris for VM {} to ports {} - {}",
+                create ? "Creating" : "Deleting", vm.getUuid(), rule.getDestinationPortStart(), rule.getDestinationPortEnd());
+        Long vpcId = netrisObject.getVpcVO() != null ? netrisObject.getVpcVO().getId() : null;
+        String vpcName = netrisObject.getVpcVO() != null ? netrisObject.getVpcVO().getName() : null;
+        Long networkId = netrisObject.getNetworkVO() != null ? netrisObject.getNetworkVO().getId() : null;
+        String networkName = netrisObject.getNetworkVO() != null ? netrisObject.getNetworkVO().getName() : null;
+        String vpcCidr = netrisObject.getVpcVO() != null ? netrisObject.getVpcVO().getCidr() : null;
+        SDNProviderNetworkRule baseNetRule = networkRule.getBaseRule();
+        return create ?
+                netrisService.createPortForwardingRule(baseNetRule.getZoneId(), baseNetRule.getAccountId(), baseNetRule.getDomainId(),
+                        vpcName, vpcId, networkName, networkId, netrisObject.isVpcResource(), vpcCidr, baseNetRule) :
+                netrisService.deletePortForwardingRule(baseNetRule.getZoneId(), baseNetRule.getAccountId(), baseNetRule.getDomainId(),
+                        vpcName, vpcId, networkName, networkId, netrisObject.isVpcResource(), vpcCidr, baseNetRule);
     }
 
-    protected boolean canHandle(Network network, Network.Service service) {
-        logger.debug("Checking if Nsx Element can handle service " + service.getName() + " on network "
-                + network.getDisplayText());
+    private boolean applyPFRulesInternal(Network network, List<PortForwardingRule> rules) {
+        return Transaction.execute((TransactionCallback<Boolean>) status -> {
+            boolean result = true;
+            for (PortForwardingRule rule : rules) {
+                IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId());
+                UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId());
+                if (vm == null && rule.getState() != FirewallRule.State.Revoke) {
+                    continue;
+                }
+                SDNProviderOpObject netrisObject = getNetrisOpObject(network);
+                String publicPort = PortForwardingServiceProvider.getPublicPortRange(rule);
+                String privatePort = PortForwardingServiceProvider.getPrivatePFPortRange(rule);
+                FirewallRuleDetailVO ruleDetail = firewallRuleDetailsDao.findDetail(rule.getId(), ApiConstants.NETRIS_DETAIL_KEY);
+
+                SDNProviderNetworkRule baseNetworkRule = new SDNProviderNetworkRule.Builder()
+                        .setDomainId(netrisObject.getDomainId())
+                .setAccountId(netrisObject.getAccountId())
+                .setZoneId(netrisObject.getZoneId())
+                .setNetworkResourceId(netrisObject.getNetworkResourceId())
+                .setNetworkResourceName(netrisObject.getNetworkResourceName())
+                .setVpcResource(netrisObject.isVpcResource())
+                .setVmId(Objects.nonNull(vm) ? vm.getId() : 0)
+                .setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null)
+                .setPublicIp(publicIp.getAddress().addr())
+                .setPrivatePort(privatePort)
+                .setPublicPort(publicPort)
+                .setRuleId(rule.getId())
+                .setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT))
+                        .build();
 
-        if (!networkModel.isProviderForNetwork(getProvider(), network.getId())) {
-            logger.debug("Nsx Element is not a provider for network " + network.getDisplayText());
-            return false;
-        }
+                NetrisNetworkRule networkRule = new NetrisNetworkRule.Builder().baseRule(baseNetworkRule).build();
 
-        return true;
+                if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) {
+                    boolean pfRuleResult = addOrRemovePFRuleOnNetris(vm, rule, networkRule, netrisObject, true);
+                    if (pfRuleResult) {
+                        logger.debug("Port forwarding rule {} created on Netris, adding detail on firewall rules details", rule.getId());
+                        if (ruleDetail == null && FirewallRule.State.Add == rule.getState()) {
+                            logger.debug("Adding new firewall detail for rule {}", rule.getId());
+                            firewallRuleDetailsDao.addDetail(rule.getId(), ApiConstants.NETRIS_DETAIL_KEY, "true", false);
+                        } else if (ruleDetail != null) {
+                            logger.debug("Updating firewall detail for rule {}", rule.getId());
+                            ruleDetail.setValue("true");
+                            firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
+                        }
+                    }
+                    result &= pfRuleResult;
+                } else if (rule.getState() == FirewallRule.State.Revoke) {
+                    boolean pfRuleResult = addOrRemovePFRuleOnNetris(vm, rule, networkRule, netrisObject, false);
+                    if (pfRuleResult && ruleDetail != null) {
+                        logger.debug("Updating firewall rule detail {} for rule {}, set to false", ruleDetail.getId(), rule.getId());
+                        ruleDetail.setValue("false");
+                        firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
+                    }
+                    result &= pfRuleResult;
+                }
+            }
+            return result;
+        });
     }
 
-    private final LongFunction<DataCenterVO> zoneFunction = zoneId -> dataCenterDao.findById(zoneId);
+    private SDNProviderOpObject getNetrisOpObject(Network network) {
+        Pair<VpcVO, NetworkVO> vpcOrNetwork = getVpcOrNetwork(network.getVpcId(), network.getId());
+        VpcVO vpc = vpcOrNetwork.first();
+        NetworkVO networkVO = vpcOrNetwork.second();
+        long domainId = getResourceId("domain", vpc, networkVO);
+        long accountId = getResourceId("account", vpc, networkVO);
+        long zoneId = getResourceId("zone", vpc, networkVO);
 
-    @Override
-    public IpDeployer getIpDeployer(Network network) {
-        return this;
+        return new SDNProviderOpObject.Builder()
+                .vpcVO(vpc)
+                .networkVO(networkVO)
+                .domainId(domainId)
+                .accountId(accountId)
+                .zoneId(zoneId)
+                .build();
     }
 
-    @Override
     public boolean applyStaticNats(Network config, List<? extends StaticNat> rules) throws ResourceUnavailableException {
         for(StaticNat staticNat : rules) {
             long sourceIpAddressId = staticNat.getSourceIpAddressId();
@@ -526,6 +686,7 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
             if (vm == null || networkModel.getNicInNetworkIncludingRemoved(vm.getId(), config.getId()) == null) {
                 continue;
             }
+            long vmId = vm.getId();
             Pair<VpcVO, NetworkVO> vpcOrNetwork = getVpcOrNetwork(config.getVpcId(), config.getId());
             VpcVO vpc = vpcOrNetwork.first();
             NetworkVO network = vpcOrNetwork.second();
@@ -533,90 +694,17 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
             String networkResourceName = Objects.nonNull(vpc) ? vpc.getName() : network.getName();
             boolean isVpcResource = Objects.nonNull(vpc);
             if (!staticNat.isForRevoke()) {
-                return nsxService.createStaticNatRule(config.getDataCenterId(), config.getDomainId(), config.getAccountId(),
-                        networkResourceId, networkResourceName, isVpcResource, vm.getId(),
-                        ipAddressVO.getAddress().addr(), staticNat.getDestIpAddress());
+                return netrisService.createStaticNatRule(config.getDataCenterId(), config.getAccountId(), config.getDomainId(),
+                       networkResourceName, networkResourceId, isVpcResource, vpc.getCidr(),
+                        ipAddressVO.getAddress().addr(), staticNat.getDestIpAddress(), vmId);
             } else {
-                return nsxService.deleteStaticNatRule(config.getDataCenterId(), config.getDomainId(), config.getAccountId(),
-                        networkResourceId, networkResourceName, isVpcResource);
+                return netrisService.deleteStaticNatRule(config.getDataCenterId(), config.getAccountId(), config.getDomainId(),
+                        networkResourceName, networkResourceId, isVpcResource, ipAddressVO.getAddress().addr(), vmId);
             }
         }
         return false;
     }
 
-    protected synchronized boolean applyPFRulesInternal(Network network, List<PortForwardingRule> rules) {
-        return Transaction.execute((TransactionCallback<Boolean>) status -> {
-            boolean result = true;
-            for (PortForwardingRule rule : rules) {
-                IPAddressVO publicIp = ApiDBUtils.findIpAddressById(rule.getSourceIpAddressId());
-                UserVm vm = ApiDBUtils.findUserVmById(rule.getVirtualMachineId());
-                if (vm == null && rule.getState() != FirewallRule.State.Revoke) {
-                    continue;
-                }
-                NsxOpObject nsxObject = getNsxOpObject(network);
-                String publicPort = getPublicPortRange(rule);
-
-                String privatePort = getPrivatePFPortRange(rule);
-
-                NsxNetworkRule networkRule = new NsxNetworkRule.Builder()
-                        .setDomainId(nsxObject.getDomainId())
-                        .setAccountId(nsxObject.getAccountId())
-                        .setZoneId(nsxObject.getZoneId())
-                        .setNetworkResourceId(nsxObject.getNetworkResourceId())
-                        .setNetworkResourceName(nsxObject.getNetworkResourceName())
-                        .setVpcResource(nsxObject.isVpcResource())
-                        .setVmId(Objects.nonNull(vm) ? vm.getId() : 0)
-                        .setVmIp(Objects.nonNull(vm) ? vm.getPrivateIpAddress() : null)
-                        .setPublicIp(publicIp.getAddress().addr())
-                        .setPrivatePort(privatePort)
-                        .setPublicPort(publicPort)
-                        .setRuleId(rule.getId())
-                        .setProtocol(rule.getProtocol().toUpperCase(Locale.ROOT))
-                        .build();
-                FirewallRuleDetailVO ruleDetail = firewallRuleDetailsDao.findDetail(rule.getId(), ApiConstants.FOR_NSX);
-                if (Arrays.asList(FirewallRule.State.Add, FirewallRule.State.Active).contains(rule.getState())) {
-                    if ((ruleDetail == null && FirewallRule.State.Add == rule.getState()) || (ruleDetail != null && !ruleDetail.getValue().equalsIgnoreCase("true"))) {
-                        logger.debug("Creating port forwarding rule on NSX for VM {} to ports {} - {}",
-                                vm.getUuid(), rule.getDestinationPortStart(), rule.getDestinationPortEnd());
-                        NsxAnswer answer = nsxService.createPortForwardRule(networkRule);
-                        boolean pfRuleResult = answer.getResult();
-                        if (pfRuleResult && !answer.isObjectExistent()) {
-                            logger.debug("Port forwarding rule {} created on NSX, adding detail on firewall rules details", rule.getId());
-                            if (ruleDetail == null && FirewallRule.State.Add == rule.getState()) {
-                                logger.debug("Adding new firewall detail for rule {}", rule.getId());
-                                firewallRuleDetailsDao.addDetail(rule.getId(), ApiConstants.FOR_NSX, "true", false);
-                            } else {
-                                logger.debug("Updating firewall detail for rule {}", rule.getId());
-                                ruleDetail.setValue("true");
-                                firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
-                            }
-                        }
-                        result &= pfRuleResult;
-                    }
-                } else if (rule.getState() == FirewallRule.State.Revoke) {
-                    if (ruleDetail == null || (ruleDetail != null && ruleDetail.getValue().equalsIgnoreCase("true"))) {
-                        boolean pfRuleResult = nsxService.deletePortForwardRule(networkRule);
-                        if (pfRuleResult && ruleDetail != null) {
-                            logger.debug("Updating firewall rule detail {} for rule {}, set to false", ruleDetail.getId(), rule.getId());
-                            ruleDetail.setValue("false");
-                            firewallRuleDetailsDao.update(ruleDetail.getId(), ruleDetail);
-                        }
-                        result &= pfRuleResult;
-                    }
-                }
-            }
-            return result;
-        });
-    }
-
-    @Override
-    public boolean applyPFRules(Network network, List<PortForwardingRule> rules) throws ResourceUnavailableException {
-        if (!canHandle(network, Network.Service.PortForwarding)) {
-            return false;
-        }
-        return applyPFRulesInternal(network, rules);
-    }
-
     public Pair<VpcVO, NetworkVO> getVpcOrNetwork(Long vpcId, long networkId) {
         VpcVO vpc = null;
         NetworkVO network = null;
@@ -634,30 +722,6 @@ public class NsxElement extends AdapterBase implements  DhcpServiceProvider, Dns
         return new Pair<>(vpc, network);
     }
 
-    private static String getPublicPortRange(PortForwardingRule rule) {
-        return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ?
-                String.valueOf(rule.getSourcePortStart()) :
-                String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd()));
-    }
-
-    private static String getPrivatePFPortRange(PortForwardingRule rule) {
-        return rule.getDestinationPortStart() == rule.getDestinationPortEnd() ?
-                String.valueOf(rule.getDestinationPortStart()) :
-                String.valueOf(rule.getDestinationPortStart()).concat("-").concat(String.valueOf(rule.getDestinationPortEnd()));
-    }
... 10598 lines suppressed ...