weizhouapache commented on code in PR #13032:
URL: https://github.com/apache/cloudstack/pull/13032#discussion_r3342343041


##########
framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/NetworkExtensionElement.java:
##########
@@ -0,0 +1,2728 @@
+// 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.framework.extensions.network;
+
+import java.io.File;
+import java.net.URI;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+
+import com.cloud.agent.api.to.LoadBalancerTO;
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.Vlan;
+import com.cloud.dc.VlanVO;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.VlanDao;
+import com.cloud.deploy.DeployDestination;
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.network.IpAddressManager;
+import com.cloud.network.IpAddress;
+import com.cloud.network.Network;
+import com.cloud.network.Network.Capability;
+import com.cloud.network.Network.Provider;
+import com.cloud.network.Network.Service;
+import com.cloud.network.NetworkModel;
+import com.cloud.network.Networks;
+import com.cloud.network.dao.NetworkDetailVO;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.PhysicalNetworkDao;
+import com.cloud.network.dao.PhysicalNetworkVO;
+import com.cloud.network.PhysicalNetworkServiceProvider;
+import com.cloud.network.PublicIpAddress;
+import com.cloud.network.addr.PublicIp;
+import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.dao.IPAddressDao;
+import com.cloud.network.dao.IPAddressVO;
+import com.cloud.network.dao.NetworkDetailsDao;
+import com.cloud.network.dao.NetworkServiceMapDao;
+import com.cloud.network.vpc.dao.VpcDao;
+import com.cloud.network.element.AggregatedCommandExecutor;
+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.NetworkElement;
+import com.cloud.network.element.PortForwardingServiceProvider;
+import com.cloud.network.element.SourceNatServiceProvider;
+import com.cloud.network.element.StaticNatServiceProvider;
+import com.cloud.network.element.UserDataServiceProvider;
+import com.cloud.network.element.VpcProvider;
+import com.cloud.network.vpc.NetworkACLItem;
+import com.cloud.network.vpc.PrivateGateway;
+import com.cloud.network.vpc.StaticRouteProfile;
+import com.cloud.network.vpc.Vpc;
+import com.cloud.network.lb.LoadBalancingRule;
+import com.cloud.network.rules.FirewallRule;
+import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.rules.PortForwardingRule;
+import com.cloud.network.rules.StaticNat;
+import com.cloud.offerings.NetworkOfferingVO;
+import com.cloud.offerings.dao.NetworkOfferingDao;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.storage.dao.GuestOSCategoryDao;
+import com.cloud.storage.dao.GuestOSDao;
+import com.cloud.user.AccountService;
+import com.cloud.uservm.UserVm;
+import com.cloud.offering.NetworkOffering;
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.Nic;
+import com.cloud.vm.NicProfile;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.ReservationContext;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.VMInstanceDetailVO;
+import com.cloud.vm.VmDetailConstants;
+import com.cloud.vm.dao.NicDao;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.VMInstanceDetailsDao;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.apache.cloudstack.api.ApiConstants;
+import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
+import org.apache.cloudstack.extension.Extension;
+import org.apache.cloudstack.extension.ExtensionHelper;
+import org.apache.cloudstack.extension.NetworkCustomActionProvider;
+import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao;
+import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao;
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.stream.Collectors;
+
+
+public class NetworkExtensionElement extends AdapterBase implements
+        NetworkElement, SourceNatServiceProvider, StaticNatServiceProvider,
+        PortForwardingServiceProvider, IpDeployer, NetworkCustomActionProvider,
+        DhcpServiceProvider, DnsServiceProvider, FirewallServiceProvider,
+        UserDataServiceProvider, LoadBalancingServiceProvider,
+        VpcProvider, NetworkACLServiceProvider, AggregatedCommandExecutor {
+
+    private static final Map<Service, Map<Capability, String>> 
DEFAULT_CAPABILITIES = new HashMap<>();
+
+
+    /**
+     * When non-null, restricts all operations to the extension whose name
+     * matches this provider name.
+     */
+    private String providerName;
+
+    @Inject
+    private NetworkModel networkModel;
+    @Inject
+    private NetworkServiceMapDao ntwkSrvcDao;
+    @Inject
+    private ExtensionHelper extensionHelper;
+    @Inject
+    private NetworkDetailsDao networkDetailsDao;
+    @Inject
+    private IpAddressManager ipAddressManager;
+    @Inject
+    private NetworkOrchestrationService networkManager;
+    @Inject
+    private AccountService accountService;
+    @Inject
+    private PhysicalNetworkDao physicalNetworkDao;
+    @Inject
+    private ExtensionDetailsDao extensionDetailsDao;
+    @Inject
+    private NetworkDao networkDao;
+    @Inject
+    private DataCenterDao dataCenterDao;
+    @Inject
+    private VlanDao vlanDao;
+    @Inject
+    private GuestOSCategoryDao guestOSCategoryDao;
+    @Inject
+    private GuestOSDao guestOSDao;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private VMInstanceDetailsDao vmInstanceDetailsDao;
+    @Inject
+    private UserVmDao userVmDao;
+    @Inject
+    private NicDao nicDao;
+    @Inject
+    private NetworkOfferingDao networkOfferingDao;
+    @Inject
+    private ServiceOfferingDao serviceOfferingDao;
+    @Inject
+    private FirewallRulesDao firewallRulesDao;
+    @Inject
+    private IPAddressDao ipAddressDao;
+    @Inject
+    private VpcDao vpcDao;
+    @Inject
+    private VpcDetailsDao vpcDetailsDao;
+
+    // ---- Script argument names ----
+
+    public static final String ARG_PHYSICAL_NETWORK_EXTENSION_DETAILS = 
"physical-network-extension-details";
+    public static final String ARG_NETWORK_EXTENSION_DETAILS = 
"network-extension-details";
+    public static final String ARG_PAYLOAD = "payload";
+    public static final String ARG_ACTION_PARAMS = "action-params";
+
+    public static final int DEFAULT_SCRIPT_TIMEOUT_SECONDS = 60;
+
+    public static final int EXIT_CODE_SUCCESS = 0;
+    public static final int EXIT_CODE_FAILURE = -1;
+
+    // ---- Script command names ----
+
+    public static final String CMD_IMPLEMENT_NETWORK = "implement-network";
+    public static final String CMD_SHUTDOWN_NETWORK = "shutdown-network";
+    public static final String CMD_DESTROY_NETWORK = "destroy-network";
+    public static final String CMD_ENSURE_NETWORK_DEVICE = 
"ensure-network-device";
+    public static final String CMD_ASSIGN_IP = "assign-ip";
+    public static final String CMD_RELEASE_IP = "release-ip";
+    public static final String CMD_ADD_STATIC_NAT = "add-static-nat";
+    public static final String CMD_DELETE_STATIC_NAT = "delete-static-nat";
+    public static final String CMD_ADD_PORT_FORWARD = "add-port-forward";
+    public static final String CMD_DELETE_PORT_FORWARD = "delete-port-forward";
+    public static final String CMD_ADD_DHCP_ENTRY = "add-dhcp-entry";
+    public static final String CMD_CONFIG_DHCP_SUBNET = "config-dhcp-subnet";
+    public static final String CMD_REMOVE_DHCP_SUBNET = "remove-dhcp-subnet";
+    public static final String CMD_SET_DHCP_OPTIONS = "set-dhcp-options";
+    public static final String CMD_REMOVE_DHCP_ENTRY = "remove-dhcp-entry";
+    public static final String CMD_ADD_DNS_ENTRY = "add-dns-entry";
+    public static final String CMD_CONFIG_DNS_SUBNET = "config-dns-subnet";
+    public static final String CMD_REMOVE_DNS_SUBNET = "remove-dns-subnet";
+    public static final String CMD_SAVE_VM_DATA = "save-vm-data";
+    public static final String CMD_SAVE_PASSWORD = "save-password";
+    public static final String CMD_SAVE_USERDATA = "save-userdata";
+    public static final String CMD_SAVE_SSHKEY = "save-sshkey";
+    public static final String CMD_SAVE_HYPERVISOR_HOSTNAME = 
"save-hypervisor-hostname";
+    public static final String CMD_APPLY_LB_RULES = "apply-lb-rules";
+    public static final String CMD_APPLY_FW_RULES = "apply-fw-rules";
+    public static final String CMD_RESTORE_NETWORK = "restore-network";
+    public static final String CMD_IMPLEMENT_VPC = "implement-vpc";
+    public static final String CMD_SHUTDOWN_VPC = "shutdown-vpc";
+    public static final String CMD_UPDATE_VPC_SOURCE_NAT_IP = 
"update-vpc-source-nat-ip";
+    public static final String CMD_APPLY_NETWORK_ACL = "apply-network-acl";
+    public static final String CMD_CUSTOM_ACTION = "custom-action";
+
+    // ---- Network detail key ----
+
+    /**
+     * Key used to persist the per-network JSON blob in {@code 
network_details}.
+     * The blob is produced by the network-extension.sh's {@code 
ensure-network-device}
+     * command and may contain any fields the script needs (e.g. selected host,
+     * namespace name, VRF ID, …).
+     */
+    public static final String NETWORK_DETAIL_EXTENSION_DETAILS = 
"extension.details";
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    /**
+     * Returns a new {@link NetworkExtensionElement} scoped to {@code 
providerName},
+     * sharing all injected dependencies with this instance.
+     */
+    public NetworkExtensionElement withProviderName(String providerName) {
+        NetworkExtensionElement copy = new NetworkExtensionElement();
+        copy.networkModel                   = this.networkModel;
+        copy.ntwkSrvcDao                    = this.ntwkSrvcDao;
+        copy.extensionHelper                = this.extensionHelper;
+        copy.networkDetailsDao              = this.networkDetailsDao;
+        copy.ipAddressManager               = this.ipAddressManager;
+        copy.physicalNetworkDao             = this.physicalNetworkDao;
+        copy.extensionDetailsDao            = this.extensionDetailsDao;
+        copy.networkDao                     = this.networkDao;
+        copy.dataCenterDao                  = this.dataCenterDao;
+        copy.vlanDao                        = this.vlanDao;
+        copy.guestOSCategoryDao             = this.guestOSCategoryDao;
+        copy.guestOSDao                     = this.guestOSDao;
+        copy.hostDao                        = this.hostDao;
+        copy.vmInstanceDetailsDao           = this.vmInstanceDetailsDao;
+        copy.userVmDao                      = this.userVmDao;
+        copy.nicDao                         = this.nicDao;
+        copy.networkManager                 = this.networkManager;
+        copy.networkOfferingDao             = this.networkOfferingDao;
+        copy.serviceOfferingDao             = this.serviceOfferingDao;
+        copy.accountService                 = this.accountService;
+        copy.firewallRulesDao               = this.firewallRulesDao;
+        copy.ipAddressDao                   = this.ipAddressDao;
+        copy.vpcDao                         = this.vpcDao;
+        copy.vpcDetailsDao                  = this.vpcDetailsDao;
+        copy.providerName                   = providerName;
+
+        logger.debug("NetworkExtensionElement initialised with provider name 
'{}'", providerName);
+        return copy;
+    }
+
+    // ---- Capabilities ----
+
+    @Override
+    public Map<Service, Map<Capability, String>> getCapabilities() {
+        try {
+            // If this element is scoped to a provider name, prefer 
capabilities stored
+            // in the extension's "network.service.capabilities" detail.  The 
ExtensionHelper
+            // exposes a helper that loads the Service→Capability map from the 
DB.
+            if (providerName != null && !providerName.isBlank()) {
+                Map<Service, Map<Capability, String>> caps = 
extensionHelper.getNetworkCapabilitiesForProvider(null, providerName);
+                if (caps != null && !caps.isEmpty()) {
+                    return caps;
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("Failed to load network service capabilities from 
extension details for provider '{}': {}", providerName, e.getMessage());
+        }
+
+        return DEFAULT_CAPABILITIES;
+    }
+
+    @Override
+    public Provider getProvider() {
+        if (providerName != null) {
+            return Provider.createTransientProvider(providerName);
+        }
+        return Provider.NetworkExtension;
+    }
+
+    // ---- Extension / provider resolution ----
+
+    protected Extension resolveExtension(Network network) {
+        Long physicalNetworkId = network.getPhysicalNetworkId();
+        if (physicalNetworkId == null) {
+            logger.warn("Network {} has no physical network — cannot resolve 
extension", network.getId());
+            return null;
+        }
+        if (providerName != null && !providerName.isBlank()) {
+            Extension ext = 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, 
providerName);
+            if (ext != null) {
+                return ext;
+            }
+            logger.warn("No extension found for scoped provider '{}' on 
physical network {}", providerName, physicalNetworkId);
+        }
+        List<String> providers = 
ntwkSrvcDao.getDistinctProviders(network.getId());
+        if (providers != null) {
+            for (String p : providers) {
+                Extension ext = 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, p);
+                if (ext != null) {
+                    return ext;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected boolean canHandle(Network network, Service service) {
+        Long physicalNetworkId = network.getPhysicalNetworkId();
+        if (physicalNetworkId == null) {
+            return false;
+        }
+        if (providerName != null && !providerName.isBlank()) {
+            boolean hasExt = 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, 
providerName) != null;
+            if (!hasExt) {
+                return false;
+            }
+            if (service == null) {
+                return true;
+            }
+            List<String> sp = 
ntwkSrvcDao.getProvidersForServiceInNetwork(network.getId(), service);
+            return sp != null && sp.stream()
+                    .anyMatch(p -> 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, p) 
!= null);
+        }
+        List<String> providers = 
ntwkSrvcDao.getDistinctProviders(network.getId());
+        if (providers == null || providers.isEmpty()) {
+            return false;
+        }
+        boolean hasExtProv = providers.stream().anyMatch(
+                p -> 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, p) 
!= null);
+        if (!hasExtProv) {
+            return false;
+        }
+        if (service == null) {
+            return true;
+        }
+        List<String> sp = 
ntwkSrvcDao.getProvidersForServiceInNetwork(network.getId(), service);
+        return sp != null && sp.stream()
+                .anyMatch(p -> 
extensionHelper.getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, p) 
!= null);
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws 
ConfigurationException {
+        super.configure(name, params);
+        return true;
+    }
+
+    // ---- NetworkElement lifecycle ----
+
+    @Override
+    public boolean implement(Network network, NetworkOffering offering, 
DeployDestination dest,
+            ReservationContext context) throws ConcurrentOperationException,
+            ResourceUnavailableException, InsufficientCapacityException {
+        if (!canHandle(network, null)) {
+            return false;
+        }
+        logger.info("Implementing network extension for network {} (VLAN {})", 
network.getId(), network.getBroadcastUri());
+
+        // Step 1: Ensure a network device is selected and its details stored.
+        ensureExtensionDetails(network);
+
+        // Step 2: Allocate the IPs for DHCP/DNS/UserData service if needed
+        String extensionIp = ensureExtensionIp(network);
+
+        String vlanId = getVlanId(network);
+
+        // Step 2: Create the network on the device.
+        JsonObject implementPayload = new JsonObject();
+        implementPayload.addProperty("network_id", 
String.valueOf(network.getId()));
+        implementPayload.addProperty("vlan", safeStr(vlanId));
+        implementPayload.addProperty("gateway", safeStr(network.getGateway()));
+        implementPayload.addProperty("cidr", safeStr(network.getCidr()));
+        implementPayload.addProperty("extension_ip", safeStr(extensionIp));
+        addVpcIdToPayload(implementPayload, network);
+
+        Pair<Integer, String> result = executeScriptAndReturnOutput(network, 
CMD_IMPLEMENT_NETWORK, implementPayload);
+
+        if (result.first() != EXIT_CODE_SUCCESS) {
+            return false;
+        }
+
+        // Update the network properties from the output
+        applyNetworkUpdateFromScriptOutput(network, result.second());
+
+        // Step 3: Configure source NAT for both VPC and non-VPC networks for
+        // compatibility (other network-element providers may also implement 
VPC tiers).
+        // When this is a VPC tier, the script's assign-ip does nothing for 
source-nat
+        // because VPC source NAT is managed at the VPC level by 
implementVpc().
+        if (canHandle(network, Service.SourceNat)) {
+            try {
+                if (network.getVpcId() == null) {
+                    // Isolated network: apply the network's own source NAT IP.
+                    Account owner = 
accountService.getAccount(network.getAccountId());
+                    PublicIpAddress existingIp = 
networkModel.getSourceNatIpAddressForGuestNetwork(owner, network);
+                    if (existingIp != null) {
+                        applyIps(network, List.of(existingIp), 
Set.of(Service.SourceNat));
+                    }
+                } else {
+                    // VPC tier: apply the VPC-level source NAT IP (script is 
a no-op for SNAT).
+                    final PublicIpAddress vpcSourceNatIp = 
getVpcSourceNatIp(network.getVpcId());
+                    if (vpcSourceNatIp != null) {
+                        applyIps(network, List.of(vpcSourceNatIp), 
Set.of(Service.SourceNat));
+                    }
+                }
+            } catch (Exception e) {
+                logger.warn("Failed to configure source NAT IP for network {}: 
{}", network.getId(), e.getMessage(), e);
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean prepare(Network network, NicProfile nic, 
VirtualMachineProfile vm,
+            DeployDestination dest, ReservationContext context)
+            throws ConcurrentOperationException, ResourceUnavailableException, 
InsufficientCapacityException {
+        // Copy from VirtualRouterElement.java
+        if (vm.getType() != VirtualMachine.Type.User || vm.getHypervisorType() 
== Hypervisor.HypervisorType.BareMetal) {
+            return false;
+        }
+
+        if (!canHandle(network, null)) {
+            return false;
+        }
+
+        if 
(!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network),
 getProvider().getName())) {
+            return false;
+        }
+
+        // Sync nic with network
+        applyNicUpdateFromNetwork(network, nic.getId());
+
+        final NetworkOfferingVO offering = 
networkOfferingDao.findById(network.getNetworkOfferingId());
+        implement(network, offering, dest, context);
+
+        return true;
+    }
+
+    private void applyNicUpdateFromNetwork(Network network, Long nicId) {
+        if (nicId == null) {
+            return;
+        }
+        try {
+            NicVO nicVo = nicDao.findById(nicId);
+            if (nicVo == null) {
+                return;
+            }
+            if (network.getBroadcastUri() != null) {
+                nicVo.setBroadcastUri(network.getBroadcastUri());
+                nicVo.setIsolationUri(network.getBroadcastUri());
+            }
+            nicDao.update(nicId, nicVo);
+        } catch (Exception e) {
+            logger.debug("Failed to update nic {}: {}", nicId, e.getMessage());
+        }
+    }
+
+    @Override
+    public boolean release(Network network, NicProfile nic, 
VirtualMachineProfile vm,
+            ReservationContext context) throws ConcurrentOperationException, 
ResourceUnavailableException {
+        return true;

Review Comment:
   I will check if prepare and release nic can be improved



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to