diff --git a/api/src/main/java/com/cloud/agent/api/to/NicTO.java b/api/src/main/java/com/cloud/agent/api/to/NicTO.java index ca95fcfd6790..658c7f40d0e8 100644 --- a/api/src/main/java/com/cloud/agent/api/to/NicTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/NicTO.java @@ -17,6 +17,7 @@ package com.cloud.agent.api.to; import com.cloud.offering.NetworkOffering; +import com.cloud.vm.Nic; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ public class NicTO extends NetworkTO { boolean dpdkEnabled; Integer mtu; Long networkId; + Nic.LinkState linkState; String networkSegmentName; @@ -154,4 +156,12 @@ public String getNetworkSegmentName() { public void setNetworkSegmentName(String networkSegmentName) { this.networkSegmentName = networkSegmentName; } + + public Nic.LinkState getLinkState() { + return linkState; + } + + public void setLinkState(Nic.LinkState linkState) { + this.linkState = linkState; + } } diff --git a/api/src/main/java/com/cloud/vm/Nic.java b/api/src/main/java/com/cloud/vm/Nic.java index afc44b8d39fa..3c8868270686 100644 --- a/api/src/main/java/com/cloud/vm/Nic.java +++ b/api/src/main/java/com/cloud/vm/Nic.java @@ -37,6 +37,10 @@ enum Event { ReservationRequested, ReleaseRequested, CancelRequested, OperationCompleted, OperationFailed, } + enum LinkState { + Enabled, Disabled + } + public enum State implements FiniteState { Allocated("Resource is allocated but not reserved"), Reserving("Resource is being reserved right now"), Reserved("Resource has been reserved."), Releasing( "Resource is being released"), Deallocating("Resource is being deallocated"); @@ -162,4 +166,6 @@ public enum ReservationStrategy { String getIPv6Address(); Integer getMtu(); + + LinkState getLinkState(); } diff --git a/api/src/main/java/com/cloud/vm/NicProfile.java b/api/src/main/java/com/cloud/vm/NicProfile.java index a0c80ceb1bfb..e233005f8b71 100644 --- a/api/src/main/java/com/cloud/vm/NicProfile.java +++ b/api/src/main/java/com/cloud/vm/NicProfile.java @@ -52,6 +52,7 @@ public class NicProfile implements InternalIdentity, Serializable { boolean defaultNic; Integer networkRate; boolean isSecurityGroupEnabled; + Nic.LinkState linkState; Integer orderIndex; @@ -87,6 +88,7 @@ public NicProfile(Nic nic, Network network, URI broadcastUri, URI isolationUri, broadcastType = network.getBroadcastDomainType(); trafficType = network.getTrafficType(); format = nic.getAddressFormat(); + linkState = nic.getLinkState(); iPv4Address = nic.getIPv4Address(); iPv4Netmask = nic.getIPv4Netmask(); @@ -414,6 +416,14 @@ public void setIpv4AllocationRaceCheck(boolean ipv4AllocationRaceCheck) { this.ipv4AllocationRaceCheck = ipv4AllocationRaceCheck; } + public Nic.LinkState getLinkState() { + return linkState; + } + + public void setLinkState(Nic.LinkState linkState) { + this.linkState = linkState; + } + // // OTHER METHODS // diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 01f11b73cd41..09af5c0b9a44 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -152,6 +153,8 @@ void startVirtualMachineForHA(VirtualMachine vm, Map dc = new ArrayList(); + dc.add(ApiConstants.VMDetails.valueOf("nics")); + EnumSet details = EnumSet.copyOf(dc); + if (result != null){ + UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Restricted, "virtualmachine", details, result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update NIC from VM."); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java index f992514b8db2..cd3ae07b31b7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java @@ -18,6 +18,7 @@ import java.util.List; +import com.cloud.vm.Nic.LinkState; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; @@ -146,6 +147,10 @@ public class NicResponse extends BaseResponse { @Param(description = "Public IP address associated with this NIC via Static NAT rule") private String publicIp; + @SerializedName(ApiConstants.STATE) + @Param(description = "NIC's link state") + private LinkState linkState; + public void setVmId(String vmId) { this.vmId = vmId; } @@ -416,4 +421,12 @@ public void setPublicIpId(String publicIpId) { public void setPublicIp(String publicIp) { this.publicIp = publicIp; } + + public LinkState getLinkState() { + return linkState; + } + + public void setLinkState(LinkState linkState) { + this.linkState = linkState; + } } diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java new file mode 100644 index 000000000000..0af6d7fa3a3d --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java @@ -0,0 +1,27 @@ +// 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.api; + +public class UpdateVmNicAnswer extends Answer { + public UpdateVmNicAnswer() { + } + + public UpdateVmNicAnswer(UpdateVmNicCommand cmd, boolean success, String result) { + super(cmd, success, result); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java new file mode 100644 index 000000000000..34407dd47341 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java @@ -0,0 +1,53 @@ +// 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.api; + +import com.cloud.vm.Nic; + +public class UpdateVmNicCommand extends Command { + + String nicMacAddress; + String instanceName; + Nic.LinkState linkState; + + @Override + public boolean executeInSequence() { + return true; + } + + protected UpdateVmNicCommand() { + } + + public UpdateVmNicCommand(String nicMacAdderss, String instanceName, Nic.LinkState linkState) { + this.nicMacAddress = nicMacAdderss; + this.instanceName = instanceName; + this.linkState = linkState; + } + + public String getNicMacAddress() { + return nicMacAddress; + } + + public String getVmName() { + return instanceName; + } + + public Nic.LinkState getLinkState() { + return linkState; + } +} diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 702404546894..734bfedcbbc5 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -230,6 +230,8 @@ NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile request Boolean updateDefaultNicForVM(VirtualMachine vm, Nic nic, Nic defaultNic); + boolean updateVmNic(VirtualMachine vm, Nic nic, Nic.LinkState linkState) throws ResourceUnavailableException; + /** * @param vm * @param network diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index e8796fb02529..d3294df8bb25 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -153,6 +153,8 @@ import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UnmanageInstanceCommand; import com.cloud.agent.api.UnregisterVMCommand; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; import com.cloud.agent.api.VmStatsEntry; @@ -6270,6 +6272,80 @@ private Pair orchestrateUpdateDefaultNic(final VmWorkUpd _jobMgr.marshallResultObject(result)); } + @Override + public boolean updateVmNic(VirtualMachine vm, Nic nic, Nic.LinkState linkState) { + Outcome outcome = updateVmNicThroughJobQueue(vm, nic, linkState); + + retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateVmNic"); + + try { + Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + if (jobResult instanceof Boolean) { + return BooleanUtils.isTrue((Boolean) jobResult); + } + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new CloudRuntimeException(String.format("Exception while updating VM [%s] NIC. Check the logs for more information.", vm.getUuid())); + } + throw new CloudRuntimeException("Unexpected job execution result."); + } + + private boolean orchestrateUpdateVmNic(final VirtualMachine vm, final Nic nic, final Nic.LinkState linkState) throws ResourceUnavailableException { + if (vm.getState() == State.Running) { + try { + UpdateVmNicCommand updateVmNicCmd = new UpdateVmNicCommand(nic.getMacAddress(), vm.getName(), linkState); + Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("updatevmnic", updateVmNicCmd); + + _agentMgr.send(vm.getHostId(), cmds); + + UpdateVmNicAnswer updateVmNicAnswer = cmds.getAnswer(UpdateVmNicAnswer.class); + if (updateVmNicAnswer == null || !updateVmNicAnswer.getResult()) { + logger.warn("Unable to update VM %s NIC [{}].", vm.getName(), nic.getUuid()); + return false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException(String.format("Unable to update NIC %s for VM %s.", nic.getUuid(), vm.getUuid()), vm.getHostId(), e); + } + } + + NicVO nicVo = _nicsDao.findById(nic.getId()); + nicVo.setLinkState(linkState); + _nicsDao.persist(nicVo); + + return true; + } + + public Outcome updateVmNicThroughJobQueue(final VirtualMachine vm, final Nic nic, final Nic.LinkState linkState) { + Long vmId = vm.getId(); + String commandName = VmWorkUpdateNic.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkUpdateNic workInfo = new VmWorkUpdateNic(newVmWorkJobAndInfo.second(), nic.getId(), linkState); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @ReflectionUse + private Pair orchestrateUpdateVmNic(final VmWorkUpdateNic work) throws Exception { + VMInstanceVO vm = findVmById(work.getVmId()); + final NicVO nic = _entityMgr.findById(NicVO.class, work.getNicId()); + if (nic == null) { + throw new CloudRuntimeException(String.format("Unable to find NIC with ID %s.", work.getNicId())); + } + final boolean result = orchestrateUpdateVmNic(vm, nic, work.getLinkState()); + return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result)); + } + private Pair findClusterAndHostIdForVmFromVolumes(long vmId) { Long clusterId = null; Long hostId = null; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java new file mode 100644 index 000000000000..592d9f6892c7 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java @@ -0,0 +1,38 @@ +// 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.vm; + +public class VmWorkUpdateNic extends VmWork { + private static final long serialVersionUID = -8957066627929113278L; + + Long nicId; + Nic.LinkState linkState; + + public VmWorkUpdateNic(VmWork vmWork, Long nicId, Nic.LinkState linkState) { + super(vmWork); + this.nicId = nicId; + this.linkState = linkState; + } + + public Long getNicId() { + return nicId; + } + + public Nic.LinkState getLinkState() { + return linkState; + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 6c569e22dd95..86809c71a3e2 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -131,6 +131,9 @@ protected NicVO() { @Column(name = "mtu") Integer mtu; + @Column(name = "link_state") + LinkState linkState; + @Transient transient String nsxLogicalSwitchUuid; @@ -143,6 +146,7 @@ public NicVO(String reserver, Long instanceId, long configurationId, VirtualMach this.networkId = configurationId; this.state = State.Allocated; this.vmType = vmType; + this.linkState = LinkState.Enabled; } @Override @@ -397,6 +401,15 @@ public void setNsxLogicalSwitchPortUuid(String nsxLogicalSwitchPortUuid) { this.nsxLogicalSwitchPortUuid = nsxLogicalSwitchPortUuid; } + @Override + public LinkState getLinkState() { + return linkState; + } + + public void setLinkState(LinkState linkState) { + this.linkState = linkState; + } + @Override public int hashCode() { return new HashCodeBuilder(17, 31).append(id).toHashCode(); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index d69b524b85d9..23d05f647cea 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -114,3 +114,6 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKey -- Add conserve mode for VPC offerings CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' '); + +--- Disable/enable NICs +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','link_state', 'VARCHAR(10) NOT NULL DEFAULT ''Enabled'' COMMENT ''Indicates the link state of the NIC'''); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql index a12e02bcfdb8..4b38d5de8a3b 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql @@ -76,6 +76,7 @@ select nics.broadcast_uri broadcast_uri, nics.isolation_uri isolation_uri, nics.mtu mtu, + nics.link_state nic_link_state, vpc.id vpc_id, vpc.uuid vpc_uuid, vpc.name vpc_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index 94bc8640fd54..4a0026ef5be3 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -141,6 +141,7 @@ SELECT `nics`.`mac_address` AS `mac_address`, `nics`.`broadcast_uri` AS `broadcast_uri`, `nics`.`isolation_uri` AS `isolation_uri`, + `nics`.`link_state` AS `nic_link_state`, `vpc`.`id` AS `vpc_id`, `vpc`.`uuid` AS `vpc_uuid`, `networks`.`uuid` AS `network_uuid`, 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 da60f6fd7177..2960ab19de9f 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 @@ -30,6 +30,7 @@ import com.cloud.utils.net.NetUtils; import com.cloud.utils.script.OutputInterpreter; +import com.cloud.vm.Nic; import org.apache.commons.lang3.StringUtils; import org.libvirt.LibvirtException; @@ -275,6 +276,7 @@ public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicA if (nic.getPxeDisable()) { intf.setPxeDisable(true); } + intf.setLinkStateUp(nic.getLinkState() == Nic.LinkState.Enabled); return intf; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 55bab118ad00..46cf1da461e7 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -4851,6 +4851,12 @@ public List getInterfaces(final Connect conn, final String vmName) } } + public InterfaceDef getInterface(final Connect conn, final String vmName, final String macAddress) { + List interfaces = getInterfaces(conn, vmName); + return interfaces.stream().filter(interfaceDef -> interfaceDef.getMacAddress().equals(macAddress)) + .findFirst().orElseThrow(() -> new CloudRuntimeException(String.format("Unable to find NIC with MAC address %s.", macAddress))); + } + public List getDisks(final Connect conn, final String vmName) { final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUpdateVmNicCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUpdateVmNicCommandWrapper.java new file mode 100644 index 000000000000..9c416a96a09c --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUpdateVmNicCommandWrapper.java @@ -0,0 +1,68 @@ +// +// 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.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.vm.Nic; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.LibvirtException; + +@ResourceWrapper(handles = UpdateVmNicCommand.class) +public final class LibvirtUpdateVmNicCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(UpdateVmNicCommand command, LibvirtComputingResource libvirtComputingResource) { + String nicMacAddress = command.getNicMacAddress(); + String vmName = command.getVmName(); + Nic.LinkState linkState = command.getLinkState(); + + Domain vm = null; + try { + final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); + vm = libvirtComputingResource.getDomain(conn, vmName); + + final InterfaceDef nic = libvirtComputingResource.getInterface(conn, vmName, nicMacAddress); + nic.setLinkStateUp(linkState == Nic.LinkState.Enabled); + vm.updateDeviceFlags(nic.toString(), Domain.DeviceModifyFlags.LIVE); + + return new UpdateVmNicAnswer(command, true, "success"); + } catch (final LibvirtException e) { + final String msg = String.format(" Update NIC failed due to %s.", e); + logger.warn(msg, e); + return new UpdateVmNicAnswer(command, false, msg); + } finally { + if (vm != null) { + try { + vm.free(); + } catch (final LibvirtException l) { + logger.trace("Ignoring libvirt error.", l); + } + } + } + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index cde87fd93842..b96295240076 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -7203,4 +7203,35 @@ public void defineDiskForDefaultPoolTypeHandlesNullDetails() { libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, null); Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2); } + + @Test + public void getInterfaceTestValidMacAddressReturnInterface() { + String macAddress = "a0:90:27:a9:9e:62"; + final String vmName = "Test"; + final InterfaceDef interfaceDef = Mockito.mock(InterfaceDef.class); + final List interfaces = new ArrayList<>(); + interfaces.add(interfaceDef); + + Mockito.doReturn(macAddress).when(interfaceDef).getMacAddress(); + Mockito.doReturn(interfaces).when(libvirtComputingResourceSpy).getInterfaces(Mockito.any(), Mockito.anyString()); + + InterfaceDef result = libvirtComputingResourceSpy.getInterface(connMock, vmName, macAddress); + + Assert.assertNotNull(result); + } + + @Test(expected = CloudRuntimeException.class) + public void getInterfaceTestInvalidMacAddressThrowCloudRuntimeException() { + String invalidMacAddress = "ea:57:5d:f1:64:05"; + String macAddress = "a0:90:27:a9:9e:62"; + final String vmName = "Test"; + final InterfaceDef interfaceDef = Mockito.mock(InterfaceDef.class); + final List interfaces = new ArrayList<>(); + interfaces.add(interfaceDef); + + Mockito.doReturn(macAddress).when(interfaceDef).getMacAddress(); + Mockito.doReturn(interfaces).when(libvirtComputingResourceSpy).getInterfaces(Mockito.any(), Mockito.anyString()); + + libvirtComputingResourceSpy.getInterface(connMock, vmName, invalidMacAddress); + } } diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 1d6d5d5c5004..702b3afddf15 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -2230,6 +2230,10 @@ public static List findNicSecondaryIps(long nicId) { return s_nicSecondaryIpDao.listByNicId(nicId); } + public static NicVO findNicById(long nicId) { + return s_nicDao.findById(nicId); + } + public static TemplateResponse newTemplateUpdateResponse(TemplateJoinVO vr) { return s_templateJoinDao.newUpdateResponse(vr); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index cf98df0da243..bdfb3220893f 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -1929,6 +1929,12 @@ public UserVm findUserVmById(Long vmId) { } + @Override + public UserVm findUserVmByNicId(Long nicId) { + NicVO nic = ApiDBUtils.findNicById(nicId); + return ApiDBUtils.findUserVmById(nic.getInstanceId()); + } + @Override public VolumeVO findVolumeById(Long volumeId) { return ApiDBUtils.findVolumeById(volumeId); @@ -4844,6 +4850,8 @@ public NicResponse createNicResponse(Nic result) { VpcVO vpc = _entityMgr.findByUuidIncludingRemoved(VpcVO.class, userVm.getVpcUuid()); response.setVpcName(vpc.getName()); } + + response.setLinkState(result.getLinkState()); return response; } diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java index 2c6c64816619..f598d927d2d8 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java @@ -196,6 +196,7 @@ public DomainRouterResponse newDomainRouterResponse(DomainRouterJoinVO router, A nicResponse.setMtu(router.getMtu()); } nicResponse.setIsDefault(router.isDefaultNic()); + nicResponse.setLinkState(router.getNicLinkState()); nicResponse.setObjectName("nic"); routerResponse.addNic(nicResponse); } @@ -289,6 +290,7 @@ public DomainRouterResponse setDomainRouterResponse(DomainRouterResponse vrData, nicResponse.setMtu(vr.getMtu()); } nicResponse.setIsDefault(vr.isDefaultNic()); + nicResponse.setLinkState(vr.getNicLinkState()); nicResponse.setObjectName("nic"); vrData.addNic(nicResponse); } diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 93dca8cc07a1..740ddd992d48 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -358,6 +358,7 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us nicResponse.setIp6Address(userVm.getIp6Address()); nicResponse.setIp6Gateway(userVm.getIp6Gateway()); nicResponse.setIp6Cidr(userVm.getIp6Cidr()); + nicResponse.setLinkState(userVm.getNicLinkState()); if (userVm.getBroadcastUri() != null) { nicResponse.setBroadcastUri(userVm.getBroadcastUri().toString()); } @@ -625,6 +626,7 @@ public UserVmResponse setUserVmResponse(ResponseView view, UserVmResponse userVm /*17: default*/ nicResponse.setIsDefault(uvo.isDefaultNic()); nicResponse.setDeviceId(String.valueOf(uvo.getNicDeviceId())); + nicResponse.setLinkState(uvo.getNicLinkState()); List secondaryIps = ApiDBUtils.findNicSecondaryIps(uvo.getNicId()); if (secondaryIps != null) { List ipList = new ArrayList(); diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java index 485b8f0a6b93..ebec5bde4cd3 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java @@ -37,6 +37,7 @@ import com.cloud.resource.ResourceState; import com.cloud.user.Account; import com.cloud.utils.db.GenericDao; +import com.cloud.vm.Nic; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; @@ -274,6 +275,9 @@ public class DomainRouterJoinVO extends BaseViewVO implements ControlledViewEnti @Column(name = "mtu") private Integer mtu; + @Column(name = "nic_link_state") + private Nic.LinkState nicLinkState; + public DomainRouterJoinVO() { } @@ -577,4 +581,8 @@ public String getSoftwareVersion() { public Integer getMtu() { return mtu; } + + public Nic.LinkState getNicLinkState() { + return nicLinkState; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index eab34081d514..a894450a276b 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -44,6 +44,7 @@ import com.cloud.user.Account; import com.cloud.util.StoragePoolTypeConverter; import com.cloud.utils.db.GenericDao; +import com.cloud.vm.Nic; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import org.apache.cloudstack.util.HypervisorTypeConverter; @@ -345,6 +346,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "is_default_nic") private boolean isDefaultNic; + @Column(name = "nic_link_state") + private Nic.LinkState nicLinkState; + @Column(name = "ip_address") private String ipAddress; @@ -1089,4 +1093,8 @@ public void setLeaseExpiryAction(String leaseExpiryAction) { public String getLeaseActionExecution() { return leaseActionExecution; } + + public Nic.LinkState getNicLinkState() { + return nicLinkState; + } } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index 97c453003a86..6e8a62d95f2c 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -205,6 +205,7 @@ public NicTO toNicTO(NicProfile profile) { to.setIp6Dns1(profile.getIPv6Dns1()); to.setIp6Dns2(profile.getIPv6Dns2()); to.setNetworkId(profile.getNetworkId()); + to.setLinkState(profile.getLinkState()); NetworkVO network = networkDao.findById(profile.getNetworkId()); to.setNetworkUuid(network.getUuid()); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index c68d9ea47984..5bfa6f7cd5b9 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -562,6 +562,7 @@ import org.apache.cloudstack.api.command.user.vm.StopVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -4144,6 +4145,7 @@ public List> getCommands() { cmdList.add(StopVMCmd.class); cmdList.add(UpdateDefaultNicForVMCmd.class); cmdList.add(UpdateVMCmd.class); + cmdList.add(UpdateVmNicCmd.class); cmdList.add(UpgradeVMCmd.class); cmdList.add(CreateVMGroupCmd.class); cmdList.add(DeleteVMGroupCmd.class); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 4597ef81965b..7475f2c49f51 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -99,6 +99,7 @@ import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -1899,6 +1900,54 @@ public void doInTransactionWithoutResult(TransactionStatus status) { return vm; } + @Override + public UserVm updateVirtualMachineNic(UpdateVmNicCmd cmd) { + Long nicId = cmd.getNicId(); + Nic.LinkState linkState = cmd.getLinkState(); + Account caller = CallContext.current().getCallingAccount(); + + NicVO nic = _nicDao.findById(nicId); + if (nic == null) { + throw new InvalidParameterValueException("Unable to find the specified NIC."); + } + + UserVmVO vmInstance = _vmDao.findById(nic.getInstanceId()); + if (vmInstance == null) { + throw new InvalidParameterValueException("Unable to find a virtual machine associated with the specified NIC."); + } + + if (linkState == null) { + return vmInstance; + } + + if (vmInstance.getHypervisorType() != HypervisorType.KVM) { + throw new InvalidParameterValueException("Updating the VM NIC is only supported by the KVM hypervisor."); + } + + NetworkVO network = _networkDao.findById(nic.getNetworkId()); + if (network == null) { + throw new InvalidParameterValueException("Unable to find NIC's network."); + } + + _accountMgr.checkAccess(caller, null, true, vmInstance); + + boolean success = false; + try { + success = _itMgr.updateVmNic(vmInstance, nic, linkState); + } catch (ResourceUnavailableException e) { + throw new CloudRuntimeException(String.format("Unable to update NIC %s of VM %s in network %s due to: %s.", nic, vmInstance, network.getUuid(), e.getMessage())); + } catch (ConcurrentOperationException e) { + throw new CloudRuntimeException(String.format("Concurrent operations while updating NIC %s for VM %s: %s.", nic, vmInstance, e.getMessage())); + } + + if (!success) { + throw new CloudRuntimeException(String.format("Failed to update NIC %s of VM %s in network %s.", nic, vmInstance, network.getUuid())); + } + + logger.debug("Successfully updated NIC {} in network {} for VM {}.", nic, network.getUuid(), vmInstance); + return vmInstance; + } + private void updatePublicIpDnatVmIp(long vmId, long networkId, String oldIp, String newIp) { if (!_networkModel.areServicesSupportedInNetwork(networkId, Service.StaticNat)) { return; diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index f7439386fab4..743b3290a13f 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -77,6 +77,7 @@ import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd; import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupVO; @@ -244,6 +245,9 @@ public class UserVmManagerImplTest { @Mock private UpdateVMCmd updateVmCommand; + @Mock + private UpdateVmNicCmd updateVmNicCmd; + @Mock private AccountManager accountManager; @@ -271,6 +275,9 @@ public class UserVmManagerImplTest { @Mock private UserVO callerUser; + @Mock + private NicVO nicMock; + @Mock private VMTemplateDao templateDao; @@ -454,6 +461,8 @@ public class UserVmManagerImplTest { private static final long vmId = 1l; private static final long zoneId = 2L; private static final long accountId = 3L; + private static final long nicId = 4L; + private static final long networkId = 5L; private static final long serviceOfferingId = 10L; private static final long templateId = 11L; private static final long volumeId = 1L; @@ -4251,4 +4260,59 @@ public void updateVmExtraConfigDoesNothingWhenExtraConfigIsBlank() { verify(vmInstanceDetailsDao, never()).removeDetailsWithPrefix(anyLong(), anyString()); verify(userVmManagerImpl, never()).addExtraConfig(any(UserVmVO.class), anyString()); } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicThrowInvalidParameterValueException() { + Long invalidId = -1L; + Mockito.doReturn(invalidId).when(updateVmNicCmd).getNicId(); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicUserVmThrowInvalidParameterValueException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicNetworkThrowInvalidParameterValueException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(Nic.LinkState.Enabled).when(updateVmNicCmd).getLinkState(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = CloudRuntimeException.class) + public void updateVirtualMachineNicTestInvalidNicNetworkThrowCloudRuntimeException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(Nic.LinkState.Enabled).when(updateVmNicCmd).getLinkState(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test + public void updateVirtualMachineNicTestValidInputReturnNicUserVm() throws ResourceUnavailableException { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(Nic.LinkState.Enabled).when(updateVmNicCmd).getLinkState(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(userVmVoMock).getHypervisorType(); + Mockito.doReturn(networkId).when(nicMock).getNetworkId(); + Mockito.doReturn(networkMock).when(_networkDao).findById(networkId); + Mockito.doReturn(true).when(virtualMachineManager).updateVmNic(Mockito.any(), Mockito.any(), Mockito.any()); + + UserVm result = userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + + Assert.assertNotNull(result); + } } diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index e41a04ff2e1b..292f52d809bf 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -174,6 +174,7 @@ 'removeIpFromNic': 'Nic', 'updateVmNicIp': 'Nic', 'listNics':'Nic', + 'updateVmNic': 'Nic', 'AffinityGroup': 'Affinity Group', 'ImageStore': 'Image Store', 'addImageStore': 'Image Store', diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 38b7a8eede69..8e6a06804cb9 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -985,6 +985,7 @@ "label.edit.acl": "Edit ACL", "label.edit.acl.rule": "Edit ACL rule", "label.edit.autoscale.vmprofile": "Edit AutoScale Instance Profile", +"label.edit.nic": "Edit NIC", "label.edit.project.details": "Edit project details", "label.edit.project.role": "Edit project role", "label.edit.role": "Edit Role", @@ -3719,6 +3720,7 @@ "message.network.selection": "Choose one or more Networks to attach the Instance to.", "message.network.selection.new.network": "A new Network can also be created here.", "message.network.updateip": "Please confirm that you would like to change the IP address for this NIC on the Instance.", +"message.network.update.nic": "Please confirm that you would like to update this NIC.", "message.network.usage.info.data.points": "Each data point represents the difference in data traffic since the last data point.", "message.network.usage.info.sum.of.vnics": "The Network usage shown is made up of the sum of data traffic from all the vNICs in the Instance.", "message.nfs.mount.options.description": "Comma separated list of NFS mount options for KVM hosts. Supported options : vers=[3,4.0,4.1,4.2], nconnect=[1...16]", @@ -4002,13 +4004,14 @@ "message.success.delete.vgpu.profile": "Successfully deleted vGPU profile", "message.success.update.custom.action": "Successfully updated Custom Action", "message.success.update.extension": "Successfully updated Extension", -"message.success.update.sharedfs": "Successfully updated Shared FileSystem", "message.success.update.ipaddress": "Successfully updated IP address", "message.success.update.iprange": "Successfully updated IP range", "message.success.update.ipv4.subnet": "Successfully updated IPv4 subnet", "message.success.update.iso": "Successfully updated ISO", "message.success.update.kubeversion": "Successfully updated Kubernetes supported version", "message.success.update.network": "Successfully updated Network", +"message.success.update.nic": "Successfully updated NIC", +"message.success.update.sharedfs": "Successfully updated Shared FileSystem", "message.success.update.template": "Successfully updated Template", "message.success.update.user": "Successfully updated User", "message.success.update.vpn.customer.gateway": "Successfully updated VPN Customer Gateway", @@ -4058,6 +4061,7 @@ "message.two.fa.setup.page": "Two factor authentication (2FA) is an extra layer of security to your account.
Once setup is done, on every login you will be prompted to enter the 2FA code.
", "message.two.fa.view.setup.key": "Click here to view the setup key", "message.two.fa.view.static.pin": "Click here to view the static PIN", +"message.update.nic.processing": "Updating NIC...", "message.update.ipaddress.processing": "Updating IP Address...", "message.update.resource.count": "Please confirm that you want to update resource counts for this Account.", "message.update.resource.count.domain": "Please confirm that you want to update resource counts for this domain.", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 59123a5e45c3..c7ca36c1278d 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -623,6 +623,7 @@ "label.edit": "Editar", "label.edit.acl": "Editar lista ACL", "label.edit.acl.rule": "Editar regra ACL", +"label.edit.nic": "Editar NIC", "label.edit.project.details": "Editar detalhes do projeto", "label.edit.project.role": "Editar fun\u00e7\u00e3o do projeto", "label.edit.role": "Editar fun\u00e7\u00e3o", @@ -2298,6 +2299,7 @@ "message.network.removenic": "Por favor, confirme que deseja remover esta interface de rede, esta a\u00e7\u00e3o tamb\u00e9m ir\u00e1 remover a rede associada \u00e0 VM.", "message.network.secondaryip": "Por favor, confirme que voc\u00ea gostaria de adquirir um novo IP secund\u00e1rio para este NIC. \n NOTA: Voc\u00ea precisa configurar manualmente o IP secund\u00e1rio rec\u00e9m-adquirido dentro da m\u00e1quina virtual.", "message.network.updateip": "Por favor, confirme que voc\u00ea gostaria de mudar o endere\u00e7o IP da NIC em VM.", +"message.network.update.nic": "Por favor, confirme que voc\u00ea gostaria de atualizar esta NIC.", "message.network.usage.info.data.points": "Cada ponto no gr\u00e1fico representa a diferen\u00e7a de dados trafegados desde a \u00faltima coleta de estat\u00edstica realizada (o ponto anterior)", "message.network.usage.info.sum.of.vnics": "O uso de rede apresentado \u00e9 composto pela soma de dados trafegados por todas as vNICs da VM", "message.no.data.to.show.for.period": "Nenhum dado para mostrar no per\u00edodo selecionado.", @@ -2455,6 +2457,7 @@ "message.success.update.iprange": "Intervalo de IP atualizado com sucesso", "message.success.update.kubeversion": "Vers\u00e3o compat\u00edvel com Kubernetes atualizada com sucesso", "message.success.update.network": "Rede atualizada com sucesso", +"message.success.update.nic": "NIC atualizada com sucesso", "message.success.update.user": "Usu\u00e1rio atualizado com sucesso", "message.success.upgrade.kubernetes": "Cluster do Kubernetes atualizado com sucesso", "message.success.upload": "Carregado com sucesso", @@ -2471,6 +2474,7 @@ "message.template.iso": "Selecione o template ou ISO para continuar", "message.tooltip.reserved.system.netmask": "O prefixo de rede que define a subrede deste pod. utilize a nota\u00e7\u00e3o CIDR.", "message.traffic.type.to.basic.zone": "Tipo de tr\u00e1fego para a zona b\u00e1sica", +"message.update.nic.processing": "Atualizando NIC...", "message.update.ipaddress.processing": "Atualizando endere\u00e7o IP...", "message.update.resource.count": "Por favor confirme que voc\u00ea quer atualizar a contagem de recursos para esta conta.", "message.update.resource.count.domain": "Por favor, confirme que voc\u00ea deseja atualizar as contagens de recursos para este dom\u00ednio.", diff --git a/ui/src/views/network/NicsTab.vue b/ui/src/views/network/NicsTab.vue index 9c16865e0c6e..20d4e012c94c 100644 --- a/ui/src/views/network/NicsTab.vue +++ b/ui/src/views/network/NicsTab.vue @@ -54,6 +54,13 @@ icon="environment-outlined" :disabled="(!('addIpToNic' in $store.getters.apis) && !('addIpToNic' in $store.getters.apis))" @onClick="onAcquireSecondaryIPAddress(record)" /> +